That's trivial, we can exploit how WordPress URLs work and pass the query vars directly in the URL:
<form action="/" method="GET">
... etc ..
I've found the problem, and now I realise I had this exact same problem years ago on a different project:
'exclude_from_search' => true
Needed changing to:
'exclude_from_search' => false
'has_archive' => true,
I don't get why excluding a post type from search should affect archive templates, but at least it's fixed now.
I see this in a few comments on the accepted answer, but wanted to make this point clear for others skimming over these answers: The global $wp_query object is more reliable for getting the archive's post type. Specifically from $wp_query->query['post_type'].
You can use get_queried_object() but it has caveats. Namely if you have other query parameters ...