Spaces:
Paused
Paused
Commit History
-
Phase 5 fix: the ⌂ Home confirm was the third, un-closed door to Save
Claude +1: Claude Opus 4.8Unverified signaturePhase 5 closed the save path for a WATCHED / REPLAYED AI-vs-AI match in two places — hud.js hides the topbar Save/Load buttons, and saveShape.js's resumableMode refuses to checkpoint one — but the ⌂ Home button is visible in EVERY mode and its confirm dialog knew nothing about game.spectateMatch. During a spectated match `scenario` is false and liveCompetitionFixture() is null, so the dialog fell through to the ordinary skirmish copy: "Your progress autosaves — Save & Exit checkpoints it now so you can Continue later", with **Save & Exit** as the default-focused primary button. Every word of that was false, and clicking it was worse than the copy. autoSave() returns false (resumableMode refuses a spectated match, so snapshot() is null), so it fell through to saveToFile(), which: • downloaded stellar-frontier-skirmish-seed<N>-<stamp>.json of the AI-vs-AI exhibition — a plain skirmish save that loads back handing the human FULL COMMAND of one entrant's army, the exact outcome…22ad0a6 -
Phase 4 (part 2): the playable Gauntlet — play a real match, rate it, resume it
Claude +1: Claude Opus 4.8Unverified signatureThe human-inclusive half of docs/competitions-and-elo.md Phase 4, on top of part 1's persisted ledger support: a fifth Competition tab where you play ONE live match against each AI entrant in a field, in order, and every result lands on the same bracketed ladder the AI entrants are rated on. One live match per opponent is the format's defining decision, not an economy measure: an AI-vs-AI pairing is worlds x seeds x 2 sides of simulated matches (seconds of compute), but a person cannot play 40 real matches. That is why Gauntlet is the human-inclusive format that ships first, and why the config screen prices a run in HOURS OF REAL PLAY before Start is clickable ("4 live matches — one per opponent — up to about 1 h 20 min"), defaulting to Quick (20 min), since the same field at Standard is over three hours. - boot.js startCompetitionMatch(fixture): a near-copy of startGame, deliberately — same resolveSeed/difficultyDials/createGameState/bootState chain, same loop, same HUD. A gaunt…1ff6f70
-
Phase 3 follow-ups: a false comment, a misplaced label, a false alarm
Claude +1: Claude Opus 5Unverified signatureThree nits from Phase 3's two review lenses, all cheap and all real: - pairing.js's knockout reconciliation note claimed "n - p is exactly the number of FIRST-ROUND MATCHES here", which is false when n is itself a power of two (n=8: n-p=0, but round 1 plays 4). Restated precisely. A comment asserting an invariant the code does not hold is a named defect class in this repo (docs/code-improvement-tiers.md pattern 1), so this is worth the words. - The Tournament screen's "Field" label was appended straight into .comp-screen while every sibling row (Format, Difficulty, Swiss rounds) wraps its label in a .setup-row, so it sat misaligned. Wrapped to match; the field list itself stays outside the row, being a full-width block rather than a label-plus-control pair. - A red "Autosave is failing -- use Save to export a file." banner fired on any menu screen after 36s, with localStorage perfectly healthy. autoSave() returns false both when a write throws AND when there is…f606a83
-
Two-generation autosave with fallback load and surfaced failure
Claude +1: Claude Sonnet 5Unverified signatureAn Odyssey campaign (or skirmish) lived in exactly one localStorage slot, overwritten every 12 seconds, and autoSave() failures were silently swallowed by the interval/visibilitychange/beforeunload callers — one corrupt or quota-failed write and Continue had nothing. saveload.js now rotates two generations per mode: - writeGeneration() moves the current primary to KEY+'.prev' before writing a fresh one. A setItem quota throw (from either write) gets exactly one retry: drop '.prev' and write the primary alone, so the backup can never starve the primary out of ever landing. - loadGame/loadOdyssey try the primary generation first, then fall back to '.prev' (tryDeserialize) if it's missing, corrupt, or fails deserializeGame/deserializeGalaxy's own checks — reusing those unchanged, so '.prev' gets identical sanitization to the primary. hasSave/hasOdysseySave/storedSaveVersions read both generations too, so "Continue" and the update-compatibility banner stay consistent w…5181359
-
Persistence: fix docs contradicting the code, document a hazard, add dialog a11y
Claude +1: Claude Sonnet 5Unverified signature- CONTRIBUTING.md told contributors to make the loader tolerant of older saves; README.md had two passages that disagreed with each other, one correctly saying an incompatible save is rejected, the other claiming loaders stay "backward-tolerant." The actual code (deserializeGame/deserializeGalaxy) does an exact version-match throw, no migration, no tolerance at all. Rewrote all three passages to describe what the code actually does, and to explain when a version bump is (and isn't) needed. - peekEntityId/restoreEntityId touch a module-global counter in engine/state.js from four call sites in persist.js, with no comment in persist.js itself flagging the shared-mutable-state hazard - the only place this was documented was a test comment elsewhere. Added a clear comment at the import site. - saveload.js's goHome() hand-built confirmation modal (gates real data loss) had no role="dialog", no aria-modal, and no focus management. Added both, plus focus-into-dialog-o…b6109dc
-
Tier 5: make the UI layer import-safe + unit-test the save-format decisions
Claude +1: Claude Opus 4.8Unverified signatureThe UI modules crashed at import under Node (`document is not defined` on the first line of dom.js), so none of their logic could be reached by `node --test`. Make the seams defensive and pull the correctness-critical save decisions into a DOM-free module that IS testable. - dom.js: resolve `document` through a `typeof document` guard, so every UI module that imports it loads under Node (handles come back null instead of throwing). In a real browser `doc` IS document and every handle resolves exactly as before — zero behavioural change. - saveload.js / overlays.js: guard the top-level browser-only wiring (the autosave setInterval + window visibility/unload hooks; the seed-chip, help and global-hotkey listeners) behind a `typeof window` / element-truthiness check, so importing the module doesn't start a real timer or dereference null. - saveShape.js (new, no imports): the two save-format decisions factored out — isGalaxySave (a save with a planets[] is an Odyssey) and res…9b5e621 -
Tier 4: UI/audio/save cost — HUD topbar guard, audio-clock notes, stringify-once
Claude +1: Claude Opus 4.8Unverified signature- HUD topbar (hud.js): signature-guard the resource/supply/credits/power/stance readout the same way the selection panel is guarded — it was torn down and rebuilt (~8 createElement/ appendChild) every 150 ms even when nothing changed. Now the DOM is only rebuilt when a displayed value actually moves; the clock + idle count stay cheap per-tick text writes. - Sound (sound.js): schedule multi-note cues (the under-attack second blip, the victory/defeat jingles) on the sample-accurate WebAudio clock via a `when` offset on tone(), instead of setTimeout callbacks the browser throttles under main-thread load — exactly during a big battle — which smeared the rhythm. All four setTimeout wrappers removed. - Autosave (engine/persist.js, saveload.js): add serializeGameString/serializeGalaxyString that return the JSON string directly, and have the 12 s autosave use them — so the fog-heavy payload is stringified ONCE instead of stringify→parse→stringify. serializeGame/serializeGalaxy …b6d76f7 -
Tier 3 UX: pause, toast stack, control groups, hotkeys, touch & colour cues
Claude +1: Claude Opus 4.8Unverified signatureSeven UI/controls fixes from the review (all UI-side — no engine changes, so replay determinism is untouched and the 549-test engine suite is unaffected). - Pause (boot.js/overlays.js/saveload.js/style.css): a refcounted pause gates the update() callback (render keeps drawing the frozen frame). Opening Help or the Home-confirm modal pauses the sim; a P key toggles a manual pause with a centered PAUSED banner. The accumulator self-bounds (loop's backlog-drop), so resume never spirals and the fixed-dt sequence is intact. - Toast stack (overlays.js/style.css/index.html): #galaxyToast is now a column of up to 3 toasts, each with its own timer/onclick — a routine "Researched…" can no longer overwrite a one-shot clickable "colony has fallen — click to retake"; a plain toast never evicts a clickable one. Same showGalaxyToast signature. - Control groups (session.js/input.js/hud.js/dom.js/index.html/style.css): moved onto game.groups keyed per planet, so a group survives an Ody…1ad681c -
Tier 1 review fixes: save-load hardening, engine guards, UX bugs
Claude +1: Claude Opus 4.8Unverified signatureAddresses the Tier-1 (correctness & safety) findings from the codebase review. Save/load safety (engine/persist.js, saveload.js): - Validate values on load, not just JSON shape: drop entities of unknown type (an undefined def threw on the first tick, freezing the game AFTER load's try/catch returned), coerce numeric fields to finite/in-range (a string "time" concatenated; a NaN coord poisoned the spatial hash), and recompute the entity-id counter past every loaded id so a low/missing nextEntityId can't mint a colliding id. All identity for a valid save, so determinism is untouched. - Persist aiWaveCount — omitting it reset the AI economy-raid cadence on every reload, breaking continue-identically for Tactical games. - deserializeGalaxy now throws on structural nonsense (no worlds/planets, or an activeId with no planet) BEFORE the caller tears down the live game, and filters the world roster to real world ids. - autoSave returns success; "Save & Exit" falls back to a …a4bbde9
-
Add app versioning, auto-update check, save-compat signal, load sanitization
Claude +1: Claude Opus 4.8Unverified signature- Versioning: version.js is the source of truth (APP_VERSION semver, bump per release/merge) plus pure, tested helpers (parseVersion/compareVersions/isNewer/ saveImpact). version.json is the deployed release manifest. A version chip shows in the topbar and the setup menu. - Auto-update check (update.js): a long-lived tab can outlast a deploy, so it fetches version.json (cache-busted) on load + every 30 min and, when the deployed release is newer than this build, raises a dismissible banner with Reload / Later. Network/parse errors are silent. - Backward-compat signal: the banner tells the truth about save data — comparing the manifest's minimum readable save-format versions against the player's stored saves (saveImpact), it says whether they carry over ("✓ Backward compatible") or would be orphaned ("⚠ won't load — export to a file first"), nudging a file Save. - Load sanitization (engine/persist.js sanitizeSave, run at the top of both deserializers): a save from a …755b556 -
Save/Load to file; autosave + resume via localStorage
Claude +1: Claude Opus 4.8Unverified signatureSplit the two save channels so each does one job well: - The topbar Save / Load buttons now move a save to and from a FILE. Save downloads the current game as stellar-frontier-<mode>-<stamp>.json (galaxy or skirmish); Load opens a file picker and imports one, auto-detecting the mode by shape (a galaxy carries `planets`). Explicit backups you can keep and transfer. - localStorage is now the automatic checkpoint. autoSave() serializes the current game (skipping a finished game or a non-savable scenario) on a 12s timer, when the tab is hidden, and on unload — no manual step. The map-select "Continue" buttons resume straight from it, and Home's "Save & Exit" checkpoints on the way out. loadGame/loadOdyssey (the resume path) still read localStorage. setup.js: the resume buttons are relabelled "Continue — resume autosave". index: the Save/Load buttons get ⭳/⭱ glyphs and file-oriented tooltips. Engine/persist untouched (493 tests still pass). Browser-verified: autoSave writes…f80d3ea
-
Add a Home button that confirms saving before returning to the menu
Claude +1: Claude Opus 4.8Unverified signatureA topbar ⌂ Home button (beside Save/Load) returns to the setup screen, first asking whether to save. A skirmish/Odyssey gets a "Return to the menu?" modal with Save & Exit / Exit without Saving / Cancel; a scenario (which can't be resumed) gets a plain "Leave the mission?" with Leave / Cancel. Cancel, the backdrop, and Esc all dismiss it. - index.html/dom.js: the button. - boot.js: export restartToMapSelect and stop the loop on the way out (idempotent — game over already stopped it), so the game freezes behind the menu. - saveload.js: goHome() builds the confirm modal, routing Save & Exit through the existing mode-aware save() (galaxy vs single-state). - style.css: modal + .btn primary/ghost variants. UI only; 468 tests pass. Browser-verified all paths: Cancel keeps playing, Exit writes no save, Save & Exit writes one, and a scenario offers no save option. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AEQUVetLk31Jj7niDQA…cf3fc9b -
Odyssey: perf throttling, galaxy save/load, aggression ramp, cleanups
Claude +1: Claude Opus 4.8Unverified signatureImplements the three remaining review items (each brainstormed by a subagent, then reconciled into one plan). Perf — background-world throttling (engine/galaxy.js, boot.js): - stepGalaxy() advances the whole galaxy per frame: the active world at full cadence, background colonies on a coarser fixed step (BG_STEP×dt) spread round-robin by roster index. Keyed to an integer galaxy.tick, so it's deterministic (no wall-clock, no Map-order dependence) and conserves each colony's sim time. boot.js's loop calls it instead of ticking every world every frame. Galaxy save/load (engine/persist.js, saveload.js, boot.js, setup.js, hud.js): - serializeGalaxy/deserializeGalaxy extend the existing seed+delta persistence: a shared serPlanet/rehydratePlanet core (maps regenerate from seed; entities, node amounts, fog memory, AI bookkeeping restored), plus per-world market pressure + diplomacy stance and the galaxy meta (credits, active world, tick, entity counters, the shared global id…7e2a606
-
Refactor: split the main.js god-file into focused modules
Claude +1: Claude Opus 4.8Unverified signaturePure code-health, behavior-preserving: main.js goes from 1001 lines to a 117-line composition root, with its concerns extracted into cohesive modules: session.js — the live { state, input, supplyBlockedUntil } holder that boot reassigns and every UI module reads (was the main.js closure vars). dom.js — every getElementById handle + MINIMAP_W/H + isTouchMode(), once. setup.js — the splash / map-select screen and the shared `setup` config. boot.js — startGame, bootState, the fixed-timestep loop, the frame-event sound/effects pump, and the under-attack alert. hud.js — renderHUD, the selection panel (rebuild + live patch), the button / tooltip factories and formatters. overlays.js — seed + faction chips, the objectives strip, the help overlay, and the game-over screen. saveload.js — localStorage save / load. main.js — imports the graph (each module self-wires its own listeners), owns …d32ddf2