refactor: persist replay/save moves as KlondikeInstruction, not pile coords (#89)
Pile-position types (Tableau, Foundation, KlondikePile, KlondikePileStack) are runtime-only and have no serde upstream. Per Rhys's guidance, the persistence layer now stores the moves (KlondikeInstruction) rather than board coordinates, decoding back to runtime pile positions on demand. Core / data: - game_state: instruction_history() -> Vec<KlondikeInstruction>; add instruction_to_piles() and apply_instruction(); drop AnyInstruction. - klondike_adapter: delete the entire Saved* serde mirror section (SavedTableau/Foundation/SkipCards/KlondikePile/TableauStack/ KlondikePileStack/DstFoundation/DstTableau/SavedInstruction). - replay: drop the bespoke ReplayMove serde mirror; Replay.moves is now Vec<KlondikeInstruction>; REPLAY_SCHEMA_VERSION 2 -> 3. - storage: game_state save format v3 rejected (v4/v5 only). Engine / wasm consumers: - record via KlondikeInstruction (stock click = RotateStock). - playback decodes each instruction to (from, to, count) against the live state via instruction_to_piles, then fires the canonical event; undecodable instructions are skipped with a warning, never panic. - remove all use solitaire_data::ReplayMove and Saved* imports. Workspace check, clippy -D warnings, and the full test suite all pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -583,24 +583,16 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
/// A schema v3 save (instruction history using u8 indices) must load
|
||||
/// successfully and be transparently migrated to schema v4.
|
||||
///
|
||||
/// This verifies the `AnyInstruction` untagged deserialization migration
|
||||
/// path. v3 files with `RotateStock` (unit variant, format-identical in
|
||||
/// v3 and v4) load correctly and report `schema_version == 4` after load.
|
||||
/// The `SavedInstruction` boundary tests in `proptest_tests.rs` cover the
|
||||
/// u8-to-named conversion for `DstFoundation` / `DstTableau` indices.
|
||||
/// A schema v3 save (instruction history using the old u8-index mirror
|
||||
/// types) is no longer loadable. The legacy migration path was dropped,
|
||||
/// so any file claiming `schema_version: 3` must be rejected and the
|
||||
/// player started on a fresh game.
|
||||
#[test]
|
||||
fn game_state_v3_migrates_to_v4() {
|
||||
use solitaire_core::game_state::GameState;
|
||||
|
||||
let path = gs_path("v3_migrate");
|
||||
fn game_state_v3_is_rejected() {
|
||||
let path = gs_path("v3_reject");
|
||||
let _ = fs::remove_file(&path);
|
||||
|
||||
// Hand-crafted schema v3 JSON: one RotateStock (draw) instruction.
|
||||
// RotateStock serialises as the string "RotateStock" in both v3 and v4,
|
||||
// so this exercises the schema version acceptance code path.
|
||||
let v3_json = r#"{
|
||||
"draw_mode": "DrawOne",
|
||||
"mode": "Classic",
|
||||
@@ -615,13 +607,12 @@ mod tests {
|
||||
}"#;
|
||||
fs::write(&path, v3_json).expect("write v3 fixture");
|
||||
|
||||
let loaded = load_game_state_from(&path)
|
||||
.expect("schema v3 must be accepted and migrated to v4");
|
||||
assert!(
|
||||
load_game_state_from(&path).is_none(),
|
||||
"schema v3 must be rejected (no migration path)",
|
||||
);
|
||||
|
||||
// The loaded game should match a fresh game that had one draw applied.
|
||||
let mut expected = GameState::new(42, DrawStockConfig::DrawOne);
|
||||
expected.draw().expect("draw must succeed on a fresh game");
|
||||
assert_eq!(loaded, expected, "migrated v3 game state must match equivalent v4 state");
|
||||
let _ = fs::remove_file(&path);
|
||||
}
|
||||
|
||||
/// Schema v2 stored raw pile arrays and undo snapshots (no instruction
|
||||
|
||||
Reference in New Issue
Block a user