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
-
Document the main-branch gate, and fix the docs my own changes falsified
Claude +1: Claude Opus 4.8Unverified signatureThe three items left open after the review merge. BRANCH PROTECTION. I cannot set it: there is no MCP tool for the branch protection API and a direct call returns 403, so this is a repository setting only a human with admin can apply. What I can do is remove the guesswork, so CONTRIBUTING.md now carries the exact steps — target `main`, require status checks, and the two check names verbatim (`tests (node 20)` and `tests (node 22)`, both, because the matrix produces one per Node version and requiring only one lets a version-specific regression through), plus require-up-to-date and block-force-pushes. It also notes the coupling nobody would otherwise notice: those names come from the workflow's job `name:` line, so editing that silently unhooks the gate. DOCS THAT CONTRADICT THE CODE. Fixed the ones this branch's own work falsified, rather than pruning anyone's design work: - README's module map predated inputCommands.js and described input.js as one thing. It also called tools/ "a …2b4cf02
-
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
-
Add an Odyssey total-domination strategy guide (Hard/Gigantic/Abundant/Pop-200)
ClaudeUnverified signatureDocuments a conquest playbook for pacifying all 11 worlds against Hard difficulty, grounded directly in engine/galaxy.js, engine/diplomacy.js, engine/aiArchetypes.js/aiDifficulty.js/aiStrategy.js, engine/supply.js and engine/combat.js, and backed by real tools/ailab.js probes per archetype. Links from README's Odyssey section.236011f
-
Update README and the player field manual for everything shipped since 1.0.0
Claude +1: Claude Sonnet 5Unverified signatureThe docs had fallen behind a large batch of unreleased work: Observer Mode, veterancy, three new static-defense buildings (Bastille, Aegis Bastion, Plasma Torpedo Battery), a Grid Substation, the Chemical Plant/Fabricator trade-industry branch, three-tier Assault/Bulwark doctrines with timed research, formations ranked by weapon range, Patrol orders, a Tech & Industry Chart hotkey, a rival AI Antimatter Gate, deeper Domination/diplomacy (gifts, favor requests, faction stance ripples), Freight Lanes, colony standing orders, bulk trading, a Technologist AI archetype, configurable match length, and an Odyssey starting-world picker — plus fixes to since-corrected claims (the AI's "no counter outside the triangle" blind spot, gatherers idling at a dry node, high ground's blind spot against itself). Every new number (veterancy ranks, building stats, doctrine costs/timings, concealment fractions, AI counter-pick cadence, etc.) was verified directly against engine/*.js and data.js rather than…d3432c5
-
Make Bulwark's structure shielding official
Claude +1: Claude Sonnet 5Unverified signatureattackDamage (engine/combat.js) already applies damageTakenMult to every target it computes damage for -- units AND buildings alike, with no kind/role filter -- so a Bulwark player's turrets, Command Center, and Habitats already took reduced damage, not just their combat units. The UPGRADES.reinforcedPlating/reinforcedBulwark desc strings (updated in the prior doctrine-timing commit, since it was already touching these same entries) now say "all units and structures" instead of "all combat units", and README.md's doctrine paragraph now says so too: Bulwark is the turtle doctrine for the base as much as the army. This is documentation plus coverage of live behavior, not a new mechanic -- confirmed by reading attackDamage/performAttack/ updateBuildingCombat directly (performAttack is shared verbatim by updateCombat, updateWorkerCombat, and updateBuildingCombat, and attackDamage's damageTakenMult multiply has no target.kind check). Added two new pinning tests to test/combat.test.js: a tu…4692ce2
-
Add an AI bench for Odyssey, and a measured review of the opponent
Claude +1: Claude Opus 5Unverified signatureThe engine suite guards invariants (determinism, purity, "a skirmish resolves to a winner") and says nothing about AI QUALITY, which in a play-forever mode is a curve over 40+ minutes rather than an assertion. So several real defects were sitting in a fully green suite. tools/ailab.js is a headless bench: it runs the real sim (a 40-sim-minute world costs ~7s), drives the player side with scripted sparring bots, samples an AI-quality metric set, and prints a scoreboard plus a named-defect list. Subcommands: probe / sweep / compare / search / check. The key seam is that ARCHETYPES, STRATEGIES and DIFFICULTY_OPTIONS are plain exported tables read through defensive `|| 1` accessors, so a candidate AI is JSON injected via --overrides, not an engine patch — the search space is already a data document. Nothing under engine/ changed here. docs/odyssey-ai-review.md carries the review and the tuning loop, with a reproduction command per finding. A full-roster soak (11 worlds x 4 strategies, 4…953e69e
-
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: release hygiene — docs, license, dev server, npm start
Claude +1: Claude Opus 4.8Unverified signatureTurn the repo into something a stranger can clone, run, and contribute to. - README: rewritten to lead with how to run it (npm start), fix the stale "no tech tree" claim (there's a Refinery doctrine system, a Foundry gate and Datacenter research), and document the whole Odyssey open-world layer, the two save channels, and the versioning scheme — none of which were mentioned. - tools/serve.js + `npm start`: a zero-dependency static server so the ES-module game runs with nothing but Node (no npx, no Python), serving .js as application/javascript (or the modules won't load) with a path-traversal guard. - LICENSE: MIT. - CONTRIBUTING.md: the load-bearing invariants (engine purity, determinism, no build step, versioned saves) with the guarding test for each, plus a release checklist. - CHANGELOG.md: the 1.0.0 entry. Full suite green (560 tests). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AEQUVetLk31Jj7niDQAsXLd0e9298
-
Asymmetric world modifiers: two starts that differ
Claude +1: Claude Fable 5Unverified signatureWorld modifiers could only tilt both sides equally. A world may now carry an `asym: { player, ai }` block that overrides a modifier for just ONE side, so the two corners play as a real asymmetric matchup. - map.js: sideMod(state, owner, key) resolves the owner's asym override, then the shared modifier, then the default — so symmetric worlds and map-less test stubs behave exactly as before. Movement (speed), fog (sight), combat (aggro sight), production (build time), and gather (a new per-side gatherMult economy edge) all read it with owner context. - Two asymmetric worlds, telegraphed on the map card: - Oort "Contested frontier": player banks 20% more per haul (rich claim); the AI builds 18% faster (war factory). Out-mine vs out-build. - Nimbus "Storm front": player sees almost normally (0.95) where the AI stays half-blind (0.75), but the AI's units are 12% faster. Out-scout vs out-tempo. Safe for the resolves-to-a-winner guarantee by construction: an AI-side edge…d228ca7 -
Content: extend terrain to Nimbus, Helix, and Oort
Claude +1: Claude Fable 5Unverified signatureSix of the nine worlds now carry terrain (was three); the original three (Ferros/Korrath/Vesper) stay clean open plains — a valid map archetype, and it keeps Ferros a terrain-free baseline for the exact-value combat tests. - Nimbus (storm bands, short sight): two high-ground vantages that rise above the murk — doubly worth taking, since high ground extends sight on a world where everyone else is half-blind. - Helix (dense crystal belt): a high-ground crystal ridge down the centreline, the contested spine of the belt. - Oort (rich frontier): rugged rough ground on the flanks funnels the fight through the open centre. All mirrored for fairness, mid-map (clear of the near-base build bands), and never impassable. New test locks the coverage (which six carry terrain, which three stay open); all nine worlds resolve, determinism holds. README updated. 227 pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AEQUVetLk31Jj7niDQ…01bf120 -
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: terrain — rough ground, high ground, and no-build zones
Claude +1: Claude Fable 5Unverified signatureGives the map spatial identity without any pathfinding rewrite: a static per-cell terrain field (a flat Uint8Array, fog-grid idiom) drives three purely local effects. - map.js: TERRAIN table + generateTerrain (fixed fractional feature specs, mirrored for fairness, scaling self-similarly) + sampleTerrain (O(1), OPEN out of bounds). Consumes ZERO rng draws, so the node layout stays byte-identical and determinism is untouched. Terrain is static for the whole match. Per-world specs on Glacius (rough ice fields flanking a central lane — a soft choke), Forge (rough industrial sprawl), and Pyralis (a high-ground mesa); the original three worlds stay all-open. - movement.js: rough ground slows the step (never to zero — no unit can ever be trapped, so a wave can't deadlock and the resolves-to-a-winner guarantee holds). Honours an optional per-unit allTerrain flag (forward-compat). - fog.js: high ground sees farther (per-source sight scaled by terrain). - combat.js: an attacker …03f71c7 -
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 -
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 -
Give the AI its own fog of war — it must scout for intel too
Claude +1: Claude Fable 5Unverified signatureThe AI is no longer omniscient. It now plays under its own fog grid (state.fogAI, updated every tick just like the player's), and its intel-dependent decisions are gated by what it has actually seen: - Counter-building only counts player combat units currently in the AI's vision. No sight of your army means no counter-pick — it falls back to its archetype mix until it scouts or fights you. - Worker assignment and expansion only consider nodes the AI has discovered: charted surface deposits always (map knowledge for both sides), hidden caches only once scouted. It can't mine or expand to a cache it hasn't found. To earn that intel it keeps one spare unit scouting once its army can lend one — a box sweep of the contested middle (plain-move waypoints, so it reveals fog rather than diving into a fight), re-issued whenever the scout falls idle or dies, and kept out of the attack wave. The base-march attack is unchanged: in a mirror 1v1 both sides know the enemy start, so that's …816885c -
Add hidden resource caches discovered by exploration
Claude +1: Claude Fable 5Unverified signatureBeyond the charted deposits near each base, every map now seeds eight hidden caches out in the contested middle and along the vertical extremes — extra ore, crystal, and radioactive deposits the survey missed. They stay invisible (main view and minimap) and untargetable until one of your units scouts their cell; the fog's permanent "explored" memory doubles as the discovery record, so a found cache stays on the map. A new isNodeDiscovered(fog, node) in fog.js is the single gate render, minimap, and right-click picking all share. Caches often hold a commodity a world's surface lacks (crystals on Korrath, radioactives on Vesper), so scouting can unlock a resource — and the tech that needs it — you'd otherwise have no access to, giving exploration a real strategic payoff. They're placed after the near-base ore guarantee (so a hidden cache can never satisfy it) at fixed, mirrored, deterministic positions well clear of both bases. The AI's effectiveMix now keys off surface deposits only, …df20f1d -
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
-
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 -
Document Tier 4 in the README
Claude +1: Claude Fable 5Unverified signatureCover the Breacher and its place outside the RPS triangle, buildable expansion Command Centers, the Sentinel Turret, the supply cap and Habitat, the crystal/radioactive sinks those add, and the nine-world roster with its per-world rule modifiers and AI temperaments. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AEQUVetLk31Jj7niDQAsXLe55eec2 -
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 -
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