diff --git a/src/simulator/crates/mc-turn/src/processor.rs b/src/simulator/crates/mc-turn/src/processor.rs index 9384eedf..377abef9 100644 --- a/src/simulator/crates/mc-turn/src/processor.rs +++ b/src/simulator/crates/mc-turn/src/processor.rs @@ -3459,9 +3459,39 @@ impl TurnProcessor { // last city is captured. Matches the // `eliminated_player_skipped_in_domination` test's manual // setup at `victory.rs:798` (`p1.capital_position = None`). - if defender.capital_position == Some(pos) { + // + // Communications Phase 6 wiring: when the defender's + // capital is lost (and they still have surviving cities), + // begin a comms blackout. Drops in-flight outbound + // envelopes, penalises comm-tier, accelerates last-seen + // decay. Emits `CapitalBlackoutBegan` for the replay + // viewer; the next-turn handler enforces the 1-turn UX + // gate per `project_capital_blackout_design.md`. + let lost_capital = defender.capital_position == Some(pos); + if lost_capital { defender.capital_position = None; } + let defender_cities_remaining = !defender.cities.is_empty(); + // End the &mut state.players[defender_pi] borrow before + // touching state.comms (also a child of state). + let _ = defender; + if lost_capital && defender_cities_remaining { + let turn_now = state.turn; + mc_comms::blackout::begin_blackout( + &mut state.comms, + defender_pi as u8, + turn_now, + ); + result.events_emitted.push( + mc_replay::TurnEvent::CapitalBlackoutBegan { + turn: turn_now, + player: mc_replay::ClanId(defender_pi as u32), + lost_capital_hex: mc_replay::TileCoord::new( + pos.0, pos.1, + ), + }, + ); + } // Transfer to attacker. let attacker = &mut state.players[attacker_pi]; diff --git a/src/simulator/crates/mc-turn/tests/event_collector_wiring.rs b/src/simulator/crates/mc-turn/tests/event_collector_wiring.rs index c8e424c0..1446d661 100644 --- a/src/simulator/crates/mc-turn/tests/event_collector_wiring.rs +++ b/src/simulator/crates/mc-turn/tests/event_collector_wiring.rs @@ -232,6 +232,13 @@ fn ten_turn_run_emits_each_wired_variant() { TurnEvent::HeartbeatMissed { .. } => "HeartbeatMissed", TurnEvent::VisionShareCollapsed { .. } => "VisionShareCollapsed", TurnEvent::VisionShareRestored { .. } => "VisionShareRestored", + // Communications Phase 6 (envelope-flow + link-flow) — labelled + // for exhaustiveness; the 10-turn fixture does not exercise them. + TurnEvent::EnvelopeDispatched { .. } => "EnvelopeDispatched", + TurnEvent::EnvelopeDelivered { .. } => "EnvelopeDelivered", + TurnEvent::EnvelopeIntercepted { .. } => "EnvelopeIntercepted", + TurnEvent::LinkSevered { .. } => "LinkSevered", + TurnEvent::LinkRestored { .. } => "LinkRestored", }) .collect();