From 12de49a16a405b651210b5601e8cf206a6c33d78 Mon Sep 17 00:00:00 2001 From: Natalie Date: Wed, 24 Jun 2026 21:17:24 -0400 Subject: [PATCH] =?UTF-8?q?test(@projects/@magic-civilization):=20?= =?UTF-8?q?=F0=9F=90=9B=20fix=20AI-never-builds-units=20in=20claude-vs-ai?= =?UTF-8?q?=20harness=20(race=5Fid=20+=20unit=5Ftype)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `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) --- .../crates/mc-player-api/tests/common/mod.rs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/simulator/crates/mc-player-api/tests/common/mod.rs b/src/simulator/crates/mc-player-api/tests/common/mod.rs index 2fde6e6a..d267fde1 100644 --- a/src/simulator/crates/mc-player-api/tests/common/mod.rs +++ b/src/simulator/crates/mc-player-api/tests/common/mod.rs @@ -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 { 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 { 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(),