Skip to content

fix(save): preserve future-build saves on fresh boot (#196)#198

Merged
LightAxe merged 2 commits into
mainfrom
fix/196-preserve-future-build-save
Jun 5, 2026
Merged

fix(save): preserve future-build saves on fresh boot (#196)#198
LightAxe merged 2 commits into
mainfrom
fix/196-preserve-future-build-save

Conversation

@LightAxe

@LightAxe LightAxe commented Jun 5, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes #196 — a save written by a newer build (snapshot.simVersion > LATEST_SIM_VERSION) was silently overwritten by autosave / "Save Now" on the older build, destroying recoverable bytes.

Root cause

hasIncompatibleSave() (src/platform/save.ts) ran a full deserializeWorldState and returned true on any throw, including FutureSimVersionError. So decideBootMode(() => hasSave() && !hasIncompatibleSave()) routed a future-sim save to a fresh boot (showDifficultySelectThenBoot) instead of the Continue/New Game SavePrompt. autosaveSuspended was only armed inside bootFromSave's FutureSimVersionError catch — reached only via the SavePrompt's Continue — so for a future-sim save it never fired, and bootFresh → resetSessionState left it false. The preserve-future-save intent was inert at boot.

Fix

  • save.ts — new classifySaveCompatibility(): 'none' | 'compatible' | 'incompatible-future' | 'incompatible-corrupt' distinguishes a recoverable future-build save (FutureSimVersionError) from definitive corruption. hasIncompatibleSave() now delegates to it (behavior unchanged for the Save/Load dialog).
  • game-scene.ts — boot classifies the save once; the fresh-boot branch arms autosaveSuspended = true after bootFresh only for incompatible-future, mirroring bootFromSave's existing FutureSimVersionError catch. Corrupt saves still overwrite freely.
  • tests/menu-and-dialog.spec.ts — un-quarantined the Future-build save is not protected from overwrite (autosaveSuspended never armed on incompatible-save boot) #196 regression test and re-derived it against the real fixed flow: seed a real captured save → bump simVersion past LATEST → reload → assert bootScreen === 'difficulty-select' (not a Continue prompt) → pick a difficulty → assert Save Now is a no-op (preserved future-sim bytes survive unchanged).

No simVersion bump — this is a boot/save-flow fix, not a sim algorithm/field change.

Verification

  • npx playwright test tests/menu-and-dialog.spec.ts → exit 0 (11 passed, the two e2e: pheromone ON/OFF screenshot-diff test is flaky (unfit for hard gate) #193 flaky tests remain quarantined). Non-vacuous: reverting only the source files makes the new test fail at the Save-Now assertion.
  • npm run verify → exit 0 (prettier, eslint, tsc, lint:types, sim/asset guards, 2303 Vitest tests).
  • Internal ship-review (base main): passed: true, 0 actionable / 0 advisory.

UAT for the owner

Seed a "future-build" save (bump snapshot.simVersion past LATEST), confirm it survives a fresh boot and that Save Now / autosave no longer overwrite it.

🤖 Generated with Claude Code

A save written by a newer build (snapshot.simVersion > this build's
LATEST_SIM_VERSION) was silently overwritten by autosave / "Save Now" on
the older build — data loss.

Root cause: hasIncompatibleSave() returned true on ANY deserialize throw,
including FutureSimVersionError, so decideBootMode routed a future-sim save
to a FRESH boot rather than the Continue/New Game SavePrompt.
autosaveSuspended was only armed in bootFromSave's FutureSimVersionError
catch (reached only via the SavePrompt's Continue), so for a future-sim save
it never fired and bootFresh -> resetSessionState left it false. The
preserve-future-save intent was therefore inert at boot.

Fix: classify the incompatibility in save.ts (classifySaveCompatibility:
incompatible-future via FutureSimVersionError vs incompatible-corrupt for any
other throw) and, in decideBootMode's fresh-boot branch, arm
autosaveSuspended=true after bootFresh only for the future case so autosave +
Save Now no longer clobber the recoverable bytes. Corrupt saves still
overwrite freely (unchanged). hasIncompatibleSave() now delegates to the
classifier (behavior unchanged for the Save/Load dialog).

Un-quarantine the #196 regression test and re-derive it against the real
fixed flow: seed a real captured save, bump simVersion past LATEST, reload ->
boot shows Choose Difficulty (not a Continue prompt) -> pick difficulty ->
assert Save Now is a no-op (preserved future-sim bytes survive unchanged).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@LightAxe

LightAxe commented Jun 5, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6b8d230d7f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/render/game-scene.ts
…deleted (Codex P2)

Codex review of PR #198 flagged that the new fresh-boot path arms
autosaveSuspended for a recoverable future-build save but never clears it
when the player explicitly deletes that save via the Save/Load dialog. The
dialog calls deleteSave() directly and never notified GameScene, so after a
Delete the running fresh session could no longer save (onSaveNow returned
false, the autosave guard skipped every write) until a page reload.

Wire an onDelete callback through SaveLoadDialogCallbacks: the dialog invokes
it right after deleteSave(), and GameScene clears autosaveSuspended (the bytes
that suspension was protecting are gone). Add an e2e regression test that
deletes the preserved future save and asserts Save Now then succeeds.

Also add direct unit tests for classifySaveCompatibility's four verdicts
(none / compatible / incompatible-future / incompatible-corrupt), locking in
the load-bearing distinction that a future-sim save suspends autosave while a
sub-MIN / tampered save does not (ship-review advisory).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@LightAxe

LightAxe commented Jun 5, 2026

Copy link
Copy Markdown
Owner Author

Addressed the P2 in f0556ce:

  • Clear suspension when the preserved save is deleted — wired an onDelete callback through SaveLoadDialogCallbacks. The dialog invokes it right after deleteSave(), and GameScene clears autosaveSuspended (the bytes it was protecting are gone). Added an e2e regression test (Deleting the preserved future-build save re-enables saving) asserting Save Now succeeds after the delete.
  • Also added direct unit tests for classifySaveCompatibility's four verdicts, locking in the future→suspend vs corrupt→overwrite distinction (internal-review advisory).

npm run verify green (2311 unit tests); the two #196 e2e tests pass in isolation.

One low-severity item I'm deferring for your judgment (not fixed): when autosave is suspended to protect a future-build save, Save Now shows the generic "failed" flash rather than a reason specific to the suspended state. It's a UX-wording/product decision (and the suspended-Save-Now-returns-false behavior predates this PR via the Continue path), so I left it rather than expand scope. Happy to follow up if you'd like it surfaced distinctly.

@LightAxe

LightAxe commented Jun 5, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 🎉

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@LightAxe LightAxe merged commit 8eb917e into main Jun 5, 2026
3 checks passed
@LightAxe LightAxe deleted the fix/196-preserve-future-build-save branch June 5, 2026 17:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Future-build save is not protected from overwrite (autosaveSuspended never armed on incompatible-save boot)

1 participant