Imagination pass 1 against dcd7bbc5, designed from the operator's own
words (quoted in the map). Cuts 0-4; four operator questions open
(Q1 arena in a real run, Q2 who records results, Q3 test-only designs,
Q4 which ad hoc affordances retire).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
49 KiB
Scenarios: Cut Map
Date: 2026-09-30, 02:10 CEST. Imagination pass.
Status: cut map. Nothing has landed. The target lives in section 0 until Self splits it out into
docs/scenarios-target.md; this document owns the means.
Anchors are against Aetheria origin/master dcd7bbc5 (the fire-control merge), read in the clone
C:\ws72-fuse5 with git show/git grep. The operator's tree F:\Projects\Aetheria was not touched. No
build ran and no Yggdrasil job ran. (read) marks a claim read from source at the anchor. (probe) marks a
claim measured against a real file.
0. Target
0.1 The request, in the operator's words (2026-09-30)
"I feel like this would be much more manageable as a list of things to verify over time rather than a monolithic smoke script that doesn't respect how the game works. Move gear onto Djinni? That's a lot of items to spawn, and am I expected to have the catalog memorized to spawn them? If you want me to test a bunch of behaviors under specific conditions, find a way to spawn me in an arena where those conditions are present. If you want me to kill a guy and loot him, spawn me next to a ship with no armor or weapons, that sort of thing. I wouldn't mind a scenario affordance in the main menu for this sort of job, that way the test fixtures can be authored, retained and available. But that sounds like a Eureka cut."
What those words settle, so no question below re-asks them:
- A scenario is an arena whose conditions are present at spawn. The player's ship and every other entity are already fitted and placed. The operator never spawns gear by catalog name and never fits a ship by hand.
- The fixtures are authored and retained, and the main menu lists them.
- Verification is a list of behaviours checked over time, not a play order.
0.2 Ends
- The main menu lists scenarios. Picking one starts a run in its arena.
- A scenario is typed catalog data. It is authored once, kept in git, and replayed at any later revision.
- Every operator check is a typed check record: what to do, what to observe, where the rule came from, and the scenario that sets it up. Results accumulate as typed records carrying the revision they were observed at.
- Checks A-F and checklist steps 7-20 are the first consumers (section 5).
0.3 Invariants
- One owner decides what a new run starts with. A plain New Game and a scenario launch share the menu path,
the galaxy build,
StartGameand the staging owner. Scenario launch is New Game with a scenario as input; there is no second boot path. A plain New Game is the absent-scenario case. - One primitive admits an entity into a zone. Zone construction, staging, warp arrival and undock all admit through it, and agents are created there and nowhere else.
- A scenario sets conditions and nothing else. Once staged, its entities are ordinary run state. The rules under test run through the same code whether or not a scenario started the run. No scenario-only branch exists in simulation code.
- Fixtures are typed state. Scenario, loadout presets and test designs are CultCache catalog records. Checks and results are CultCache records. No load-bearing JSON, text scripts or prose checklists.
- A scenario that cannot stage is refused before anything is cleared, with its failures listed.
- The catalog stays read-only at runtime (
AetheriaStores.cs:13-21).
0.4 Not in scope
- The full run lifecycle owner (
Run) ofdocs/headless-playground-cut.mdCut 1 (warp, dock, save, death as one ServerShared owner) and itsAetherDb playinterpreter (Cut 3). This map takes over only the new-run staging half. The playground map is stale (anchors1e647953, fork S superseded by settings-globals) and must be re-anchored before Hands. When it lands, itsnewcommand stages aScenariorecord instead of reading a.playtext line. - Brokkr. It is not needed for any cut here: agents author scenarios headless through
AetherDb, and the operator launches them from the menu. Brokkr's Unity actions (setEditorPlayState,captureEditorView, scene edits;F:\Projects\Brokkr1357705, read) have no way to pick a scenario in the menu. Agent-driven Unity play would need an Aetheria install cut plus a "launch scenario" intent. Recorded as a follow-up, not a fork. - The ship-purchase docking-bay gap (checklist section 8). It is a design gap in
TradeMenu.BuyandCommissionShip, not a verification affordance.CommissionShipstays;TradeMenu.Buyis its consumer. - Tier colours (settings-globals Cut 1). The check is recorded as a known failure (section 5).
- Balance and feel tuning. A check can record a feel call; it does not tune.
1. Body findings
1.1 The new-run path today (read)
| Concern | Anchor | Notes |
|---|---|---|
| Menu | MainMenu.cs:92-184 |
Continue :103-108; New Game :110-176; Settings; Quit. The same component runs in-game (InGame, :96), so the menu reappears on death (ActionGameManager.cs:1171). |
| Clear the run | MainMenu.cs:113 |
RunSave.Clear at click time, before the galaxy is built. |
| Galaxy | MainMenu.cs:124-175 |
TutorialPassed is never set anywhere, so New Game always takes the prelude branch :146-175. The background noise position comes from UnityEngine.Random (:128,153). The seed defaults to the clock (Galaxy.cs:106,158). |
| Handoff | statics ActionGameManager.CurrentGalaxy, IsTutorial (:96-97) |
read by StartGame, SectorMap, and nulled by Die (:1173). |
| Start | StartGame :796-834, called from Start :491 |
New run :803-815: PopulateLevel(Entrance), LoadoutGenerator.GenerateShipLoadout filtered by GameSettings.StartingHullName (GameSettings.cs:13, authored LonginusX), unpack, Zone.Entities.Add, Activate, BindToEntity. Continue :817-833. |
| Zone entry | PopulateLevel :710-744 |
generates the pack lazily (:714-722), moves the pilot (:731-737). |
1.2 What a scenario can reuse (read, one probe)
- Loadout presets already exist.
aetheria.loadout(Loadout.cs:13-20) holds a hull design, per-cell slot designs with rotation, and weapon groups. It is a catalog type (AetheriaStores.cs:9), authored in Studio or by the editor console commandcapturepreset(ActionGameManager.cs:624-662,Loadouts.CommitLoadout.cs:70-87).Loadouts.Materialize(:96-159) builds a liveShipall-or-nothing, listing every failure.- No game path calls
Materialize(LoadoutGenerator.cs:152-153says so; onlyLoadoutTests.cs:310,899call it). - (probe) The live catalog holds no preset: the schema name
aetheria.loadoutdoes not occur inGameData/Aetheria.cc(12,698,108 bytes atdcd7bbc5), while every populated type's name does (for exampleaetheria.hulldata,aetheria.weaponitemdata). Method:grep -a -oover the file. Materializealways builds aShip(:131). A turret hull cannot be materialized.- It resolves every design through a product (
Resolve,:103-115). A design with no product fails.
- No game path calls
- Unbranded lots exist.
ItemManager.CreateLot(design, maker, quality)(ItemManager.cs:161) with no maker is howgivemade its items (ActionGameManager.cs:538,542). Provenance's "no Manufacturer row" check depends on them. - Economy exclusion follows from products. Generation iterates products and requires
product.Manufacturer.IsSet()anddesign.Price > 0(LoadoutGenerator.cs:126-133). A design with no product is never generated, stocked or sold. Loot is what a killed entity carries (EntityInstance.cs:302-321), so it cannot leak through loot either unless a scenario fits it. - Prelude galaxies offer every product.
IsAvailablereturns true whenGalaxy.IsPrelude(LoadoutGenerator.cs:154-156). A preset materialized in a prelude arena never fails on faction availability. - Agents are made only at zone construction.
Zone's constructor gives every packed non-playerShipaMinion(Zone.cs:120-131,CreateAgent:135-142). An entity added later gets none. AMiniontargets the first visible enemy and fights (Minion.cs). - An entity joins a zone in four other places, each by hand:
spawnturret(ActionGameManager.cs:587),PopulateLevel(:735),StartGame(:813) andEntity.TryUndock(Entity.cs:1006). - IFF overrides are entity-local and unsaved.
Entity.SetIff(other, bool?)(Entity.cs:529-533) decidesIsHostileToahead of faction rules (:535-552). AMaterialized ship has no faction, so it is neutral unless overridden. - A target that dies and a target that leaves are the same case in fire control.
targetGone = shot.Target != null && !zone.Entities.Contains(shot.Target)(FireControl.cs:610), and death removes the entity fromZone.Entities(Zone.cs:93). Check A.3 ("the target warps away") is therefore set up by killing a fragile target while a fused round is in flight. No departure machinery is needed. - An orbital entity with no orbit stays where it is put (
OrbitalEntity.csUpdate: position follows the orbit only when one is set). That is howspawnturretplaced turrets (ActionGameManager.cs:583-588). - Headless tests already open the live catalog and the authored settings
(
RestoredHullsTests.cs:AuthoredSettings.Load,OpenReadOnlyRealCatalog). A staging test can generate a prelude galaxy, build the arena zone and stage a scenario with no Unity.
1.3 Debug affordances a scenario replaces (read)
| Affordance | Anchor | Added for |
|---|---|---|
give <name> |
ActionGameManager.cs:515-544 |
ad hoc items; the hull route through CommissionShip was added tonight in f935c29b for step 7 |
spawnturret |
:564-589 |
a hostile turret next to the player |
| Neutral wanderers | ZoneGenerator.cs:342-356, EligibleWandererFactions :361-365, ZoneGenerationSettings.NeutralWandererCount (Settings.cs:156), tests IffAndCombatTests.cs:329-350 |
commit f732cff9 (2026-09-17): "Spawn neutral wanderers so combat has something non-hostile to target". The code comment calls it a "Testing affordance". Every generated zone carries two. |
Staying, because no scenario replaces them: iff (flip a live target's stance mid-play), trackmissile (camera),
revealzones (map), tow (a verb bound to the console), capturepreset (the in-play authoring tool for presets).
1.4 Collisions with open branches (read)
codex/miningrewritesZone.cs(+239/-). Cut 1 edits the constructor loop and adds the admission primitive. Land Cut 1 after mining merges, or re-anchor.codex/moddable-shipseditsAetheriaStores.cs(line 9, the catalog type list). Cuts 1 and 3 edit the same lines.codex/item-provenancecarries the narrative map, which editsMainMenu.cs:132,160and theGalaxyconstructors. Cut 2 edits the same New Game block.
0b. Identity, lifecycle, authority
No cut is mapped against an empty cell.
| Kind | What names it | Lifecycle | Who decides |
|---|---|---|---|
Scenario (aetheria.scenario, catalog) |
CultName Name; key scenario:<Name>, derived like Loadouts.KeyOf |
Authored, then edited in place. A rename is a new record; the old one is deleted, and the checks that pointed at it are repointed in the same commit. A result never points at a scenario (it points at a check), so history survives. A scenario whose presets stop materializing is caught by the staging test (Cut 4), not at play time. | Authored by agents through AetherDb or by the operator in Studio. The operator owns what an arena contains. |
Loadout preset (aetheria.loadout, existing) |
key loadout:<Name> (Loadout.cs:62) |
Unchanged: authored, replaced only explicitly (Commit(..., replace)). |
Unchanged. |
Test design (catalog ItemData, no product) |
name; key minted by Upsert |
Authored with the scenario that needs it. Lives as long as a preset references it. Per Q3. | Operator (Q3) on whether they exist at all. |
Check (aetheria.check, ledger store) |
CultName Slug; key check:<Slug> |
Authored. Its statement is edited in place when a ruling changes (git keeps the history). It is retired, never deleted, once any result references it: Retired plus a reason. |
The rule's owner (a ruling or a map) decides what it says. Agents transcribe it with a source pointer. |
Check result (aetheria.checkresult, ledger store) |
key checkresult:<Slug>:<At as UTC ticks> |
Append-only. Never edited or deleted. A correction is a new result whose note says what it corrects. The latest by At is the current status. Order is carried in the data (At), not in delivery. |
The operator's observation. Who writes the record is Q2. |
| Pending scenario (runtime) | ActionGameManager.PendingScenario, a static |
Written once by the menu's launch, read and nulled once by StartGame's new-run branch. Null for Continue. Never saved. |
Command-only transport. It decides nothing. |
| Staged entities (runtime, then run state) | ordinary entities | Born at staging. From then on they are run state and saved with the zone like any other entity. Two things are not saved: whether a ship was left unpiloted, and scenario IFF overrides. After Continue, an unpiloted scenario ship gets a Minion by the constructor rule and its stance reverts to faction rules (neutral, since presets carry no faction). A scenario run is meant to be played through in one sitting. |
RunStart at birth; the zone afterwards. |
2. Target shape
2.1 Types (names and rules; bodies are Hands')
Assets/Scripts/ServerShared/Scenario.cs, catalog:
Scenario(aetheria.scenariov1):Name(CultName).Brief: the conditions in one or two lines, shown in the menu.Seed(uint): the arena galaxy and the background noise both derive from it, so the arena is the same layout every launch. Combat rolls are already zone-seeded (Zone.cs:84). Lot quality still comes from the clock-seededItemManager.Random(ItemManager.cs:20), so stats vary slightly per launch. Accepted.Ambient(bool): true keeps the arena zone as generated. False keeps planets, orbits and stations but drops generated ships and turrets, so nothing wanders into the conditions.Player(ScenarioShip).Entities(List<ScenarioEntity>).
ScenarioShip:Loadout(CultRecordRef<Loadout>),Position(float2, zone xz),Direction(float2),Cargo(List<CultRecordRef<EquippableItemData>>, stored in the first cargo bay).ScenarioEntity : ScenarioShip:Stance(Neutral | Hostile, applied both ways throughSetIff) andPiloted(bool: aMinion, or no agent at all).- A preset's hull type picks the entity class: a ship hull gives a
Ship; a turret hull gives an orbital entity with no orbit, stationary at its position. A station hull is refused as a staging failure.
Assets/Scripts/ServerShared/Checks.cs, ledger store (GameData/Checks.cc, committed, LFS by the existing
GameData/*.cc rule):
Check(aetheria.checkv1):Slug(CultName),Statement(what to do and what must be observed),Source(the ruling or map pointer,doc:line),Scenario(CultRecordRef<Scenario>, optional),Setup(text, only when there is no scenario: "FieldShieldTest scene", "CultCache Studio", "Addressables play mode Use Existing Build"),RetiredandRetiredReason.CheckResult(aetheria.checkresultv1):Check(ref),Result(Pass | Fail | Partial | Blocked),Revision(the commit the operator played, plus+dirtywhen the tree had changes),At(UTC),Note,Recorder.
Naming: "check", not "behaviour". Behavior is already the gear-behaviour type family
(Assets/Scripts/ServerShared/Behaviors/), and a document named Behaviour beside it reads as a typo.
Where they live, and why:
- Scenarios, presets and test designs are design-side fixtures. They go in the catalog: presets are already catalog data, cross-type refs resolve inside one store, and Studio edits them side by side.
- Checks and results are the verification ledger. They go in their own store. The shipped catalog carries no history, results never trigger a catalog write, and "one commit lands in one store" holds: recording a result touches the ledger only.
2.2 The staging owner
Owner: RunStart, a static class in Assets/Scripts/ServerShared/RunStart.cs. It decides what a new run
starts with: how the arena zone is generated, the player ship, the other entities, their stance, pilot and cargo.
It holds no Unity type.
- Inputs:
ItemManager(and through it the catalog), theGalaxy,ZoneGenerationSettings, the optionalScenario, and, for the absent-scenario case, the default hull filter and factionStartGameuses today (:805-809). - Outputs:
- the arena's
ZonePack, with theAmbientrule applied inside generation (ZoneGenerator.GenerateZonegains the parameter), not generated and then pruned; - the admitted player
Ship, flaggedIsPlayerShip; - the admitted scenario entities;
- a failure list. Staging is all-or-nothing, as
Materializeis.
- the arena's
- A check entry that validates a scenario without a zone (every preset materializes, every cargo design
exists). The menu calls it before
RunSave.Clear; the staging tests call it on every catalog scenario. - Derived state:
ActionGameManager.CurrentEntityis bound to the returned player.PendingScenariois transport only. - Forbidden writers:
StartGamemay not generate, unpack or admit the player ship.MainMenumay not stage. No console command admits entities (giveandspawnturretdie, Q4). - Shared paths: New Game and scenario launch both run menu, galaxy,
StartGame,RunStart. Continue does not stage. - Deletion line:
StartGame:805-814(generator, unpack, add, activate). The only surviving line is the bind.
Admission primitive: Zone.Admit(Entity entity, bool piloted). It adds the entity to Entities, activates it,
and, when piloted, gives it the zone's Minion (the CreateAgent rule, Zone.cs:135-142). It is the only writer
of Entities.Add and the only creator of agents.
- The constructor loop (
:120-131) calls it withpiloted: ship && !IsPlayerShip. It keeps its own rule that moves a ship packed at the origin (:129-130); that is generation placement, not admission. PopulateLevel(ActionGameManager.cs:735),Entity.TryUndock(Entity.cs:1006-1007) andRunStartcall it.spawnturret(:587) is deleted.- The
Deathsubscription (Zone.cs:93) stays onObserveAddand still covers every join.
Loadouts.Materialize gains two rules (Loadout.cs:96-159):
- A design with no product at all is built unbranded, through
CreateLot(design, default, quality)at one named quality constant (0.95, the qualitygiveused). A design whose products are all unavailable still fails, as today. The rule turns "no manufacturer makes it" into "outside the economy", which generation already obeys. - The hull type picks the entity class (section 2.1).
- The comment at
LoadoutGenerator.cs:150-153("No game path materializes a preset yet") is rewritten to nameRunStart.
2.3 The Unity lowering
MainMenu.ShowMain(:92-184) gains Scenarios, shown whenDebug.isDebugBuild(editor and development builds). It opens a panel with one button per catalog scenario, ordered by name. Under each button is its brief and its checks, each with its latest result and revision, read from the ledger (display only). A scenario whose check entry fails shows the failures and is disabled.- One launch body is extracted from the New Game lambda (
:110-176). New Game calls it with no scenario; a scenario button calls it with one. With a scenario, it runs the prelude branch (:146-175) withSeed, derives the noise position fromSeedinstead ofUnityEngine.Random, setsPendingScenario, and loadsARPG. The check entry runs first;RunSave.Clear(:113) moves after it, so a refused scenario leaves the saved run intact. StartGame's new-run branch (:803-815) callsRunStart, binds the returned player and nullsPendingScenario. The Continue branch asserts it is null.AetheriaStores.Opengains an optional ledger path. The game attachesGameData/Checks.ccread-only;AetherDbattaches it writable for the record commands (Cut 3).
2.4 Authoring
- Agents author headless through
AetherDb. A seed command (Cut 4) declares the first set in C#. It writes a record only when the key is absent. When the key exists and differs from what it would write, it refuses and reports; that means Studio edited it, and Studio wins. When the key exists and matches, it does nothing. It validates every scenario through theRunStartcheck entry before committing. So the catalog is the authority, and the seed code never clobbers it. - The operator authors in Studio and in play: Studio for scenario fields;
capturepresetfor a ship fitted by hand in play. - Results are recorded per Q2.
3. Cuts
Build budget, every cut:
Aetheria.Shared(netstandard2.1, the Unity-independence check),tests/Aetheria.Shared.Tests,tools/AetherDb(net10.0), Debug.- CultLib is unchanged; the pins are those in
hands-fuse8/run.sh. - No new project, target, package or daemon. One new store file (
GameData/Checks.cc), three new schemas. - Build host: Yggdrasil builds the headless projects (
run.sh <rev>).Assembly-CSharpis proven only by a Unity 6000.3.24f1 batchmode compile on Starfire, with the editor closed by the operator. Never kill Unity. run.shmounts a pinneddata/Aetheria.ccover the tree's LFS file. Any cut that changes the catalog (Cut 4) must refresh that pin, or its content tests read the old catalog. Cut 4's tests that read the ledger mount it the same way. Yggdrasil's disk is saturated: one job per cut,--artifacts-pathin the container.
Order: 0, 1, 2, 3, 4. Cut 0 is subtraction alone, so Soul can falsify it alone. Cuts 1 and 3 are headless. Cut 2 is the only Unity-side cut. Cut 4 is content.
Cut 0. Retire the ad hoc test affordances (Q4)
- Repo/branch: Aetheria,
codex/scenariosfrommasterdcd7bbc5. - Deletes first (per Q4 A; B keeps the item branch of
giveand the wanderers):ActionGameManager.cs:515-544,give(30 lines).ActionGameManager.cs:564-589,spawnturret(26 lines).ZoneGenerator.cs:342-356, neutral wanderers (15 lines), andEligibleWandererFactions:361-365(5).Settings.cs:156,NeutralWandererCount(1). It is absent fromSettings.asset(settings-globals:72), so no asset edit is needed.IffAndCombatTests.cs:329-350, the two wanderer tests (22).
- Keeps:
CommissionShip(:898-903) and its consumerTradeMenu.Buy; the console fixes inf935c29b. - Docs:
docs/merge-to-master-checklist.mdsection 8, thegivebullet: add "retired by the scenarios cut" with the commit.docs/settings-globals-cut.md:72,348get a one-line history note. - Verification:
- builds:
Aetheria.Shared, the test project. Batchmode compile (ActionGameManager.cschanged). - tests: the full suite is green. The locomotion "real enemy-count check" (
95394f78) counts generated ships; if it counted wanderers, fix its expectation and say so. - negative:
rg -n '"give"|"spawnturret"' Assets/Scriptsis empty;rg -n "NeutralWanderer|EligibleWandererFactions" Assets tests toolsis empty. Both patterns were checked to match only these sites today. - operator: none. There is nothing to see until Cut 2.
- builds:
- Ledger: about -99 lines; 0 added.
Cut 1. RunStart, Zone.Admit, and the scenario type (headless)
- Repo/branch: Aetheria
codex/scenarios, on Cut 0. Re-anchorZone.csif mining has merged. - First: with
AetherDb hardpoint-fitand a scratchMaterializeagainst the live catalog, confirm thatDjinni,LonginusXand one turret hull each have a product and materialize bare. If one has no product, stop and report: the Q3 rule would make it unbranded, which may not be intended for a real hull. - Deletes first:
ActionGameManager.cs:805-814, the new-run spawn (it moves intoRunStart; this cut leavesStartGamecallingRunStartwith no scenario, so behaviour is unchanged).- The four hand-written joins:
ActionGameManager.cs:735-736,Entity.cs:1006-1007,Zone.cs:124-125, and the agent add atZone.cs:128. Each becomesAdmit.
- Adds:
Scenario.cs,RunStart.cs,Zone.Admit, theambientparameter onZoneGenerator.GenerateZone(:38-43; the ship loop:334-340andPlaceTurretscalls:309,331honour it),ScenarioinAetheriaStores.CatalogTypes(:9), and the twoMaterializerules (Loadout.cs:103-115,131). - Authority map: section 2.2. Owner
RunStart; admissionZone.Admit; deletion line as above. - Verification:
- builds:
Aetheria.Shared, tests,AetherDb. Batchmode compile (ActionGameManager.cs,Entity.cschanged). - tests (new
RunStartTests: live catalog, authored settings, a prelude galaxy at a fixed seed). Each rule must fail under its own mutation:- With no scenario, staging yields a player of the
StartingHullNamehull,IsPlayerShip, admitted, at the origin. This pins that New Game is unchanged. - With a scenario, the player is the preset's hull and fit at the authored position and direction, and its cargo holds the listed designs.
- A piloted entity has exactly one agent in
Zone.Agents; an unpiloted one has none and stays put over 5 s ofZone.Update. Hostilemakes bothIsHostileTodirections true.Neutralmakes both false.Ambient: falseleaves no ship or turret in the arena pack that the scenario did not place. Stations remain.- A turret preset stages as a stationary orbital entity. A station preset is a staging failure.
- A scenario naming a missing design fails with that design named. The zone is unchanged, and no
RunSavecall was made (the check entry touches no store). - A product-less design materializes unbranded; a design whose only product is unavailable still fails. This is two tests, because the second case is the existing contract.
Admitis the only agent creator: after a warp (PopulateLevel's join throughAdmit(pilot, false)) the pilot has no agent. The constructor's pack path still gives every non-player ship aMinion.
- With no scenario, staging yields a player of the
- Stryker on
RunStart.cs,Zone.cs(Admitand the constructor loop),Loadout.cs(Materialize), scoped to the diff. Triage survivors by name. - negative:
rg -n "Entities\.Add\(" Assets/Scripts --glob '!**/ZoneGenerator.cs'matches onlyZone.cs(once, insideAdmit),Sensor.cs:169andEntity.cs:242(both areVisibleEntities/pings, not zones).rg -n "new Minion\(" Assets/Scriptsmatches onlyZone.cs. - operator: none beyond the Cut 2 smoke.
- builds:
- Ledger:
Scenario.cs+60;RunStart.cs+110;Zone+12 -8;Loadout.cs+20;ZoneGenerator+4;ActionGameManager-11 +4;Entity±2;AetheriaStores±1; tests +300.
Cut 2. The main menu launches scenarios (Unity)
- Repo/branch: Aetheria
codex/scenarios, on Cut 1. - Deletes first: the duplicated body of the two galaxy branches in the New Game lambda (
MainMenu.cs:124-175) collapses into one launch body. The standard branch (:124-145) is live code only whenTutorialPassedis true, and it is kept. Only the duplication goes. - Adds: the Scenarios panel,
PendingScenario, the seed-derived noise, the check entry beforeRunSave.Clear.StartGamepassesPendingScenariotoRunStart. - Authority map:
- Owner of what the run starts with:
RunStart(unchanged). MainMenuowns which scenario was picked and when the run is cleared.PendingScenariois a command-only transport, consumed once.- Forbidden:
MainMenustages nothing and admits nothing.StartGamereadsPendingScenarioonly in the new-run branch. - Shared paths: both buttons run one launch body. In-game menu (after death) and title menu run the same one.
- Owner of what the run starts with:
- Verification:
- builds: batchmode compile, editor closed.
- negative:
rg -n "RunSave\.Clear" Assets/Scripts/UI/MainMenu.csappears once, after the check entry.rg -n "PendingScenario\s*=" Assets/Scriptsmatches the launch body andStartGame's null only. - operator (Unity, Starfire):
- Title menu: Scenarios is listed. New Game still starts a LonginusX at the entrance with a Zenith station (the Cut 1 test pins the ship; this proves the lowering).
- Launch any scenario. The arena matches its brief; launch it again and the layout is the same.
- Die in a scenario. From the in-game menu, relaunch the same scenario.
- Quit mid-scenario, then Continue. The run continues (the 0b rule: unpiloted ships now patrol).
- Break a scenario on purpose in a scratch catalog (a missing design). It is shown disabled with the failure, and Continue still offers the previous run.
- Ledger:
MainMenu+45 -25;ActionGameManager+4; one static.
Cut 3. The verification ledger (headless; Q2)
- Repo/branch: Aetheria
codex/scenarios, on Cut 1 (independent of Cut 2). - Adds:
Checks.cs(Check,CheckResult);AetheriaStores.LedgerTypesand the optional path;GameData/Checks.cc(empty, committed).- Under Q2 B,
AetherDbgains two commands:checks [scenario]: each check with its latest result, its revision, and "never" when there is none.record <slug> <pass|fail|partial|blocked> --revision <sha> [--note <text>]. It refuses an unknown or retired slug. It opens the ledger writable and the catalog read-only.
- Under Q2 A, the menu panel gains Pass/Fail/Partial and a note field per check. It writes through the same ledger primitive, and the game attaches the ledger writable.
- Under Q2 B,
- Authority map:
- Owner of results: the ledger store. The only writer is the Q2 writer, which calls one append primitive,
Ledger.Record. - Derived, display only: the menu's latest-status line and
checks' output. - Forbidden: nothing edits or deletes a
CheckResult; nothing writes a result into the catalog.
- Owner of results: the ledger store. The only writer is the Q2 writer, which calls one append primitive,
- Verification:
- tests (new
LedgerTests, temp stores):- Recording appends and never replaces: two results for one check both survive a reopen, and the latest is
the one with the later
At, even when written first. - A retired or unknown slug is refused with nothing written.
- The catalog file is byte-identical after
record(a catalog opened read-only cannot be written; this pins that the command opened it that way). - A result carries the revision it was given, and
recordwithout--revisionis a usage error.
- Recording appends and never replaces: two results for one check both survive a reopen, and the latest is
the one with the later
- Stryker on
Checks.csand theAetherDbrecord path. - negative:
rg -n "CheckResult" Assets/Scripts --glob '!**/Checks.cs'matches only the menu's read (and under Q2 A its record call).
- tests (new
- Ledger:
Checks.cs+50;AetheriaStores+4;AetherDb+90 (B) orMainMenu+50 (A); tests +120.
Cut 4. The first scenarios, checks and history (content)
- Repo/branch: Aetheria
codex/scenarios, on Cuts 2 and 3. Hands authors on the branch clone, never inF:\Projects\Aetheria. - First: tell the operator that the uncommitted smoke weapons in their tree's
GameData/Aetheria.ccmust be restored (git checkout -- GameData/Aetheria.cc, their own command) before they pull. This cut recreates them as committed designs. - Adds:
- A transient
AetherDb scenario-seed [apply]command, with the never-clobber rule of section 2.4.- It writes the test designs (section 5.3), the presets (5.4), the scenarios (5.2) and the checks (5.1).
- It imports the 2026-09-30 smoke results (checklist section 8) as
CheckResults at revision113164fa, recorder "operator (play), recorded by agent". - It runs once, the data is committed, and the command is deleted in the same cut. The catalog is the authority from then on.
ScenarioConditionTests(live catalog; pinned catalog refreshed, see the build budget). Per scenario, stage it and assert the conditions its checks need, not the behaviour, which existing suites own. For example:Fused rounds: the bare hull lies within the arc on the player's initial aim line and has no target selected; the close hull is nearer thanSmoke Proximity'sBlastRadius;Smoke RefusedhasRange < BlastRadius; a direct-fire weapon can kill the fragile hull.Duel: the AI is piloted, hostile both ways, and carries a slow missile weapon; the player can see it.Arcs: the three bare hulls lie in the bow, beam and stern sectors of the player's mounts; the turret is hostile.- Every catalog scenario passes the check entry. This is the rot test.
- Every non-retired check with a scenario points at an existing one. Every check without a scenario has a
Setup.
- Docs:
docs/merge-to-master-checklist.mdsections 2 and 8 get one line: steps 7-20 and handoff checks A-F are now checks inGameData/Checks.cc, listed byAetherDb checks. The prose steps stay as history.
- A transient
- Verification:
- tests:
ScenarioConditionTests, with mutation by editing a fixture. Moving the close hull outside the blast radius, dropping the AI's missile weapon, or flipping a stance must each fail its condition. This is a one-off Soul probe, not a committed suite. AetherDb checkslists every check in section 5.1, with the imported results on 3, 4, 5, 6 and "never" elsewhere.- operator: play each scenario and report per check. That report is the first real use of the ledger.
- tests:
- Ledger: seed command +300 then -300; catalog +5 designs, +11 presets, +10 scenarios; ledger +44 checks, +7 results; tests +250; docs ±4.
4. Authority map (whole campaign)
- Owner of a new run's contents:
RunStart. It reads the scenario (or its absence) and writes the arena pack and the admitted entities. - Owner of zone membership and agents:
Zone.Admit. - Owner of fixtures: the catalog (
Scenario,Loadout, test designs), written by the seed, Studio orcapturepreset, and read-only at runtime. - Owner of verification history: the ledger store. It is append-only and written by the Q2 writer.
- Derived: menu status lines;
AetherDb checks;PendingScenario(transport). - Forbidden writers:
StartGamespawning ships;MainMenustaging; console commands admitting entities; anything editing a result; any catalog write at runtime. - Shared paths: New Game and every scenario launch: one launch body,
StartGame,RunStart,Admit. Warp, undock, construction and staging:Admit. - Deletion line: Cut 0's affordances;
StartGame:805-814; the four hand-written joins; the duplicated New Game branches; the transient seed command.
5. The first consumers
Sources: docs/merge-to-master-checklist.md section 2 (steps 7-20) and section 8; checks A-F from
F:\Projects\HANDOFF-aetheria-play-smoke-2026-09-29.md section 3. That file sits outside the repo, so the checks
below restate A-F and the ledger becomes their home. The rulings behind A-F are in fire-control-cut.md, in the
block "Ruled (operator, 2026-09-30)" (:2625-2690).
5.1 Checks
| Slug | Statement (abridged; the record carries the full text) | Source | Setup |
|---|---|---|---|
restored-hull-flies |
Thrusters move and turn the Djinni. | step 7; locomotion-cut.md:501 |
Djinni shakedown |
restored-hull-speed-cap |
Take the hull offline with thrusters firing; speed does not run away. | step 7; :377 |
Djinni shakedown |
weak-reactor-starves-legibly |
A reactor too small for the loadout shows starvation, not a cooked reactor. | step 8; stats-and-power-cut.md:705-706 |
Starved reactor |
brownout-degrades |
Brownout reads as degradation, not breakage. | step 9; :774,990 |
Starved reactor |
brownout-refill-stutters |
A weapon refilling under brownout stutters rather than firing at full rate. | step 9; :729-730 |
Starved reactor |
memory-flat-across-zones |
Several zones with kills; memory does not climb. | step 10; :672-673,980-982 |
Long haul |
side-mounts-fire-abeam |
Side-mounted weapons fire at the beam target. | step 11; fire-control-cut.md:397-398 |
Arcs |
turret-tracks-round |
The hostile turret tracks the player all the way round. | step 11 | Arcs |
no-fire-through-hull |
Nothing fires at the stern target through the hull. | step 11 | Arcs |
shots-roll |
Rolled impacts and misses; a miss reads as a near-miss; damage matches the HUD. | step 12; :659-661 |
Duel |
subsystem-aim |
Reveal and selection work; aiming at a revealed subsystem concentrates damage. | step 12 | Duel |
kill-drops-loot |
The kill drops its cargo; pickup stores it; the tractor beam pulls. | step 12; headless-playground-cut.md:789-794 |
Duel |
loot-keeps-brand |
Picked-up loot shows its manufacturer. | step 6; item-provenance-cut.md:528-539 |
Duel |
target-vanish-safe |
The target dies with shots in flight: no crash, no frozen ship. | step 12; fire-control-cut.md:1464-1470 |
Duel |
beam-reads-higher |
The HUD hull factor reads higher from the beam than from the bow. |
step 13; :2380-2383 |
Duel |
armour-face-missile |
Turn the armoured face into the AI's slow missile; it lands there. | step 13 | Duel |
ai-engages |
The AI still engages normally. | D; :2658-2660 |
Duel |
launcher-edge-pulse |
Launcher into the bow, then a flank: the schematic pulses the facing edge only. | step 14; :2519-2520 |
Launcher angles |
guided-explodes-on-detonation |
A guided round's explosion shows only when the simulation detonated. | E | Launcher angles |
guided-target-left-fades |
Kill the target while a guided round flies: the round fades with no explosion. | E | Launcher angles |
guided-hit-at-target |
A guided hit plays its effect at the target. | E | Launcher angles |
fused-clear-line-max-range |
Nothing selected, clear line: the round bursts at max range along the arc-clamped aim. | A.1; :2625,2636-2642 |
Fused rounds |
fused-first-hull-stop |
Nothing selected, aim at the bare hull: the round stops and detonates on it. | A.2; :2632-2634 |
Fused rounds |
fused-target-left-no-burst |
Fire at the fragile target, kill it with the direct weapon before arrival: no burst. | A.3; :2636-2642, FireControl.cs:610 |
Fused rounds |
fused-out-of-arc |
Select the stern target: the round fires and bursts at its range along the arc-clamped aim. | A.4; :2643-2645 |
Fused rounds |
fused-arming-pushout |
Select the close hull: the burst is pushed out to BlastRadius from the ship's centre. |
A.5; :2646-2653 |
Fused rounds |
airburst-at-model |
A proximity airburst beside the LonginusX lands damage where the model shows the blast. | step 15; :3094-3097 |
Fused rounds |
penetrator-reaches-interior |
A delayed penetrator into the LonginusX nose detonates inside. | step 15 | Fused rounds |
hud-forecast-fused |
The HUD shows Direct, Burst at N m or Refused for fused weapons, and a percentage for direct weapons. | C; :2661-2663 |
Fused rounds |
unbranded-no-manufacturer |
An unbranded smoke weapon shows no Manufacturer row. | step 6 | Fused rounds |
refused-shot-free |
A refused shot costs no ammo, energy, heat, sound, visibility or cooldown. | B; :2664-2666 |
Refused rounds |
refused-beam-no-flicker |
A refused beam never starts. | B | Refused rounds |
refused-charge-never-charges |
A refused charged weapon never starts charging. | B | Refused rounds |
instant-and-charged-fire |
Autocannon and ChargeBlast SG fire; effects, particles and action-bar icons show. | step 5; addressables-cut.md:362-366 |
Weapon feel |
burst-cadence |
Burst cadence is unchanged. | F | Weapon feel |
charge-release-press |
A fast release-and-press on a charged weapon starts no second charge. | F | Weapon feel |
existing-build-play |
Under "Use Existing Build", the Weapon feel checks hold (GUID keys in the player catalog). | step 20; addressables-cut.md:367-370 |
Weapon feel, Setup: play mode Use Existing Build |
ai-never-fires-refused |
The hostile AI whose only weapon is refused never fires it. | D | AI fused discipline |
ai-never-bomb-fishes |
The neutral AI carrying a fused weapon never fires it. | D | AI fused discipline |
continue-keeps-items |
Warp, quit, Continue: same items, same tier and brand. | step 16; item-provenance-cut.md:536-537 |
Long haul |
death-ends-run |
Die: Continue is disabled and run.cc holds no ledger. |
step 17; :540-541 |
Long haul |
shield-panel-rig |
The numbered shield-panel look, including the Cut 5 dicing and break-through. | step 18; shield-panel-cut.md:683-700,737-739 |
Setup: FieldShieldTest scene |
studio-* (5 checks) |
The fire-control union renders; the schematic underlay; restored hull records; the fuse fields edit; the provenance ledger renders from a real run.cc. |
step 19 | Setup: CultCache Studio |
addressables-duplicates |
Analyze the duplicate dependencies: 5 known leftovers. | step 20 | Setup: Addressables Analyze |
new-game-start, purchase-keeps-brand, tier-colour |
New Game; buying keeps the brand; tier colours show. | steps 3, 4, 6 | Setup: New Game |
Imported results (checklist section 8, revision 113164fa, 2026-09-30):
new-game-start: pass.purchase-keeps-brand: pass.instant-and-charged-fire: partial ("charged weapon unprovable: 1x2 hardpoint").tier-colour: fail ("known defect,settings-globals-cut.md:150; fixed by settings-globals Cut 1").unbranded-no-manufacturer: blocked ("give Lampnamed a test fixture").loot-keeps-brand: no result ("not yet reported").
5.2 Scenarios
All use Ambient: false except Long haul. Positions are authored in the seed and pinned by the condition tests.
| Scenario | Player | Entities | Checks |
|---|---|---|---|
| Djinni shakedown | Djinni, full fit | none | 2 |
| Starved reactor | a combat hull with an undersized reactor, energy weapons and a shield | 1 unpiloted bare hull ahead, neutral | 3 |
| Arcs | a hull with side mounts | 3 unpiloted bare hulls (bow, beam, stern), neutral; 1 turret preset, hostile | 3 |
| Duel | combat fit | 1 piloted LonginusX, hostile, carrying a slow missile weapon, cargo holding a branded item | 8 |
| Launcher angles | GT 3K or pswarm plus a direct weapon | 1 unpiloted LonginusX, hostile; 1 fragile bare hull | 4 |
| Fused rounds | Smoke Proximity, Smoke Delayed, a direct weapon | unpiloted: LonginusX mid range, hostile; bare hull on the clear bearing; fragile bare hull; close bare hull inside BlastRadius; stern target |
9 |
| Refused rounds | Smoke Refused (instant, beam, charged) plus one working weapon | 1 unpiloted bare hull | 3 |
| Weapon feel | a hull with a 2x2 energy hardpoint: ChargeBlast SG, Autocannon, a burst weapon | 1 unpiloted bare hull | 4 |
| AI fused discipline | a sturdy hull | 1 piloted neutral with a fused weapon; 1 piloted hostile whose only weapon is refused | 2 |
| Long haul | standard combat fit, Ambient: true |
none | 3 |
5.3 Test designs (Q3)
None has a product. They are unbranded and outside the economy.
Smoke Proximity: Proximity, blast 8.Smoke Delayed: Delayed, blast 4, penetration 2 cells.Smoke Refused: Proximity, blast 30, range 20.
These three are the operator's own designs from tonight, Spectra clones, energy, 1x2.
Smoke Refused BeamandSmoke Refused Charge: a constant weapon and a charged weapon with a fuse andRange < BlastRadius.- All must pass
CultRecordRefs.Validate(R-heat, stat modifiers, roles).
5.4 Presets
About eleven, named after their scenarios. The seed author picks designs with AetherDb hardpoint-fit. The only
hard constraints are those the condition tests pin.
6. Subtraction ledger
| Cut | Removed | Added | Targets, schemas, stores |
|---|---|---|---|
| 0 | give 30, spawnturret 26, wanderers 20, setting 1, tests 22 |
0 | no change |
| 1 | StartGame spawn 10, joins 6 |
Scenario.cs 60, RunStart.cs 110, Admit 12, Materialize 20, GenerateZone 4; tests 300 |
+1 schema (aetheria.scenario) |
| 2 | duplicated New Game body about 25 | menu panel and launch body about 45; 1 static | none |
| 3 | none | Checks.cs 50, stores 4, AetherDb 90 (or the menu 50); tests 120 |
+2 schemas, +1 store file |
| 4 | the seed command, once spent (300) | seed 300; condition tests 250; catalog and ledger data | catalog content, ledger content |
Net production code is about +250. That buys:
- the main-menu scenario affordance;
- typed checks with history;
- one staging owner and one admission primitive (from five hand-written joins);
- the first game consumer of the presets that already existed.
It removes three ad hoc test affordances and the wanderers that ran in every zone.
7. Operator questions
Each question stands alone. The first is what the scenario request leaves open.
Q1. Is a scenario arena part of a real run? Your words settle what an arena holds. They leave open what surrounds it. Checks 16 and 17 (warp, save, Continue, death) need a real run. Every other check needs only the arena.
- A. The arena is the entrance zone of a small prelude galaxy built from the scenario's seed. Stations, planets and wormholes are there, and the run saves on warp and quit, continues and ends at death like any run. Launching a scenario replaces your saved run, as New Game does. Scenario-only details (unpiloted ships, forced stances) do not survive a Continue.
- B. A sealed arena: one zone, no galaxy, no saving. Launching never touches your saved run. Checks 16 and 17
go back to plain New Game play. This is a second zone-entry path with no galaxy under it, which
PopulateLeveland the sector map do not support today. - Recommended: A. It is the same New Game path with an input, so there is no second boot path. Every rule under test then runs exactly as it does in a real run. Replacing the saved run seems harmless for a dev affordance.
- Depends on it: Cut 2's launch body; the 0b lifecycle row for staged entities; whether Long haul exists.
Q2. Who records a check's result, and where?
You play and observe. Something has to write "pass at abc123, note ...".
- A. In game. The Scenarios panel gets Pass/Fail/Partial and a note per check. The game writes the ledger file in your working tree, and you commit it with your other work.
- B. The agent. You tell the session what you saw, and it records typed results with
AetherDb recordon a branch and pushes. The menu shows each check's latest result, read-only. - C. No typed results. Checks are typed, and results go in a prose ledger doc, as checklist section 8 does now.
- Recommended: B. It matches how tonight's smoke already worked: you play, the agent records. Nothing in the game writes a file that git tracks, and no agent needs to touch your tree. The menu still shows what is stale. A can be added later on the same append primitive.
- Depends on it: Cut 3's writer and whether the game attaches the ledger writable.
Q3. Where do test-only item designs live? Checks A-C need fused weapons the catalog does not ship. Tonight they were authored in place and left uncommitted.
- A. Committed catalog designs with no product. With no product nothing makes them, so they are never
generated, stocked, sold or looted. A preset builds them unbranded (a new
Materializerule). Studio lists them beside real content. - B. Committed designs with a product and price 0. Generation skips price 0 today, but only through a filter written for another reason, so the exclusion is a coincidence.
- C. A scratch catalog swapped in per session, as checklist step 2a proposed. Scenarios would then depend on which catalog is loaded, and the fixtures would not be retained.
- Recommended: A.
- Depends on it: the
Materializerule in Cut 1; section 5.3.
Q4. Which ad hoc test affordances do scenarios retire?
give got its hull route tonight (your ruling: "mothballed ships do not require docking bays"), and step 7 was its
reason. spawnturret places a turret. Neutral wanderers spawn two non-hostile ships in every generated zone, "so
combat has something non-hostile to target" (f732cff9).
- A. Delete all three. Scenarios cover ships, cargo, turrets and neutral targets.
CommissionShipstays for purchases. - B. Delete the hull route and
spawnturret. Keepgivefor items (ad hoc poking outside scenarios) and keep the wanderers. - C. Keep everything.
- Recommended: A. Each one is a second way to create what scenarios now author, and the wanderers change every
zone of real play for a testing reason.
iff,trackmissile,revealzones,towandcapturepresetstay. - Depends on it: Cut 0.
Defaults Self took, which you may overrule:
- Scenarios show only in editor and development builds.
- The ledger is its own committed store,
GameData/Checks.cc, not the catalog. - "Check", not "behaviour", because
Behavioris the gear type family. - Brokkr is not installed in this campaign (section 0.4).
- The scenario record holds no "start docked" field. No first consumer needs one.
8. Follow-ups outside this campaign
- Re-anchor
docs/headless-playground-cut.md. Its Cut 1 now builds onRunStartandZone.Admit; itsnewcommand stages aScenario. - Brokkr install plus a "launch scenario" intent, if agents should drive Unity play.
- A durable agent authoring command for presets (fit by design name with validation), instead of transient seed commands.
AetherDbcarries spent one-shot migration commands (targeting-catalog-6c,-6d,firing-arc-migrate, and others;Program.csis 1,477 lines). This is a subtraction pass of its own.ItemManager's clock seed (ItemManager.cs:20) makes lot quality vary per scenario launch. It is recorded, not fixed here.