test(@projects/@magic-civilization): 🛤️ Rail-1 Phase-1 — end-to-end live-unit-store loop test
Proves the spawn → command → view contract the GdGameState bridge exposes for the render-gated live flip, at the mc_player_api layer its shims call: a MapUnit pushed onto inner (as spawn_unit_into_inner produces) appears in project_view; a Fortify via apply_action is reflected in the next view; a command on a stale unit id is a typed error, not a panic. Existing integration tests load pre-built states — none exercised the spawn-then-act-then-view triple a freshly-spawned live unit goes through. De-risks the foundation before the GDScript flip depends on it. mc-player-api 3/3. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
b4c402e766
commit
24c0e0c24c
1 changed files with 73 additions and 0 deletions
|
|
@ -0,0 +1,73 @@
|
|||
//! Rail-1 Phase-1 — the live unit-store loop, end-to-end.
|
||||
//!
|
||||
//! Proves the exact three-call contract the new `GdGameState` bridge exposes for
|
||||
//! the (later, render-gated) live flip — `spawn_unit_into_inner` →
|
||||
//! `apply_action_json` → `inner_view_json` — at the `mc_player_api` layer those
|
||||
//! shims call into. The existing integration tests load pre-built states; none
|
||||
//! exercise the **spawn → command → view** triple a freshly-spawned live unit
|
||||
//! goes through. This is that proof: a unit pushed onto `inner` shows up in the
|
||||
//! projected view and responds to a command, with the command's effect visible
|
||||
//! in the next view.
|
||||
|
||||
use mc_player_api::{apply_action, project_view, PlayerAction};
|
||||
use mc_state::game_state::{GameState, MapUnit, PlayerState};
|
||||
|
||||
/// Build a one-player `GameState` holding a single durable unit at `(2, 2)`,
|
||||
/// mirroring what `GdGameState::spawn_unit_into_inner` produces (a `MapUnit`
|
||||
/// pushed onto `players[pi].units`).
|
||||
fn state_with_spawned_unit() -> GameState {
|
||||
let mut state = GameState::default();
|
||||
state.players.push(PlayerState {
|
||||
units: vec![MapUnit {
|
||||
id: 1,
|
||||
col: 2,
|
||||
row: 2,
|
||||
hp: 10,
|
||||
max_hp: 10,
|
||||
unit_id: "dwarf_warrior".into(),
|
||||
..Default::default()
|
||||
}],
|
||||
..Default::default()
|
||||
});
|
||||
state
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spawned_unit_appears_in_the_projected_view() {
|
||||
let state = state_with_spawned_unit();
|
||||
// `inner_view_json` → project_view(omniscient=true is the harness path; the
|
||||
// bound player sees its own units regardless of fog).
|
||||
let view = project_view(&state, 0, true);
|
||||
let u = view
|
||||
.units
|
||||
.iter()
|
||||
.find(|u| u.id == "1")
|
||||
.expect("the spawned unit must appear in the owner's view");
|
||||
assert_eq!(u.position, [2, 2], "view carries the spawn position");
|
||||
assert_eq!(u.type_id, "dwarf_warrior");
|
||||
assert!(!u.fortified, "freshly spawned unit is not fortified");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_via_act_is_reflected_in_the_next_view() {
|
||||
let mut state = state_with_spawned_unit();
|
||||
// act(): Fortify the unit (no grid/movement preconditions — isolates the
|
||||
// act→view round-trip from pathfinding).
|
||||
let res = apply_action(&mut state, 0, &PlayerAction::Fortify { unit_id: "1".into() });
|
||||
assert!(res.is_ok(), "Fortify must dispatch on the live store: {res:?}");
|
||||
|
||||
// The very next view reflects the command — the contract the live renderer
|
||||
// relies on (render strictly from getState() after each act()).
|
||||
let view = project_view(&state, 0, true);
|
||||
let u = view.units.iter().find(|u| u.id == "1").expect("unit still present");
|
||||
assert!(u.fortified, "the view must show the unit fortified after the act()");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn act_on_a_missing_unit_is_rejected_not_panicking() {
|
||||
let mut state = state_with_spawned_unit();
|
||||
// The bridge must surface a clean error (not panic) for a stale unit id —
|
||||
// the live UI can desync transiently and must get a typed rejection.
|
||||
let res = apply_action(&mut state, 0, &PlayerAction::Fortify { unit_id: "999".into() });
|
||||
assert!(res.is_err(), "commanding a non-existent unit must be a typed error");
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue