From 11988d60a6e43c43ee21bbf082fada3f28e0ae86 Mon Sep 17 00:00:00 2001 From: Natalie Date: Thu, 25 Jun 2026 06:26:01 -0400 Subject: [PATCH] =?UTF-8?q?test(@projects/@magic-civilization):=20?= =?UTF-8?q?=E2=9B=B5=20p3-18=20P6-core=20=E2=80=94=20end-to-end=20proof=20?= =?UTF-8?q?an=20army=20fords=20open=20ocean=20to=20a=20far=20landmass?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The scenario that motivated p3-18: an army on landmass A reaching landmass B across an ocean strip. teched_army_fords_open_ocean_to_far_landmass asserts the full chain through process_move_requests — without naval tech the ocean rejects the move (army stays put); with ocean_navigation (Ocean embark) the army crosses the strip onto the far landmass and auto-disembarks on arrival. Deterministic, cargo-verifiable evidence (the project's preferred proof form), no dylib/Godot needed. The full headless 1v1-to-game_over demo (P6-full) is the heavier dylib follow-up. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/simulator/crates/mc-turn/src/processor.rs | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/src/simulator/crates/mc-turn/src/processor.rs b/src/simulator/crates/mc-turn/src/processor.rs index 27a44ee9..ab3a563d 100644 --- a/src/simulator/crates/mc-turn/src/processor.rs +++ b/src/simulator/crates/mc-turn/src/processor.rs @@ -5230,6 +5230,61 @@ mod move_request_tests { assert_eq!(state.players[0].unit_upkeep.len(), 1, "unit_upkeep stays index-aligned"); } + /// p3-18 end-to-end — the motivating scenario: an army on landmass A reaches + /// landmass B across an open-ocean strip. Without naval tech the ocean blocks + /// it; with `ocean_navigation` (Ocean embark) it fords the water and lands. + #[test] + fn teched_army_fords_open_ocean_to_far_landmass() { + // Landmass A (cols 0–1) | ocean strip (col 2) | landmass B (cols 3–4). + let mut grid = GridState::new(5, 5); + for r in 0..5 { + for c in 0..5 { + let idx = grid.idx(c, r); + grid.tiles[idx].biome_label_id = + if c == 2 { "ocean" } else { "plains" }.to_string(); + } + } + let mut state = GameState::default(); + state.grid = Some(grid); + state + .units_catalog + .load_json_str(r#"[{"id":"warrior","movement":10,"domain":"land"}]"#) + .expect("catalog loads"); + state.players.push(PlayerState { + player_index: 0, + units: vec![MapUnit { + id: 1, col: 0, row: 0, unit_id: "warrior".into(), ..MapUnit::default() + } + .with_moves(10)], + ..PlayerState::default() + }); + + // No naval tech (embark None) → the ocean strip is impassable. + state.pending_move_requests.push(move_req(0, (4, 0))); + let out = process_move_requests(&mut state); + assert!( + matches!(out[0], MoveOutcome::Rejected { .. }), + "without embark the ocean blocks the army, got {:?}", + out[0] + ); + assert_eq!((state.players[0].units[0].col, state.players[0].units[0].row), (0, 0)); + + // ocean_navigation grants Ocean embark → the army fords to landmass B. + state.players[0].embark_level = mc_core::EmbarkLevel::Ocean; + state.players[0].units[0].movement_remaining = 10; + state.pending_move_requests.push(move_req(0, (4, 0))); + process_move_requests(&mut state); + assert_eq!( + (state.players[0].units[0].col, state.players[0].units[0].row), + (4, 0), + "ocean-teched army crossed the strip onto the far landmass" + ); + assert!( + !state.players[0].units[0].is_embarked, + "auto-disembarked on arrival at land" + ); + } + #[test] fn zero_budget_rejects() { let mut state = build_state_with_unit(7, (0, 0), 0, |_, _| "plains");