Spaces:
Paused
Paused
Commit History
-
AI: give the Odyssey economy something to bootstrap from
Claude +1: Claude Opus 5Unverified signatureRan the four shipped strategies against EACH OTHER (`ailab duel --candidates`, 6 worlds x 2 seeds x both owner slots, Medium and Hard) instead of against a scripted sparring bot. Economic won both brackets outright -- 71% at Medium, 68% at Hard -- and Aggressive came LAST at Medium on 25W-47L. The solo sweep says the same thing from the other side: on ferros, the same Economist archetype reaches development 3 under `default` and 27 under `economic`. One cause, visible twice. aiEconomy.js sizes the Odyssey worker target as `archetype x strategy x difficulty + industryCount * 2`, and the only term that grows during a session is a reward for industry the AI has to already be rich enough to have built. On a strategy that also spends freely on units -- default and aggressive, which galaxy.js hands to roughly half the galaxy -- unit production takes every ore first, industry is never funded, the target never grows, and the loop never starts. Confirmed by a single-dial diagnostic rather than…bc8dc44 -
Tier 1, step 2b: expand // @ts-check coverage from 10 files to 20 (D3)
Claude +1: Claude Opus 5Unverified signatureAdopted in the order docs/code-improvement-tiers.md sets out, cheapest and highest bug-class value first: supply, aiCommon, colliders, victory, production, scout, the three AI leaves (aiStrategy, aiDifficulty, aiArchetypes), and finally persist.js — the prize, because every save field is a State/Unit/Building field read by name, so annotating it makes save-shape drift a check-time error. Each file's exports are annotated with the shared typedefs, not just given the pragma: per the guard added in the previous commit, a pragma without annotations checks nothing. Doing it surfaced three more genuine drifts in engine/types.js, all found by the checker rather than by reading: - Building.rally was typed {x, y} but carries nodeId — the rally-to-resource feature, where a rally dropped on a live node makes a produced unit spawn already mining (engine/production.js). - Building.queue entries were typed {unitType, progress} but carry `alt`, which records that a job was charged the unit's…7e764b0
-
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
-
Make the grievance / aggression cooldown part of the AI's personality
Claude +1: Claude Opus 5Unverified signatureSouring was already flavoured by temperament — a Warlord world turns on you twice as fast as a patient one (grievanceMult) — but COOLING OFF was flat. Every world recovered from being bled at the same DRIFT_RATE, and `provoked`, the flag that lets a never-initiating strategy answer back, latched for good. So once you had fought anyone, every temperament held the grudge identically and for ever, which is the one place the three archetypes read the same. One `forgiveness` dial now drives both halves of that cooldown: - The stance drift is ASYMMETRIC. Recovery runs at DRIFT_RATE * forgiveness; souring keeps the stock rate, because that direction is already grievanceMult's job. A test pins the separation — the obvious implementation (scaling the whole drift) would have made a slow-forgiving personality also turn hostile slowly, which is not what was asked for. - Provocation is a TIMESTAMP, not a latch. It fades after PROVOKE_MEMORY / forgiveness seconds of quiet, …f8f75e7 -
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
-
Fix Economic/Force Parity AI attacking an unprovoked player after enough time passes
Claude +1: Claude Sonnet 5Unverified signatureBug report: with Economic strategy selected, an AI that a player had never threatened (no military built, just a scout) would still eventually attack — "become more aggressive and raid the player" — despite doing nothing to provoke it. Root cause: scouts were never the issue (Ranger is role:"scout", already excluded from every threat/hostility check). The real cause was that neverInitiates only blocked the THRESHOLD-based voluntary commit, not the skirmish desperation-timeout fallback — so even a strategy whose whole premise is "never attacks unprovoked" would still throw an all-in wave once enough real time passed (~23 minutes for an Economist archetype), regardless of anything the player did. Fix: neverInitiates now blocks every voluntary commit, including the desperation timeout, so it means what it says. The resolves-to-a-winner guarantee still holds for a mutual-turtle skirmish — victory.js's existing score-based 40-minute match time limit settles it without requiring combat, wh…ec3d9d8 -
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