Follow-up to the TechWeb fix: research advanced to 109 techs but every city
still spawned tier-1 dwarf_warrior. Five stacked defects severed the
tech→unit→production chain; all are fixed here. Proven in a 599-turn duel
self-play (seed 42): slot 0 fields a 222-strong tier-8 dwarf_adamantine_champion
army; both clans now CHOOSE tier-2..8 units by tech (was tier-1 only).
1. units_catalog (#[serde(skip)]) was never stamped onto the dispatch
GdPlayerApi — only GdGameState. The harness comment claimed 'both must be
re-stamped at boot' but only did GdGameState. Added
GdPlayerApi::set_units_runtime_catalog_json + a post-load harness re-stamp.
Without it apply_queue_production/try_spawn_unit ran catalog-blind.
2. apply_queue_production classified queued ids as unit-vs-building by a
starts_with("dwarf_") prefix. Replaced with an authoritative
units_catalog.get() lookup (prefix kept only as empty-catalog fallback).
The prefix leaked dwarf_-prefixed BUILDINGS (dwarf_deep_forge) onto the map
as units and misfiled every non-dwarf unit.
3. project_tactical_player hardcoded race_id: None, filtering out every
race_required:dwarf unit. Added PlayerState.race_id (race gates production →
it is sim state, not pure presentation per p2-72a), stamped it in
set_player_presentation_json + the headless harness (sourced from
setup.json::default_race), and projected it.
4. build_unit_catalog loaded faction:"wild" monsters (ancient_hydra, …) and
freepeople into the AI production catalog. With race_required:null they
passed every race filter and, being high-tier, the picker preferred them.
Excluded non-player factions from the production catalog (runtime catalog
still carries them for encounters).
5. Generic warrior/spearmen/archer carried clan_affinity to ALL FIVE clans at
tier 1, so clan_affinity_score=2 dominated tier and every named clan locked
onto tier-1 'warrior'. Cleared their affinity (they are neutral baselines,
not clan signatures).
mc-player-api 132 + mc-state 12 tests green. Known next layer (not this fix):
production VOLUME — try_spawn_unit's empty-queue dwarf_warrior auto-spawn still
floods tier-1, and the loser snowballs; the picker is correct, army composition
tuning is separate.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>