docs(core,data): complete Phases 0–2 of in-place card_game rewrite
Phase 0 – doc fixes (docs/card-game-integration.md): - Correct stale "no serde" claim: upstream has serde at rev 99b49e62 - Correct take_from_foundation default description (Allowed, not Disallowed) - Document schema v3→v4 migration and AnyInstruction strategy Phase 1 – delegate check_win / check_auto_complete to upstream: - Proptests verify semantic agreement with is_win() / is_win_trivial() across 256 random states before delegation Phase 2 – schema v4 with v3 auto-migration: - SavedInstruction mirror types kept as legacy compat module (needed by solitaire_data::ReplayMove and solitaire_wasm replay layer) - klondike_adapter.rs: add comprehensive legacy-purpose doc comment - proptest_tests.rs: add check_auto_complete/check_win semantic proofs - storage.rs: rename round-trip test to v4, add v3-migrates-to-v4 test Also track the rewrite plan (docs/in-place-card-game-rewrite-plan.md). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -495,22 +495,24 @@ mod tests {
|
||||
assert_eq!(loaded, StatsSnapshot::default());
|
||||
}
|
||||
|
||||
/// Schema v3 serialises the instruction history, not raw pile state. The
|
||||
/// deserialiser replays all `saved_moves` to reconstruct every pile.
|
||||
/// A fresh-game test (zero moves) never exercises that replay path, so
|
||||
/// this test plays several real moves — including an undo — before
|
||||
/// saving, then asserts the full pile layout round-trips exactly.
|
||||
/// Schema v4 serialises the instruction history using upstream
|
||||
/// `KlondikeInstruction` serde (named enum variants). The deserialiser
|
||||
/// replays all `saved_moves` to reconstruct every pile.
|
||||
///
|
||||
/// A fresh-game test (zero moves) never exercises that replay path, so this
|
||||
/// test plays several real moves — including an undo — before saving, then
|
||||
/// asserts the full pile layout round-trips exactly.
|
||||
///
|
||||
/// `GameState::PartialEq` covers stock, waste, all four foundations, all
|
||||
/// seven tableau columns, `score`, `move_count`, `undo_count`, and
|
||||
/// `recycle_count`. Any breakage in `From<KlondikeInstruction>` or
|
||||
/// `TryFrom<SavedInstruction>` will cause at least one pile to disagree.
|
||||
/// `recycle_count`. Any breakage in the upstream serde or replay path
|
||||
/// will cause at least one pile to disagree.
|
||||
#[test]
|
||||
fn game_state_v3_mid_game_round_trip() {
|
||||
fn game_state_v4_mid_game_round_trip() {
|
||||
use solitaire_core::KlondikePile;
|
||||
use solitaire_core::game_state::{DrawMode, GameState};
|
||||
use solitaire_core::game_state::{DrawMode, GameState, GAME_STATE_SCHEMA_VERSION};
|
||||
|
||||
let path = gs_path("v3_mid_game");
|
||||
let path = gs_path("v4_mid_game");
|
||||
let _ = fs::remove_file(&path);
|
||||
|
||||
let mut gs = GameState::new(42, DrawMode::DrawOne);
|
||||
@@ -538,23 +540,77 @@ mod tests {
|
||||
let _ = gs.undo();
|
||||
}
|
||||
|
||||
// The instruction history must be non-empty for this test to exercise
|
||||
// the schema-v3 replay path at all. Seed 42 + 6 draws always succeeds.
|
||||
assert!(
|
||||
gs.undo_stack_len() > 0,
|
||||
"instruction history must be non-empty (seed 42 always produces draws)",
|
||||
);
|
||||
|
||||
save_game_state_to(&path, &gs).expect("save");
|
||||
|
||||
// Verify the file contains the v4 schema marker (tolerates pretty-print whitespace).
|
||||
let json = fs::read_to_string(&path).expect("read json");
|
||||
assert!(
|
||||
json.contains("schema_version") && json.contains('4') && !json.contains(": 3"),
|
||||
"saved file must use schema version 4",
|
||||
);
|
||||
|
||||
let loaded = load_game_state_from(&path)
|
||||
.expect("a valid in-progress game must load without error");
|
||||
|
||||
assert_eq!(loaded.schema_version, GAME_STATE_SCHEMA_VERSION);
|
||||
assert_eq!(
|
||||
loaded, gs,
|
||||
"all pile layouts and counters must be identical after schema-v3 round-trip",
|
||||
"all pile layouts and counters must be identical after schema-v4 round-trip",
|
||||
);
|
||||
}
|
||||
|
||||
/// 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.
|
||||
#[test]
|
||||
fn game_state_v3_migrates_to_v4() {
|
||||
use solitaire_core::game_state::{DrawMode, GameState, GAME_STATE_SCHEMA_VERSION};
|
||||
|
||||
let path = gs_path("v3_migrate");
|
||||
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",
|
||||
"score": 0,
|
||||
"elapsed_seconds": 0,
|
||||
"seed": 42,
|
||||
"undo_count": 0,
|
||||
"recycle_count": 0,
|
||||
"take_from_foundation": true,
|
||||
"schema_version": 3,
|
||||
"saved_moves": ["RotateStock"]
|
||||
}"#;
|
||||
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");
|
||||
|
||||
// After migration, the in-memory schema version must be current.
|
||||
assert_eq!(
|
||||
loaded.schema_version, GAME_STATE_SCHEMA_VERSION,
|
||||
"migrated game must report current schema version",
|
||||
);
|
||||
|
||||
// The loaded game should match a fresh game that had one draw applied.
|
||||
let mut expected = GameState::new(42, DrawMode::DrawOne);
|
||||
expected.draw().expect("draw must succeed on a fresh game");
|
||||
assert_eq!(loaded, expected, "migrated v3 game state must match equivalent v4 state");
|
||||
}
|
||||
|
||||
/// Schema v2 stored raw pile arrays and undo snapshots (no instruction
|
||||
/// history). Any file claiming `schema_version: 2` must be rejected so
|
||||
/// players upgrading from an older build start with a fresh game rather
|
||||
|
||||
Reference in New Issue
Block a user