Index template always follows page_template() template?

template_include is a filter hook so you should use add_filter() instead of add_action(), it’s breaking the semantics here. Though both functions internally do the same thing.

Try the following code, I haven’t tested it but it should work. Simplified and removed some codes.

add_filter('template_include', [$self, 'load_route_template']);
public function load_route_template($template)
    {
        if (!$this->matched_route instanceof Route || !$this->matched_route->has_template() || empty( $this->matched_route->get_template() ) ) {
            return $template;
        }

        // No file found yet
        $located = false;

        // Check child theme first
        if ( file_exists( trailingslashit( get_stylesheet_directory() ) . 'rcp/' . $this->matched_route->get_template().'.php' ) ) {
            $located = trailingslashit( get_stylesheet_directory() ) . 'rcp/' . $this->matched_route->get_template().'.php';
        // Check parent theme next
        } elseif ( file_exists( trailingslashit( get_template_directory() ) . 'rcp/' . $this->matched_route->get_template().'.php' ) ) {
            $located = trailingslashit( get_template_directory() ) . 'rcp/' . $this->matched_route->get_template().'.php';
        // check plugin directory
        } elseif ( file_exists( plugin_dir_path( dirname( __FILE__ ) ) . 'public/' . $this->matched_route->get_template().'.php' ) ) {
            $located = plugin_dir_path( dirname( __FILE__ ) ) . 'public/' . $this->matched_route->get_template().'.php';
        }

        if ( $located ) {
            $template = $located;
        }

        return $template;
    }