Spaces:
Paused
Paused
Commit History
-
Tier 1, step 5: close the coverage gaps and the doc drift (C6-C16, E1, E2)
Claude +1: Claude Opus 5Unverified signatureThe mutation-proven gaps first. Each of these was verified by re-introducing the exact defect and confirming the new test goes red. C8 — drawBuildingBars and drawBuildGhost were covered only by assert.doesNotThrow, so replacing either body with `return;` survived the full suite: every health bar, build-progress bar, output gauge and paused/stalled badge in the game could vanish with CI green on both Node versions. The recording ctx this file already had is now used to assert real output, that a damaged building gets a bar, and that a paused producer is drawn differently from a running one. (Not "draws more" — a running-but-throttled factory draws the busier badge of the two.) C9 — dropping aiColonyTarget, aiLastThreatAt, or the entire playerAi block from persist.js also survived. Re-checked by mutation: both single-field drops now fail. The failure mode is "the AI behaves differently after reload", the hardest class of bug to report. C14 — commit ac44339's ore-surplus army-cap featu…cce54f5
-
Make the right-side selection panel foldable on desktop/landscape
ClaudeUnverified signatureThe panel could only be collapsed in portrait mode (the bottom-sheet .sheet-toggle handle) — on desktop/landscape it was permanently visible with no way to reclaim that width for the view. Added a second, parallel toggle (#panelToggle / .panel-toggle) for the normal side-column layout: a small handle riding the seam between #view and #selectionPanel (vertically centered, right edge of #view), visible everywhere the portrait bottom-sheet mode isn't. Clicking it hides #selectionPanel via a new `panel-collapsed` body class, and #view's existing flex:1 automatically reclaims the freed width — the canvas refit comes for free from main.js's existing ResizeObserver on #field, same as the portrait toggle already relies on. Each toggle's click handler clears the other's collapsed class, so resizing/rotating across the portrait/landscape breakpoint can never leave one toggle's glyph out of sync with the panel's actual visibility. Verified in-browser: folding grows the canvas from 996px to 125…e6ecfb1 -
Fix Tech & Industry Chart never opening: main.js never imported it
ClaudeUnverified signaturetechChart.js is a self-wiring leaf module, same pattern as starmap.js and update.js — its topbar-button click listener, T-key toggle, Esc, and backdrop-click handlers are all attached at module-load time. But main.js never imported it, so none of that wiring ever ran: the 📊 Tech Chart button and the T hotkey silently did nothing. Verified in-browser (Skirmish match): button click, T key, Esc, and backdrop click all now open/close the overlay correctly.2a07a69
-
HUD alerts + selection UX: minimap attack pings, base cycling, idle-production chip, Alt-subtract
Claude +1: Claude Sonnet 5Unverified signatureThree related input.js proposals landed as one coherent pass, per the roadmap's Phase 3 UX grouping (docs/improvement-proposals.md lines 351-372, 563-572): - Attack pings on the minimap plus a jump-to-last-alert key: effects.js exports activePings() (the same under-attack pings addUnderAttackPing already feeds, kept alive far longer via a new MINIMAP_PING_LIFETIME_MS so the minimap can still show them once the world-space ping and the banner have faded); minimap.js draws pulsing red rings for them; boot.js threads activePings() into its drawMinimap call and moves the remembered attack site onto game.lastAttackAt (session.js, next to supplyBlockedUntil, which already crosses this exact module boundary) so input.js's new Backspace binding can jump to it too, not just the banner's own click handler. - Space cycles bases and an idle-production topbar chip: centerOnBase() used to always land on the first completed Command Center a .find() happened to hit; repeated presses wi…18ec8e7
-
Refit the canvas on any layout change, not just window resize (topbar-wrap bug)
Claude +1: Claude Opus 4.8Unverified signatureOn a narrow window the topbar's flex-wrap pushes its actions (the Help "?" among them) onto a second line, growing the topbar and shrinking the view below it — but that fires no window `resize` event, so resizeCanvas never ran. The backing store kept its old, taller size: the never-redrawn strip showed as a thick band at the bottom of the view, and because the render viewport and the click→world transform then disagreed on the canvas height, every selection landed offset. A ResizeObserver on the canvas refits the backing store the instant its box changes — covering the topbar wrap, the Home button appearing, and the portrait sheet collapse in one place — and re-clamps the camera into the map. Verified in a real browser: narrowing to 460px wraps the topbar to 83px and the backing store tracks the new client box exactly (no stale strip, no offset). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AEQUVetLk31Jj7niDQAsXLd90ceb8 -
Tier 4: draw-cost cuts — minimap cache, health-bar pass, spriteIcon, DPR
Claude +1: Claude Opus 4.8Unverified signature- Minimap (minimap.js): the explored-fog wash and terrain features were re-scanned cell-by-cell every animation frame (16k+ sub-pixel fillRects into a 200x125 canvas). Both are static/near-static, so they're now rendered once into an offscreen canvas and blitted each frame, rebuilt only on a new map or every ~12 frames. Nodes, entity dots and the camera rectangle stay live. - Health bars (render.js): split drawUnits into a hull pass and an overlay pass, and defer building health bars to after all hulls (drawBuildingBars) — so in a dense melee a later unit's hull no longer paints out an earlier unit's bar, and a ship passing over a base no longer erases the base's bar. - spriteIcon (render.js): only cache a SUCCESSFUL render (a transient failure used to be cached as "" forever, permanently downgrading that button to text-only); rasterize at device-pixel density (min 2x) so button art stays crisp on high-DPR displays. - DPR (main.js): a self-rearming matchMedia watcher …2d6f3fe -
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
-
Odyssey Phase 5: the starmap — the galaxy's capstone view
Claude +1: Claude Opus 4.8Unverified signatureA full-screen galaxy map ties the Odyssey together. Every world sits on a ring, coloured by your relationship to it: your active capital, a colony you hold (with its neighbour's stance), or somewhere still unexplored (shown with its AI doctrine). If your capital has a Spaceport and the credits for fuel, clicking a world jumps there straight from the map; otherwise the header prompts you to build one. A visited-worlds count echoes the original game's "see every world" thread. - starmap.js (new): the overlay, self-wired to a topbar "Galaxy" button and the M key (Esc / backdrop closes). Pure view over a new galaxy.js galaxyStatus() snapshot (per-world status + stance + the visited/total counts). - hud.js: the Galaxy button shows only in Odyssey; the skirmish Save/Load buttons hide there (galaxy persistence isn't supported, so a skirmish save would mislead). - index.html/dom.js/style.css: the button, the overlay, and its styles — including the per-component `.hidden` rules thi…842eae1 -
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 -
Portrait mobile layout — a collapsible bottom sheet for one-handed play
Claude +1: Claude Opus 4.8Unverified signatureFinishes the mobile story: held upright on a phone, the game now reshapes from the desktop "canvas + right-hand panel" into a full-width battlefield with the selection panel as a BOTTOM SHEET — the standard one-handed mobile shape. Layout (pure CSS, scoped to `@media (orientation: portrait) and (max-width:820px)`): - #main stacks into a column: the battlefield spans the full width up top, the panel becomes a full-width sheet along the bottom. - The sheet is height-adaptive (as tall as its content, capped at 42vh so the battlefield keeps most of the screen), scrolls when it overflows, and pairs its buttons two-per-row so it shows more at a glance. - `min-height: 0` on #view lets the flex column shrink the canvas so the sheet actually gets its share — without it the canvas's intrinsic height shoves the sheet off the bottom of the screen. - The minimap is repositioned/shrunk for the portrait view. Collapse handle: a floating ▾/▴ button (a sibling of the panel, so panel rebuil…d088d6d -
Faction asymmetry — pick a side with its own passive identity
Claude +1: Claude Opus 4.8Unverified signatureThree playable factions, each a small bundle of passive trait multipliers — so a faction changes how a side plays without touching the shared roster, the rock-paper-scissors triangle, the tech tree, or the AI's build logic: • Frontier — mobility & vision (speed +8%, sight +6%): strikes first, holds the map. • Miners — industry (haul +15%, build/train −10%): out-scales in the long game. • Syndicate — firepower on a lean economy (damage +10%, haul −8%): win the fight, fast. Distinct axes with a real opportunity cost (Syndicate pays economy for its punch), so the pick is a strategic fork, not power creep. How it plugs in (engine/factions.js): A faction's traits are keyed on the exact side-modifier names the engine already reads, so they fold straight into map.js's sideMod — one seam, and every existing consumer (movement speed, fog/aggro sight, gather yield, build/train time) picks up the faction edge for free, stacking multiplicatively with a world's own per-side mod…ec7c7c8 -
Touch / mobile controls — play the whole game by finger
Claude +1: Claude Opus 4.8Unverified signatureA full one-button, no-hover touch scheme layered alongside the existing mouse + keyboard path, plus a responsive HUD so a phone or tablet in landscape (the natural RTS orientation) is fully playable. Gestures (input.js touch handlers): • tap your unit/building ... select it (double-tap = all of that type on screen) • tap elsewhere ........... command the selection — move · tap a foe to attack · tap a node to gather · set a producing building's rally • one-finger drag ......... box-select your units • two-finger drag ......... pan · pinch ...... zoom (anchored on the pinch) • Build / Attack-Move ..... tap places the building / commits the attack-move The touch and mouse paths now share one set of helpers (applyBoxSelection / commandAt / selectSameTypeAt), so a finger-tap issues byte-identical orders to a right-click — the mouse handlers were refactored onto the same helpers with no behaviour change. camera.js: dragCamera + pinchZoomPan — pure v…e7bc56d -
Tier 4: the Mender — a support drone that repairs the army and base
Claude +1: Claude Opus 4.8Unverified signatureA new "support" role, the roster's first non-combatant that isn't a worker or a scout. The Mender is a fragile, unarmed drone (70 hp, no attack of any kind) trained at the Barracks and gated behind the Foundry. Each tick it heals every damaged friendly unit AND building within repairRange at repairRate hp/sec — capped at each target's maxHp, never itself, never a construction site. Why it matters: until now every hp lost to a fight was permanent, so attrition always favoured whoever could out-produce. The Mender makes *keeping* an army alive a real strategy — a bruised force can heal between waves, and a besieged base can patch its buildings — without any new resource or macro burden, since the healing is a passive area effect. Engine (deterministic, replay-safe): - entities.js: the Mender unit def + "mender" in barracks.produces. - repair.js (new): updateRepair — one global pass after all combat has resolved for the tick. Each Mender adds a fixed repairRate*dt and clamps to maxH…6f0f031 -
Tier 3: accessibility — foe marker on enemy buildings + click-to-jump alert
Claude +1: Claude Fable 5Unverified signatureTwo colour-independent cues so the game reads without relying on the cyan-vs-red hue. - Enemy buildings now carry the same downward foe ▼ marker that enemy units do (render.js, reusing drawEnemyPip). Friend/foe becomes a SHAPE cue, not colour alone — a colourblind player can tell an enemy base from their own. - The "Under Attack" banner is now clickable and jumps the camera to the attack (main.js tracks the last hit even while the alert is throttled; the banner hints "▸" and a title). A raid on the far side of a Gigantic map is one click away instead of a frantic scroll. Render/UI only — no engine change, 297 tests pass. Browser-verified the enemy building shows the foe marker and the friendly one does not. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AEQUVetLk31Jj7niDQAsXL406718a -
Tier 3: save / load — resume a game exactly where you left off
Claude +1: Claude Fable 5Unverified signatureThe payoff of Tier 1's seeding. Because the sim is deterministic and seed-driven, a save doesn't need the whole world — the map (terrain, node positions) regenerates from the seed, so we persist only the seed + the DYNAMIC state: entities, economies, fog memory, and the AI's bookkeeping. - engine/persist.js: serializeGame(state) -> a detached, JSON-safe snapshot; deserializeGame(save) rebuilds a state that ticks on from exactly where it was saved. visible fog is recomputed on load (only permanent `explored` memory is stored); the entity-id counter is snapshotted/restored (new state.js peekEntityId/restoreEntityId) so a loaded game keeps minting non-colliding ids. - state.js records planetId/sizeMult/resourceMult so the map can regenerate. - main.js: Save/Load buttons in the topbar and a "Resume saved game" button on the splash; startGame's boot path is factored into a shared bootState() that both fresh and loaded games funnel through. localStorage-backed. Additive — it ne…dfed415 -
Tier 2: a third upgrade doctrine — Logistics (economy + tempo)
Claude +1: Claude Fable 5Unverified signatureUpgrades were only ever ±damage; add a macro path so "out-produce them" is a real strategic fork against "out-fight them". Logistics is a third mutually-exclusive doctrine (via the same committedDoctrine lock) with two tiers: Logistics Network (T1) — +25% resource yield from every haul Rapid Fabrication (T2) — -20% unit & building production time Committing to it locks Assault AND Bulwark, and vice-versa — so it's a genuine opportunity cost, not a free third buy. Plumbed data-driven: a new shared entities.upgradeMult(upgrades, field) is the one place combat/gather/production read upgrade effects, so a new upgrade is pure data. combat.js's two inline damage-mult loops are refactored onto it (behaviour identical); gather.js reads gatherYieldMult, production.js reads produceTimeMult (construction and unit production), both optional-chained so map-less test stubs still pass. The Refinery UI is already data-driven — it picks up the new doctrine from the UPGRADES table plus a label …3195ab6 -
Difficulty levels: fold AI speed + tactics into Easy / Medium / Hard
Claude +1: Claude Fable 5Unverified signaturePlaytest feedback: the Tactical AI reads as very smart, so surface it through a single, standard difficulty pick instead of two separate expert dials. The old "AI speed" slider and "AI tactics" toggle are replaced by one Difficulty selector: Easy — 20 APM, no micro (slow, holds formation) Medium — 65 APM, no micro (a fair fight; the default) Hard — 140 APM, Tactical (fast; focus-fires, kites, scouts with a Ranger) Each level maps to {aiApm, aiMicro}, which flow through unchanged — so Hard is the tactical opponent, Easy/Medium are approachable, and the setup is simpler. Per- world opponent doctrine (Rusher/Economist/Balanced) is unchanged and orthogonal. UI-only; the engine, determinism, and the resolve guarantee are untouched (279 tests pass). Browser-verified: the selector shows Easy/Medium/Hard with Medium active, and starting on Hard boots clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AEQUVetLk…72fd796 -
Tier 2 UX: Hold-position stance + a topbar idle-worker indicator
Claude +1: Claude Fable 5Unverified signatureTwo safe quality-of-life additions while balance items await playtesting. Hold position (H / a panel button): a combat unit stands its ground and fires on anything that enters weapon range but never CHASES an auto-acquired target out of position — the standard defensive stance for holding a line or a choke. combat.js honours a new unit.hold; any move/attack order or Stop clears it. Player-only — the AI never sets it — so determinism and the resolve guarantee are untouched. Idle-worker indicator: a lost worker on a big map is easy to miss, so the topbar now shows a "N idle" badge (hidden at zero) that jumps to the next idle worker on click, mirroring the ` key. Both surfaced in the help overlay and the controls legend. Tests (+2, 279 total): a holding unit fires an in-range enemy but won't chase an out-of-range one; issueHold sets the stance on combat units only and a move/Stop clears it. Browser-verified: the badge shows "3 idle" at start and the help lists Hold; clean boot. Co-Au…fea25dd -
Tier 2: Tactical AI focus-fire + an AI-tactics setup toggle
Claude +1: Claude Fable 5Unverified signatureThe single biggest AI upgrade: an opt-in "Tactical" mode where the opponent micros its army like a skilled player instead of leaving every shot to the dispersed auto-acquire. Focus-fire (engine/ai.js applyFocusFire + combat.js): when Tactical and it can SEE enemy combat, the AI points its whole nearby army at one target — the lowest-HP visible enemy, tie-broken toward the more dangerous one — so kills land faster and incoming damage drops sooner (Lanchester's square law). combat.js reads a new unit.focusId and prefers it while it's a live enemy in aggro, else falls straight through to the normal dispersed acquire, so a stale or too-far focus never freezes a unit and orders/wave-tracking are untouched. In a 10v10 skiff mirror this flips a 0-4 loss under dispersed fire into a decisive 5-0 win. Crucially it's a NO-OP unless real enemy combat is in sight, so razing an undefended base — every resolve test — is unchanged. Difficulty is now more than an APM dial: a new "AI tactics" setup o…e42e5cb -
Fix: move the waypoint-queue modifier off Shift (Firefox forces the native menu)
Claude +1: Claude Fable 5Unverified signatureShift+right-click can't be intercepted in Firefox — it force-shows the browser's own context menu and bypasses the page's preventDefault, so a Shift-queued waypoint never registered. Move the queue back to Ctrl+right-click, which honors preventDefault everywhere, and update the help overlay, controls legend, and the selection hint to match. (Ctrl+left stays add-to-selection; different buttons, no conflict.) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AEQUVetLk31Jj7niDQAsXL4695c42 -
Tier 1: seed the sim, attack-move/hotkeys, onboarding, rally-to-mine, tiebreak
Claude +1: Claude Fable 5Unverified signatureSix high-leverage quick wins from the improvement review. Seed the sim (engine/rng.js, state.js, main.js): production now draws a seed at match start and flows it through a shared mulberry32, so a match is reproducible — the seed shows on a topbar chip (click to copy) and the game-over screen, and can be re-entered on the setup screen to replay the exact same world. This is the keystone that later unlocks replays/save-load. Engine determinism is now guarded by a test that scans engine/ for Math.random/Date/performance.now (the two sanctioned defaults + the render clock are marked deterministic-exempt). Standard army verbs (input.js): A-then-click issues attack-move (the missing genre default — plain right-click still moves and ignores enemies); the waypoint queue moves from Ctrl to Shift so Ctrl is add-to-selection only; Esc cancels a pending attack-move or build placement. A crosshair cursor shows when armed. A is no longer a pan key (pan left with arrows/edge-scroll). Onboarding (…4f1cb54 -
Ranger scout unit + AI hunts down hidden Command Centers
Claude +1: Claude Fable 5Unverified signatureTwo related recon features. New unit — the Ranger (engine/entities.js, scout.js, render.js): - Trained at the Command Center like a Worker; cheap (45 ore) and fragile (50 hp) with only a token bite for self-defence. Its own role "scout", so it never auto-acquires and is never swept into the combat army. - Its three edges are a scout's: far sight (340, more than any combat unit), all-terrain movement (the existing `allTerrain` flag — ice fields and rough ground never slow it), and an autonomous SCOUT MODE (order "scout", the E key / a panel button). In scout mode it heads for the nearest unexplored ground on its own, its sight reveals it, it re-picks — a frontier that expands from home until the map is charted, then it patrols to keep vision fresh. Persistent and deterministic (a row-major fog scan, no rng), so it's safe in the sim. - Draws as a light recon craft with a sensor ring, distinct from the Skiff. A generic-unit fallback in the renderer keeps the "every enti…c310233 -
Industrial buildings double as resource drop-off points
Claude +1: Claude Fable 5Unverified signatureDecentralize the economy so reaching a distant deposit no longer forces a whole second Command Center. The Refinery, Foundry and Arsenal — the industrial line — now double as resource drop-offs that proxy the CC: workers bank their haul at the NEAREST completed drop-off, so planting one of these cheaper, faster buildings forward shortens the mining run. A forward Refinery (200 ore / 16s) collects for a claim instead of a full Command Center (400 ore / 30s), while the CC keeps its unique roles — training workers and granting supply — so it's shortened hauls, not an obsolete CC. - entities.js: `dropOff` flag on refinery/foundry/arsenal + an `isDropOff` helper (CC via isCommandCenter, or the flag) as the single source of truth. - gather.js: the deposit phase routes to the nearest drop-off, not only a Command Center. Still requires a COMPLETED building. - main.js: build-button tooltips note "resource drop-off" so the role is discoverable (and Habitat/CC now show their supply grant …eb17bae -
Tech tier 3: the Arsenal unlocks the Dreadnought capital ship
Claude +1: Claude Fable 5Unverified signatureExtends the tech tree one step past the Foundry: Barracks -> Foundry (Tier-2 units) -> Arsenal (Tier-3), reusing the requires/prereqsMet machinery. - entities.js: new Arsenal building (requires Foundry, ore-only, a pure gate) and the Dreadnought — a Tier-3 capital ship (requires Arsenal, trained at the Barracks). Tanky, long-reaching, heavy-hitting, 4 supply, and deliberately OUTSIDE the rock-paper-scissors triangle (no bonusVs, nothing bonuses it). Measured: at supply parity it out-values Bastion/Lancer/Breacher (a real late-game power spike), but a cheap Skiff swarm hard-counters it (0-10) — so it anchors an army without being spammable, and light units stay relevant. - ai.js: builds the Arsenal OPPORTUNISTICALLY from surplus (no reserve, so the Economist's core timing is unchanged — verified back to its prior ~360s); the Dreadnought is in the Economist's deep mix and effectiveMix keeps it out of the cycle until the Arsenal is up. The Tier-3 path is primarily a str…1ae322f -
Rework upgrades into two mutually-exclusive doctrines
Claude +1: Claude Fable 5Unverified signatureFixes the "buy both, no decision" upgrade design the review flagged: which upgrade you research is now a real strategic fork with an opportunity cost. - entities.js: the two flat upgrades become two doctrines of two tiers each — Assault (Overcharged Weapons -> Overcharged Core, +damage dealt) and Bulwark (Reinforced Plating -> Reinforced Bulwark, -damage taken). Each carries a doctrine + tier + a Tier-2 `requires` its Tier-1. New committedDoctrine() reports the path a player has locked into. T1 mults eased (1.2->1.15, 0.85->0.88) since a fully-teched doctrine now stacks two tiers. - production.js: researchUpgrade enforces the doctrine lock (committing to one locks the other out) and the tier gate (T2 needs T1, via the tech tree's prereqsMet). Assault costs radioactives, Bulwark crystals, so a world's deposit specialty tilts which comes easier. - combat.js: upgrade multipliers are now applied by iterating researched upgrades (data-driven), so the tiers stack multiplic…d69dcb5 -
Content: tech tree — a Foundry gate for the Tier-2 units
Claude +1: Claude Fable 5Unverified signatureTurns "all four combat units from turn one" into a real build-order fork. - entities.js: new Foundry building (ore-only, requires a Barracks) and a requires field gating Lancer and Breacher behind it. prereqsMet() is the shared source of truth (a building token needs a COMPLETED building; an upgrade token needs it researched). RPS triangle and stats untouched — only WHEN each unit is reachable changes. - production.js / commands.js: queueProduction and issueBuild reject a locked unit/building (prereq checked before affordability, so "locked" outranks "too expensive"). - ai.js: effectiveMix drops tech-locked units so the cycle runs Skiff/Bastion until the Foundry is up and can never stall on a locked entry. The AI builds the Foundry as priority infrastructure and reserves its ore from the unit stream while banking — otherwise it spends every spare 100 on a Skiff and never techs. Rushers drop Lancer from their mix (pure Tier-1, no tech delay); economist/balanced re…e3fdeef -
Tier 9: polish & accessibility
Claude +1: Claude Fable 5Unverified signatureAudio (sound.js / main.js / input.js): - Order-acknowledgement and selection cues — the most-missed feedback; a quiet click confirms a move/attack/gather/rally order landed and a blip confirms a selection. - Stereo panning by on-screen position, so you can hear which flank a fight is on; per-play pitch jitter so repeated hits/spawns aren't mechanically identical; a master-gain bus and a top-bar volume slider (separate from mute). Accessibility: - Enemy units carry a small downward pip — a SHAPE cue, so friend-vs-foe no longer rests on the cyan-vs-red colour alone (three-band health bars from Tier 7 help here too). - prefers-reduced-motion: the under-attack banner holds steady and the world/minimap pings de-animate to a static ring instead of pulsing. Visual juice: - A brief impact spark where each shot lands, so a hit reads as connecting rather than a line drawn through the target. Docs: README updated for the new controls (groups, Q/X/`, additive select, edge/minima…4f2ab31 -
Tier 7: bring controls & UX up to RTS standard
Claude +1: Claude Fable 5Unverified signatureControls (input.js / commands.js): - Control groups: Shift+1–9 bind, 1–9 recall (Ctrl+digit is the browser's own tab-switch shortcut, so Shift is the conflict-free bind modifier; matched on physical key code so it works with Shift held). - Stop (X) drops orders + queued waypoints; Select Army (Q); Idle Worker (`) cycles to and centres on the next idle gatherer. - Ctrl+drag/click adds to the current selection; double-click selects every on-screen unit of that type. - Edge scroll: cursor at a canvas edge pans the camera. - Right-click the minimap to order the selection there (move / attack-move) without scrolling the main view first. HUD & readability (main.js / render.js / style.css): - Unaffordable produce/build/research buttons grey out and play the denied buzz on click instead of failing silently; buttons carry stat tooltips. - Selected units/buildings keep their health bar even at full HP, and the bar now has three colour bands (green/amber/red) rather than a green/re…4e300be -
Tier 0: fix latent defects, add determinism/perf tests and CI
Claude +1: Claude Fable 5Unverified signature- Building construction no longer heals away combat damage every tick: updateBuildingConstruction now ADDS the hp built this tick (capped at the progress ceiling) instead of overwriting hp downward, so sustained fire can actually raze a half-built structure — raiding buildings-in-progress works for both sides now. - createGameState resets the entity-id counter so a fresh game is a pure function of its seed; same-seed runs replay byte-identically. - A worker re-tasked to a different-commodity node hauls its existing load home instead of silently discarding the mined resources. - render.js prunes dead entities from the facing Map each frame and exposes resetFacing(), wired into new-game startup — no more unbounded growth. - New tests: full-sim determinism, id-reset reproducibility, a perf guard, construction-under-fire persistence + destruction, cargo preservation. - Add GitHub Actions workflow running `node --test` on push/PR. Co-Authored-By: Claude Fable 5 <noreply@ant…430c532 -
Configurable skirmish: AI speed, map size, and resource abundance
Claude +1: Claude Fable 5Unverified signatureThe splash screen now configures the match before you pick a world. AI speed is a 1–150 actions-per-minute cap on the opponent. Every command it issues — produce a unit, place a building, expand, research, send the scout — spends one action from a budget that accrues at APM/60 per second and caps at a few seconds' burst; when it runs out it waits. So a low setting is a sluggish, forgiving foe and a high one is relentless. The attack commit is deliberately exempt, so even a crawling AI still throws its army at you and the game always resolves. Default (and every headless test) is unthrottled, so existing behaviour is unchanged. Map size scales the field self-similarly — Small (1600×1000), Standard, Large, Gigantic (4×) — with more hidden caches seeded into the bigger contested space, and the camera can now zoom out far enough to take a Gigantic map fully into view (and opens on your own base, not the map centre). At sizeMult 1 the layout is byte-identical to the original. Resources (…19ca109 -
Use Ctrl instead of Shift as the waypoint-queue modifier
Claude +1: Claude Fable 5Unverified signatureRebind the queue-a-waypoint gesture from Shift+right-click to Ctrl+right-click, and update the in-game hint, README controls, and engine comments to match. The command layer stays modifier-agnostic (it takes a queue boolean); only the input layer names the key. Ctrl also sidesteps the Firefox quirk that let Shift+right-click bypass the page's context-menu suppression, so the window-level preventDefault now holds across every browser — dropped that stale caveat from the comment. Verified in headless Chromium: Ctrl+right-click chains waypoints and the dashed path renders; 162 tests pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AEQUVetLk31Jj7niDQAsXL3f927e2
-
Suppress the browser context menu across the whole game window
Claude +1: Claude Fable 5Unverified signatureRight-click is a game command (move / attack / gather / queue a waypoint), but the canvas only suppressed the native context menu for clicks landing squarely on it. The view has 12px of padding around the canvas and the minimap sits on top of it, so a right-click on either opened the browser's own menu instead of issuing an order. A single window-level contextmenu preventDefault covers every surface at once. (Firefox deliberately lets Shift+right-click bypass this, a browser policy no page can override; every Chromium browser and Safari respect it. Verified in headless Chromium: a right-click over the view padding is now prevented, and canvas move/waypoint orders still fire.) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AEQUVetLk31Jj7niDQAsXLde0a7f9 -
Merge Tier 4 into main alongside Tier 3
Claude +1: Claude Fable 5Unverified signaturemain had advanced with Tier 3 (repeated AI attack waves, counter-building, minimap, under-attack alert, combat tracers/death flashes, build-ghost preview, production-queue display + cancel, panel aggregation) while Tier 4 (buildable expansion Command Center, Sentinel Turret + Breacher siege unit, supply cap + Habitat, 9-world roster with per-planet modifiers, AI parity) was built in parallel. This reconciles the two feature sets. Notable resolutions: - Placement validation: the two tiers independently shipped engine/placement.js (isValidPlacement) and engine/colliders.js (canPlaceBuilding + findPlacement). Consolidated on colliders.js — it's a superset the AI's expansion needs — and repointed the build-ghost preview and issueBuild at it; removed placement.js and its now-redundant test. - Combat: kept Tier 4's shared performAttack fire path and folded Tier 3's tracer data (fromX/fromY/unitType) back into its attackHit event, so both the siege heavy-hit sound and the attack …c769ac5 -
Tier 4 AI parity + planet roster with per-world rule modifiers
Claude +1: Claude Fable 5Unverified signatureThe AI now plays all of Tier 4, driven by extended archetype profiles rather than hardcoding: it fortifies with Sentinel Turrets on the approach vector (Economist 2, Balanced 1, Rusher 0), expands to a second Command Center when home ore runs thin (banking for it via an ore reserve that pauses the unit mix instead of starving it), runs one shared production cycle across multiple Barracks (fixing the one-unit-at-a-time ceiling), and folds the Breacher into the patient mixes where the map can pay for it. A spendable-node worker filter keeps a poor-economy world's AI off dead-end commodities. Every new field is read with a use-site default, so a legacy profile still runs. Opens the picker from 3 to 9 curated worlds, each assigned an archetype and up to one data-driven rule modifier threaded symmetrically through the engine: Glacius slows all units, Nimbus/Pyralis shorten/lengthen sight and aggro, Helix adds a crystal cluster, Oort enriches deposits, Forge speeds construction. All modifie…ac7d42f -
Tier 4 economy: supply cap and the Habitat that raises it
Claude +1: Claude Fable 5Unverified signatureArmy size was bounded only by ore, which flattened macro decisions. Now every unit costs supply (worker/skiff 1, bastion/lancer/breacher 2), Command Centers grant 10, and a cheap Habitat (75 ore, fast) grants 8 — a macro choice, a raidable weak point, and a hard bound on the sim's O(n^2) hot loops all at once. engine/supply.js recomputes used/cap on demand from state — no stored counter, so a destroyed building's queued jobs free their reservation with zero bookkeeping and drift is impossible. queueProduction reserves at queue time (all queued jobs count), rejecting after the affordability check and before payment so nothing needs refunding; a blocked attempt raises a productionBlocked event that the HUD turns into a beep and a red supply readout. Losing a Habitat can leave a player legally over cap — nothing dies, production just blocks until they rebuild. The AI raises a Habitat as it nears the cap (in the same change, so its production never silently stalls) and the HUD shows live…04b2d3e -
Tier 4 combat: Sentinel Turret and the Breacher siege unit
Claude +1: Claude Fable 5Unverified signatureStatic defense and the answer to it, shipped together so neither creates a degenerate strategy on its own. Sentinel Turret: a crystal-costed building (the first repeatable crystal sink) that acquires and fires through a new updateBuildingCombat in the sim's building loop. Combat's fire path is refactored into a shared performAttack used by units and turrets alike, so Overcharged Weapons already multiplies turret damage and all kill/event bookkeeping stays in one place. attackDamage stays strictly additive — a new bonusVsBuildings class bonus is 0 for every unit-vs-unit pairing. Breacher: a radioactive-costed Barracks unit deliberately outside the Skiff/Bastion/Lancer triangle. It outranges the turret and hits every building for a flat class bonus (prefersBuildings targeting), but has the worst anti-unit DPS in the game and dies to a single Skiff at half its cost — a turtle-breaker that is helpless without an escort. acquireTarget is refactored around a nearestEnemy helper; the defau…e6d8701 -
Tier 4 foundation: placement validation, buildable expansion CC, guaranteed ore
Claude +1: Claude Fable 5Unverified signatureAdd engine/colliders.js as the single source of truth for building placement: canPlaceBuilding rejects overlap with buildings, live resource nodes, and map bounds (units are deliberately ignored — they move, and separation already untangles them), and findPlacement does a deterministic outward ring search so the AI can slide a fouled build spot to nearby valid ground. issueBuild now validates placement before charging, so a rejected spot never leaks resources. The AI routes its barracks/refinery spots through findPlacement in the same change, so validation can't stall its build order. Make the Command Center buildable (400 ore, 30s) instead of free and instant; the starting CC is still seeded complete. Multi-CC dropoff and all-CCs victory already worked, so an expansion is a real second base with no further plumbing. generateMap guarantees a lean mirrored ore cluster near each base on any world whose deposit table yields no ore (distance-triggered, so ore-bearing maps stay byte-iden…1c88bda -
Tier 3: repeated AI waves + counter-building, combat feedback, minimap, build validation
Claude +1: Claude Sonnet 5Unverified signatureAI: - Replace the one-shot attack flag with a home-army/committed-army split, so the AI launches repeated waves for the rest of the match instead of attacking exactly once and then sitting on an ever-growing army. - Every third unit built reacts to whatever combat type the player fields most, building its hard counter (derived from the bonusVs tables, not hardcoded) instead of blindly following the archetype's mix -- the Skiff/Bastion/Lancer triangle now actually matters to the AI's play. Combat feedback: - attackHit/entityKilled sim events now carry enough info for a new effects.js (transient, client-side) to drive tracer lines and death flashes, drained each render frame the same way sound events already were. Situational awareness: - A minimap (fog, deposits, units/buildings, camera viewport) that doubles as a click-to-jump camera shortcut. - An "Under Attack" banner + alarm sound + world/minimap ping when the AI lands a hit on the player, throttled so a siege …cae3dc6 -
Add the Lancer: close the rock-paper-scissors triangle
Claude +1: Claude Sonnet 5Unverified signatureSkiff/Bastion was a single hard counter, not a real RPS cycle. The Lancer is a third combat unit (long-ranged, armor-piercing, squishier than a Bastion) whose bonus damage targets Bastion specifically, while Skiff's existing speed/cost edge is repurposed with a new bonus against Lancer — so each unit counters exactly the matchup that would otherwise be its worst, and nothing beats all three at once. - entities.js: new lancer unit def, bonusVs wired for all three legs of the cycle, added to Barracks' production list - render.js: distinct 4-pointed star silhouette so all three combat types plus Worker read apart at a glance - main.js: Produce Lancer button - aiArchetypes.js/ai.js: bastionRatio replaced with a generic unitMix cycle per archetype (all three now include Lancer), and the AI's army filter/attack-wave logic generalized off UNITS[type].role instead of hardcoded Skiff/Bastion checks - new combat.test.js/entities.test.js coverage proving each leg of the cycle and tha…89c12eb -
Tier 2: rally points, Refinery upgrades, AI archetypes, and sound
Claude +1: Claude Sonnet 5Unverified signatureFour features, in dependency order (smallest/most self-contained first): ## Rally-point UI (engine/commands.js, input.js, render.js) Right-click a single selected production building (Command Center or Barracks) to set where it sends newly produced units — the engine already stored an arbitrary rally point per building and read it fresh at spawn time, there was just no way to change it. Shows a dashed line + marker for the currently selected building only, to avoid cluttering the view with every building's rally line at once. ## Refinery + upgrades (engine/entities.js, production.js, combat.js) A new building (built by a Worker like the Barracks) researches two one-time, player-wide purchases: Reinforced Plating (crystals, -15% damage taken) and Overcharged Weapons (radioactives, +20% damage dealt). Both are live multipliers applied in combat.js's damage calculation rather than baked into unit stats at spawn, so they affect a player's whole army immediately, not just future product…9eded26 -
Tier 1: camera + zoom, fog of war, and a Skiff-countering unit
Claude +1: Claude Sonnet 5Unverified signatureThree independent features, the biggest-leverage items from a brainstormed improvement list, built in dependency order (fog of war's reveal effect only means something once you can actually pan around to see it). ## Camera + zoom (camera.js, new) The whole 1600x1000 map used to be squeezed into one screen — units were tiny dots. The canvas is now a fixed-resolution viewport with a camera (pan via WASD/arrows, zoom via mouse wheel toward the cursor, clamped to map bounds) instead of a 1:1 map-to-canvas mapping. render.js draws through a camera transform; input.js's toWorld() converts screen->world through it too, so selection/commands keep working exactly as before. Fixed a latent bug while wiring this up: attachInput() was never cleaning up its event listeners, so every "Choose another battlefield" replay stacked a fresh full set on the same canvas/window — added an AbortController-based destroy(). ## Fog of war (engine/fog.js, new) A coarse grid tracks which cells are currently in…34ea8bc -
Multiple workers can assist a build; fix skiffs getting stuck fighting
Claude +1: Claude Sonnet 5Unverified signatureTwo independent fixes, both about giving the player more control: 1. Multiple workers can now speed up the same construction. Right- click a worker onto an already-founded (constructing) friendly building to send it in as an extra builder — no new cost, it's not founding a second building. updateBuildingConstruction now scales its rate by how many same-owner workers are actually standing on site (capped at 4, so massing a crowd on one site has a limit), while a lone founding worker still builds at the original, unchanged pace. issueAssistBuild (commands.js) is the new player-facing command; BUILD_REACH is now shared between production.js and sim.js so "counts as on-site" and "close enough to stop walking" agree. 2. Skiffs (and any future combat unit) could not be redirected once an enemy wandered into aggro range: updateCombat called acquireTarget unconditionally whenever there wasn't an explicit 'attack' order, so a plain 'move' order got silently…e735d2f -
Fix build-button clicks getting dropped, not a sizing issue
Claude +1: Claude Sonnet 5Unverified signature"only takes effect on very small area of the button" turned out not to be about hit-area size at all — bounding-box hit-testing on the buttons was already fine (verified: every corner of the button resolved to the BUTTON element). The real cause: renderSelectionPanel tore down and rebuilt the entire panel's innerHTML on every ~150ms HUD tick, unconditionally, even when the selection hadn't changed. If that rebuild landed between a mousedown and mouseup, the button under the cursor got swapped for a brand-new DOM node mid-click, and the pending click could get dropped — which reads to a player as "only worked if I clicked exactly right." Reproduced it directly: a slow click (mousedown, wait 400ms spanning a couple of HUD ticks, mouseup) intermittently failed to register, with no spatial pattern to the failures (center and edge clicks both failed at different times) — a timing race, not a hit-area problem. Fix: only fully rebuild the panel when the set of buttons it should show would a…0fdd1b1 -
Fix worker stacking on a shared node; add a battlefield picker
Claude +1: Claude Sonnet 5Unverified signatureWorkers sent to the same node all walked to its exact center and rendered fully stacked. gather.js now sends each one to a stable per-unit point on a small ring around the node instead, so a group visibly spreads out around what it's mining. Also replaced the hardcoded single map with a pre-game picker (three ore-bearing worlds pulled straight from data.js: Ferros Prime, Korrath, Vesper), each with a genuinely different resource layout since node cluster count scales with the planet's own deposit yields. The post-game overlay now offers "Choose another battlefield" to start a fresh skirmish on a different world without reloading the page. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BDmJ8vc9jxc6TUegK9F2YJ6fe5e5b -
First playable slice: 1v1 skirmish vs scripted AI on Ferros Prime
Claude +1: Claude Sonnet 5Unverified signatureGather ore/crystals/radioactives with Workers, build a Barracks, produce Skiffs, fight, win by destroying the enemy Command Center. engine/ holds the sim: a fixed-timestep loop decoupled from render rate, movement, the gather/deposit loop, combat with auto-acquire, building construction + unit production queues, a scripted AI opponent, and the win condition. render.js/input.js are the canvas view and mouse controls (drag-select, right-click context orders, build placement); main.js wires it to a DOM HUD. Converted data.js's top-level tables to ES module exports so the engine can import them directly. Covered by 23 node:test cases including a full simulated skirmish that plays to a winner, and manually playtested end-to-end in a real browser (gather -> build -> produce -> victory/defeat UI) via Playwright. One real bug caught in that process: constructed buildings never got a `radius` copied onto the instance, which went on to poison unit spawn points and building hit-testing with Na…17641e3 -
Seed repo: shared universe data + real-time loop bootstrap
Claude +1: Claude Sonnet 5Unverified signatureCarries over data.js verbatim from the original Stellar Frontier repo (pure static data: factions, commodities, recipes, the 20 charted worlds) plus the style.css color palette, so this reads as the same universe. Everything else is new — index.html/main.js is a minimal canvas + requestAnimationFrame loop proving out the real-time tick, with no game state or mechanics yet. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BDmJ8vc9jxc6TUegK9F2YJ7a2b109