test(@projects/@magic-civilization): p3-25 — end-to-end headless trade pipeline proof

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) <noreply@anthropic.com>
This commit is contained in:
Natalie 2026-06-26 02:09:20 -04:00
parent 507a87104f
commit 7e93dcef42

View file

@ -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;