Spaces:
Paused
Paused
Commit History
-
Tier 2a: collapse the cross-module duplicates, pin the tie-breaks
Claude +1: Claude Opus 5Unverified signatureradiusOf existed THREE times in engine/ and bomb.js's comment asserted the copies were "the same". They were not: only bomb.js's handled a building, by reading the instance radius state.js stamps at construction; movement.js and formation.js returned the magic constant 9 for any building — while movement.js is a // @ts-check'ed file whose JSDoc advertises @param {Unit|Building}. The type layer was actively telling callers a Building was supported on a path that silently under-sized it, which is the exact class of bug MAX_UNIT_RADIUS exists to prevent and which no determinism test can see, being wrong-but-stable on every seed. One shared helper now lives in colliders.js, which already reasoned about def.radius and b.radius. Consolidating it surfaced a real subtlety the plan didn't predict: keying on `kind` alone broke formation.js, whose callers are frequently unit-shaped literals carrying no `kind` field at all. The helper resolves by TYPE first, with a comment recording why. clamp a…e847942 -
Tier 1, step 2b: expand // @ts-check coverage from 10 files to 20 (D3)
Claude +1: Claude Opus 5Unverified signatureAdopted in the order docs/code-improvement-tiers.md sets out, cheapest and highest bug-class value first: supply, aiCommon, colliders, victory, production, scout, the three AI leaves (aiStrategy, aiDifficulty, aiArchetypes), and finally persist.js — the prize, because every save field is a State/Unit/Building field read by name, so annotating it makes save-shape drift a check-time error. Each file's exports are annotated with the shared typedefs, not just given the pragma: per the guard added in the previous commit, a pragma without annotations checks nothing. Doing it surfaced three more genuine drifts in engine/types.js, all found by the checker rather than by reading: - Building.rally was typed {x, y} but carries nodeId — the rally-to-resource feature, where a rally dropped on a live node makes a produced unit spawn already mining (engine/production.js). - Building.queue entries were typed {unitType, progress} but carry `alt`, which records that a job was charged the unit's…7e764b0
-
P3 T2 industry/tech depth: Grid Substation, scoped Heavy Alloys, standing
Claude +1: Claude Sonnet 5Unverified signatureFoundry/Arsenal bonuses, cancelable research queue Four proposals sharing engine/entities.js/industry.js/techtree.js, landed as one coherent pass per the roadmap's own grouping: Grid Substation (engine/entities.js, engine/industry.js): a cheap odysseyOnly BUILDINGS.substation (60 ore + 30 crystals; no energyGrants, no fuel) that acts as a passive one-hop power-grid relay. bestGridDist splits its old body into bestSourceDist (real sources only) plus a new relay pass: a Substation counts as a virtual source point (its own shorter powerRange ~0.8) only while it stands within an active real source's own 'linked' band (checked via bestSourceDist, never via another relay, so a relay can never chain through a second relay). powerDraw adds the relay's own flat draw (the ELECTRIFY_POWER idiom); powerCap is untouched (a relay never grants capacity, only reach). Verified hudSelection.js's gridEfficiencyRow and the render-side ghost-cue label pick this up for free via the existing powerEfficienc…af15726 -
Doctrine research develops over time instead of landing instantly
Claude +1: Claude Sonnet 5Unverified signatureGeneralizes engine/techtree.js's updateResearch to resolve its node table by building type (datacenter -> TECHS, refinery -> UPGRADES) instead of being hardcoded to the Datacenter, so the Refinery's doctrine upgrades now run through the same queued/timed development loop as the Odyssey tech tree. production.js's researchUpgrade becomes an enqueue: it pays up front, keeps the committedDoctrine lock and prereqsMet check, and adds a queued-dupe guard, instead of flagging the upgrade researched the instant it's paid for. Each UPGRADES entry gets a ~25-40s `time` field, scaled by researchTimeScale like every TECHS node, so a skirmish world's own tech rating finally matters for doctrine research too. Because the upgrade no longer lands in player.upgrades until it finishes developing, entities.js's committedDoctrine now also commits from a upgrade that's merely queued (paid for, still developing) at a Refinery — otherwise both doctrines could sit queued side-by-side during the development wi…4b95948
-
Remove Miner, Engineer, and Technician — Worker builds everything again
Claude +1: ClaudeUnverified signatureThe three-way specialist split made the worker roster harder to manage, not easier: three unit types to remember and train instead of one, for a "which building categories can this thing build" distinction the Build panel's own collapsible per-category sections already handle. Reverts to just Worker as the one economy unit, trainable on ore or biomass (altCost, unchanged) and able to build/gather/haul every category, same as before the split. - engine/entities.js: delete the miner/engineer/technician UNITS entries and drop them from the Command Center's produces list. canBuildCategory/ canGatherType/canLogisticsType stay — they're still the single source of truth issueBuild, issueGather, and the haul/service/ferry auto-assign read, now simply true only for Worker. - renderUnits.js/hudSelection.js/input.js/engine/aiMilitary.js/ engine/commands.js/engine/production.js: drop the now-dead sprite dispatch and produce-list entries, and reword comments that explained the specia…53ddc42 -
Split the generalist Worker into category-owning specialists (#28)
Unverified signatureThe single Worker did everything — mining, founding every building type, hauling, servicing factories, ferrying freighters — across ~22 building types, making its job list unmanageable. Adds three new specialist unit types, each owning construction (and, for Technician, ongoing operation) end-to-end for a building category, mirroring the existing Mender precedent (a proven specialist split with its own tech gate): - Miner: gather-only, no tech gate, cheaper and faster-mining than Worker. - Engineer: builds Infrastructure + Military buildings (Command Center, Habitat, Spaceport, Datacenter, Antimatter Gate; Barracks, Turret, Star Dock) — the buildings with no ongoing haul/service loop. - Technician: builds + hauls/services/ferries Industrial buildings (Refinery, Foundry, Arsenal, the factory chain, power stations, Plasma Rig), gated behind Foundry like Mender. Foundry/Arsenal land here (not Military) because both are forward drop-offs that already need this logistics capabi…340ed0f
-
Fix B1 + B2 from the architecture review
Claude +1: Claude Opus 4.8Unverified signatureB1 — faction expansion could flip the AI faction of the world the player is ON. checkExpansion picks the nearest UNCLAIMED world and sets its AI's faction to the expander's; faction feeds live combat/economy multipliers (factions.js → sideMod, read on every hit and haul), and there was no activeId exclusion — so a distant faction "expanding" into the player's current world silently changed the enemy's damage/sight/ speed/gather mid-battle, with only a starmap toast. Now expansion skips the active world and any world the player holds a foothold on; it only reaches the unclaimed, unheld fringe. (engine/galaxy.js checkExpansion.) B2 — a corrupt production queue bricked the game. updateProductionQueue did `UNITS[job.unitType].buildTime` with no guard, so a bogus unitType (localStorage bit-rot or a hand-edited save) threw on the first tick — inside the rAF loop, past load's try/catch — and since it's the autosave, every reload re-crashed. Two-layer fix: • runtime guard in updateProductio…785c4de
-
Electrify: wire non-power buildings into the grid for +30%
Claude +1: Claude Opus 4.8Unverified signatureAn Odyssey non-power building — a Command Center, Barracks, Star Dock or Habitat — can now be ELECTRIFIED: wired into the power grid to run 30% better. A producer trains its units 30% faster; a Habitat houses 30% more. The upgrade isn't free. An electrified building adds a steady grid draw (ELECTRIFY_POWER, grid-efficiency scaled like any consumer), so lighting up the whole base competes with the factories for Reactor Power. And the payoff scales with the player-wide power throttle: the full +30% only while the grid can carry the load, tapering as it runs short and 0 with no active source at all — so it's worth it once you have Power to spare, not as a free stack. - isElectrifiable(type) (entities.js): a building not already on the power economy (no recipe/reactor/generator/rig/wonder) that has something to boost (produces || supplyGrants). - ELECTRIFY_POWER draw + electrifyBoost(state, owner) = 0.3 × throttle (industry.js); powerDraw counts each electrified building. - Boost a…c2a29e2 -
Workers trainable on biomass as an alternative to ore
Claude +1: Claude Opus 4.8Unverified signatureA worker now carries an `altCost` ({biomass: 50}) alongside its ore price — an either/or, so a biomass-rich but ore-poor claim can still grow its workforce. - entities.js: worker.altCost. - production.js: queueProduction(state, id, type, alt) charges the alternative price when asked and tags the queued job (job.alt); cancelProduction refunds whichever commodity was actually charged. - hud.js: the Command Center shows a second "Produce Worker (50 biomass)" button beside the ore one; the affordability signature tracks altCosts so it greys live. Tests: production.test.js (biomass charged not ore, job tagged, cancel refunds biomass). Full suite 597 green, tsc clean. AI path unchanged (it queues via the ore cost only). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AEQUVetLk31Jj7niDQAsXLccc9661
-
Odyssey: colony ships — start with a mobile ship, deploy it into a base
Claude +1: Claude Opus 4.8Unverified signatureReframes Odyssey expansion. Both sides now START with a mobile Colony Ship instead of a placed Command Center; you DEPLOY it in place to found your first base (which can then be upgraded to the Capital). New bases are no longer built from scratch by a worker — you build a Colony Ship at a CC, move it to the site, and deploy it there. Core (engine/colony.js, engine/entities.js, engine/state.js): - colonyship UNIT — odysseyOnly, non-combat (unarmed, never auto-acquires, transparent to the army/threat scans, but still shootable so it's a real escort risk), hp 600, cost 500 ore (a mobility premium over the CC's 400), supply 3. - deployColonyShip(state, id): validates the footprint (canPlaceBuilding), consumes the ship, mints a COMPLETED Command Center + disembarking colonists (3 workers). Instant and pure — no clock/RNG. hasColonyShip is the shared foothold predicate. - seedPlayer seeds a colony ship for endless; the SKIRMISH branch (a placed CC + 3 workers) is byte-identical, s…24f7ec5
-
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 -
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 -
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 -
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 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
-
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 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 -
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 -
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 -
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