Spaces:
Paused
Paused
Commit History
-
Tier 3f: split the selection panel's derived state out of its DOMβ
Claude +1: Claude Opus 5Unverified signaturehudSelection.js was 2242 lines. The natural seam is not "which panel" but "derive vs render": panelSignature is the panel's entire reactivity model β it decides whether renderSelectionPanel rebuilds AT ALL, so a panel whose own state contributes no term to it silently never redraws, which is exactly what left the Freight Lane and Colony Policy controls inert. A model that load-bearing should be readable without scrolling past 800 lines of button construction. hudPanelSignature.js takes panelSignature and the derivations it shares with the renderers β factoryStatus, loadableComs, countByType, summaryRowLabel, counterInfo and the hoisted cost/counter tables β because both sides call them and the two must never drift. It imports no DOM and no hud.js, so unlike the panel itself it is a genuine leaf: drivable from a test, outside the hud/hudSelection import cycle. hudSelection.js drops to 1915 lines with the panel's output byte-identical, which the golden lock added in the previous commit β¦97f3bdf -
Tier 1, step 4c: the AI bench lies, and an owner-parametric leak (A9, A12)β
Claude +1: Claude Opus 5Unverified signatureA9 β runDuel applied BOTH candidates' overrides into the same live STRATEGIES table, and applyOverrides merges rather than replaces. Two variants of the same dial under the same strategy key β which is the natural A/B tuning workflow β collapsed into one Frankenstein row belonging to neither candidate, played by BOTH seats, and reported a perfectly normal-looking winner that was pure seat/seed noise. This is the function every AI tuning decision runs through, and `search --tournament-against` scores every dial value through it, so a session could "promote" a change that was never actually measured. A collision is now refused up front with a message naming the shared key and suggesting distinct ones; round-robin and Swiss inherit the check across every pair before a long tournament starts rather than partway through. A12 β effectiveMix threads `owner` into prereqsMet but called plannedMix with none, and plannedMix called strategyFor(state)/difficultyFor(state), both of which default toβ¦5853645
-
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
-
Colony economy rework: Freight Lanes + Colony standing ordersβ
Claude +1: Claude Sonnet 5Unverified signatureThe roadmap calls these "two halves of a single colony-economy rework" and they share the same background-scheduler plumbing in stepGalaxy/persist.js, so they land together as designed, even though each is its own mechanic: Freight Lanes (engine/galaxy.js): a galaxy.lanes array. At a Spaceport, assign parked freighters to a LANE (source -> destination, commodity filter) via createLane/assignShipToLane. Every LANE_PERIOD galaxy ticks, runLanes moves min(assigned cargoHold, filtered stock) straight from the source world's treasury to the destination's β a standing investment, not free teleportation: assigned ships are revalidated every cycle (still alive, still parked within JUMP_LOAD_RADIUS of a source Spaceport) and are excluded from stagedRiders/jumpManifestAll so they never get swept off on a jump. UI: a lane-management panel on the Spaceport selection panel (hudSelection.js) plus a read-only summary on the starmap. Colony standing orders (new engine/colonyPolicy.js): a per-planet β¦84220dc -
Graduation reaches the army: a developed Rusher techs the Foundryβ
Claude +1: Claude Sonnet 5Unverified signatureA graduated Hard Rusher (or an Economic-strategy wantsIndustryAlways override) climbed aiIndustry's full factory chain to a Star Dock and could field Leviathans, but its Barracks still cycled Skiff/Bastion forever β aiEconomy's wantsFoundry/wantsArsenal gates read only the raw archetype.unitMix, which a pure Tier-1 Rusher profile never mentions a gated unit in. Move wantsDeepIndustry + RUSHER_GRADUATE_TIME from aiIndustry.js into aiWorkers.js (a leaf module; aiIndustry now imports it back, no cycle: aiStrategy.js/aiDifficulty.js are pure data tables with no imports of their own). Add plannedMix(state, archetype) there: archetype.unitMix extended once with ['lancer','lancer','dreadnought'] when wantsDeepIndustry(state) is true in Odyssey, but only if the base mix has no Foundry-gated entry already (so Economist/Balanced, which already carry one, are a no-op). effectiveMix filters this planned mix exactly as it filtered the raw archetype mix before, so the extension is deadlock-free β aβ¦729f7b4
-
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
-
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 Command Center zone affinity for haulers, plus a worker repair jobβ
Claude +1: Claude Sonnet 5Unverified signatureHaulers/servers/ferriers used to pick jobs by raw nearest-distance across the whole empire, with no notion of "which base do I belong to." Once a busy base's own β€2-per-target caps filled up, idle labour got routinely poached across the map by another base's job. engine/gather.js zoneFirst now restricts each search to the worker's own Command Center zone first, only widening to the whole empire when the home zone has nothing to offer (byte-identical to before on any single-CC game). The auto-repair Mender's roam target-pick adopts the same zone-first logic via a shared pickRepairTarget helper (engine/repair.js). Buildings could previously only be healed passively by a Mender in range. Workers can now be assigned a repair job (auto, zone-first, after ferry/haul/service in priority; or manual via right-click, issueRepairBuilding), patching a damaged own building at a flat rate, capped per building like the existing haul/service caps. Both changes are pure functions of state and reused β¦32d2559
-
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