test(@projects/@magic-civilization): 🐛 fix AI-never-builds-units in claude-vs-ai harness (race_id + unit_type)

`claude_vs_ai_full_game_transcript` failed: "no AI slot built a unit by turn 10".
Root cause was two test-fixture values diverging from the real engine, both in the
`build_3_player_state_like_harness` construction:

1. `add_player_militarist_inline` never set `PlayerState.race_id`. The real engine
   sets it from presentation metadata (api-gdext set_player_presentation ->
   p.race_id); the tactical production picker filters race-gated units
   (`dwarf_warrior` has race_required="dwarf") by it, so an empty race_id rejected
   every unit. Set race_id="dwarf" (Game 1 is all-dwarf).

2. `build_unit_catalog` set `unit_type: "military"` for dwarf_warrior/pikeman, but
   `pick_best_unit_of_type` filters on the combat archetype unit_type ∈
   {melee,ranged,siege} — the real units JSON uses "melee" (72 melee / 41 ranged /
   20 siege across public/resources/units). "military" matched no preferred type.

With either wrong, `pick_best_unit_for_clan` returned None and fell back to the
phantom id "warrior", which the queue classifier (units_catalog keyed by
"dwarf_warrior") then treated as a *building* (Queueable::Item) — so try_spawn_unit
skipped it, no unit ever spawned, no UnitCreated event fired, and the AI bled units
(4->3->2->1) with no replacement. After the fix the AI queues
`Unit { dwarf_warrior }`, spawns via step's try_spawn_unit (units 4->6 by turn 3),
and emits UnitCreated on the wire. Full mc-player-api suite green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Natalie 2026-06-24 21:17:24 -04:00
parent 1bd1a29d9a
commit 12de49a16a

View file

@ -83,6 +83,14 @@ pub fn add_player_militarist_inline(
state.players.push(PlayerState {
player_index: pi,
// Game 1 is all-dwarf. The real engine sets this from presentation
// metadata (api-gdext set_player_presentation -> p.race_id); the
// tactical production picker filters race-gated units (dwarf_warrior
// has race_required="dwarf") by it, so an empty race_id makes
// pick_best_unit_for_clan reject every unit and fall back to the
// phantom "warrior" id — which the queue classifier then treats as a
// building, so the AI never spawns a unit. Mirror the engine here.
race_id: "dwarf".into(),
gold: 60,
cities: vec![mc_city::CityState::starter()],
unit_upkeep: Vec::new(),
@ -133,7 +141,12 @@ pub fn build_unit_catalog() -> Vec<TacticalUnitSpec> {
id: "dwarf_warrior".into(),
tier: 1,
tech_required: None,
unit_type: "military".into(),
// Combat archetype, NOT the "military" ai_tag. The tactical picker
// (pick_best_unit_of_type) filters on unit_type ∈ {melee,ranged,siege};
// the real units JSON uses "melee" for dwarf_warrior. A bogus
// "military" here matches no preferred type, so the picker falls back
// to the phantom "warrior" id and the AI never spawns a unit.
unit_type: "melee".into(),
requires_resource: None,
race_required: Some("dwarf".into()),
clan_affinity: Vec::new(),
@ -155,7 +168,7 @@ pub fn build_unit_catalog() -> Vec<TacticalUnitSpec> {
id: "pikeman".into(),
tier: 2,
tech_required: Some("iron_working".into()),
unit_type: "military".into(),
unit_type: "melee".into(),
requires_resource: None,
race_required: None,
clan_affinity: Vec::new(),