Spaces:
Paused
Paused
Commit History
-
Close the guard gaps behind the last three bugs; withdraw one review finding
Claude +1: Claude Opus 4.8Unverified signatureFindings 4, 5 and 8 of docs/code-review-2026-08.md, plus a correction to 7. #8 tools/ ships to the browser but was outside the purity guard. boot.js imports tools/selfplay.js; competition.js, competitionWorker.js and playerFingerprint.js import tools/duelCore.js and tools/genome.js. So three "Node bench" files run in a browser Worker and decide the outcomes that become persisted Elo ratings, while the purity scan only ever walked engine/ and what engine/ imports — structurally identical code guarded on one side of a directory line and unguarded on the other. The review said moving the files was "simplest and better". Measuring first showed otherwise: 30+ prose references across engine/, README and docs would have had to move with them, for no safety gain. Extended the scan instead, and DERIVED the extra roots by walking out from index.html (plus the Worker entry, whose `new Worker(new URL(...))` edge no import walk can see) so a fourth file pulled into the app tomorrow is covered the…021c865
-
Phases 2-3: the AI answers what it reads
Claude +1: Claude Opus 5Unverified signaturePHASE 2 -- the raid stops being a metronome. It fired on `waveCount % RAID_EVERY` and only on Hard: harassment on a schedule, regardless of whether harassment was the right answer. It now ALSO fires when the read says the enemy is visibly investing in economy over defence and enough of them has been scouted to act on it. That is harassment as a response: punish greed, and specifically punish the hoarder docs/ai-evolution-design.md found winning by never committing its army. An opponent that can be SEEN to be undefended should not be safe -- which removes that degenerate strategy structurally instead of penalising turtling inside a fitness function, where evolution would just route around it. The read is deliberately not gated on controller.micro. Reacting to what you scouted is ordinary competence, not the unit-level micro Hard buys, and an Easy/Medium AI that never once punishes a greedy opening is the passivity the bench keeps measuring. PHASE 3 -- a damped stance. posture is far t…5d60e36 -
Phase 1: the AI gets a belief about its opponent
Claude +1: Claude Opus 5Unverified signatureengine/aiIntel.js. Every other reactive read in the AI answers a question about right now -- are there enemies at my base, how many can I see, what do they field most -- and none of them survives losing sight of the enemy, because entity sighting goes through the LIVE fog layer. Kill the scout and the AI's picture of its opponent is empty, and it has no way to know it. This is the other kind of knowledge: what sort of game the opponent is playing. It persists between sightings and decays when unrefreshed. It is the first piece of AI state that can be WRONG -- everything else is a fact about the current frame -- and that is the point. An opponent model you can deceive is what makes scouting worth its cost and killing the scout a real counter-play. Three numbers, and they are three for a reason: mil / eco ore-value of what has been SEEN, split by whether it shoots posture mil / (mil + eco): 0 is a pure economy game, 1 is a pure war game confidence how much has been see…2991d58
-
Make the AI controller owner-parametric: true two-sided self-play
Claude +1: Claude Sonnet 5Unverified signaturerunAI(state, dt, owner = "ai") and every AI phase module now resolve their controller, opponent, and fog grid from `owner` instead of hardcoding "ai"/ state.fogAI — 100% backward compatible (every pre-existing call site keeps the default and behaves byte-for-byte as before). A new, independently configured controller slot (state.playerAi, null by default) lets a second real AI drive the "player" seat in a skirmish, headless-only (tools/ selfplay.js) — nothing in setup.js/boot.js/the shipped game changed. Independent adversarial review (fairness lens + correctness lens) caught a real bug before this was accepted: Hard difficulty's economic edge (hardEdge) was seeded onto state.players.ai only, never a self-play "player" controller configured for Hard. Fixed via seedDifficultyEdge (engine/state.js), called for "player" once state.playerAi exists. engine/persist.js's save/load extension is purely additive (no SAVE_VERSION bump) — old saves round-trip unaffected. Co-Authored-By: Claude …916c3ad
-
Fix the five Odyssey AI defects the bench found, TDD
Claude +1: Claude Opus 5Unverified signatureEvery change below is red-first: the failing test states the contract, the engine change makes it pass. Full suite 1209 green, typecheck clean. engine/aiWorkers.js + engine/aiEconomy.js — the supply deadlock (review §2.2). The Habitat trigger fired at a flat `used >= cap - 2`, a margin sized for a 2-supply unit, while the Odyssey roster has 4- and 8-supply ones and pickNextUnitType retries the SAME mix entry until it succeeds. It is now sized from maxSupplyDemand(): the biggest supply cost the AI could actually try to pay. Clearing that surfaced two more freezes with the same signature, both fixed here: supply already under construction is now credited (so one Habitat per 10s no longer throttles a scaled-up line), and Habitats are placed at ANY of the AI's Command Centers — measured, a 70-building capital had no valid spot left while its own expansions had room to spare. engine/aiIndustry.js + engine/ai.js — the industry bootstrap reserve (§2.4). runAI runs production before industry…42d791d -
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
-
Add a player-picked AI Strategy (Aggressive/Economic/Force Parity) and Helium Bomb use
Claude +1: Claude Sonnet 5Unverified signatureThe AI never touched the Helium Bomb (built at the Star Dock alongside the Leviathan) despite having full logic for the Plasma Rig. engine/aiSuperweapon.js gives every strategy a use for one: armed as a home-defense trap the instant the base is attacked, or — Aggressive only — walked to the attack target and triggered there, mirroring the player's own "Detonate Now". engine/aiStrategy.js adds a new player-selectable stance, orthogonal to the planet-driven archetype (which stays purely a flavor pick): Aggressive presses the attack earlier/smaller and forces the issue in Odyssey instead of waiting out diplomacy; Economic keeps a minimal standing army until it's actually attacked, then surges, and leans into the deep industry chain (Plasma Rig included) regardless of archetype; Force Parity continuously mirrors whatever it's seen of the enemy's army and never picks the fight itself. The default strategy is a byte-identical no-op, so every existing archetype/resolve guarantee is untouched…be1f1f9
-
Split engine/ai.js god file into cohesive phase modules
Claude +1: Claude Opus 4.8Unverified signatureengine/ai.js was a 1147-line god object holding the whole AI. Decompose it into five cohesive sibling modules along an acyclic import graph, leaving ai.js as a 110-line orchestrator that keeps exporting runAI (so sim.js and the AI tests are unchanged): - aiCommon.js — the APM action budget (accrueActionBudget/canAct/spend), reserve-aware canAffordKeeping, and the pickBuilder worker pick. A leaf with no engine imports, shared by every phase. - aiWorkers.js — idle-worker logistics + gather steering (assignAiLogistics/ assignIdleWorkers) and the map-aware mix filters (effectiveMix, affordableOnSurface, aiDoctrine). - aiMilitary.js — defend/attack waves (aiMilitary/aiOffense), focus-fire, the scout sweep, and the target/mix picks (chooseAttackTarget, raidTarget, pickNextUnitType, counterToPlayerArmy). - aiEconomy.js — base build-out, expansion, the Foundry/Arsenal tech gates, …240c1d3 -
AI logistics: true symmetry — the AI hauls its own industry, no more free treasury
Claude +1: Claude Opus 4.8Unverified signatureResolves the AI-logistics asymmetry from the architecture review. The AI's factories/rigs used to run off an owner==="ai" abstraction — inputs drawn straight from the treasury, outputs banked straight back, no buffers, no workers, no stalls — so the finite-storage/ haulage system (the headline Odyssey reshape) was a labour tax only the human paid, and the two economies weren't comparable. Now the AI runs the SAME finite-buffer + worker-haulage model as the player: - Deleted the abstract branches in industry.js/rig.js. updateProduction/updatePlasmaRig are owner-agnostic again: a factory only produces once workers fill its input larder and keep its output buffer clear (else it stalls), and a rig's finite buffer must be hauled off. - engine/ai.js assignAiLogistics: dedicates a BOUNDED share of idle workers (<= half the pool, so gathering never starves) to service factories / haul the rig, reusing the exact owner-generic machinery the player's workers use (assignService/assignHaul…8cd6f2e
-
AI industry (4/N): the capital path — Star Dock, Leviathans, Plasma Rig
Claude +1: Claude Opus 4.8Unverified signatureThe payoff for the deep climb and the sink for the AI's strategic goods. Once the whole Strategic tree stands (a Star Dock's prereqs — AI Foundry + Torpedo Works — are met), the developer AI: - founds a Star Dock and trains LEVIATHANS from it — a real capital ship (role "combat", so the existing wave logic folds it into the AI's attacks), costed in the AI Cores + Plasma Torpedoes its own chain manufactures, gated on supply headroom; - raises a Plasma Rig once it has the AI Foundry (pilot) + a Reactor (grid) and the manufactured machinery/electronics/AI Cores it costs — an unlimited late-game ore source, banked straight to the treasury via the phase-1 abstraction. Emergent, not scripted: only a patient developer on a resource-rich world researches deep enough to reach it. Odyssey-only; skirmish untouched. - engine/ai.js: aiIndustry extended with the Star Dock / Leviathan / Plasma Rig steps. - test/aiIndustry.test.js: with the Strategic tree pre-placed, the AI founds a Star Do…060fae6 -
AI industry (3/N): the AI climbs the factory chain and researches the tech tree
Claude +1: Claude Opus 4.8Unverified signatureA patient developer AI (Economist/Balanced — the same signal as the Refinery; a Rusher stays lean) now raises the whole industrial chain and works the Datacenter tech tree: - FACTORY CHAIN: builds the next building whose prereqs are met and it lacks, lowest tier first (Smelter → Datacenter → Assembler → Chip Fab → Machine Works → Antimatter Forge → AI Foundry → Torpedo Works). prereqsMet enforces the ordering, so it can only climb as the research beneath each factory lands. Spread in a spiral around the CC so the power grid stays tight; reserve-aware, one per think cycle. - RESEARCH: at a completed Datacenter it queues the next unowned node lowest-tier-first (Metallurgy, Fusion Containment, Microelectronics, Automation, Heavy Alloys, Precision Machining, Antimatter Containment, Machine Minds) — paid in gathered crystals/radioactives. So a world's deposits gate how deep its AI can climb. Also fixes a Reactor over-build: powerCap counts only completed reactors, so while o…dd7cbe7 -
AI industry (2/N): the AI powers and electrifies its base
Claude +1: Claude Opus 4.8Unverified signatureNew Odyssey-only aiIndustry phase in the AI brain: once it has a base + Barracks it raises a Reactor (scaled to need — another when the grid runs tight) and, once the grid is live, wires its non-power buildings in (electrified). That's 30% faster unit production at the Barracks/CC and +30% supply at Habitats — a universal "develop your base" behaviour that makes every Odyssey neighbour, on every background world, visibly stronger and more alive. Runs after unit production so power is raised from surplus (the army never freezes to build it), APM-budgeted and reserve-aware. Skirmish is a no-op (the endless gate), so the byte-identical short game is untouched. - engine/ai.js: aiIndustry(state, ctx) — Reactor when none/grid tight; electrify one eligible building per think cycle once powered. Imports powerCap/powerDraw and isElectrifiable. - test/aiIndustry.test.js: an Odyssey AI builds a Reactor and electrifies; a skirmish AI builds neither. Co-Authored-By: Claude Opus 4.8 <norepl…a271172
-
Decompose runAI into an orchestrator + named phase functions (byte-identical)
Claude +1: Claude Opus 4.8Unverified signatureAddress the second architectural-review finding: runAI was a 453-line function carrying the AI's entire economy/build/tech/expand/military decision tree inline — the hardest file in the repo to reason about or change safely. Pure extract-method. runAI is now a 33-line orchestrator that snapshots the world once (aiContext) and calls the phases in their load-bearing order: aiFoundOrSurvive → aiExpand → aiBaseAndTech → aiProduceAndFortify → aiResearch → aiMilitary (→ aiOffense) → focus fire Each phase is a named, single-concern function taking (state, ctx); the shared per-cycle snapshot and the running ore holdbacks (oreReserve / foundryReserve / refineryReserve that one phase banks and the next must respect) travel on the ctx object, typed by a new AiContext typedef in engine/types.js. No phase exceeds ~96 lines. Behaviour is provably unchanged: the action-budget accounting makes every statement's order load-bearing, so the extraction preserves that order exactly. A same-seed skirm…3553703 -
Tier 5: group the AI controller's bookkeeping under state.ai (no behaviour change)
Claude +1: Claude Opus 4.8Unverified signatureMove the 13 flat state.aiThink / state.aiScoutId / state.aiWaveCount / … fields into a single grouped state.ai object, decluttering the top-level state (which is the shared sim world) from the AI *controller's* private scratch state. This is the safe half of the two deferred Tier-5 refactors; the entity-id-counter move was deliberately skipped (moving it per-state would reintroduce cross-planet id collisions — the current single reset-per-construct counter is correct). Kept strictly behaviour- and format-neutral: - The save WIRE shape is unchanged: serPlanet still writes `ai:{aiThink,…}` and rehydrate still reads `P.ai.aiThink`, so every existing save loads untouched — no GALAXY_SAVE_VERSION / SAVE_VERSION bump. Only the live in-memory shape nests. - The think/wave/attack-schedule fields (previously set lazily on first AI tick and read `|| 0` / `?? …`) are now initialised in createGameState, so state.ai has a complete, self-documenting shape. Values are identical to the old …f1048a5 -
Tier 2 AI: Odyssey overlay so archetype temperaments actually diverge
Claude +1: Claude Opus 4.8Unverified signatureIn the play-forever Odyssey the three AI archetypes had collapsed into one: the skirmish desperation-timeout never fires, diplomacy guarantees a long opening peace, and the PROBE_MIN floor flattened every muster — so a "Warlord World" hosted the same passive, 3-unit-probing, 4-worker-forever neighbour as a patient research capital. Each archetype now carries an `odyssey` overlay (aiArchetypes.js), read ONLY when state.diplomacy exists: - Rusher: half the grace window, 2x grievance, bigger probes (5), and — vital for a mode with no end — a sustained economy (6 workers) that actually expands. It now turns hostile sooner and harasses harder, as its world advertises. - Economist: stays patient (full grace) but out-scales harder — fatter worker economy, earlier/greedier expansion, slightly bigger probe floor. Wiring: ai.js reads the effective field via a guarded `arch()` helper (workerTarget, expandWhenNodesBelow, probe floor); diplomacy.js scales grace/creep/grievance by the overla…206e424
-
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-4 "the finale": AI sieges a charging Gate, war provocation, tribute agency
Claude +1: Claude Opus 4.8Unverified signatureThe economic (Antimatter Gate) win was a ~2.5-min passive wait — nothing shot at the Gate, and on a rich world the neighbour might not even be at war. Tier 4 turns it into a DEFENDED finale, adds a diplomacy lever, and removes the late-game plateau. Siege targeting (engine/ai.js): once the AI can SEE the player's charging Gate, every wave converges on it — razing the galaxy-ender outranks even an economy raid. Shares one detector, chargingPlayerWonder (engine/wonder.js). Fog-gated (the AI must have eyes on the Gate) and Odyssey-only: in a skirmish state.diplomacy is undefined, so the target pick is byte-for-byte the original — skirmish replay untouched. War provocation (engine/diplomacy.js): a charging Gate sinks the neighbour's stance target from just-past-war (charge→0) to fully hostile (charge→1), flowing through the existing hostility() channel so muster, committed fraction AND wave cadence all escalate as the Gate nears firing. Only ever lowers the target, so on an already- depl…1204447 -
Odyssey: perf throttling, galaxy save/load, aggression ramp, cleanups
Claude +1: Claude Opus 4.8Unverified signatureImplements the three remaining review items (each brainstormed by a subagent, then reconciled into one plan). Perf — background-world throttling (engine/galaxy.js, boot.js): - stepGalaxy() advances the whole galaxy per frame: the active world at full cadence, background colonies on a coarser fixed step (BG_STEP×dt) spread round-robin by roster index. Keyed to an integer galaxy.tick, so it's deterministic (no wall-clock, no Map-order dependence) and conserves each colony's sim time. boot.js's loop calls it instead of ticking every world every frame. Galaxy save/load (engine/persist.js, saveload.js, boot.js, setup.js, hud.js): - serializeGalaxy/deserializeGalaxy extend the existing seed+delta persistence: a shared serPlanet/rehydratePlanet core (maps regenerate from seed; entities, node amounts, fog memory, AI bookkeeping restored), plus per-world market pressure + diplomacy stance and the galaxy meta (credits, active world, tick, entity counters, the shared global id…7e2a606
-
Odyssey Phase 4: living galaxy — scarcity diplomacy + colony alerts
Claude +1: Claude Opus 4.8Unverified signatureThe neighbours are no longer just obstacles: a world can be shared in harmony and then turn on you as its ground runs out, and the colonies you leave behind report in when they come under fire. Engine: - diplomacy.js (new): each Odyssey world's AI holds a stance (-1 hostile .. +1 allied) that drifts toward a target set by how depleted the world's deposits are — cordial while rich, hostile once mined out. - ai.js: the offense commit is gated on the stance — at peace the neighbour keeps building economy and army and still DEFENDS itself, it just launches no attacks on you until scarcity turns it hostile. A skirmish has no diplomacy, so the gate is a no-op there (the resolves-to-a-winner guarantee is untouched). - galaxy.js: every world gets a diplomacy stance; sweepColonies() watches the background colonies — reporting one coming under attack or being lost — and is the single place their sim events are drained (a colony isn't rendered, so nothing else consumes them). -…11037b3 -
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 3: AI raiding — feint-resistant defence + economy harassment
Claude +1: Claude Fable 5Unverified signatureTwo Tactical-gated (aiMicro) behaviours that make the opponent play like a human who pressures your economy and can't be jerked around by a poke. Both are Standard-AI-off and only ever engage a player who actually acts, so a passive opponent is untouched and the nine-world resolve guarantee holds with Tactical forced on (verified). - Feint-resistant defence: a small threat no longer yanks the whole army home. When the seen threat is under half the army, only a proportionate force (the closest units, ~2x the threat) peels off to deal with it while the rest hold their assignment; a genuinely large attack still triggers the full recall. Standard AI keeps the whole-army recall. - Economy raid: every 3rd committed wave, if it can see the player's worker line, goes for the economy instead of grinding the defended main base — sniping production the way a human harasses. The other waves still assault the base (and a desperation/timeout commit always does), so the CC still falls.…cd173c8 -
Tier 2: the Tactical AI builds a Ranger and scouts with it
Claude +1: Claude Fable 5Unverified signatureA smarter opponent scouts smarter. When Tactical, the AI trains one cheap Ranger (45 ore, far sight, all-terrain) up front and hands it the scouting job, instead of lending a combat unit off the line — better intel that costs the army nothing, and it feeds the existing hidden-CC search. updateScout now prefers a Ranger and only falls back to lending a fighter when there's none. Gated on aiMicro, so the Standard AI's economy and opening are byte-identical. Tests (+2, 277 total): the Tactical AI builds a Ranger and makes it the scout; the Standard AI never trains one. The nine-world resolve guarantee still holds with Tactical (Ranger + micro) forced ON; determinism intact. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AEQUVetLk31Jj7niDQAsXL08f6a8e -
Tier 2: Tactical AI focus-fire + an AI-tactics setup toggle
Claude +1: Claude Fable 5Unverified signatureThe single biggest AI upgrade: an opt-in "Tactical" mode where the opponent micros its army like a skilled player instead of leaving every shot to the dispersed auto-acquire. Focus-fire (engine/ai.js applyFocusFire + combat.js): when Tactical and it can SEE enemy combat, the AI points its whole nearby army at one target — the lowest-HP visible enemy, tie-broken toward the more dangerous one — so kills land faster and incoming damage drops sooner (Lanchester's square law). combat.js reads a new unit.focusId and prefers it while it's a live enemy in aggro, else falls straight through to the normal dispersed acquire, so a stale or too-far focus never freezes a unit and orders/wave-tracking are untouched. In a 10v10 skiff mirror this flips a 0-4 loss under dispersed fire into a decisive 5-0 win. Crucially it's a NO-OP unless real enemy combat is in sight, so razing an undefended base — every resolve test — is unchanged. Difficulty is now more than an APM dial: a new "AI tactics" setup o…e42e5cb -
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 -
AI: plant forward Refinery drop-offs to shorten a long ore haul
Claude +1: Claude Fable 5Unverified signatureTeach the AI to exploit the drop-off mechanic: the Refinery both researches its doctrine AND doubles as a resource drop-off, so once a macro AI is hauling ore a long way from every drop-off it plants additional Refineries out at those far seams — cheap, decentralized collection points that shorten the haul without the cost of a whole second Command Center. It keeps building a full CC for a genuine new base (on-site worker production + supply), but reaches further clusters with drop-offs instead of stacking CCs. Behavior: - forwardDropoffAnchor keys off ore the AI's workers are ACTUALLY mining, so a "far" seam is far from its own base (workers pick the nearest ore), never across the map at the enemy — and it only fires when the round trip is genuinely long. - Forward drop-offs spend genuine surplus only (the expansion reserve and a mix buffer stay untouched) and are capped (MAX_AI_REFINERIES), so they never run away with the AI's ore. - No seam on a small map is ever far enou…626d115 -
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 -
AI army-retreat: pull a losing attack back so engagements trade
Claude +1: Claude Fable 5Unverified signatureA committed, non-desperation attack that's been ground down to under 40% of what was sent — and can still SEE enemy combat units at the fight — now pulls its survivors home (a plain move, so they disengage rather than stop to trade) instead of feeding the last of them into a lost engagement. The saved veterans re-form into the next wave, so a repelled attack becomes a rebuild-and-retry rather than a total loss: engagements genuinely trade now, with both sides keeping a remnant. Two guards keep the resolves-to-a-winner guarantee intact, by construction: - A desperation (timeout) commit never retreats — it fights to the death, so the game always resolves. - A retreat only fires while the AI can see live opposition near the fight. Against an undefended/passive base (every headless resolve test) that count is zero, so the AI never retreats and razes the base exactly as before — verified that all nine worlds resolve at byte-identical timings to pre-retreat. engine/state.js carries…672f44c -
AI reliably teches its doctrine (Refinery ore-reserve + adaptive choice)
Claude +1: Claude Fable 5Unverified signatureThe macro archetypes now actually research their upgrade doctrine in a real match instead of leaving the Refinery unbuilt (units always ate the ore first). - ai.js: a Refinery ore-reserve, the same banking trick as the Foundry but sequenced AFTER it (and skipped while banking for an expansion), so units only ever pause for one investment at a time and the AI reaches a Refinery. Gated by a new archetype `wantsRefinery` flag — Economist and Balanced bank for it; Rushers skip it (their game resolves before an upgrade pays off). - aiDoctrine(): the AI prefers its archetype's doctrine flavour but follows the world's economy when it's clearly richer in the other doctrine's commodity (Assault = radioactives, Bulwark = crystals), so it never builds a Refinery it can't research — e.g. radioactive-poor Vesper now commits to Bulwark and gets both tiers instead of an idle Refinery. Result: five of six macro worlds now tech a full/partial doctrine (Ferros/ Glacius Assault, Vesper/…22b2d4c -
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 -
Content: economy saturation (diminishing returns on over-mined nodes)
Claude +1: Claude Fable 5Unverified signaturePiling workers on one deposit now has diminishing returns, so spreading out and expanding to a fresh field is a real economic decision instead of a free lunch. - entities.js: worker gains minerSoftCap (3) and minerFalloff (0.4). The first three miners on a node run at full rate; each extra pulls only 40% of a share, so the node's per-worker efficiency is the average — floored above zero, so a lone remaining seam never softlocks a poor world. - sim.js: countMiners() tallies gather assignments per node once per tick (frozen before any worker mines, so every miner reads the same count regardless of Map order — determinism-safe). - gather.js: miningEfficiency() applies the falloff at the take; no field / no count reads as full rate, so direct-call unit tests are unchanged. - ai.js: assignIdleWorkers now fills a seam to the soft cap then hops to the next-nearest (a saturation-steer term added to the distance score), shipped atomically so the AI doesn't pile everyone on one …1b1f280 -
Tier 6: performance & scale for large maps
Claude +1: Claude Fable 5Unverified signature- Uniform spatial grid (engine/grid.js), rebuilt once per tick, retires the three per-tick O(n^2) neighbour scans: separation, movement avoidance, and combat target acquisition now query only nearby cells. ~2.6x faster on the separation pass alone at 500 units, and the gap widens with scale. A full-scan fallback keeps every direct unit test byte-for-byte unchanged. - Viewport culling in render.js: fog cells, nodes, buildings and units outside the camera's world rect are skipped, so draw cost tracks what's on screen rather than total map size (16k+ fog cells on a 4x map). - Cache each resource node's silhouette (the seeded-PRNG polygon) instead of recomputing it every frame; only its radius changes as it depletes. - Index map.nodes by id (map.nodesById) — O(1) node lookups in gather, the AI, replacing per-tick linear .find scans that grew with map size. - Loop substep cap so a heavy tick degrades to slow-motion instead of spiralling into an ever-deepening backlog. - Pr…6d7e8fa -
Tier 5: make the AI a real opponent, not a fixed script
Claude +1: Claude Fable 5Unverified signature- Home defense with recall: when the AI can SEE enemy combat units pressing one of its buildings, the whole army (bar the scout) drops its attack and rushes the threat — so hitting its base while it marches on yours is no longer a free win. Exempt from the APM budget like the attack commit. - Garrison: a massed (size-triggered) attack now holds a home guard back (economist 3, balanced 2, rusher 0); a timeout commit still throws everything so the game always resolves. - Dynamic attack targeting: waves march on the nearest SEEN enemy Command Center, then any seen building, then a seen unit (raiding the army/worker line), and only fall back to the fixed start coordinate when it has spotted nothing — instead of forever walking to bases.player even after it's razed. - Scout swings toward the player's side after its centre sweep so the counter-picker actually sees the army it needs to counter; the AI also stops lending a scout while its base is under attack. - New tests: …2cc5301 -
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
-
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 economy fixes: expansion no longer starves the army; balance ore mining
Claude +1: Claude Fable 5Unverified signaturePost-review headless simulations across all nine worlds surfaced two AI economy regressions, both fixed here. The expansion reserve gated unit production, so on an ore-poor world (Glacius, Forge) the AI would freeze its army for minutes banking toward a 400-ore Command Center its income could never reach before the last seam dried — ending with a token army. The reserve now pauses only the lower-priority infrastructure spends (a second Barracks, the Refinery); unit production always flows, so the army keeps growing while the AI banks, and simply skips the expansion on a world too poor to afford it. Worker assignment sent every idle worker to the nearest spendable node, so on crystal-heavy Helix (its extra-seam modifier) workers piled onto crystals and starved the ore that funds the whole army — it fielded a single unit while banking thousands of crystals. Workers now keep the bulk of the crew on ore (the universal currency) with only a small secondary crew on crystals/radioactives fo…d6f4b4f -
Tier 4 AI parity + planet roster with per-world rule modifiers
Claude +1: Claude Fable 5Unverified signatureThe AI now plays all of Tier 4, driven by extended archetype profiles rather than hardcoding: it fortifies with Sentinel Turrets on the approach vector (Economist 2, Balanced 1, Rusher 0), expands to a second Command Center when home ore runs thin (banking for it via an ore reserve that pauses the unit mix instead of starving it), runs one shared production cycle across multiple Barracks (fixing the one-unit-at-a-time ceiling), and folds the Breacher into the patient mixes where the map can pay for it. A spendable-node worker filter keeps a poor-economy world's AI off dead-end commodities. Every new field is read with a use-site default, so a legacy profile still runs. Opens the picker from 3 to 9 curated worlds, each assigned an archetype and up to one data-driven rule modifier threaded symmetrically through the engine: Glacius slows all units, Nimbus/Pyralis shorten/lengthen sight and aggro, Helix adds a crystal cluster, Oort enriches deposits, Forge speeds construction. All modifie…ac7d42f -
Tier 4 economy: supply cap and the Habitat that raises it
Claude +1: Claude Fable 5Unverified signatureArmy size was bounded only by ore, which flattened macro decisions. Now every unit costs supply (worker/skiff 1, bastion/lancer/breacher 2), Command Centers grant 10, and a cheap Habitat (75 ore, fast) grants 8 — a macro choice, a raidable weak point, and a hard bound on the sim's O(n^2) hot loops all at once. engine/supply.js recomputes used/cap on demand from state — no stored counter, so a destroyed building's queued jobs free their reservation with zero bookkeeping and drift is impossible. queueProduction reserves at queue time (all queued jobs count), rejecting after the affordability check and before payment so nothing needs refunding; a blocked attempt raises a productionBlocked event that the HUD turns into a beep and a red supply readout. Losing a Habitat can leave a player legally over cap — nothing dies, production just blocks until they rebuild. The AI raises a Habitat as it nears the cap (in the same change, so its production never silently stalls) and the HUD shows live…04b2d3e -
Tier 4 foundation: placement validation, buildable expansion CC, guaranteed ore
Claude +1: Claude Fable 5Unverified signatureAdd engine/colliders.js as the single source of truth for building placement: canPlaceBuilding rejects overlap with buildings, live resource nodes, and map bounds (units are deliberately ignored — they move, and separation already untangles them), and findPlacement does a deterministic outward ring search so the AI can slide a fouled build spot to nearby valid ground. issueBuild now validates placement before charging, so a rejected spot never leaks resources. The AI routes its barracks/refinery spots through findPlacement in the same change, so validation can't stall its build order. Make the Command Center buildable (400 ore, 30s) instead of free and instant; the starting CC is still seeded complete. Multi-CC dropoff and all-CCs victory already worked, so an expansion is a real second base with no further plumbing. generateMap guarantees a lean mirrored ore cluster near each base on any world whose deposit table yields no ore (distance-triggered, so ore-bearing maps stay byte-iden…1c88bda -
Tier 3: repeated AI waves + counter-building, combat feedback, minimap, build validation
Claude +1: Claude Sonnet 5Unverified signatureAI: - Replace the one-shot attack flag with a home-army/committed-army split, so the AI launches repeated waves for the rest of the match instead of attacking exactly once and then sitting on an ever-growing army. - Every third unit built reacts to whatever combat type the player fields most, building its hard counter (derived from the bonusVs tables, not hardcoded) instead of blindly following the archetype's mix -- the Skiff/Bastion/Lancer triangle now actually matters to the AI's play. Combat feedback: - attackHit/entityKilled sim events now carry enough info for a new effects.js (transient, client-side) to drive tracer lines and death flashes, drained each render frame the same way sound events already were. Situational awareness: - A minimap (fog, deposits, units/buildings, camera viewport) that doubles as a click-to-jump camera shortcut. - An "Under Attack" banner + alarm sound + world/minimap ping when the AI lands a hit on the player, throttled so a siege …cae3dc6 -
Add the Lancer: close the rock-paper-scissors triangle
Claude +1: Claude Sonnet 5Unverified signatureSkiff/Bastion was a single hard counter, not a real RPS cycle. The Lancer is a third combat unit (long-ranged, armor-piercing, squishier than a Bastion) whose bonus damage targets Bastion specifically, while Skiff's existing speed/cost edge is repurposed with a new bonus against Lancer — so each unit counters exactly the matchup that would otherwise be its worst, and nothing beats all three at once. - entities.js: new lancer unit def, bonusVs wired for all three legs of the cycle, added to Barracks' production list - render.js: distinct 4-pointed star silhouette so all three combat types plus Worker read apart at a glance - main.js: Produce Lancer button - aiArchetypes.js/ai.js: bastionRatio replaced with a generic unitMix cycle per archetype (all three now include Lancer), and the AI's army filter/attack-wave logic generalized off UNITS[type].role instead of hardcoded Skiff/Bastion checks - new combat.test.js/entities.test.js coverage proving each leg of the cycle and tha…89c12eb -
Tier 2: rally points, Refinery upgrades, AI archetypes, and sound
Claude +1: Claude Sonnet 5Unverified signatureFour features, in dependency order (smallest/most self-contained first): ## Rally-point UI (engine/commands.js, input.js, render.js) Right-click a single selected production building (Command Center or Barracks) to set where it sends newly produced units — the engine already stored an arbitrary rally point per building and read it fresh at spawn time, there was just no way to change it. Shows a dashed line + marker for the currently selected building only, to avoid cluttering the view with every building's rally line at once. ## Refinery + upgrades (engine/entities.js, production.js, combat.js) A new building (built by a Worker like the Barracks) researches two one-time, player-wide purchases: Reinforced Plating (crystals, -15% damage taken) and Overcharged Weapons (radioactives, +20% damage dealt). Both are live multipliers applied in combat.js's damage calculation rather than baked into unit stats at spawn, so they affect a player's whole army immediately, not just future product…9eded26 -
Tier 1: camera + zoom, fog of war, and a Skiff-countering unit
Claude +1: Claude Sonnet 5Unverified signatureThree independent features, the biggest-leverage items from a brainstormed improvement list, built in dependency order (fog of war's reveal effect only means something once you can actually pan around to see it). ## Camera + zoom (camera.js, new) The whole 1600x1000 map used to be squeezed into one screen — units were tiny dots. The canvas is now a fixed-resolution viewport with a camera (pan via WASD/arrows, zoom via mouse wheel toward the cursor, clamped to map bounds) instead of a 1:1 map-to-canvas mapping. render.js draws through a camera transform; input.js's toWorld() converts screen->world through it too, so selection/commands keep working exactly as before. Fixed a latent bug while wiring this up: attachInput() was never cleaning up its event listeners, so every "Choose another battlefield" replay stacked a fresh full set on the same canvas/window — added an AbortController-based destroy(). ## Fog of war (engine/fog.js, new) A coarse grid tracks which cells are currently in…34ea8bc -
Fix skiff visibility and group-move stacking
Claude +1: Claude Sonnet 5Unverified signatureThe triangle shape from the last commit was there but nearly invisible: its outline color (#05070f) was literally identical to the canvas background, so anti-aliasing blurred its corners into what read as just another circle. Outline is now a light color that actually contrasts, and the triangle draws bigger (radius x2). Separately, any multi-unit move or attack-move sent every unit to the exact same point, so a group converged into one visually stacked pile mid-transit — including the AI's own attack wave, which set each skiff's order directly rather than going through the shared command helpers. issueMove/issueAttackMove now spread a group across a small grid centered on the clicked point, and ai.js routes its attack order through issueAttackMove instead of hand- rolling it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BDmJ8vc9jxc6TUegK9F2YJ3ea9b20 -
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