fix(core): save files store the deal via upstream serializers (schema v6)
Test / test (pull_request) Successful in 36m38s

GameState's save serde had the same latent flaw PR #170 removed from
replays: v5 persisted seed + saved_moves and re-dealt the board from the
seed on load, so any RNG or upstream upgrade that shifts the seed->deal
mapping would invalidate every in-progress save (graceful error, but the
player loses their game). v6 persists the existing SessionRecording
payload (upstream card_game session serde: config + dealt board +
instructions) instead; seed stays as presentation metadata only.

- v4/v5 files still load through the legacy seed path and are rewritten
  as v6 on the next save; v6 files load from the recording and replay
  with per-instruction validation, so tampered or corrupt files surface
  an error rather than a silently wrong board
- saved_moves() removed (dead once serialisation reads recording())
- tests: v6 round-trip via storage, seed-corruption immunity, v5 legacy
  load, missing-recording rejection, invalid-history rejection

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
funman300
2026-07-10 10:21:01 -07:00
parent c60d465711
commit 07044f439c
2 changed files with 155 additions and 46 deletions
+10 -5
View File
@@ -477,7 +477,7 @@ mod tests {
/// again must reproduce byte-identical JSON. `undo_count` deliberately resets
/// to 0 on load because only the forward instruction history is persisted.
#[test]
fn game_state_v5_mid_game_round_trip() {
fn game_state_v6_mid_game_round_trip() {
use solitaire_core::KlondikeInstruction;
use solitaire_core::game_state::GameState;
@@ -518,11 +518,16 @@ mod tests {
save_game_state_to(&path, &gs).expect("save");
// Verify the file carries the v5 schema marker.
// Verify the file carries the v6 schema marker and the recording.
let json = fs::read_to_string(&path).expect("read json");
let parsed: serde_json::Value = serde_json::from_str(&json).expect("parse saved json");
assert_eq!(
parsed["schema_version"], 6,
"saved file must use schema version 6",
);
assert!(
json.contains("\"schema_version\"") && json.contains('5'),
"saved file must use schema version 5",
parsed["recording"].is_object(),
"saved file must embed the session recording",
);
let loaded =
@@ -530,7 +535,7 @@ mod tests {
// The forward instruction history round-trips, so the reconstructed board
// re-serialises to byte-identical JSON.
let path_reload = gs_path("v5_mid_game_reload");
let path_reload = gs_path("v6_mid_game_reload");
let _ = fs::remove_file(&path_reload);
save_game_state_to(&path_reload, &loaded).expect("re-save loaded");
assert_eq!(