16 KiB
Aetheria arena: deterministic headless matches between swappable brains
Date: 2026-10-21. Status: target, drafted by Imagination for Self to commit and admit.
Body pinned at GameCult/Aetheria origin/master d52b50f9. Probes ran on that tree.
1. Why
The operator ruled that arenas are a design instrument (aetheria-release:ruling:arenas-explore-the-metagame). Agents
explore the metagame through virtual play, and the design iterates on what they find. Her words: "We use agents to explore the
metagame space so that we can iterate on the game design through virtual play." The combat Colosseum develops NPC
behaviour by implementing many strategies over shared primitives, and the arena selects among them
(colosseum-is-the-npc-behaviour-lab). Colosseum statistics fit the composition model that settles fights off screen
(colosseum-feeds-matchup-model, matchup-composition-model). The strategic arena trains on generated galaxies varied
across seeds (strategic-start-r2-generator-trains). The demo waits on how well that sim performs
(demo-start-gated-on-sim-quality).
All of that rests on one foundation. That foundation is the campaign:
- A match is a pure function of its inputs.
- A brain reaches the world only through a typed seam.
- A match can be recorded, replayed and checked.
- Matches run headless on Yggdrasil fast enough to beat rating noise.
The strategic state, its rules, the matchup model and the strategic arena are later campaigns (section 6). They consume this foundation and do not reshape it.
2. Body facts (probed at d52b50f9)
Randomness:
- Combat dice. Combat dice are per shot:
FireControl.Commitseeds a local generator fromZone.CombatSeedand the shot id (FireControl.cs:931). AI dice are per pilot:SimulationDice.For(CombatSeed, CombatSampleStream|PatrolStream, Agent.Id)(SimulationDice.cs:9-38,Combat.cs:178,Zone.cs:320-323). Drop dice useZone.DropSeedand the death ordinal (Zone.cs:131,159,460). - CombatSeed ignores the run seed.
Zone.CombatSeedisGalaxyZone.Name.StableHash()(Zone.cs:158), with no run seed in it. This is ruled:aetheria-release:ruling:run-seed-drops-only(2026-10-18, operator). It means one scenario replayed at one galaxy seed rolls identical dice every round. - The item stream at run start.
ItemManager.Randomis still clock-seeded at construction (ItemManager.cs:20).RunStart.SeedItemsreseeds it from the galaxy seed before arena generation (step 1) and before staging (step 2) (RunStart.cs:62-65). After staging, the stream runs on shared. Its draws are lot quality on every mint (ItemManager.cs:147,165), the scatter of an unpacked ship packed at the origin (Zone.cs:276), the wormhole exit and console spawns (ActionGameManager.cs:589-593,710), and staged and generated fits (ScenarioStage.cs:31,92). - Zone generation. It seeds a local generator from the zone's name hash and its position (
ZoneGenerator.cs:52-53). A zone reached by warp is generated inActionGameManager.PopulateLevel(:716-731), a Unity owner, and not in ServerShared. - MIConvexHull is gone. Commit
e56af288replaced it with a double-precision Delaunay (Delaunay.cs). Thecolosseum-npc-labfollow-up's "unseeded MIConvexHull randoms" item is stale. - Trig is the host's libm. CultMath trig and pow forward to
MathF(CultLib packages/cultmath/src/CultMath/math.cs:62-70). Cross-host float identity is therefore unproven: Windows UCRT and glibc may differ in the last ulp.
Determinism probe:
- What ran. A scratch partial of
RunStartTestson Yggdrasil, Linux, .NET 10.0.12. It ranLaunchDuel: the Duel scenario, seed 104, the item stream reseeded. The player is scripted to fire every 90 steps and the rival is an AI. It stepped 36,000 steps at 1/60 s and hashedSnapshotevery step. - Result. Three separate processes gave the same hash,
082F7400.... The first death came at step 1445. Generation took about 325 ms per launch, and stepping ran about 9,700 steps/s. That is about 160x real time, but mostly with one ship left after the first death, so it is not yet a two-ship throughput figure. - Existing pin.
SimClockTests.OneHistoryTwicealready pins in-process repeatability of the Duel.
How brains reach the world:
- The pilot writes ship fields directly.
Agent.UpdatewritesShip.Turn,Ship.MovementDirectionandTryActivateConsumable(Agent.cs:53-110).CombatwritesTurnand calls fire control (Combat.cs:149and others).MoveTowrites too. - The flight decides targets.
FlightpicksShip.Target(Flight.cs:178) andAgent.Taskfrom the union of its members'VisibleEnemies, so target choice is sensor-gated. Flights are keyed by faction, one per zone (Zone.cs:75,312), and tick inZone.Update(:363). - Aiming reads ground truth.
Combatreads the target's truePosition(Combat.cs:52). Its picture of the target's weapons comes from the EW book (Combat.cs:55-60). - The player has its own writes. The player path is
ActionGameManager, with 12 direct writes of the same fields. - Agents hold live references. An agent holds live
Ship,Zone(throughShip.Zone) andItemManagerreferences (Agent.cs:27-31). Nothing narrows what a brain can read or write. - Defined but unused tasks.
HaulingTask,Mining,StationTowingandSurveyare defined, but nothing constructs them.
Harness:
- The run owner.
RunStartowns new runs and every run is aScenario, compiled C# (Scenario.cs,Scenarios/*.cs: 17 scenarios, Duel and Pursuit among them). - Headless today.
Zone.Updatesteps headless. Fights resolve headless, with commit and resolution inFireControl.SimClockis the fixed-step owner. - No runner. There is no headless runner command:
tools/AetherDb/Program.cs:25-56has catalog commands only. The scenario tests are the only headless driver. - Off screen. Only the player's zone ticks (
ActionGameManager.cs:1357-1361). Zone ownership is written only at generation (Galaxy.cs:346) and on load (:77). There is no job or mission generator (Galaxy.cs:578).
3. Invariants
A-pure: A match is a pure function of (code revision, catalog revision, scenario id, match seed, brain ids per side, host determinism class). The same inputs give the same per-tick state digest, in any process, run alone or in a batch. The step is part of the code revision:SimClock.GameStep, never a driver's own literal.A-no-clock: No input to a match comes from the wall clock, process identity, hash-code ordering of reference types, or a stream another subsystem advances. Every draw a match reaches comes fromSimulationDice(or a successor owner) keyed by a structured seed. A missing seed is refused, never defaulted to the clock.A-seam: A brain reads only its side'sPilotViewand writes onlyPilotCommands. One applier in the zone step writes ship control fields from commands. Brains, the player's input and the playground use that applier and nothing else. A brain holds no liveShip,ZoneorItemManagerreference.A-truthful-view: A view holds what that side's sensors and book give, the player's information. It never holds ground truth about another side. (Depends on questionobservation-fidelity.)A-replay: A replay holds the match inputs, the command stream and periodic digests. Replaying it reproduces every digest, or it names the first tick that diverges. A replay records its host determinism class. Undercross-host-determinism, it is trusted only on that class.A-compiled: Brains and arena scenarios ship as compiled C# content in the Aetheria repo, under stable ids. No model call, interpreter or DSL runs inside a match (arenas-explore-the-metagame, Aetheria's compiled-mods rule).A-one-sim: The arena runs the game's own simulation. There is no arena-only physics, combat or rules fork. An exploit the arena finds is a game bug.A-headless: A match runs with no Unity type loaded, from a command that runs in Yggdrasil's dotnet container.A-records: Match results, replays and tournaments are CultCache documents in a directory the runner is given. They never land inGameData/run.cc,player.ccor the catalog.
4. Not in scope
- Strategic state: holdings, stockpiles, faction relations, strength or fronts. Also any off-screen zone tick, ownership change, job board or generator starting-infrastructure rule. These belong to campaign 2.
- The matchup composition model and its fitting or residual retraining (campaign 2 or 3).
- Character or fun scoring, the model judge and the operator's pairwise calibration
(
strategic-scoring-success-and-character; strategic arena). - A large strategy library. This campaign lands the seam and two brains, enough to prove a tournament. The vocabulary grows as content afterwards.
- An Eve/CultMesh operator surface for tournaments. Results are CultCache documents, and a surface is a follow-up once there is a consumer.
- Moving warp-time zone generation out of
ActionGameManager. It becomes in scope only if a match crosses a wormhole, and arena matches are one zone. - Cross-host deterministic trig in CultMath. It is a CultLib follow-up if
cross-host-determinismrules that way. - The Unity presentation of arena matches, and replay viewing in the client.
5. Identity, lifecycle, authority
| Kind | Identity | Lifecycle | Authority (owner; forbidden writers) |
|---|---|---|---|
| Brain (compiled strategy) | A stable string id declared by the class, such as doctrine.v1. Ids are unique across the brain registry, which refuses a duplicate. Behaviour is versioned by code revision, not by the id |
Added as code. Revised by commits: a behaviour change that should not merge ratings bumps the id's version. Retired brains stay compiled, become products or faction behaviour (colosseum-is-the-npc-behaviour-lab), or are deleted |
Owner: the brain registry, compiled. Forbidden: any runtime-loaded or model-written brain, and a brain writing ship fields directly |
| Arena scenario | A stable id string on Scenario. Today the identity is Name, a display string, so a stable Id is a finding to add |
Same as brains: code, revised by commits. A match records its id and the code revision | Owner: the Scenarios registry. Forbidden: a match staging entities outside RunStart.Stage |
| Match seed | A uint chosen by the tournament, nonzero. It feeds the galaxy seed, the item-stream steps and the start variance (per question match-variance) |
Fixed per match, never rerolled. Replay reuses it | Owner: the runner, from the tournament spec. Forbidden: the clock, and Scenario.Seed's clock fallback inside a match |
| Combat, drop and pilot dice | SimulationDice.For(zoneSeed, stream, ordinal). CombatSeed is the zone name hash (ruled) and DropSeed mixes in the run seed |
Derived each draw, never stored | Owner: SimulationDice. Forbidden: any draw on ItemManager.Random from behaviour or combat (ai-sample-offset-off-item-stream) |
Item stream (ItemManager.Random) |
One stream per ItemManager |
Reseeded by RunStart steps 1 and 2. After that it advances on every mint |
Owner: RunStart seeds it and ItemManager draws. Forbidden: its clock-seeded default reaching a match (A-no-clock) |
| PilotCommand | (match, tick, side, ship ordinal Agent.Id) |
Produced by a brain each tick and consumed by the applier the same tick. Recorded in the replay, never re-derived | Owner: the side's brain, or player input. Forbidden: any writer of ship control fields other than the applier |
| PilotView | (match, tick, side) | Derived each tick from the side's sensors and book. Never stored, and never in the replay except as a digest if wanted | Owner: the view builder, one function. Forbidden: a brain reading Zone, Ship or ItemManager |
| Replay | A CultCache document keyed by match id = (tournament id, ordinal) | Written once when the match ends and never edited. Superseded only by a new match. A divergence on replay is a finding, not an edit | Owner: the runner's recorder. Forbidden: a replay written by the game client, until a cut says so |
| Match result | Keyed by match id, like the replay | Written with the replay. Read by the tournament and, later, by the matchup model | Owner: the runner. Forbidden: hand edits, and results with no replay |
| Tournament | A CultCache document keyed by a tournament id (date plus label). It names its brains, scenarios, seed range, code and catalog revisions | Created by the runner. Ratings are derived from results and recomputable. A rerun is a new tournament | Owner: the runner. Forbidden: ratings stored as truth without the results they derive from |
| Host determinism class | (OS, architecture, runtime) | Recorded on every replay and result | Owner: the runner. Forbidden: comparing digests across classes unless cross-host-determinism rules them one class |
No cell is empty. The Scenario row exposes a gap: Name is today's only identity, and it is a display string. Cut 1 adds Id.
6. Campaign split
-
aetheria-arena(this one): determinism, the seam, replay, the headless runner and the first tournament. -
aetheria-living-world(later):- strategic state owned in ServerShared;
- the galaxy generator plus faction starting-infrastructure rules;
- the off-screen strategic tick at reduced detail;
- ownership changing hands;
- the lore institutions as damping rules (
snowball-damping-lore-institutions); - the job board from needs;
- the strategic arena on generated seeds, scored on success and character.
It owns the demo gate (
demo-start-gated-on-sim-quality) and absorbs follow-uprelease-target-living-demo's map. -
aetheria-matchups(later, after 1 and enough brains): Colosseum statistics fitting the composition model and the on-screen residual loop. It is split from 2 because its foundation, a fitted statistical model, is a different pipeline. Campaign 2 consumes its output through one typed predictor.
7. Cut sequence (campaign 1)
- Determinism ledger.
- Add
Scenario.Id. - Refuse an unseeded
ItemManagerin a match (delete the clock default and pass the seed in). - Move the
Zone.cs:276scatter ontoSimulationDice. - Pin a two-ship AI-versus-AI digest test.
- Run the same digest on Starfire and on Yggdrasil, and record the cross-host result.
- Add
- Command seam. Add
PilotCommandand one applier inZone.Update. Route Agent, Combat, MoveTo and ActionGameManager's direct writes through it, and delete their direct writes. - View seam. Add a
PilotViewbuilt from sensors and book. Brains lose their liveShip,ZoneandItemManagerreferences.FlightandMinionbecomeDoctrineBrainbehind anIBrainregistry. Aiming moves to the view ifobservation-fidelityrules for it. - Sim rate (ruling
lower-game-sim-rate, mapdocs/aetheria-arena-sim-rate-map.md, speccut-sim-rate.r1).- One game tick,
SimClock.TickRate, lowered for game and arena alike, soA-one-simholds unchanged. - Before it, timers carry their overshoot and passive sensing decays exactly, so content authored in seconds means the same thing at any tick.
- The two-AI digest is repinned once, at the game step, for those two causes.
- It lands before replay and the runner, so recorded digests and measured throughput are at the real rate.
- The rate is the one question
sim-tick-ratedecides, then the equivalence test, then her playtest.
- One game tick,
- Replay. The recorder writes inputs, commands and digests every N ticks as CultCache. The replayer reproduces them and names the first divergent tick. Replay tests read an existing match.
- Runner.
AetherDb arena: a scenario, two brain ids and a seed range produce results and replays in a given directory. It runs in Yggdrasil's dotnet container, with two-ship throughput measured and recorded. - First tournament. Round-robin over two or more brains (
DoctrineBrainplus one variant) and the combat scenarios, with enough seeds per pairing. Ratings are derived with confidence intervals, and the results document is ready to feedaetheria-matchups.