test(@projects/@magic-civilization): p3-18 P6-core — end-to-end proof an army fords open ocean to a far landmass

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) <noreply@anthropic.com>
This commit is contained in:
Natalie 2026-06-25 06:26:01 -04:00
parent 5aefbff8a2
commit 11988d60a6

View file

@ -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 01) | ocean strip (col 2) | landmass B (cols 34).
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");