diff --git a/src/simulator/crates/mc-turn/src/processor.rs b/src/simulator/crates/mc-turn/src/processor.rs index f1cbf763..b705a698 100644 --- a/src/simulator/crates/mc-turn/src/processor.rs +++ b/src/simulator/crates/mc-turn/src/processor.rs @@ -5727,6 +5727,75 @@ mod tests { assert!(el.is_empty() && es.is_empty(), "no categories → no tradeables"); } + #[test] + fn process_trade_phase_forms_and_persists_strategic_swap() { + // p3-25 end-to-end: complementary owned-tile strategics → evaluate_trades + // forms a StrategicSwap → persisted to state.trade_ledger → fanned onto + // both players' traded_strategics. Biomes guarantee complementarity: + // rainforest yields only hardwood (categorized strategic) for p0; mountains + // yield only iron_ore (categorized) for p1 — neither has the other's. + use mc_core::grid::GridState; + let mut state = GameState::default(); + state.turn = 5; + state.map_seed = 0x1234_5678; + let mut grid = GridState::new(40, 40); + for c in 0..10 { + if let Some(t) = grid.tile_mut(c, 0) { + t.biome_label_id = "tropical_rainforest".into(); + t.quality = 6; + } + } + for c in 0..12 { + if let Some(t) = grid.tile_mut(c, 10) { + t.biome_label_id = "mountains".into(); + t.quality = 6; + } + } + state.grid = Some(grid); + state + .resource_categories + .insert("hardwood".into(), "strategic".into()); + state + .resource_categories + .insert("iron_ore".into(), "strategic".into()); + + let mut p0 = crate::game_state::PlayerState::default(); + p0.player_index = 0; + p0.cities = vec![CityState::starter()]; + p0.cities[0].owned_tiles = (0..10).map(|c| (c, 0)).collect(); + p0.city_positions = vec![(0, 0)]; + p0.strategic_axes.insert("trade_willingness".into(), 9); + let mut p1 = crate::game_state::PlayerState::default(); + p1.player_index = 1; + p1.cities = vec![CityState::starter()]; + p1.cities[0].owned_tiles = (0..12).map(|c| (c, 10)).collect(); + p1.city_positions = vec![(0, 10)]; + p1.strategic_axes.insert("trade_willingness".into(), 9); + state.players = vec![p0, p1]; + + let processor = TurnProcessor::new(100); + processor.process_trade_phase(&mut state); + + let has_swap = state.trade_ledger.agreements.iter().any(|ag| { + matches!(ag, mc_trade::DiplomaticAgreement::StrategicSwap(ta) if ta.partners == (0, 1)) + }); + assert!( + has_swap, + "strategic swap should form + persist: {:?}", + state.trade_ledger.agreements + ); + assert!( + state.players[0].traded_strategics.contains("iron_ore"), + "p0 gains iron_ore via the swap: {:?}", + state.players[0].traded_strategics + ); + assert!( + state.players[1].traded_strategics.contains("hardwood"), + "p1 gains hardwood via the swap: {:?}", + state.players[1].traded_strategics + ); + } + #[test] fn processor_is_deterministic() { use mc_core::grid::GridState;