000143231b
Classic, Zen, and Challenge already auto-saved correctly via the existing game_state.json path — GameState carries mode and the save/restore systems are mode-agnostic. Time Attack was the gap: the per-deal GameState round-tripped fine, but the session-level TimeAttackResource (10-minute countdown + accumulated wins) defaulted on every launch, so closing mid-session reset the timer and erased the win count. Adds a sibling time_attack_session.json next to game_state.json, atomic .tmp + rename via the existing save pattern. The new TimeAttackSession struct carries remaining_secs, wins, and saved_at_unix_secs (wall-clock anchor for stale-session detection). load_time_attack_session_from_at takes an injectable now() so tests can drive deterministic clock scenarios. Load logic: if now_unix - saved_at_unix_secs > remaining_secs the window expired in real time while the app was closed — return None so the player isn't dropped into a session whose timer ran out behind their back. Otherwise restore remaining_secs minus the real-world elapsed delta. Handles clock-running-backwards (NTP correction, VM clock drift) by clamping the elapsed delta at zero. time_attack_plugin wires four new systems: load on Startup, clear stale file when a fresh session starts (rare — only matters when the previous session was abandoned + a new one started without exit/relaunch), 30-second auto-save while a session is active, delete file on natural expiry, and save on AppExit. The save file is removed every time the session ends so a stale "session exists" state can't pollute the next launch. No GameState schema bump needed — the per-mode session lives in its own file. stats / progress / achievements / settings unaffected. 8 new storage tests cover round-trip, expired-discard, time-decay, atomic-write, missing-file, corrupt-file, delete idempotency, and clock-backwards. 6 new plugin tests cover exit-persists, exit-clears, auto-save-cadence, auto-save-noop-when-inactive, new-session-clears-stale, and natural-expiry-clears. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>