Which filter/action should I use to serve content for “virtual” files

I think the earliest actions you can hook are muplugins_loaded and plugins_loaded. muplugins_loaded will only fire for Must Use Plugins.

Some plugins_loaded pseudo-code:

add_action( 'plugins_loaded', 'wpd_plugin_routes' );
function wpd_plugin_routes() {
    if( is_a_virtual_file() ){
        serve_file();
        exit;
    }
}

If you want the full WordPress environment, plugins, theme, and authenticated user, right before WordPress parses the request, you’ve got the do_parse_request filter:

add_filter( 'do_parse_request', 'wpd_do_parse_request', 1, 3 );
function wpd_do_parse_request( $continue, WP $wp, $extra_query_vars ){
    // do something and halt execution, or
    return $continue; // true by default, meaning continue parsing the request
}

If you want all of the above after WordPress parses the request, you’ve got the parse_request action. Here you can alter the main query vars:

add_action( 'parse_request', 'wpd_parse_request_thing' );
function wpd_parse_request_thing( $query ) {
    if( isset( $query->query_vars['pagename'] ) ){
        if( 'some-page' == $query->query_vars['pagename'] ){
            $query->query_vars['pagename'] = 'some-other-page';
        }
    }
}

The last action right before the template is loaded is template_redirect. Here all of the is_ conditionals work:

add_action( 'template_redirect', 'wpd_page_template_redirect' );
function wpd_page_template_redirect() {
    if( is_404() ) {
        // do something
        die;
    }
}