From 5eed0bb57937c393b774f308a5e3a5287f0a299c Mon Sep 17 00:00:00 2001 From: Natalie Date: Tue, 23 Jun 2026 18:57:15 -0400 Subject: [PATCH] =?UTF-8?q?fix(simulator):=20=F0=9F=90=9B=20project=20real?= =?UTF-8?q?=20unit=20movement=20into=20the=20tactical=20AI=20state?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit project_tactical_player hardcoded moves_left: 2 for every unit (a stale 'bench MapUnit doesn't model moves_left' comment) while MapUnit::movement_remaining is the field the move dispatch actually decrements and the player view gates legal moves on. The AI therefore believed every unit always had movement, planned moves for already-exhausted units, and the dispatch rejected them. Measured over a 200-turn hotseat self-play (seed 42): 'no movement points remaining' move rejections dropped 10,862 → 0 (total controller misfires 10,972 → 110, the rest legitimate path/stacking edge cases). The AI's world-model now matches enforcement; turns no longer churn through ~54 dead move attempts each. Co-Authored-By: Claude Opus 4.8 --- .../crates/mc-player-api/src/projection.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/simulator/crates/mc-player-api/src/projection.rs b/src/simulator/crates/mc-player-api/src/projection.rs index b7d38f63..9417aa94 100644 --- a/src/simulator/crates/mc-player-api/src/projection.rs +++ b/src/simulator/crates/mc-player-api/src/projection.rs @@ -1169,10 +1169,14 @@ fn project_tactical_player( hex: (u.col, u.row), hp: u.hp.max(0) as u32, hp_max: u.max_hp.max(0) as u32, - // Bench `MapUnit` doesn't model moves_left — the turn processor - // refreshes it implicitly. v1 reports full (= 2) so the AI - // can plan a single move per turn. - moves_left: 2, + // Real remaining movement so the AI's world-model matches the + // dispatch's enforcement. Hardcoding 2 made the controller plan + // moves for already-exhausted units, so ~99% of its move + // suggestions were rejected "no movement points remaining" and + // armies barely maneuvered. `MapUnit::movement_remaining` is the + // same field the move dispatch decrements and the player view + // gates legal moves on (projection.rs ~444). + moves_left: u.movement_remaining.max(0) as u32, fortified: u.is_fortified, // p2-71b — flip the founder flag for the canonical Game-1 // founder so `TacticalUnit::is_founder()` returns true and the @@ -1377,6 +1381,7 @@ mod tests { u.unit_id = "dwarf_warrior".into(); u.hp = 100; u.max_hp = 100; + u.movement_remaining = 2; ps.units.push(u); } } else {