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
-
P8 Combat Depth: uphill concealment (real LOS), veterancy ranks, tiered-kill event fields
Claude +1: Claude Sonnet 5Unverified signatureThree combat.js-anchored proposals from Phase 8 of the improvement roadmap, landed as one coherent pass through engine/fog.js and engine/combat.js since a later team's fog-render-perf work depends on this cell/fog-grid contract: 1. Real LOS pass (docs/improvement-proposals.md lines 831-839): fog.js's reveal() now takes the source's own terrain (one sampleTerrain call per source) and caps how far a NON-high-ground source reveals a high-ground CELL at UPHILL_FRAC (0.55) of its normal sight radius — a mesa is a stat pad AND a genuine blind spot for anyone still in the lowland. combat.js's nearestEnemy/spreadEnemy apply the identical rule to acquisition via a shared highGroundCap helper, so a unit can't shell a held ridge blind just because its raw aggro happens to reach that far. Both fog grids (state.fog, state.fogAI) inherit this through the same updateFog/reveal path — no AI-side code needed, verified with a dedicated symmetry test. 2. Veterancy ranks (line…8f89fac -
Promote legacy consumer-goods recipes into a trade-industry branch
Claude +1: Claude Sonnet 5Unverified signatureWires the exact "tech + wiring" promotion path data.js's own LIVE-vs-LEGACY comment reserves for the 'chem' and 'consumer' recipes (both already existed in data.js RECIPES, untouched here): - engine/techtree.js: two new TECHS nodes. `chemistry` is a genuine second ROOT of the tree (no `requires` at all, same shape as `metallurgy`) so it opens a truly separate branch rather than a leaf off the existing ore->metals->alloys spine. `consumerfab` is gated on `chemistry` alone, not metallurgy. - engine/entities.js: two new odysseyOnly BUILDINGS, following the Smelter/Assembly Plant recipe/dropOff/prodRate idiom exactly. The Chemical Plant (recipe 'chem': biomass+power -> chemicals) requires only the chemistry tech - no Smelter, no metallurgy. The Fabricator (recipe 'consumer': alloys+chemicals+power -> goods) is where the branch MEETS the metallurgy spine - it requires the Assembly Plant (for alloys) plus the Chemical Plant and its own tech, the same two-supplying-bui…26b3c86 -
Doctrine depth redesign: Assault tempo verbs + Bulwark regen (Tier 2 & 3, merged)
Claude +1: Claude Sonnet 5Unverified signatureMerges the two related doctrine-depth proposals (docs/improvement-proposals.md lines 217-225 and 433-441) into one coherent redesign per Phase 5 of docs/improvement-roadmap.md, instead of shipping them as separate tickets. Assault: overchargedCore (Tier 2) now also grants chaseSpeedMult 1.1 ("combat drive" - closes on an acquired target ~10% faster, applied only in updateCombat's chase branch). A new Arsenal-gated capstone, Overdrive Actuators (Tier 3, requires ["overchargedCore","arsenal"]), grants attackCooldownMult 0.9 - a distinct tempo identity, applied at both cooldown- reset sites (updateCombat's main attack path and updateWorkerCombat), but deliberately NOT at updateBuildingCombat's - this is the army's tempo upgrade, not a base-defense buff. Bulwark: THE RECONCILIATION - proposal 1's Tier-2 grant and proposal 2's Tier-3 capstone both independently proposed "out-of-combat hp regen" as Bulwark's identity. Rather than ship two separately-tracked regen fields, this is one mechan…f0783f6 -
Static-defense ladder: Bastille, Aegis Bastion, Plasma Torpedo Battery
Claude +1: Claude Sonnet 5Unverified signatureShips as one rationalized tier per docs/improvement-roadmap.md Phase 4, not three unrelated additions: all three new structures are tuned against a single shared invariant (every siege unit — Breacher 150, Colossus 185, Leviathan 200 — must still strictly outrange every static-defense structure added here, with one deliberate, documented exception at the top of the ladder), and land together so that invariant is verified once, holistically, rather than three separate times. - Bastille (engine/entities.js): a second, Foundry-gated static-defense tier — hp 600, attack 32, cooldown 1.4, range 115 (deliberately under even the plain turret's 130: a tankier, shorter-ranged brawler, not a sniper). Same combat stat field names the turret uses, so combat.js's updateBuildingCombat needed zero changes to run it. Wired into hudSelection.js's skirmish build list and the Odyssey GROUPS/alwaysShow sets alongside the turret, and into aiEconomy.js's fortify block: the AI's second fortifi…45b37a3 -
Colossus splash: def-driven area damage as the T3 anti-mass verb
Claude +1: Claude Sonnet 5Unverified signatureAdds a generic def.splash = {radius, frac} mechanic and puts it on the Colossus (radius 26, 50% falloff damage), giving T3 a real answer to "spread out vs artillery" that punishes separation.js's own same-owner unit packing — until now every T3 unit was single-target, so clumped deathballs were never punished by anything. engine/combat.js: performAttack's new applySplash helper runs after the primary hit lands. It mirrors acquireTarget's own grid/full-scan idiom (queryNeighbors(state.unitGrid, ...) when a grid exists, else a plain scan over state.units.values()) and, by construction, walks only state.unitGrid/state.units — never state.buildings — so splash can never reach a building or the attacker's own side, however close either stands to the impact point. The primary target itself is excluded from its own splash (it already took the full direct hit). HP is subtracted for every splash victim in one pass before any death is resolved, so the outcome never depends on queryNeighbors' it…8f2921c -
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 -
Unify the AI's counter-pick: out-of-triangle counters, soft-answer
Claude +1: Claude Sonnet 5Unverified signaturefallback, turret-wall reading, and a difficulty-shaped cadence Three catalog proposals all independently patched engine/aiMilitary.js's ~12-line counterToPlayerArmy; this lands them as one coherent rewrite of that function (plus pickNextUnitType's cadence) instead of three overlapping diffs, per docs/improvement-roadmap.md's Phase 2 collision call, and adds the difficulty dial on top of the unified result. - entities.js: an optional `counteredBy` field on out-of-triangle unit defs (dreadnought/breacher/colossus/wraith -> skiff, aegis -> lancer), each empirically proven by a new cost- or supply-parity duel in balance.test.js (not guessed) so the field is self-verifying and can never silently go stale. Aegis specifically needs the supply-parity metric: cost parity actually favours it, but armies are supply-capped late-game, and at equal supply the Lancer wins. Leviathan is left to the soft-answer fallback below rather than a certified counteredBy. - aiMilitary.js COUNTER…2bf7ef0 -
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 the Refinery/Foundry/Arsenal forward drop-off capability
Claude +1: Claude Sonnet 5Unverified signatureThey no longer double as decentralized resource collection points — the Command Center is the only drop-off. Removes the now-dead finite-intake buffer branches in gather/haul, the AI's forward-Refinery placement logic, and updates HUD/docs/tests accordingly. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HW46d81A9V6VumZWqw7XZU4ed9122
-
Tier 3: market barter for the AI, gated by difficulty
Claude +1: Claude Sonnet 5Unverified signatureThe one genuinely new mechanic in the section-08 proposal, not just a multiplier on something that already exists. market.js's sell/buy can't be reused for the AI as-is — they're hardcoded to state.players.player.resources and the single shared galaxy.credits, and there's no AI-side credit account to plug into. Added aiBarter(state) instead: a same-planet CONVERSION, never touching galaxy.credits, priced off the world's own live market (unitPrice/applySlippage, same as a player trade). Surplus/bottleneck are measured on the same scale: coverage = resources held / the priciest single unit-or-building-or-upgrade purchase that ever costs that commodity (SPENDABLE, moved from aiWorkers.js to entities.js so market.js can share it without an aiWorkers.js -> market.js dependency running the wrong way). A commodity past 5x that yardstick is "surplus"; the lowest-coverage spendable commodity is the "bottleneck". One lot per think-cycle, same lot size the player's own trades use. Deterministic:…b02b34f -
Tier 1: worker target, war patience, and a seeded economic edge by difficulty
Claude +1: Claude Sonnet 5Unverified signatureThree economic dials on top of Tier 0's scaffold, each composing multiplicatively with the archetype/strategy layers already in place: - workerTargetMult (aiEconomy.js) scales the AI's worker headcount target — Easy 0.8x, Hard 1.25x. - graceMult/grievanceMult (diplomacy.js) nudge the Odyssey war-onset timing — Easy a touch more patient, Hard a touch less; deliberately mild since it's a third multiplicative layer, and GRACE_FLOOR still bounds the opening window regardless. - economicEdge (Hard only) seeds a synthetic `hardEdge` UPGRADES entry (gatherYieldMult/produceTimeMult) straight onto the AI's own upgrades at creation. gather.js/production.js need no changes at all to pick it up — they already read any upgrade field generically via upgradeMult. That last one required hardening committedDoctrine (entities.js), which used to return on ANY UPGRADES membership rather than checking `.doctrine` specifically — harmless while every entry had a doctrine, but hardEdge doesn't…b28c2f0
-
Add the Market building, moving trade off the Command Center
Claude +1: Claude Sonnet 5Unverified signatureWorkers can now found a Market — the cheapest, fastest building on the roster (40 ore, 6s build) and the first entry in the Odyssey Build menu's Economy group — with no prerequisites. Selecting a finished Market opens the commodity trade panel (buy/sell every tradeable local good for galaxy credits), which previously only appeared on the Command Center. That panel is now gated on the Market instead, so a destroyed or absent CC no longer strands a colony without a market. - engine/entities.js: new `market` BUILDINGS entry (ore:40, infrastructure, odysseyOnly, no requires). - hudSelection.js: renderMarket() is now called from a `market`-type selection instead of the Command Center block; "market" added to the Odyssey build menu's Economy group (first) and alwaysShow set. - Tests: BUILDINGS.market shape/category/gating (entities.test.js), and issueBuild founding it on the Odyssey path while refusing it on the skirmish path, mirroring the existing odysseyOnly coverage (command…350aab2 -
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 -
Rebalance the power stations: 10x cheaper fuel, 50% more Power (#31)
Unverified signatureThe Reactor/Combustion Generator/Biomass Reactor family was burning through its fuel larder too fast relative to how much Power it granted. For all three: - combust.rate (fuel burned per second) divided by 10 — Reactor 1.2 -> 0.12, Combustion Generator/Biomass Reactor 0.6 -> 0.06. The Reactor still burns at double its smaller siblings' rate, same ratio as before. - energyGrants (Power granted while fuelled) up 50% — Reactor 20 -> 30, Combustion Generator/Biomass Reactor 10 -> 15. The Reactor still grants double its siblings' Power, same ratio as before. Updated stale comments citing the old numbers (entities.js, and a couple of test comments), and two industry.test.js assertions that hardcoded the old 20-Power cap to instead derive their expected values/factory counts from BUILDINGS.reactor.energyGrants, so they don't go stale again the next time these numbers are tuned. Claude-Session: https://claude.ai/code/session_01Q3sL7SnGvLsgSaqoXowTGc Co-authored-by: Claude <norep…5ce9536 -
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 -
Make the Reactor burn radioactives too, refuelled by workers like its siblings
ClaudeUnverified signatureThe original power station stayed free once built while its smaller combust siblings needed a worker-hauled fuel larder — an inconsistency once the Combustion Generator/Biomass Reactor pattern existed. The Reactor now runs on the exact same mechanism: a `combust` fuel list (radioactives), its own local larder (entities.js inputCapOf), filled by a worker SERVICE run (engine/haul.js), burned by updateCombustors — at double the smaller stations' rate, matching its double Power/reach. Merged the Reactor's own HUD panel into the generic fuel-burning-power- station one (hudSelection.js) now that it shares the same mechanic, instead of leaving two separate, now-overlapping panels selecting the same building. Also fixes a real bug this exposed: assignService's "needs its output cleared" check read storeTotal(b) >= storeCapOf(b.type) * ASSIGN_FRACTION, which is true (0 >= 0) for ANY building with no output buffer at all — previously masked because non-recipe buildings were filtered out before…39bb807
-
Make the Combustion Generator gas-only
ClaudeUnverified signatureIt briefly accepted radioactives as an alternate fuel too, but the Combustion Generator burns a combustible gas — Helium-3, not fissile material. Radioactives are dropped from its fuel list; its own local larder (entities.js inputCapOf) now gives gas the whole 80-unit buffer to itself instead of splitting it with a fuel it no longer takes.ff7742b -
Have power stations refuel from a worker-hauled larder, and add the Biomass Reactor
ClaudeUnverified signatureThe Combustion Generator used to burn gas or biomass straight out of the treasury, automatically, with no worker involvement at all — unlike every other producer in the Odyssey economy, which draws from a finite local buffer a worker has to physically haul in. Gave it (and its new sibling) the same treatment: fuel now lives in the building's own `input` larder, filled by a worker SERVICE run (engine/haul.js) exactly like a factory's input, and updateCombustors burns from that larder instead of the treasury directly. Split the fuel roster while at it: the Combustion Generator now burns gas (Helium-3) or radioactives; the new Biomass Reactor is its sibling for a biomass-rich claim, same stats, burning biomass alone. Both are ordinary `combust` buildings, so entities.js's per-commodity larder-splitting (inputCapOf) already gives each accepted fuel its own guaranteed slice, same guarantee a multi-input factory recipe gets. Also fixes a bug this surfaced: countLogistics only ever reset th…a7b559b -
Give each factory input commodity its own larder slice instead of one shared pool
ClaudeUnverified signatureA multi-input recipe (Chip Fab: crystals+metals; Torpedo Works: antimatter+ alloys+radioactives; ...) shared ONE pooled input buffer across every real input commodity. Workers over-delivering whichever commodity the treasury happened to have plenty of could fill that whole pool on their own, leaving zero room for whichever OTHER input the recipe was actually starved of — haul.js's inputRoom check always read 0 for it, so it could never be hauled in and the factory stalled forever on an input it could never receive. inputCapOf now splits the larder evenly across a recipe's real (non-energy) inputs, so each gets its own guaranteed slice regardless of how much of another has been delivered. Updated every inputRoom call site to check room per-commodity, the HUD's Larder readout to show each commodity against its own slice, and the save-load clamp to clamp each commodity independently instead of sum-clamping the whole buffer.18d5122 -
Let the player recycle a completed building or unit for part of its cost back
ClaudeUnverified signatureSelect any owned, completed building or unit and Recycle it: it stands down in place over roughly half its build time (freely cancellable, no penalty) and refunds 40% of its cost, or 50% for pricier industrial/capital assets (a factory, the Plasma Rig, a Reactor/Generator, a freighter, a Mender) — boosted to 70%/80% (capped) by a new research node in either tech tree (Field Recycling, Logistics doctrine tier 3 in skirmish; Reclamation Engineering at the Datacenter in Odyssey). A Command Center can't be recycled. Persists across save/load, with save-hardening against a tampered file.79c7af7 -
Let workers load/unload freighters and add an AI-logistics freighter mode
Claude +1: Claude Sonnet 5Unverified signatureWorkers can now physically ferry cargo to and from a landed freighter (right-click it, like assigning a worker to service a building) — the freighter becomes a mobile collection point for nearby production instead of every load detouring through the Command Center. Also adds a new Datacenter research, Autonomous Freight AI, that lets a freighter join the local haul/service logistics chain on its own, like a worker but at its own much larger cargo capacity. Running autonomously burns AI Cores from the treasury while active; out of AI Cores it just pauses in place instead of losing its job or cargo. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AdsYZQyGk32n8x3UBiPGup6f39bc5
-
Add the Helium Bomb: a mobile, arm-then-detonate doomsday unit
Claude +1: Claude Sonnet 5Unverified signatureA new Odyssey unit (engine/entities.js UNITS.heliumbomb), built at the Star Dock alongside the Leviathan, costed entirely in the three named materials: gas (Helium-3), antimatter, and plasmatorp (Plasma Torpedoes) - all three already-real commodities, no new economy pieces needed. Lifecycle: built UNARMED (safe to move anywhere, no different from any other unit) - the player ARMS it via a HUD toggle (hudSelection.js, same direct-mutation pattern as the Mender's auto-repair switch) - once armed, it detonates the instant any of three triggers fires: - it's attacked at all (combat.js's performAttack calls engine/bomb.js's detonateIfAttacked before applying normal damage - any hit sets it off instead of chipping its hp) - a live enemy unit or building comes within BOMB_DETECT_RANGE (checked once per tick from sim.js for every role:"bomb" unit) - the player clicks "Detonate Now" (calls detonateBomb directly) All three funnel through the one detonateBomb() function in the …53d4247
-
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 -
Combustion Generator: cheap gas/biomass power on a tight grid
Claude +1: Claude Opus 4.8Unverified signatureA fuel-burning alternative to the Reactor. It grants half the Power (energyGrants 10 vs 20) over a much smaller grid (powerRange 0.55 shrinks its efficiency zones), and only while it's fed — it burns gas OR biomass (whichever the stockpile has more of) at combust.rate/sec from the treasury, the mirror of the Reactor's free-but-pricey grid: cheap to raise, ongoing fuel bill. Out of fuel or paused, it grants nothing. - entities.js: `combustor` def (energyGrants 10, powerRange 0.55, combust spec); the Reactor gains powerRange 1 as the reference reach. - industry.js: powerEfficiency now scales each source's distance by its powerRange (nearestReactorDist → bestGridDist), so a Generator's zones sit tight and a consumer must huddle close; powerCap and the grid only count ACTIVE sources; updateCombustors burns fuel each tick and sets the transient `powered`/`fuel`. - sim.js: settle Generators before any consumer reads powerCap. - persist.js: strip the transient powered/fuel from saves…08cde58 -
Logistics Phase C: finite forward drop-offs + gather reroute (player-only)
Claude +1: Claude Opus 4.8Unverified signatureThe last piece: collection buildings get finite storage too. A PLAYER's forward drop-offs (Refinery/Foundry/Arsenal) now have a finite intake buffer (storeCap 100). A gatherer banks its haul into that buffer; when it's full, nearestGatherDrop skips it so the gatherer reroutes to the next collection point — and the Command Center, the bottomless treasury/warehouse, never fills. Phase A haulage clears a forward buffer to the CC automatically. Goods now flow strictly through the warehouse: gather → forward drop-off buffer → haul → CC treasury → supply → factory → haul → CC. Haulage delivers and supply picks up at the nearest Command Center (nearestCommandCenter), never sideways between drop-offs. Factories are excluded from gather drop-offs (isGatherDropOff): their `store` is refined output, not a raw-intake bin. - entities.js: storeCap on refinery/foundry/arsenal; isGatherDropOff helper. - gather.js: player forward-drop-off banks to the building's buffer (capped, leftover reroutes); …89beffa -
Logistics Phase B: factory input larders + a real supply network
Claude +1: Claude Opus 4.8Unverified signatureFactories stop sipping from an infinite global pool: each now draws its inputs from a FINITE local larder (building.input) and banks output into a finite buffer (building.store), both capped at 80 by default for any building with a recipe (storeCapOf/inputCapOf). updateProduction runs a factory only while it's been supplied AND has output room — it stalls starved or backed-up, exactly like the Plasma Rig. Worker haulage gains a supply leg (engine/haul.js): an idle player worker picks the nearest own factory low on an input the treasury can provide, carries a cargo from the Command Center into its larder. Output is cleared by the Phase A haul path, which now covers factories automatically. Idle workers try haul first (clear backlogs, refill the treasury) then supply (feed the starving). Both capped at 2 per building, deterministic nearest-by-id, player-only. The industry chain is now a genuine network routed through the treasury: raws → CC → supply → smelter → metals → haul → CC → sup…832f494 -
Logistics Phase A: finite rig output buffer + worker haulage
Claude +1: Claude Opus 4.8Unverified signatureStorage stops being an infinite sink and workers get a job that lasts to the end of the game. The Plasma Rig (a leaf producer of raw goods — the safest surface to prove the mechanic, since its output feeds no other factory) now banks each dig into a FINITE local buffer (building.store, capped at def.storeCap 120) and stalls the moment it's full. Those goods aren't spendable until hauled to a Command Center. New worker haulage (engine/haul.js): an idle PLAYER worker auto-assigns to the nearest own producer whose buffer is ≥34% full (≤2 haulers per producer, ties broken by id), walks there, loads a cargo, carries it to the nearest drop-off, and banks it into the treasury 1:1 — no gather multiplier, since the rig already extracted it. Run short on haulers and the buffer fills and the rig stalls: logistics is now a live demand on labour, exactly as intended. - entities.js: storeCapOf / storeTotal / storeRoom helpers; storeCap on the rig def. - rig.js: bank to store (top off the final dig…91ad84b
-
Plasma Rig: deep-core extraction — unlimited raws at a probabilistic yield tier
Claude +1: Claude Opus 4.8Unverified signatureA new Odyssey building (engine/rig.js) that mines a raw commodity forever instead of from a finite node. Each dig cycle strikes a probabilistic tier — low, medium, good, or overwhelming — whose odds rise with the rig's spot AND the planet, so placement matters. It's costed as an engine, not free money: - "plasma to dig" = a heavy Power draw routed through industry.js's grid, so it competes with factories/the Gate and its digs slow when power is short. - "nuclear to exploit" = radioactives burned per dig; out of them, it stalls. - "AI to pilot" + manufactured materials = an expensive build cost ({ ore: 500, machinery: 8, electronics: 5, ai: 3 }), gated behind aifoundry + reactor. Which raw a rig mines (its "vein") is fixed by where you build it. Determinism-safe: every roll is a hashStr of the rig id + its dig counter (no Math.random, no clock), so a same-seed galaxy strikes the same seams on replay; the engine-purity + determinism guards stay green. dig state persists on the bui…db667bc -
Tier 2 balance: Bastion out-ranges the Skiff; Aegis projects a real anvil aura
Claude +1: Claude Opus 4.8Unverified signatureBastion (entities.js): range 24 → 44 so it now reaches PAST the Skiff it hard- counters (Skiff range 40) — a player-microed Skiff can no longer sit at 40 and orbit forever outside the reach of its counter. Kept under the AI's KITE_MIN_RANGE 50 so it still stands and trades. bonusVs skiff trimmed 14 → 10 to hold the head-on TTK. The rock-paper-scissors auto-battle triangle still closes (balance.test.js). Aegis (entities.js + sim.js + combat.js): the tank finally does its job. It now projects a guardAura — a cryo-armour bubble that multiplies DOWN the damage nearby allies take — so it soaks wherever it stands, with no need to pre-position it as a literal wall and no dependence on enemy targeting. sim.js collects the (rare, Tier-3) aura projectors once per tick into state.anvils; combat.js attackDamage applies the multiplier in O(anvils), usually zero. Speed 40 → 48 so it can keep formation with the line it shields. Deterministic (tick-start position snapshot, like the grid) and transien…30197bc
-
Icon pass: reuse the game's data icons for research, planets, market
Claude +1: Claude Opus 4.8Unverified signatureCarry the same iconography the rest of the game uses into the places that were text-only, reusing existing emblems as much as possible: - makeButton now supports an emoji icon ({emoji}) alongside the sprite icon ({kind,type}), sharing the .btn-icon tile. - Research buttons: TECHS and doctrine UPGRADES gain an `ico` — reusing the commodity icons where a node maps to a good (metals ⛓️, electronics 🖥️, antimatter 🌀, AI cores 🧠) and thematic emblems otherwise (⚡🔩🤖🛠️ / assault ⚔️, bulwark 🛡️, logistics 📦). Shown on the Research buttons, the "Researching …" row, and the "researched" rows. - Starmap: each world shows its controlling-faction emblem (data.js FACTIONS ico — ⚖️ Core, ⛏️ Miners, 🌾 Agri, 🔬 Syndicate, 🛰️ Frontier). - Market rows: prefix the commodity's data icon + proper name (🪨 Ore, 💎 Crystals …), matching the resource readout. Buildings/units already use their map sprite as the button icon. `ico` is inert data (like name/desc), so 517 tests still pass.…28e5839 -
Fix: cargo-ship buttons at the CC did nothing (missing from produces list)
Claude +1: Claude Opus 4.8Unverified signatureThe Command Center's HUD menu offered Hauler / Heavy Hauler / Bulk Freighter, but command.produces still only listed worker/ranger/colonyship — so queueProduction rejected the click and nothing happened. Added the three cargo ships to command.produces; they stay out of skirmish via the existing odysseyOnly gate in queueProduction. Added a regression test (the CC's produces list includes the cargo ships and queueProduction actually queues a Hauler). 517 pass. Browser-verified: clicking Produce Hauler queues and builds it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AEQUVetLk31Jj7niDQAsXLd39a77e -
Odyssey freight: cargo ships in three sizes set the jump's cargo hold
Claude +1: Claude Opus 4.8Unverified signatureReplaces the flat 300-unit auto cargo hold with real freight logistics: a jump's cargo hold is the combined capacity of the CARGO SHIPS you stage for it. No cargo ship, no freight — hauling goods between worlds now means building shipping. - entities.js: three Odyssey-only cargo ships, Spaceport-gated, role "freighter" (unarmed — escort them): Hauler (hold 250, 1 supply), Heavy Hauler (650, 3), Bulk Freighter (1600, 6). Bigger holds cost more ore, supply, and Spaceport jump-capacity, so shipping scales with investment. - galaxy.js: freightCapacity(riders) = summed cargoHold of the staged cargo ships; cargoManifest(from, capacity) + loadCargo fill most-valuable-first up to it; jumpCapital uses the staged ships' capacity (CARGO_CAPACITY removed). - hud.js: Command Center produces the cargo ships (locked until a Spaceport exists, with sprite icons + a "cargo N" stat in the tooltip); the Spaceport panel's cargo hold now reflects the staged ships' capacity. - render.js: the n…97f8989 -
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
-
Odyssey Tier-1 "make it playable": fix the threat curve + cheap wins
Claude +1: Claude Opus 4.8Unverified signatureA gameplay review (instrumented headless runs + a critique team) found Odyssey's threat curve and win curve ~6x out of sync: war reliably began at ~minute 4 (two economies mine the shared world to the old 23%-depletion war threshold that fast, and "peace" only gated the AI's ATTACKING, not its arming), so a turtling economy player was dead by minute 6-12 — while the Antimatter Gate win needs ~25-35 min. The whole economy was, in practice, unreachable. - engine/diplomacy.js (#1, the critical fix): a 7-minute GRACE window floors the scarcity target at cordial so a fresh economy can establish, and a much gentler souring curve (war at ~47% depletion, full hostility only near total depletion, slower drift) moves the war into the mid/late game. Empirically: war onset moves from ~min 4 to ~min 8-21, and a base with a reasonable defensive core now SURVIVES the full 35-min win window across seeds (previously the CC fell by min 6-12). #2 ("pressure, not exterminate") falls out for f…e46bcce -
Fast-follow: complete the Strategic tier — AI Cores, Plasma Torpedoes, the Leviathan
Claude +1: Claude Opus 4.8Unverified signatureFinishes the Strategic tier the Phase-3 MVP deferred, and gives the two new goods two purposes (a superweapon and the endgame wonder) — the shared-currency tension the design called for. - techtree.js: the `aicores` node (Machine Minds) unlocks the two remaining forges. - entities.js: AI Foundry (electronics+crystals → AI Cores) and Torpedo Works (antimatter+alloys+radioactives → Plasma Torpedoes), both odysseyOnly fixed-recipe factories (zero industry.js change — pure data); the Star Dock, which trains the Leviathan — an Odyssey-only capital ship costed in AI Cores + Plasma Torpedoes you must MANUFACTURE (can't be bought). It deliberately exceeds the skirmish specialists (flagged odysseyOnly so the skirmish-roster balance invariants scope around it), but 8 supply + a strategic-good price keep it in check. - The Antimatter Gate now feeds on the WHOLE tier (ai + antimatter + plasmatorp), clamped to the scarcest — so the win demands the full strategic economy, and every …b50413b -
Odyssey Phase 3: the Strategic tier + the endgame win (Antimatter Gate)
Claude +1: Claude Opus 4.8Unverified signatureOdyssey has been endless and loss-only. Phase 3 gives it a destination: complete the chain to the Strategic tier and fire the Antimatter Gate to win the galaxy. Reconciled from a three-way brainstorm toward the skeptic's "ship the win, one clean vertical slice" — one strategic good, the wonder, and the win machinery. - engine/wonder.js (new): updateWonder — the Antimatter Gate charges by consuming antimatter over a power-throttled chargeTime (competes with your factories for the grid), clamped to stock so it stalls when starved and never goes negative. Goods are spent as fed, so a razed Gate loses the whole investment. Odyssey-only, a no-op off a `wonder` building — skirmish tick untouched. - engine/victory.js: checkEndlessWin — the twin of checkEndlessLoss; a full-charge player Gate wins. Wired into sim.js's endless branch (win-first) and the building loop (updateWonder). - engine/galaxy.js: checkGalaxyWin in the stepGalaxy tail — a Gate left charging on a background CO…15e011a -
Odyssey Phase 2: the research tree (Datacenter + deeper industry)
Claude +1: Claude Opus 4.8Unverified signatureSecond slice of the economy import: an Odyssey-only research tree that gates and deepens the production chain — paid in gathered commodities and developed over time, no separate tech currency (the game's law: the resources you gather are the resources you spend). Reconciled from a three-way design brainstorm. - engine/techtree.js (new): TECHS (6 nodes — 3 unlock the buildable spine, 3 are industry passives), researchTech (start a project: prereq/afford checks, pay, timer), updateResearch (dt-driven progress → banks the node into player.upgrades, a no-op for any non-Datacenter — same inert pattern as updateProduction), researchTimeScale (a world's tech rating makes it develop faster — Syndicate hubs out-research), techMult (the tech-tree twin of upgradeMult for passives). - Research lives in player.upgrades — the SAME bag the doctrines use — so nodes gate buildings via the existing prereqsMet with zero new gating machinery, and committedDoctrine/upgradeMult (which guard o…fb3a2d7 -
Odyssey Phase 1: the production chain (Reactor → Smelter → Assembly Plant)
Claude +1: Claude Opus 4.8Unverified signatureFirst slice of importing the turn-based game's economy into the RTS: an Odyssey-only production spine that turns raw hauls into refined goods worth real credits. Proves the gather → refine → payoff loop end to end. - engine/industry.js (new): Power is a per-tick FLOW modelled on supply.js — Reactors grant capacity (energyGrants), factories draw it (a recipe's energy × prodRate), both recomputed on demand so a razed Reactor throttles industry with zero bookkeeping. updateProduction runs a building's recipe continuously and fractionally, consuming inputs from the global stockpile (intermediates are just more float keys in player.resources) and banking output, throttled by available power and clamped to inputs in stock. Deterministic, DOM-free. - entities.js: Reactor / Smelter (ore→metals) / Assembly Plant (metals→alloys), all ore-costed and flagged odysseyOnly. - sim.js: updateProduction wired into the building loop — a no-op for any building without a recipe, so the byt…5de119a
-
Odyssey Phase 2: the galaxy + the interplanetary jump
Claude +1: Claude Opus 4.8Unverified signatureThe signature open-world loop. You now build a Spaceport, relocate your single Command Center (and the units staged by the pad) to another world, and the planet you leave keeps evolving as a background colony while you develop the new one. Engine: - galaxy.js: the meta-layer grows a real galaxy. addPlanet() gains an `unsettled` option that strips the auto-seeded player base — a jump destination has only its neighbour until your capital arrives. jumpCapital() moves the Command Center + player units within JUMP_LOAD_RADIUS of the Spaceport to the destination's landing zone (assigning fresh galaxy-scoped ids so nothing collides across states), flips the origin to a background colony, and makes the destination the active seat. canJump() gates it on a finished Spaceport. - sim.js: a background colony (no capital, by design) is never "over" — only your active seat runs the capital-loss check. - entities.js: the Spaceport building (Odyssey's launch pad, gated behind the Fou…5df8767 -
Add Convoy Escort scenario: shepherd freighters across a raided route
Claude +1: Claude Opus 4.8Unverified signatureIntroduces a scenario layer over the existing skirmish engine. The Convoy Escort scenario tasks the player with shepherding four freighters along a multi-leg route through pirate territory to a destination gate, against a global mission clock. Scenario framework (engine/scenarios.js, new): - setupEscort() reuses createGameState for map/fog/players, then clears units+buildings and lays out an evenly-spaced left-to-right route of stations (one leg per configured risk value), the four freighters, and a difficulty-scaled escort fleet. - updateScenario() drives a prep -> travel -> docked phase machine: freighters advance leg by leg, pirate waves spawn per-leg by risk and steer at the lead freighter, and the player docks between legs to repair from a budget. Win on delivering >=1 freighter before the clock; loss on losing them all or the clock expiring undelivered. - Determinism preserved: scenario randomness flows through a seeded mulberry32 on state.scenario.rng, decoupled…dcbdeac -
Tech tree: spend the dead commodities — gas / ice / relics specialty units
Claude +1: Claude Opus 4.8Unverified signatureSeveral worlds deposit commodities nothing ever cost — gas (Vesper, Glacius, Nimbus), ice (Glacius), relics (Korrath, Oort) — so on a gas giant like Nimbus or the ice world Glacius the whole surface economy was dead flavor, mined by nobody. The gather/bank/spend plumbing is already commodity-generic, so the fix is pure content: give each a Tier-3 sink at the Arsenal, joining the existing radioactives→Dreadnought so a world's rare deposit shapes your elite. New units (Arsenal-gated, all deliberately OUTSIDE the Skiff/Bastion/Lancer triangle — situational power, not hard counters): • Wraith (gas) — fastest combatant, highest DPS, paper-thin: a raider. • Aegis (ice) — the tankiest hull with a token gun: a wall to hide behind. • Colossus (relics) — out-ranges everything incl. the turret, cracks buildings, but slow and fragile for its cost: screened siege. AI: the balanced archetype (which plays the gas/ice worlds Vesper + Glacius) folds Wraith/Aegis…51070b8 -
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 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 -
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