Operator ruling 2026-09-30: mount points exist only for externally visible hardpoints. No equipment role, no plain mount. Articulation points stay; they are not mounts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
516 lines
34 KiB
Markdown
516 lines
34 KiB
Markdown
# Aetheria Codex assimilation: refreshed cut maps (mining, moddable ships)
|
|
|
|
Date 2026-09-29. Imagination output for Self. It re-anchors two Codex lanes to the Body as it will stand after
|
|
`codex/fire-control-12` merges to `master`. The Codex maps are raw material, not authority. Where this file and
|
|
a Codex map disagree, this file is the proposal and the Codex map is the input.
|
|
|
|
## How this was established
|
|
|
|
- **Branch tips read, 2026-09-29, after `git fetch`.**
|
|
- `origin/master` `9b85211f`
|
|
- `origin/codex/fire-control-12` `b66ba524`
|
|
- `origin/codex/mining` `283ce7dc`
|
|
- `origin/codex/moddable-ships` `375d6bd4`
|
|
- Nothing in `F:\Projects\Aetheria`'s working tree was read or switched. Every read is `git show` or `git grep`
|
|
against a SHA.
|
|
- **The post-fire-control Body is a synthetic merge.** `git merge-tree --write-tree origin/master
|
|
origin/codex/fire-control-12` is clean, tree `91576243`. I wrapped it in an unreferenced local commit,
|
|
`8a2db490` (no ref, no push to origin), so it can be built. The fire-control checklist (`docs/merge-to-master-checklist.md:7,107`
|
|
on `b66ba524`) predicts the same clean merge.
|
|
- **Anchors.** Each `file:line` names a real SHA, never the synthetic one:
|
|
- `b66ba524` for files only fire control touched;
|
|
- `283ce7dc` for files the mining branch touched;
|
|
- `375d6bd4` for files the ships branch touched.
|
|
|
|
The three file sets are disjoint. I checked with `git diff --name-only` over each branch's own range, and none
|
|
of the three pairs shares a file. So each anchored line is byte-identical in the merged tree.
|
|
- **Tests ran on Yggdrasil, against synthetic merges** (section 0). CultLib `45c2f40` and CultMath `6d5e209` are
|
|
the pins in `Directory.Build.props`. The two LFS files the tests read (`GameData/Aetheria.cc`,
|
|
`Assets/Resources/Settings.asset`) were staged beside the job, because the verify mirror carries no LFS.
|
|
- **Catalog facts** come from a probe that opens the merged tree's `GameData/Aetheria.cc` through
|
|
`AetheriaStores.Open` (section 0).
|
|
|
|
## 0. Probe results
|
|
|
|
**Test suite on the synthetic merges**, Yggdrasil, `dotnet test tests/Aetheria.Shared.Tests`, CultLib `45c2f40`,
|
|
CultMath `6d5e209`, the `dotnet` image:
|
|
|
|
| Tree | Commit (local, unreferenced) | Result |
|
|
|---|---|---|
|
|
| master + fire-control-12 + mining | `396d19e5` | **379 passed, 0 failed** |
|
|
| master + fire-control-12 + ships | `0036204f` | **364 passed, 0 failed** |
|
|
| master + fire-control-12 + mining + ships | `474c35c3` | **381 passed, 0 failed** |
|
|
|
|
Fire control closed 12.4 at 362 tests. 379 = 362 + 17 mining tests, 364 = 362 + 2 ship tests, and 381 is both
|
|
together. Nothing on either branch breaks a fire-control test, and nothing in fire control breaks a branch test.
|
|
This proves the headless simulation only. No Unity compile ran.
|
|
|
|
**Catalog probe** (`scratchpad/probe/Program.cs`: it opens `GameData/Aetheria.cc` through `AetheriaStores.Open`
|
|
in the merged tree; the catalog oid `2aa79c0a` is the same in all three merges and at `b66ba524`):
|
|
|
|
- 124 items: 32 `GearData`, 18 `WeaponItemData`, 13 `SimpleCommodityData`, 5 `HullData`, 51 compound
|
|
commodities, 4 cargo bays, 1 docking bay.
|
|
- **Weapon damage types:** Kinetic 9, Electric 3, Optical 3, Thermal 3. There is none of Corrosive or Ionizing,
|
|
though the `DamageType` enum has all six. The mining map said Kinetic 10.
|
|
- **Penetration > 0:** only Autocannon, LRMM72 and SRMM72, each at 0.25.
|
|
- **DamageSpread > 0:** GT 3K 3.25, plight 3.3, pswarm 1.5, scorched void policy 1.5, and SRMM72 0.5. The map
|
|
listed four and missed SRMM72.
|
|
- **No weapon has `BlastRadius` or `Fuse`.** Blasts are dormant in shipped content until F12-2 authors them.
|
|
- **Simple commodities:** the 13 the map named. Every one is priced 0 **except Ammo, at 1000**. The map's
|
|
"every live simple commodity has Price 0" is off by one.
|
|
- **No live item carries a scanner or mining behaviour**, on fire control or on mining merged.
|
|
- **Pre-existing, on `b66ba524` as well as on the merges:** four `GearData` records (Refinery, Deep Ore
|
|
Extractor, Assembly Line, Shipyard) hold a **null element** in `Behaviors` (index 0 of 1). It does not come from
|
|
mining's union-26 retirement: the same four are null on fire control alone. This matters to variants C4 (A.6).
|
|
- **Shipped hull mount ids are not unique:**
|
|
|
|
| Hull | Hardpoints | Distinct `Transform`s | Empty `Transform`s |
|
|
|---|---|---|---|
|
|
| Djinni | 22 | 20 | 3 |
|
|
| Longinus | 13 | 12 | 2 |
|
|
| LonginusX | 10 | 9 | 2 |
|
|
| Turret | 7 | 3 | 5 |
|
|
| Zenith | 5 | 1 | 5 |
|
|
|
|
All five hulls use `Prefab`. This matters to ship migration (S5).
|
|
|
|
## Shared precondition for both campaigns
|
|
|
|
Both campaigns wait on one event: `codex/fire-control-12` merging into `master` with `--no-ff`, which Self does
|
|
after the operator signs off (`docs/merge-to-master-checklist.md:105-113` on `b66ba524`). Until then, neither
|
|
campaign has a base to merge onto except the fire-control branch itself. After it:
|
|
|
|
```
|
|
git switch codex/mining && git merge --no-ff master # in a worktree, never the operator's tree
|
|
git switch codex/moddable-ships && git merge --no-ff master
|
|
```
|
|
|
|
**Merge, do not rebase.** Both maps and their Soul receipts cite landed SHAs: mining cites 11 of its 13 commits,
|
|
and the ships doc cites its commits' results. A rebase rewrites every one of those SHAs, which strands the maps'
|
|
evidence. The merge has no conflicts (below), so it costs one merge commit per branch.
|
|
|
|
---
|
|
|
|
## Rulings (operator, 2026-09-30)
|
|
|
|
- **MQ1: a ship `.cc` is self-contained.** Operator: "The idea is for the new moddable ships to eventually
|
|
take over from the existing system. A ship .cc should contain everything needed to instantiate and render a
|
|
ship." The confirmed reading: the hull is its own `HullData` record and the visual is its own record, both in
|
|
the one ship `.cc`, side by side. The hull is not embedded in the visual. A hull family such as Longinus and
|
|
LonginusX lives together in one `.cc`, so variants resolve within one store. S1 follows option B, with both
|
|
records inside the ship's own store.
|
|
- **MQ2 A for now.** There are no cross-file variants of shipped hulls in this lane. **Operator note, to be
|
|
carried into CultLib's variants target:** "we'll want cross-store variant support eventually; modders should
|
|
be able to add variants of existing items without modifying the original".
|
|
- **MQ3 A.** The FBX prefab builder is scaffolding. The typed `.cc` lane replaces the existing system, and the
|
|
builder dies at S5.
|
|
- **MQ4 A.** Continue refuses loudly and names the missing mod ids.
|
|
- **MQ5 A.** The game composes the modded catalog at boot, from `GameData/Mods`, into a disposable file.
|
|
- **Soul (2026-09-30) on `375d6bd4`** found the Blender↔C# `.cc` round trip real, with no vendored codec.
|
|
Before any cut builds on it, the S-Soul fix slot must fix:
|
|
- F1: 8 of 9 validator mutations survive, including the `..` path guard and the prefab refusal.
|
|
- F2: `Compose`, `ReadPackage` and `ReadNodeIds` are untested.
|
|
- F3: there are no Python tests; slot numbers and enums are duplicated by position.
|
|
- F6: `CatalogTypes` includes `ShipAuthoring`. Check name collisions.
|
|
- F7: a drive-by `using` removal.
|
|
- F5 is settled by MQ1.
|
|
|
|
- **Pivots own firing arcs (operator, 2026-09-30, from fire control).** A turret's arc belongs to its
|
|
pivot, not its hardpoints. When this campaign adds pivots to `HullData`, the arc moves from the hardpoint
|
|
to the pivot, and the interim "intersection of hardpoint arcs per pivot" rule in fire control is deleted.
|
|
Unity's `Pivot.<group>.<yawMin>.<yawMax>...` node-name parsing (`ShipPrefabAuthoring.cs:144`) must not
|
|
survive as a second arc authority.
|
|
|
|
# Part B. Moddable ships
|
|
|
|
## B.1 Status header (verified against git)
|
|
|
|
Branch `codex/moddable-ships`, tip `375d6bd4`: 4 commits on `4b594e11`. `4b594e11` is an ancestor of fire
|
|
control, which has since moved 8 commits to `b66ba524`. Neither side touches the other's files.
|
|
|
|
| SHA | What landed |
|
|
|---|---|
|
|
| `42eb5214` | `ShipAuthoring` record and validator; `AetherDb ship-authoring create/inspect/validate`; Blender LineArt capture; 2 xunit tests |
|
|
| `4ba4bbaf` | `ShipModCatalog.Compose` (derived catalog) and GLB node-id reader |
|
|
| `24c29640` | Unity `ShipModVisual` (glTFast runtime import, line mesh) and Editor preview; `com.unity.cloud.gltfast` 6.20.0 added to `Packages/manifest.json` |
|
|
| `375d6bd4` | Blender layout editing (hull grid, hardpoints) through `ship_cc.py` |
|
|
|
|
The delta is +511 C# (including `tools/AetherDb`), +494 Python, +119 tests, and one new Unity package dependency.
|
|
It also carries a stray fix in `Assets/Scripts/UI/Menu/TradeMenu.cs` (an unused `using System.Runtime.Remoting.Contexts`).
|
|
Self's ruling: it stays on this branch as cleanup. Master compiled clean in Unity with the using present, so master does
|
|
not need it.
|
|
|
|
**Test counts.** The two ship tests pass on the merged tree: 364/364 (section 0).
|
|
|
|
**S-Soul status:** F1, F2, F3 and F6 fixed at `fe38f288` (479 tests). F4 has no spec entry. The Python tests
|
|
(`tools/blender/tests/test_ship_cc.py`, 32) have no runner yet; run them by hand.
|
|
|
|
**S0 status (2026-09-30):** master `65c63495` (fire control 12, `dcd7bbc5`) merged into `codex/moddable-ships` with
|
|
`--no-ff` at `18e12d81`, no conflicts. Corrections 1-4 applied to `docs/moddable-ship-authoring.md`. Yggdrasil full
|
|
suite on the merge (`dotnet test tests/Aetheria.Shared.Tests`, CultLib `45c2f40`, CultMath `6d5e209`): **615 passed, 0 failed**. Self's steps, 2026-09-30 05:15 CEST: the Unity 6000.3.24f1
|
|
batchmode compile of `01e9db1c` has 0 `error CS` and 0 `warning CS`, and `ShipModPreview.Smoke` passed
|
|
(`SHIP_MOD_VISUAL_SMOKE anchors=5 points=2 segments=1`, its first recorded run). **The branch has no committed
|
|
fixture package:** the smoke ran against one generated from `ShipAuthoringTests.Fixture()` with a hand-added glTF
|
|
`scene` (the test GLB has nodes but no scene; whether glTFast instantiates a scene-less GLB is unchecked). S1 should
|
|
commit a scene-bearing fixture package so the smoke is reproducible.
|
|
|
|
**Anchors re-taken against the merged tree (`18e12d81`).**
|
|
- Unchanged: `ShipAuthoring.cs:15` (`Hull`, key 1), `:84`, `:111-136` (the hardpoint checks are `:112-131`),
|
|
`ShipModCatalog.cs:20-21` (keys), `ItemData.cs:512` (`Hardpoints` `Key(23)`), `:515` (`Prefab` `Key(24)`),
|
|
`Loadout.cs:74`, `EntityInstance.cs:15-27`, `ZoneRenderer.cs:289-310` (`LoadEntity`), `AetheriaStores.cs:9`.
|
|
- Moved: the second upsert in `ShipModCatalog.Compose` is `:68-69` (was `:60-61`; the F1-F3 fix added the
|
|
name-collision checks above it). `ship_cc.py`: slot constants are `:23-25`, `replace_layout` is `:96-124`,
|
|
the `body[HULL_SLOT]` reads are `:84,101`. `ActionGameManager.cs`: the catalog opens at `:55-59`
|
|
(`AetheriaStores.Open`, not `:45-60`); `capturepreset` is `:621-662`.
|
|
- S1's key 32 for `HullData.Visual` holds: `EquippableItemData` owns 30 and 31, `HullData` uses 23-29, and
|
|
`WeaponItemData.Fuse` at 32 (`ItemData.cs:505`) is a sibling class, not an ancestor.
|
|
|
|
**Proof-gate status against the doc's own gates (`docs/moddable-ship-authoring.md:149-164` @`375d6bd4`):**
|
|
|
|
| Gate | Doc implies | Verified in git |
|
|
|---|---|---|
|
|
| 1. C#/Python round trip preserving unknown fields | met | **C# only.** `StandaloneShipRoundTripsWithoutTouchingTheCatalog` pins C#. The Python half and "preserving unknown future fields" rest on the Quiet probe, whose output `Build/quiet.cc` is git-ignored. No repository test exercises `ship_cc.py`. |
|
|
| 2. Blender edits cells and hardpoints | met, except node-id authoring | **Code exists, unpinned.** `ship_cc.replace_layout` (`ship_cc.py:75-106`) has no test. |
|
|
| 3. Validator rejects malformed records, changes nothing on failure | met, and compose checks it | **Validator pinned.** `InvalidSchematicOrDanglingMountCannotBePublished` covers 5 cases. **Compose unpinned:** no test covers key collision, the atomic replace, a missing GLB node, or "the previous derived file is unchanged on failure" (`ShipModCatalog.cs:23-73`). |
|
|
| 4. Loads in a built player | outstanding | not started |
|
|
| 5. Play checks | outstanding | not started |
|
|
|
|
**Corrections to `docs/moddable-ship-authoring.md` at `375d6bd4`:**
|
|
|
|
1. **`:9-16` and `:132-136` report the Quiet probe's numbers as facts.** They include 1,040 polylines and
|
|
"206 shipped records became 208". Nothing in git reproduces them. Label them as an unrecorded probe, or
|
|
commit a fixture that pins them.
|
|
2. **The composed catalog holds two copies of every mod hull.**
|
|
- `:114-118` says compose preserves one owner per ship.
|
|
- `ShipModCatalog.cs:60-61` upserts `ship.Hull` as a `HullData` at `mod-hull:<id>`, **and** the whole
|
|
`ShipAuthoring`, whose key 1 is the same `HullData`, at `mod-ship:<id>`.
|
|
- Gameplay can read only the first. The second is a dead copy of the same truth inside a derived file. Q1
|
|
decides the fix.
|
|
3. **`AetheriaStores.CatalogTypes` now routes `ShipAuthoring` to the shipped catalog** (`AetheriaStores.cs:9`
|
|
@`375d6bd4`), while the doc says the shipped catalog "contains no copy of that ship". Routing a type is not
|
|
holding a record, so this is harmless today. It stays correct only while nothing writes a `ShipAuthoring`
|
|
through a catalog-writable cache on `Aetheria.cc`. Name that invariant in the doc.
|
|
4. **`:98-106` "the same semantic validator" on every path.** The Blender side validates nothing semantic: it
|
|
checks grid bounds and footprint shape (`ship_cc.py:83-97`). "Shared path" is true for the C# callers only.
|
|
|
|
## B.2 Merge path onto the fire-control-12 lineage
|
|
|
|
| Probe | Result |
|
|
|---|---|
|
|
| `git merge-tree --write-tree origin/codex/fire-control-12 origin/codex/moddable-ships` | clean, tree `97711bcd` |
|
|
| `git merge-tree --write-tree 8a2db490 origin/codex/moddable-ships` (master+fc12) | clean, tree `88672977` |
|
|
| on top of mining merged into master+fc12 | clean, tree `a7410174` |
|
|
| files shared with fire control `4b594e11..b66ba524` (6 files: `Entity.cs`, `FireControl.cs`, two test files, two docs) | none |
|
|
|
|
The merged tree passes the full headless suite: 364/364 alone, and 381/381 with mining (section 0).
|
|
|
|
The only cross-lane seam is `Packages/manifest.json` (glTFast). It merges textually, but it needs Unity to
|
|
resolve the package on first open, like the CultMath tag already does (`docs/merge-to-master-checklist.md:14`).
|
|
|
|
## B.3 Ordered cuts
|
|
|
|
| Cut | Nature | Blocked by |
|
|
|---|---|---|
|
|
| S0 | Placement: merge master, correct the doc | fire-control-12 on master |
|
|
| S-Soul | Fix batch from the parallel Soul pass | Soul's report |
|
|
| S1 | One hull record, and a hull names its visual | MQ1 |
|
|
| S2 | The game composes and loads mod ships (`ShipInstance` wiring, boot preload) | S1; MQ4, MQ5 |
|
|
| S3 | Gate 4: a built player loads a mod ship | S2; a playable mod ship (content) |
|
|
| S4 | Gate 5: play checks | S3 |
|
|
| S5 | Migration of shipped hulls, onto variants; FBX builder retirement | S4; MQ2, MQ3; variants C4 |
|
|
|
|
### S0. Placement and doc correction
|
|
|
|
- **Repo/branch:** Aetheria, `codex/moddable-ships`, in a worktree.
|
|
- **Deletes first:** none.
|
|
- **Keeps/moves/adds:** `git merge --no-ff master`. Apply corrections 1-4 to the doc.
|
|
- **Verification:**
|
|
- Yggdrasil full suite on the merge (the M0 command).
|
|
- Unity batchmode compile of the merged tree with glTFast resolved. This is Self's step, and it needs network
|
|
for the package.
|
|
- The Editor smoke already in the branch: `-executeMethod ShipModPreview.Smoke -shipModPath <package/ship.cc>`
|
|
(`ShipModPreview.cs:8` @`375d6bd4`), run against a committed fixture package. It is the first recorded run
|
|
of that smoke.
|
|
- **Subtraction:** docs only.
|
|
|
|
### S-Soul. Fix slot for the parallel Soul pass
|
|
|
|
- **Repo/branch:** `codex/moddable-ships`, after S0.
|
|
- **Content:** Soul's findings. Two areas this map expects Soul to own, and does not duplicate:
|
|
- Compose's unpinned failure paths (B.1, gate 3).
|
|
- The hand-copied schema on the Python side:
|
|
- `ship_cc.py:17-21` slot constants (`HULL_SLOT 1`, `HULL_SHAPE_SLOT 5`, `HULL_HARDPOINTS_SLOT 23`);
|
|
- `ship_cc.py:88-98` hardpoint slots 0-6;
|
|
- `__init__.py:18-21`, a copy of the `HardpointType` and `ItemRotation` enums.
|
|
|
|
All of these match C# today: `ItemData.cs:287` `Shape` is `Key(5)`, `HullData.Hardpoints` is `Key(23)` at
|
|
`:512`, and `HardpointData` is `:553-563`, all @`b66ba524`. `Enums.cs:38` matches too. The hazard is drift:
|
|
fire control added slot 6 (`FiringArc`) to `HardpointData` in this same lineage. The memory
|
|
`honor-invariants-over-workarounds` names hand-copied schemas as a known failure. The coherent fix reads
|
|
the published schema catalog. Soul decides whether it is in scope.
|
|
|
|
### S1. One hull record, and a hull names its visual (written for MQ1 = B)
|
|
|
|
- **Repo/branch:** `codex/moddable-ships`, after S-Soul.
|
|
- **Deletes first:**
|
|
- `ShipAuthoring.Hull` (`ShipAuthoring.cs:15`, key 1) becomes a retired-key comment.
|
|
- The embedded-hull branch of the validator (`ShipAuthoring.cs:84-133`) moves; it does not duplicate.
|
|
- The second upsert in `ShipModCatalog.Compose` (`ShipModCatalog.cs:60-61`) becomes a verbatim copy of the
|
|
mod store's records.
|
|
- `ship_cc.py`'s `body[HULL_SLOT]` indirection (`:63-66`, `:80-100`).
|
|
- The only existing `.cc` with the old shape is the git-ignored `Build/quiet.cc` probe. No committed content
|
|
migrates.
|
|
- **Adds:**
|
|
- A mod `.cc` holds two records: a `HullData` at the deterministic key `mod-hull:<id>` (`ShipModCatalog.cs:20`)
|
|
and the `ShipAuthoring` visual package (`Id`, `ModelAsset`, `Anchors`, `SchematicLines`).
|
|
- `HullData` gains a nullable `CultRecordRef<ShipAuthoring> Visual` at the next free key on its ancestry.
|
|
`EquippableItemData` owns keys up to 31 (the comment at `ItemData.cs` `WeaponItemData.Fuse`), so it is 32,
|
|
unless C4 claims that slot first.
|
|
- The validator refuses a hull with both `Prefab` and `Visual` set, or neither, when it is a ship.
|
|
- The validator is one function over (hull, visual). It checks every `Hardpoints[].Transform` against the
|
|
visual's anchor ids, the checks that are at `ShipAuthoring.cs:111-136` today.
|
|
- **Authority map:**
|
|
- Owner: the `HullData` record owns hull semantics (cells, hardpoints, stats). The `ShipAuthoring` record owns
|
|
the visual package (model path, anchor-to-node map, lines).
|
|
- Inputs: the mod `.cc`, and the GLB node extras.
|
|
- Outputs: records copied verbatim into the derived catalog.
|
|
- Demotion: `ShipAuthoring` is no longer an owner of hull data. `mod-ship:<id>` in the derived catalog is the
|
|
same record as in the mod store.
|
|
- Forbidden writers: compose transforming records; Blender writing hull slots inside `ShipAuthoring`; any
|
|
lookup from hull to visual by key-prefix convention. The typed `Visual` ref is the only binding.
|
|
- Shared paths: `ship-authoring validate`, compose, the Editor preview and the runtime loader all call the one
|
|
`(hull, visual)` validator.
|
|
- Deletion line: after S1, `grep -n "\.Hull\b" ShipAuthoring.cs ShipModCatalog.cs` finds no embedded hull.
|
|
- **Verification** (Yggdrasil, `--filter FullyQualifiedName~ShipAuthoring`, then the full suite):
|
|
- `ComposedCatalogHoldsOneHullPerModShip`: the negative for correction 2. It fails today.
|
|
- `HullNamesExactlyOneVisual`: Prefab xor Visual.
|
|
- `HardpointsMustResolveToTheVisualsAnchors`: moved from the old validator.
|
|
- `ComposeRefusesAKeyCollisionAndLeavesThePreviousFile` and `ComposeRefusesAMissingGlbNode`: the gate 3
|
|
negatives, unless S-Soul already added them.
|
|
- `AHullVariantInheritsItsVisual`: runs only once Aetheria is on variants C4. It is the reason for B, and it
|
|
is recorded as pending until then.
|
|
- A Python check, in the Blender-free `python:3.12-slim` image with `pip install msgpack -e <CultLib>/packages/cultcache-py`:
|
|
`ship_cc.replace_layout` on a C#-written fixture preserves every untouched slot, then C# reopens it and it
|
|
validates. This pins gate 1's Python half for the first time.
|
|
- Operator: in Blender, load a layout, change a mount, save, then `ship-authoring validate`.
|
|
- **Subtraction estimate:** about 40 removed (the embedded-hull paths and the duplicate upsert), about 35 added
|
|
(`Visual`, the joint validator's plumbing), and about 25 in the Python path.
|
|
|
|
### S2. The game composes and loads mod ships
|
|
|
|
- **Repo/branch:** `codex/moddable-ships`, after S1.
|
|
- **Deletes first:** none. This cut is additive: it is the construction boundary the doc names at `:141-145`.
|
|
- **Adds, per MQ4 and MQ5:**
|
|
- **Compose at boot** (MQ5 = A): `ActionGameManager` opens its catalog at `ActionGameManager.cs:45-60`
|
|
(`b66ba524`). When `GameData/Mods` holds packages, it opens the derived file instead, composed at boot into
|
|
the persistent data path. `capturepreset` keeps writing the shipped catalog (`:605-620`, and the
|
|
`Loadout.cs:74` writable open).
|
|
- **Boot preload:** after the catalog opens and before the first zone loads, every hull with `Visual` set
|
|
imports its GLB through `ShipModVisual.LoadAsync` into an inactive prototype keyed by hull key.
|
|
`LoadEntity` then instantiates synchronously, as it does for prefabs.
|
|
- **Assembly:** a `ShipInstance` template prefab (Addressable) carries the shared effect prototypes: shield,
|
|
tractor, thruster particles, radiator material. The loader parents the GLB under it and fills
|
|
`EntityInstance`'s arrays (`EntityInstance.cs:15-27` @`b66ba524`: `MapIcon`, `Shield`, `HullColliders`,
|
|
`EquipmentHardpoints`, `RadiatorHardpoints`, `ThrusterHardpoints`, `WeaponHardpoints`,
|
|
`ArticulationPoints`, and `ShipInstance.TractorBeam`) from anchors by role. The name matches that
|
|
`EntityInstance.cs:245,260,271` and `ShipInstance.cs:69-70` do against `hp.Transform` keep working, because
|
|
anchor ids are the mount ids.
|
|
- **`ZoneRenderer.LoadEntity`** (`ZoneRenderer.cs:289-310` @`b66ba524`) branches on `hullData.Prefab` or
|
|
`hullData.Visual`. That is one branch point, owned by the hull record.
|
|
- **Authority map:**
|
|
- Owner: the hull's `Visual` ref decides the visual. The anchor role decides the component slot.
|
|
- Inputs: the derived catalog snapshot, the mod package directory, and the template prefab.
|
|
- Outputs: a `ShipInstance` indistinguishable to the simulation from a prefab one.
|
|
- Derived state: the prototypes (cache-only, rebuilt each boot) and the derived catalog (disposable).
|
|
- Forbidden writers: prefab arrays hand-wired for a mod ship; any async load during `LoadEntity`; any gameplay
|
|
read of a mod package outside the derived catalog.
|
|
- Shared paths: new game, Continue, zone entry, and wormhole transit all go through `LoadEntity`.
|
|
- **Verification:**
|
|
- Yggdrasil: `ComposeAtBootIsDeterministic`. The same packages give byte-identical derived records, which
|
|
pins run-store references across relaunches.
|
|
- `RunReferencingAMissingModRefusesContinue`, per MQ4.
|
|
- Unity batchmode (Self): an Editor play-mode smoke loads a fixture mod ship into a zone and asserts
|
|
`ThrusterHardpoints`, `WeaponHardpoints` and the collider are populated.
|
|
- Operator: none yet (S3 and S4).
|
|
- **Subtraction estimate:** 0 removed, about 150 added (loader, template, preload). This is the capability gate 4
|
|
buys.
|
|
|
|
**S2 status (2026-09-30, Self):** landed on `codex/moddable-ships` through `48cabc5f`. Soul passes on S2 and its
|
|
fixes (bad packages excluded and named, never fatal; mount anchors typed by hardpoint; `capturepreset` refuses mod
|
|
designs; explicit null checks at Continue; compose failure falls back to the shipped catalog). Headless 684/684.
|
|
Unity 6000.3.24f1: compile clean; `ShipModPlaySmoke.Run` (edit mode, real preload and `Instantiate`) prints
|
|
`SHIP_MOD_PLAY_SMOKE thrusters=1 weapons=1 collider=ok mapicon=ok`; `ShipModPreview.Smoke -assemble` passes against
|
|
the generated mesh fixture. Owed: the operator's play-mode checks (menu dialog for excluded mods, New Game/Continue
|
|
with a mod, zone entry, wormhole, Continue after removal). Recorded, not fixed: two processes sharing
|
|
`Aetheria.modded.cc`; preload running inside the `CultCache` getter; a mod updated in place that changes a hull
|
|
passes the gate. The interim ruling that a non-thruster/radiator/weapon mount must be an `articulation` anchor is
|
|
superseded by the ruling below.
|
|
|
|
**Mount-point ruling (operator, 2026-09-30):** "Regarding mount points, they're only really needed for hardpoints
|
|
that are externally visible in some way. That's just weapons, radiators, thrusters right now." So a mount anchor
|
|
exists only for the `weapon`, `radiator` and `thruster` roles. There is no `equipment` role and no plain mount.
|
|
Other hardpoints are internal and carry no anchor. Articulation points (turret pivots, `ArticulationPoint.Group`)
|
|
are a separate concept and are not mounts. They stay. Follow-up for S3 prep: find out whether
|
|
`EquipmentHardpoints` (line 314) has a consumer outside anchor wiring. If it has none, delete it, and have the
|
|
validator refuse a mount with any other role. Also: the operator said "Missing design refusal on catalog entries
|
|
is good": keep it.
|
|
|
|
### S3. Gate 4: a built player
|
|
|
|
A player build loads a composed mod ship without the Editor or Blender. Only Self (the batchmode build) and the
|
|
operator (launch) can do this. It also requires a playable mod ship: a complete package with hardpoints, not the
|
|
Quiet probe, which has four anchors and zero hardpoints (`docs/moddable-ship-authoring.md:134-136`). That is
|
|
content, and it is the operator's to author or commission.
|
|
|
|
### S4. Gate 5: play checks
|
|
|
|
The operator only: spawn, equip, thrust, fire, shield, damage, save and reload, and the schematic UI for the mod
|
|
ship. It runs on master's post-fire-control rules, so the checklist's steps 11-15 (arcs, rolled impacts, blasts)
|
|
apply to the mod hull too.
|
|
|
|
### S5. Migration onto variants
|
|
|
|
After gate 5, and after MQ2 and MQ3.
|
|
- Shipped hulls move to `Visual` one at a time, and each migration deletes that hull's `Prefab`.
|
|
- Hull families collapse into variants in `Aetheria.cc`. The variants scan found Longinus/LonginusX, differing
|
|
in `Behaviors`, `Hardpoints` and two cosmetic members (`CultLib docs/document-variants-cut.md` §6).
|
|
- It waits on variants C4 (Aetheria adoption at `caching-unity-v1.5.0`).
|
|
- **Mount ids come first.** The typed lane requires unique, non-empty mount ids (`ShipAuthoring.cs:115-117`
|
|
@`375d6bd4`). No shipped hull has them: Djinni has 3 empty `Transform`s and one duplicate, and Zenith has 5
|
|
hardpoints with one distinct `Transform` (section 0). Each migration first assigns unique mount ids and repoints
|
|
the prefab-era name matches. `Transform` is also not usable as a C2 element id, so C4's minted id and the mount
|
|
id stay two members: one is the identity for variants, and the other names the model node.
|
|
- **Deletion line:** when the last hull migrates, `HullData.Prefab` (key 24) retires, and `ShipPrefabAuthoring.cs`
|
|
goes (MQ3).
|
|
|
|
## B.4 Subtraction ledger (estimate)
|
|
|
|
| Cut | Removed | Added | Formats, targets, dependencies |
|
|
|---|---|---|---|
|
|
| landed | 1 | ~511 C#, ~494 Py, 119 test | `aetheria.ship_authoring` document type; glTFast package |
|
|
| S1 | ~40 | ~35 C#, ~25 Py | `ShipAuthoring` key 1 retired; `HullData` `Visual` slot added |
|
|
| S2 | 0 | ~150 | one template prefab; no new target |
|
|
| S5 | per hull: its prefab; finally `ShipPrefabAuthoring.cs` (252) plus its smoke (80) and `HullData.Prefab` | ~0 | FBX builder retired |
|
|
|
|
The lane is net additive until S5, and S5 is where the old authority dies. Keeping two ship-authoring paths alive
|
|
indefinitely is the liability MQ3 exists to close.
|
|
|
|
## B.5 Operator questions (moddable ships), one fork each
|
|
|
|
**MQ1. Where does a mod ship's hull live, and how does a hull find its model?** (Blocks S1.)
|
|
|
|
*Context.*
|
|
- Today a mod ship is one `ShipAuthoring` record with the `HullData` embedded inside it.
|
|
- Compose writes that hull twice into the derived catalog: once as a `HullData`, and once inside the
|
|
`ShipAuthoring` copy.
|
|
- The runtime has no binding yet from a hull to its model; the doc lists it as outstanding.
|
|
- You want variants to kill duplicate data entry for ships. A variant is a whole record whose base is another
|
|
record of the same type in the same store.
|
|
- "LonginusX is Longinus with another hardpoint layout" is therefore a `HullData` variant. An embedded hull inside
|
|
another document cannot be one.
|
|
|
|
*Options.*
|
|
- **A:** keep the embedded hull. One record per mod, but a hull family must be full copies, and the derived
|
|
catalog keeps a dead second copy.
|
|
- **B:** split it. The hull is a plain `HullData` record, and it names its visual through a `Visual` ref, the way
|
|
it names a Unity prefab today. `ShipAuthoring` becomes the visual package only. A hull variant inherits its
|
|
base's visual through normal resolution.
|
|
- **C:** split it the other way. `ShipAuthoring` references its hull. Then a hull variant has no visual unless
|
|
something reads the variant's stored base key, and no consumer may read deltas (variants target, "Readers see
|
|
complete documents").
|
|
|
|
*Recommend B.* It is the only shape where variants reach ships without either copying or delta-reading. It costs
|
|
little now, because no committed content uses the old shape.
|
|
|
|
**MQ2. Can a mod ship be a variant of a shipped hull?** (Blocks S5's plan for mods; does not block S1-S4.)
|
|
|
|
*Context.*
|
|
- Your variants ruling 3 says a variant and its base live in the same store. A mod's `.cc` is its own store, so
|
|
"my Longinus with a turret" cannot name the shipped Longinus as its base.
|
|
- Separately, Blender reads mod stores through the Python runtime, and variants ruling 5 has Python refuse any
|
|
store holding a variant until Python resolution is built (it is parked).
|
|
- So even a family within one mod's store is blocked in Blender today.
|
|
|
|
*Options.*
|
|
- **A:** no. A mod ships complete hulls, and families inside a mod wait for Python resolution.
|
|
- **B:** yes, through compose. Compose would resolve a mod-declared base against the shipped catalog, which is a
|
|
second resolver outside the cache.
|
|
- **C:** yes, by reopening ruling 3 for a mod layer.
|
|
|
|
*Recommend A.* B is exactly the bespoke inheritance the variants campaign exists to prevent. C is a CultLib
|
|
decision bigger than this lane.
|
|
|
|
**MQ3. What is the FBX prefab builder for, now that the typed `.cc` lane exists?** (Blocks S5.)
|
|
|
|
*Context.*
|
|
- `ShipPrefabAuthoring.cs` (252 lines) and its smoke (80 lines) landed on fire-control-12 at `4b594e11`, so they
|
|
reach master in this merge.
|
|
- They build Unity prefabs from FBX collections by naming convention (`docs/ship-collection-authoring.md`).
|
|
- The typed lane builds the same `ShipInstance` from a `.cc` record and a GLB.
|
|
- The ships doc keeps the FBX builder "available to existing content", with retirement "following migration".
|
|
- As things stand, the game has two ship-authoring authorities, and no ruling says which one new ships use.
|
|
|
|
*The question.* Is the FBX builder a permanent path, for example for artists who will never use Blender with
|
|
Brokkr? Or is it scaffolding until the typed lane passes gate 5?
|
|
|
|
*Options.*
|
|
- **A:** scaffolding. New ships use the typed lane from gate 5 on, and the builder dies at S5.
|
|
- **B:** permanent. Both paths stay, and S2's loader keeps the `Prefab` branch forever.
|
|
- **C:** retire it now, and author no new ships until gate 5.
|
|
|
|
*Recommend A.* Two permanent authoring authorities for one concept is the split this doctrine cuts. C stalls
|
|
content for no gain.
|
|
|
|
**MQ4. What happens to a saved run when a mod it used is removed?** (Blocks S2's Continue path.)
|
|
|
|
*Context.*
|
|
- The run store references designs by key, and a mod hull's key is `mod-hull:<id>`.
|
|
- Remove the mod, and those references dangle.
|
|
- The fire-control checklist already records that an old run store throws on its first lot lookup
|
|
(`docs/merge-to-master-checklist.md:15`).
|
|
|
|
*Options.*
|
|
- **A:** Continue refuses loudly and names the missing mod ids.
|
|
- **B:** the missing ships are stripped from the run on load.
|
|
- **C:** the run loads with placeholder hulls.
|
|
|
|
*Recommend A.* A bad mod is rejected before any scene is built (the doc's own shared-path rule). B silently edits
|
|
the player's save.
|
|
|
|
**MQ5. Who composes the derived catalog?** (Blocks S2.)
|
|
|
|
*Context.* The doc's own objective is "a mod package can be installed without opening the Unity Editor". Gate 4
|
|
says "in a built Unity player without Unity Editor or Blender". A built player ships no `AetherDb`, and today
|
|
only `AetherDb ship-authoring compose` composes.
|
|
|
|
*Options.*
|
|
- **A:** the game composes at boot, from `GameData/Mods`, into a disposable file in the persistent data path.
|
|
- **B:** an installer or `AetherDb` step composes, and the game opens `Aetheria.modded.cc` when it exists.
|
|
|
|
*Recommend A.* Compose already lives in `ServerShared` (`ShipModCatalog.cs`), so the game can call it. B makes
|
|
"is the derived file stale?" a new failure the player owns.
|
|
|
|
## B.6 Dependencies (moddable ships)
|
|
|
|
- **Fire-control-12 on master:** S0.
|
|
- **Unity package resolution:** glTFast 6.20.0 needs network on first open (S0 compile).
|
|
- **Document variants:**
|
|
- **C2 (element ids):** "ids everywhere" puts an element-id member on `ShipAnchor`, `ShipPolyline`,
|
|
`HardpointData`, `ItemRole` and `BehaviorData` (every object-list element these records reach).
|
|
Consequences I read from the code, not ran:
|
|
- **Hardpoint ids survive a Blender layout save** while the mount id is unchanged. `ship_cc.py:88-98` keeps
|
|
each hardpoint's slots from 7 on, keyed by `Transform`, so an id slot at 7 or later rides along. Renaming a
|
|
mount makes a new element, which is the ruling's own rule ("a moved element is a new element"). A
|
|
hardpoint created in Blender has no id until the next C# write mints one.
|
|
- **Line ids churn on every capture.** `replace_lines` replaces `SchematicLines` whole (`ship_cc.py:51`).
|
|
This is harmless: a variant would override lines whole anyway (variants Q7).
|
|
- **The Python side writes stores C2's registry will then require ids for.** C2 mints them on the first C#
|
|
write, and Python carries the id as an ordinary member (variants Q3 rollout). So mod stores need no
|
|
migration step.
|
|
- **Python refusal:** mod stores stay v1 (no variants) while Blender reads them through `cultcache-py`
|
|
(MQ2 = A).
|
|
- **C3 (Studio):** it edits hull variants in `Aetheria.cc`. It is a prerequisite for S5, not for S1-S4.
|
|
- **C4 (Aetheria adoption, `caching-unity-v1.5.0`):** S5, and the pending test `AHullVariantInheritsItsVisual`.
|
|
Slot coordination: S1 claims `HullData` key 32 for `Visual`, and C4's id slots must not collide with it.
|
|
- **Brokkr:** the Blender add-on depends on Brokkr's `brokkr_bridge` preferences for the CultLib Python path
|
|
(`__init__.py:182-186`). It is an operator-machine dependency with no pin in this repo.
|