Spaces:
Paused
Paused
Commit History
-
Odyssey: bound the living galaxy to 3 seed-drawn background worlds
Claude +1: Claude Opus 5Unverified signaturecreateGalaxy instantiated and simulated all 11 worlds from turn one. It now brings up the start seat plus a pseudo-random draw of BACKGROUND_WORLDS (3) others — backgroundWorldIds, keyed on the galaxy seed and the start world from its own distinctly-seeded stream, returned in fixed roster order so the background scheduler and every galaxy-wide scan read exactly as before. Which neighbours are alive is now part of what a seed means; same seed still replays the same galaxy. The rest of the roster stays DORMANT: still on the starmap, still jumpable, still a full skirmish map — built in on arrival (jumpCapital already did this for a never-visited destination) and simulating from then on like any world you leave behind. addPlanet now honours a standing claim when it builds a world in, so a dormant world the faction-spread sweep claimed flies those colours when the player finally lands on it. Three paths assumed every world's state existed, and are fixed here: - initiateJump treated a mis…b88cc9c
-
Phase 5 (replay + season): re-run any recorded match, and close a ladder
Claude +1: Claude Opus 4.8Unverified signaturedocs/competitions-and-elo.md Phase 5's remaining two bullets, which completes the plan — all six phases (0-5) have now landed, noted in a new closing section. REPLAY IS A RE-RUN, NOT A RECORDING. A stored history row already carries its world, its exact seed, its swapAsym half, its pinned difficulty and both entrants' strategies; the roster carries each entrant's archetype. That is the complete input to createSelfPlayState, so buildReplayConfig(row, ledger) rebuilds the match and startSpectatedMatch runs it through the spectator the previous stage built — same speed control, same Observer Mode, nothing captured, nothing stored. What gets rebuilt is the SEATING, not the labelling: a row is always reported A-relative but was played in one of the worker's two directions, so buildReplayConfig un-does flipRow's relabelling and restates the recorded outcome seat-relative, which is what lets spectatedMatchOutcome be compared to it directly. DETERMINISM HELD — BUT ONLY AFTER A REAL BUG, AND …090c049
-
Tier 3d: one DOM test double, not nine
Claude +1: Claude Opus 5Unverified signatureEight test files each grew their own `class FakeElement extends EventTarget` — hud, hudSelection, observer, overlays, saveload, starmap, techChart, update — plus four copies of the same no-op-Proxy fakeCtx and eight copies of a byte-identical fakeDocument. They were ~90% the same code, and the 10% that differed was not a considered difference: it was whichever subset of the DOM the file that copied it happened to need that week. overlays' copy tracked parents so `.remove()` really spliced and hudSelection's did not; hud's querySelector walked the tree and update's was hardcoded to null; observer's getBoundingClientRect returned a real 800x600 box and saveload's returned zeros; saveload's appendChild discarded its argument outright. A test written against a weak copy silently asserts less than the identical test written against a strong one, and nothing told you which one you had landed on. test/_dom.js is the union with the strongest implementation of each member. Two things stay conf…f9e883b
-
Add a configurable population cap (200/250/300/Max) for both sides
ClaudeUnverified signatureNo hard ceiling existed before this — supplyCap (engine/supply.js) was purely additive from completed buildings (Command Center +10, Habitat +8), unbounded above. Added a new setup.js option, Population cap, alongside the existing economy dials (Faction/Resources), offered in both Skirmish and Odyssey (the two modes with a real economy) — Max (the default) is byte-identical to today's always-uncapped behavior. - engine/supply.js: split the old supplyCap into buildingSupplyCap (the raw, unclamped additive total) and a new supplyCap that clamps it to state.popCap when set. Every existing caller (production.js's enforcement gate, hud.js's display) keeps calling supplyCap unchanged and transparently gets the clamped behavior. - state.popCap threads through createGameState, boot.js's startGame AND startOdyssey, and engine/galaxy.js's createGalaxy/addPlanet — applied unconditionally to every world (active seat and background neighbours alike, exactly like sizeMult/resourceMult…dadc0b2
-
Starmap live colony ledger: alert badges + garrison line
Claude +1: Claude Sonnet 5Unverified signatureMulti-colony Odyssey play routed all trouble through transient toasts (boot.js notifyColony, 5-9s, max 3 on screen); the starmap itself showed only status/income/stance/industry, so a world under attack, recently fallen, or undefended looked identical to a healthy one. - boot.js's notifyColony now records every attack/hostile/lost notification onto the session as game.colonyAlerts[planetId] = {type, at} (session.js), in addition to its existing toast. Written unconditionally for all three types, independent of the toast's own 9s throttle (which still only compares a world's own prior ATTACKED timestamp, unchanged). focusActivePlanet clears exactly the one world's entry when the player actually arrives there; bootState resets the whole ledger for a fresh game. Transient session state, like lastAttackAt/groups next to it — never persisted. - starmap.js's renderStarmap reads the ledger per world when building each node: a CSS badge class + sub-label reading "under atta…25e82b6 -
Reactive opening checklist + an honest, configurable endgame clock
Claude +1: Claude Sonnet 5Unverified signatureReactive opening checklist (replaces the static objectives strip): overlays.js's showObjectives() used to print one static sentence that auto-dismissed after 30s regardless of what the player had actually done. renderObjectives(state, galaxy) now walks a small per-mode predicate table (5 items for skirmish, 4 for Odyssey) and paints a live checklist; items check off with a flash as their predicate is satisfied. updateObjectives(game), called from hud.js's renderHUD every tick behind a cheap signature guard, keeps it reactive and retires the strip (remembering the completion via a per-mode localStorage flag) once every item is done, instead of a blind 30-second timeout. The strip is suppressed for a scripted scenario, whose own scenario bar already states that mode's objective. Make the clock endgame visible, honest, and configurable: - engine/victory.js's finish() now records state.winReason ("elimination" | "mutual-wipe-score" | "timeout-score") at each of checkWinCondition's …5bffdf0 -
Wire the start-world picker and asym-swap toggle into setup/boot
Claude +1: Claude Sonnet 5Unverified signaturesetup.js's Odyssey branch now renders a compact 11-world card row (the skirmish nine plus the two Odyssey-only extras) plus a default "Random" card, replacing the old single "land on a random world" button — reusing the skirmish card's own data (name, deposits tag, archetype doctrine, industry/tech). One click both picks the world (or Random) and begins, mirroring the skirmish cards below it. boot.js's startOdyssey threads the pick through as createGalaxy's opts.startId. The skirmish card grid gains a per-card "swap sides" toggle on any world with an asym block (Oort, Nimbus) — a plain `<span>`, not a nested `<button>` (invalid: a button can't contain another interactive control), with its own local pick so toggling one card can never leak onto a different card you click instead. boot.js's startGame threads the choice through as createGameState's opts.swapAsym. Not offered on scenario cards (setupEscort/Raider/Bounty don't consume swapAsym). setup.css companion styling: a narrower .m…2fa2e12
-
Test initiateJump gating, toast eviction, and update.js's banner logic
Claude +1: Claude Sonnet 5Unverified signatureinitiateJump's afford + needsPick branches were untested, including driving the real landingPicker.js openLandingPicker through a fake DOM rather than stubbing it away. overlays.js had no test file at all - showGalaxyToast's three-way eviction rule (evict oldest non-clickable; reject a plain toast rather than evicting a clickable one; evict oldest clickable only when the new arrival is also clickable) is now covered exactly. update.js was never imported by any test; fixes the same missing `typeof window !== "undefined"` guard saveload.js already uses (update.js's kickoff otherwise arms a real 30-minute setInterval that hangs node --test indefinitely - confirmed empirically before and after the fix) and tests showBanner's three save-compatibility branches plus checkForUpdate's exact-match dismissal via a real simulated button click. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F4Z8xu1oLPhhJsPnb3bgg3e66bcd3 -
Test boot.js's difficultyDials fallback and resolveSeed
Claude +1: Claude Sonnet 5Unverified signatureBoth are module-private, driven through a real startGame() call: an unrecognized difficulty key must silently fall back to Medium's dials (never throw, never some other plausible-but-wrong default), and resolveSeed must honor an explicit seed exactly or draw a fresh valid unsigned-32-bit one when none is given. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F4Z8xu1oLPhhJsPnb3bgg388ee238 -
Test boot.js's refcounted pause reasons
Claude +1: Claude Sonnet 5Unverified signaturepauseLoop/resumeLoop/togglePause back every pause source in the game (manual P key, Help, the landing picker, the Home-confirm modal) with a single refcounted Set specifically so closing one doesn't resume a game paused another way — untested until now. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F4Z8xu1oLPhhJsPnb3bgg300c92fc