From 7e93dcef42492a717b38a105ffb76b9f8a765d9f Mon Sep 17 00:00:00 2001 From: Natalie Date: Fri, 26 Jun 2026 02:09:20 -0400 Subject: [PATCH] =?UTF-8?q?test(@projects/@magic-civilization):=20?= =?UTF-8?q?=E2=9C=85=20p3-25=20=E2=80=94=20end-to-end=20headless=20trade?= =?UTF-8?q?=20pipeline=20proof?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit process_trade_phase_forms_and_persists_strategic_swap: a crafted 2-player state with complementary owned-tile strategics (p0 rainforest→hardwood, p1 mountains→iron_ore, biomes guarantee neither has the other's) → process_trade_phase forms a StrategicSwap, persists it to state.trade_ledger, and fans it onto both players' traded_strategics. Proves the full chain end-to-end: owned-tile territory (step 2) → resource-category classification (step 3) → real sourcing + evaluate_trades + persistence (step 4) → which DiplomacyView.trade_deals then projects (step 5, separately tested). mc-turn green. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/simulator/crates/mc-turn/src/processor.rs | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) 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;