diff --git a/src/simulator/crates/mc-ai/src/tactical/movement.rs b/src/simulator/crates/mc-ai/src/tactical/movement.rs index cd5a0704..3a4fa58a 100644 --- a/src/simulator/crates/mc-ai/src/tactical/movement.rs +++ b/src/simulator/crates/mc-ai/src/tactical/movement.rs @@ -381,20 +381,20 @@ fn is_military(unit: &TacticalUnit) -> bool { // ── Enemy / diplomacy enumeration ──────────────────────────────────────── pub(super) fn is_at_war(me: &TacticalPlayerState, opponent_index: u8) -> bool { - // Canonical model is courier-diplomacy: pairs start at PEACE and war - // begins when a player dispatches a war-dec envelope (COMMUNICATIONS.md - // §"War declaration semantics"; p1-01's "missing → war" is superseded, - // see p3-16). `project_tactical_relations` fills the relations vec, so a - // slot is normally present; the `map_or(true, …)` fallback only fires for - // a genuinely absent slot and is left open so a not-yet-projected pair - // never silently blocks retaliation. + // Canonical model is courier-diplomacy: every pair starts at PEACE and war + // begins only when a player dispatches a war-dec envelope (COMMUNICATIONS.md + // §"War declaration semantics"). p1-01's legacy "missing relation → war" is + // SUPERSEDED (p3-16): an absent slot now defaults to PEACE, so the AI never + // treats a not-yet-projected pair as a phantom war. `project_tactical_relations` + // fills the vec from the courier relation state, so real wars are present; + // `< 0` is the at-war marker. if (opponent_index as usize) == (me.index as usize) { return false; } me.relations .get(opponent_index as usize) .copied() - .map_or(true, |r| r < 0) + .map_or(false, |r| r < 0) } fn collect_enemy_units<'a>(