Pagination for Custom WP_Query not displaying page 2 results

Rather than try to wrestle with the default search query, I would take a different approach and create a custom search function.

First, we add a new custom search query var, in this case search_var. Then we add a couple of rewrite rules so we can have URLs like:

http://example.com/custom-search/1991/
http://example.com/custom-search/1991/page/2/

with this function:

function wpd_custom_search_rules() {
    add_rewrite_tag(
        '%search_var%',
        '([^/]+)'
    );
    add_rewrite_rule(
        'custom-search/([^/]+)/?$',
        'index.php?post_type=fallen_hero&search_var=$matches[1]',
        'top'
    );
    add_rewrite_rule(
        'custom-search/([^/]+)/page/?([0-9]{1,})/?$',
        'index.php?post_type=fallen_hero&search_var=$matches[1]&paged=$matches[2]',
        'top'
    );
}
add_action( 'init', 'wpd_custom_search_rules' );

Don’t forget to flush rewrite rules after adding these. You’ll also need to update your search form to use this new query var/pattern.

The above rules just set up basic Main Queries for our post type, which WordPress will identify basically as Post Type Archives.

The next step is to alter those main queries to give us the posts we want. You could just paste most of your existing query code in here, and pass the resulting array of IDs to the main query:

function wpd_custom_search_query( $query ) {
    if ( isset( $query->query['search_var'] ) ) {
        // your custom query code would go here
        $post_ids = [23,42,66];
        $query->set( 'post__in', $post_ids );
    }
}
add_action( 'pre_get_posts', 'wpd_custom_search_query', 10, 2 );

You don’t have to worry about pagination, as that happens automatically now. If you want a particular template for these that’s different from the post type archive template, you can filter the template hierarchy and inject a new one. Just check if get_query_var('search_var') contains something to know that you are serving one of these search requests.