Skip to content

feat(client): singleplayer save & resume UI and platform storage (companion to #5637) #5624

Description

@LoSt543215543

Summary

With the core engine snapshotting framework implemented in PR #5637 (Add versioned game state snapshots), the underlying simulation can now be serialized and restored at any tick boundary with zero turn re-simulation delays.

As noted in #5637, that PR provides the engine and worker APIs (WorkerClient.snapshot(), new WorkerClient(..., snapshotToRestore)) with "No UI".

This issue tracks the player-facing UI, platform save management, and runtime snapshot persistence to bring seamless singleplayer save and resume to users.


Scope of the UI & Client Save Layer

1. Singleplayer Modal UI (src/client/SinglePlayerModal.ts)

  • Resume Saved Match Card: When an in-progress solo save exists, displays a prominent resume card displaying map name and elapsed game time formatted as mm:ss (e.g. Resume Asia (03:45)).
  • Instantaneous $O(1)$ Load: Clicking "Resume" decompresses the gzip snapshot and passes the raw buffer into WorkerClient(startInfo, clientID, resumeSnapshot). The engine rehydrates in ~150 ms without any turn-replay catchup or battery/CPU drain.
  • Discard Action: Dedicated discard button with in-game confirmation modal (showInGameConfirm) to prevent accidental deletion.

2. Platform & Account Save Scoping (src/client/SinglePlayerSaveManager.ts)

  • Steam Integration: Isolates saves per 64-bit Steam ID (openfront_solo_save:steam:<steamId>:v1) via steamSDK.getSteamIdSync(), ensuring multiple Steam accounts on the same computer do not overwrite each other's games.
  • Web & CrazyGames Scoping: Keyed by persistent user identity.
  • Compression: Saves are compressed with gzip (CompressionStream / pako) to keep localStorage footprint light (~1.2 MB).
  • Legacy Migration: Automatically migrates any legacy un-scoped save keys on first boot.
  • Lifecycle Cleanup: Automatically clears the save state once a match is won or lost.

3. Auto-Save Lifecycle (src/client/ClientGameRunner.ts / src/client/LocalServer.ts)

  • Periodic Background Snapshots: Takes non-blocking engine snapshots via WorkerClient.snapshot() every 50 ticks during singleplayer gameplay and writes compressed state to storage.
  • Snapshot Preservation: Preserves the binary snapshot when LocalServer syncs sparse turns or handles beforeunload.
  • Exit Teardown: Automatically cleans up and purges saves when a match is won or lost (SendWinnerEvent).

Status & Companion Branch

  • Implementation is complete and verified on branch feat/singleplayer-ui-snapshots (built directly on top of PR Add versioned game state snapshots #5637):
    • tests/client/SinglePlayerSaveManager.test.ts (6/6 passed)
    • tests/core/snapshot/CoreSnapshot.test.ts (2/2 passed)
    • tests/client/SinglePlayerModalStart.test.ts (1/1 passed)
    • tests/EnJsonSorted.test.ts (1/1 passed)
    • tsc --noEmit, oxlint, and eslint clean (0 errors)
  • Ready to open as a companion PR once Add versioned game state snapshots #5637 lands!

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

approvedApproved for a PR, if you assigned to the issue.

Type

No type

Projects

Relationships

None yet

Development

No branches or pull requests

Issue actions