From f863d85c3591caa84ab55e45de71d0787d45e9fb Mon Sep 17 00:00:00 2001 From: funman300 Date: Wed, 6 May 2026 05:15:31 +0000 Subject: [PATCH] fix(engine): preserve saved game while restore prompt is unanswered MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Quat reported the restore prompt didn't appear and noticed their save file ended up with move_count 0 — diagnosed as a destructive overwrite. The flow: 1. Player exits with moves; game_state.json has move_count > 0. 2. Player relaunches. Plugin build sees moves > 0, holds the saved game in `PendingRestoredGame`, seeds `GameStateResource` with a fresh deal so the board doesn't show the half-played game until the player picks Continue. 3. The restore prompt should appear. (Why it didn't on Quat's run is still TBD — needs a fresh test.) 4. Player exits. `save_game_state_on_exit` writes `GameStateResource` (the fresh-deal placeholder) to disk, overwriting the meaningful saved game with move_count 0. Both `save_game_state_on_exit` and `auto_save_game_state` now check `PendingRestoredGame`: if it still holds an unanswered saved game, they save THAT (or skip entirely in the auto-save path). The real saved game on disk is preserved across launches no matter how many times the player exits without answering the prompt. Co-Authored-By: Claude Opus 4.7 (1M context) --- solitaire_engine/src/game_plugin.rs | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/solitaire_engine/src/game_plugin.rs b/solitaire_engine/src/game_plugin.rs index 7677824..9505ef3 100644 --- a/solitaire_engine/src/game_plugin.rs +++ b/solitaire_engine/src/game_plugin.rs @@ -1173,9 +1173,17 @@ fn auto_save_game_state( path: Option>, mut timer: ResMut, paused: Option>, + pending: Res, ) { - // Don't save if paused, game is won, or no moves have been made yet. - if paused.is_some_and(|p| p.0) || game.0.is_won || game.0.move_count == 0 { + // Don't save if paused, game is won, no moves have been made yet, + // or there's a pending restore the player hasn't answered — saving + // the fresh-deal placeholder we seeded GameStateResource with at + // startup would clobber the real saved game on disk. + if paused.is_some_and(|p| p.0) + || game.0.is_won + || game.0.move_count == 0 + || pending.0.is_some() + { return; } timer.0 += time.delta_secs(); @@ -1192,17 +1200,25 @@ fn auto_save_game_state( /// player can resume where they left off. Won games are not saved (the /// `save_game_state_to` helper skips them). Blocking on exit is acceptable /// because the game loop is already shutting down. +/// +/// Special case: when `PendingRestoredGame` still holds a saved game the +/// player never answered the restore prompt for, write THAT to disk +/// instead of the live `GameStateResource`. Otherwise we'd clobber a +/// real saved game with the fresh-deal placeholder we seeded +/// `GameStateResource` with at startup. fn save_game_state_on_exit( mut exit_events: MessageReader, game: Res, path: Res, + pending: Res, ) { if exit_events.is_empty() { return; } exit_events.clear(); let Some(p) = path.0.as_deref() else { return }; - if let Err(e) = save_game_state_to(p, &game.0) { + let to_save = pending.0.as_ref().unwrap_or(&game.0); + if let Err(e) = save_game_state_to(p, to_save) { warn!("game_state: failed to save on exit: {e}"); } }