aetheria-release map: boss hull derived r4 (fixed Loadouts), stand-in hulls (SI) probe and cut sequence
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
b19d6add89
commit
112dd24536
1 changed files with 362 additions and 87 deletions
|
|
@ -10786,110 +10786,192 @@ worktree C:\aeth-wt\ag6-quality-readers at 70c7a497. Catalog: GameData/Aetheria.
|
|||
|
||||
## Boss hull derived
|
||||
|
||||
Probes and source reads by imagination-ag6-boss-hull (session self-2026-10-18-ag6), 2026-10-18,
|
||||
in the detached worktree `C:\aeth-wt\ag6-boss-hull` at `origin/master` `eb27ced1`. `origin/master`
|
||||
moved to `ac835a8a` during the pass. That commit adds only
|
||||
`docs/research/2026-10-18-agent-built-hull-art-prior-art.md`, so every anchor below is the same
|
||||
at both. Read by `cut-boss-hull-derived.r3`. This section supersedes the anchors of "Demo hulls,
|
||||
the Pirates and the boss" H7 (`@2a5fc00b`) and of Products PL6. Its design rationale
|
||||
("The boss hull is derived, not named") stands, with the changes recorded under Rationale below.
|
||||
r3's probes and source reads were made by imagination-ag6-boss-hull (session self-2026-10-18-ag6),
|
||||
2026-10-18, in the detached worktree `C:\aeth-wt\ag6-boss-hull` at `origin/master` `eb27ced1`
|
||||
(BH1-BH11). r4's probes were made by imagination-ag6-boss-hull-r4 in the same worktree at the
|
||||
branch head `095baf2f`, which is r3's h1 (BH12-BH17). Read by `cut-boss-hull-derived.r4`. This
|
||||
section supersedes the anchors of "Demo hulls, the Pirates and the boss" H7 (`@2a5fc00b`) and of
|
||||
Products PL6. Under Rationale below, r4 replaces r3's boss-loadout path. Ruling
|
||||
`boss-gear-is-a-loadout` revokes that path; it replaced `boss-gear-is-fixed`.
|
||||
|
||||
### Body facts
|
||||
|
||||
- **BH1. What the cut waits on is on master.** boss-gate is merged at `39c7508e`
|
||||
(cut-boss-gate r2, verdict s3). faction-play-1 is merged at `d7f30ed1` (r3, h6), and its catalog
|
||||
replay is at `aa12cbac`. minor-power-factions is merged at `c486b06d`; it is the remap of
|
||||
maker-only-faction. pirates-record is merged at `2c065937` and package-product at `3535660c`.
|
||||
No cut that edits Faction or the main galaxy's faction draw is still in flight ahead of this
|
||||
one.
|
||||
replay is at `aa12cbac`. minor-power-factions is merged at `c486b06d`. pirates-record is merged
|
||||
at `2c065937` and package-product at `3535660c`.
|
||||
- **BH2. Faction keys at `eb27ced1`** (`Corporations.cs`): 1-5, 7, 8, a retired-key comment for 9
|
||||
(`:39`), `BossHull` Key(10) at `:42-43`, then 11-15, 17 (Doctrine), 19 (Assembly) and 21 (Places).
|
||||
- **BH3. HullData keys** (`ItemData.cs:531-556`): 23, 24, 25, retired 26 (`:542`), 27, 28, 29 and
|
||||
32 (`Visual`, `:555-556`). Key 33 is free on HullData. Fuse's Key(32) at `:526` is on a sibling
|
||||
type.
|
||||
- **BH4. Every BossHull reader at `eb27ced1`** (`git grep BossHull`):
|
||||
`Galaxy.cs:293` (`PlaceFactionsMain` picks boss factions by `BossHull.IsSet()`, `:291-295`);
|
||||
`LoadoutGenerator.cs:212-230` (`GenerateBossLoadout`);
|
||||
`tools/AetherDb/Program.cs:586` (dangling comment) and `:709-712` (the factions boss column and
|
||||
its `broken` count, the method's only use of `broken`, `:700-716`);
|
||||
`RunGoalTests.cs:74-115` (two tests) and `:199-202` (Exit_open_only_in_the_exit_zone sets one).
|
||||
No writer exists: no AetherDb command, no Pirates or minor-power command, no package path.
|
||||
`GameData/Aetheria.cc` holds the string `bossHull`, which is its schema table.
|
||||
(`:39`), `BossHull` Key(10) at `:42-43`, then 11-15, 17 (Doctrine), 19 (Assembly) and 21
|
||||
(Places). h1 deleted `BossHull` and retired key 10.
|
||||
- **BH3. HullData keys** (`ItemData.cs:531-556` at `eb27ced1`): 23, 24, 25, retired 26, 27, 28,
|
||||
29 and 32 (`Visual`). h1 added `Boss` at Key(33) (`ItemData.cs:557-560` at `095baf2f`).
|
||||
- **BH4. Every BossHull reader at `eb27ced1`** was `Galaxy.cs:293`,
|
||||
`LoadoutGenerator.cs:212-230`, `tools/AetherDb/Program.cs:586` and `:709-712`, and
|
||||
`RunGoalTests.cs:74-115` and `:199-202`. h1 removed every one.
|
||||
- **BH5. Retiring a key with stored values needs no catalog rewrite.** Precedents: `1d689adf`
|
||||
retired Faction key 9 and `be7f2b07` retired HullData key 26, and neither touched
|
||||
`GameData/Aetheria.cc`. The suite loads the shipped catalog in `RunStartTests`' constructor.
|
||||
This is a precedent read; no decode was run in this pass.
|
||||
- **BH6. Every generation hull draw goes through one availability rule.** `RandomHull`
|
||||
(`LoadoutGenerator.cs:165-170`) calls `RandomProduct`, then `RandomProducts` (`:176-185`), then
|
||||
`AvailableProducts` (`:188-210`). Station stock (`:126-127`) and the boss fallback (`:226`) also
|
||||
go through `AvailableProducts`. Its `candidates` filter (`:191-198`) runs before the
|
||||
any-manufacturer fallback (`:201-206`). An exclusion placed there therefore holds on the
|
||||
fallback too. `ChooseProduct` (`:262-273`) has its own `Where` and serves presets that name a
|
||||
design.
|
||||
- **BH7. Explicit hull draws.** `ScenarioStage.Generated` (`ScenarioStage.cs:90-97`) and AetherDb
|
||||
`loadout` (`Program.cs:805-818`, which runs every hull through `GenerateShipLoadout(candidate
|
||||
=> candidate == hull)`) both reach `AvailableProducts`. Many tests call
|
||||
`GenerateShipLoadout(h => h == hull)` on shipped hulls (for example `RestoredHullsTests.cs:606`).
|
||||
None of those hulls is a Boss hull.
|
||||
- **BH8. The boss path.** `ZoneGenerator.cs:398-412` calls `GenerateBossLoadout` for every
|
||||
`BossZones` entry in the zone and marks the result `Boss`. `Galaxy.cs:150` sets the prelude and
|
||||
demo antagonist's boss zone without reading any hull. `Galaxy` holds `_cache` (`:36`, set at
|
||||
`:51`, `:102` and `:169`), so `PlaceFactionsMain` can call a static derivation over the catalog.
|
||||
- **BH9. The package writer.** `ShipAuthoringCommands.cs` (157 lines): `Usage` at `:11`, `inspect`
|
||||
at `:29-35`, `price` at `:79-93` (it judges before it writes, and a refusal leaves the file
|
||||
byte-identical), and `product` at `:94-125`. `ShipProductTests.ProductAndPriceCommands`
|
||||
(`:259-300`) pins the price and product commands with `RefusedAndUntouched`. Compose copies a
|
||||
package's HullData verbatim, so a member set in the package reaches the composed catalog.
|
||||
`ComposeCopiesEveryProductAndEachMakerIsOfferedTheHull` (`:80-100`) byte-compares products.
|
||||
- **BH10. Zhestokost makes no ship hull.** Products PL6 still holds: its only hull product is the
|
||||
Turret. Until a Zhestokost Boss hull exists, the demo boss is the logged fallback (the priciest
|
||||
ordinary ship hull Zhestokost can be offered).
|
||||
- **BH11. Which hulls can be bosses.** Ruling `hull-programmer-art-stopgap` (2026-10-18) leaves
|
||||
open whether boss hulls come from the operator's packages or an agent-built stopgap. The Eyes
|
||||
facts file at `ac835a8a` places stopgap art on the package path. Nothing in the derivation
|
||||
reads `Prefab`, `Visual` or a model, so a hull from either source, or with neither, can be a
|
||||
boss.
|
||||
(Faction key 9) and `be7f2b07` (HullData key 26).
|
||||
- **BH6. Every generation hull draw goes through one availability rule**, `AvailableProducts`
|
||||
(`LoadoutGenerator.cs:196-219` at `095baf2f`). Its candidate filter carries h1's
|
||||
`!(entry.design is HullData { Boss: true })` (`:204`) ahead of the any-manufacturer fallback.
|
||||
`ChooseProduct` (`:278-289`) has its own `Where` and does not exclude Boss hulls. r4 never asks
|
||||
it for a hull, because `BossOf` returns only a Loadout whose `HullProduct` is set.
|
||||
- **BH7. Explicit hull draws.** `ScenarioStage.Generated` (`ScenarioStage.cs:93`) and AetherDb
|
||||
`loadout` reach `AvailableProducts`. Many tests call `GenerateShipLoadout(h => h == hull)` on
|
||||
shipped hulls, and none of those hulls is a Boss hull.
|
||||
- **BH8. The boss path at `095baf2f`.** `ZoneGenerator.cs:401-411` calls
|
||||
`GetLoadoutGenerator(bossFaction).GenerateBossLoadout()` for every `BossZones` entry in the zone.
|
||||
It logs "No boss could be generated" on null and otherwise marks the pack `Boss` and the zone
|
||||
`BossSpawned`. `:414-415` logs exit-without-boss. `GetLoadoutGenerator` (`:282-287`) caches one
|
||||
generator per faction over the zone's `random`. `GenerateBossLoadout` (`LoadoutGenerator.cs:221-233`)
|
||||
builds `BossHullsOf[0]` through `ShipLoadout(product)` (`:55-66`: instance, Ship,
|
||||
`OutfitEntity`, `EquipPickup`, `StockConsumables`, Pack). Otherwise it logs "makes no boss
|
||||
hull" and generates the priciest ship hull on offer. Its readers are only `ZoneGenerator.cs:403`
|
||||
and AetherDb `loadout` (`Program.cs:815`).
|
||||
- **BH9. The package writer.** `ShipAuthoringCommands.cs`: `Usage` at `:11`; h1 added `boss
|
||||
<ship.cc> on|off`. Compose copies a package's HullData verbatim.
|
||||
`ShipProductTests.ABossPackageIsItsMakersBossAfterCompose` passes at `095baf2f`.
|
||||
- **BH10. Zhestokost makes no ship hull, and the shipped catalog has no Boss hull.** Products PL6
|
||||
still holds. Today's demo boss is the generated fallback: the priciest ordinary ship hull
|
||||
Zhestokost can be offered. Under r4 there is no fallback, so the demo exit has no boss until a
|
||||
Zhestokost Boss hull and its Loadout exist (question `demo-boss-before-its-hull`).
|
||||
- **BH11. Which hulls can be bosses.** Ruling `hull-programmer-art-reads-as-stand-in` (it
|
||||
supersedes `hull-programmer-art-stopgap`) leaves the stopgap mechanism open. The Eyes facts file
|
||||
at `ac835a8a` places stopgap art on the package path. Nothing in either derivation reads
|
||||
`Prefab`, `Visual` or a model.
|
||||
- **BH12. The three failures in h1 are a fixture defect.** Probe p1 ran ygg-verify at `095baf2f`
|
||||
with the filter of the three tests and the detailed console logger. All three throw
|
||||
`InvalidOperationException: Sequence contains no matching element` at
|
||||
`RunStartTests.FactionNamed` (`RunGoalTests.cs:75`), called with `"Lucent"` at `:130`, `:149`
|
||||
and `:185`. The shipped faction is `"Lucent Media"`, and `"Lucent"` is its ShortName
|
||||
(`DemoTerminusTests.cs:23`, `CatalogRoleTests.cs:368`). Probes p2 and p3 used a scratch clone
|
||||
with only that string changed (`f683baa4`). All three passed: `Passed
|
||||
A_boss_hull_two_makers_sell_is_a_boss_of_each`, `Passed Boss_hulls_are_never_stock_or_traffic`
|
||||
and `Passed Another_factions_boss_hull_is_never_used`. The h1 job hid the messages because its
|
||||
grep kept only `Assert|Expected|Actual` lines. Verdicts:
|
||||
- `Boss_hulls_are_never_stock_or_traffic` is a real fixture defect. Its rule (ruling
|
||||
`boss-hulls-exclusive`) holds once the name is fixed, and the test survives unchanged
|
||||
otherwise.
|
||||
- `Another_factions_boss_hull_is_never_used` is a real fixture defect. Its fallback assertions
|
||||
die with the revoked path. Its rule, that another maker's Boss hull is not this faction's,
|
||||
survives as a `NoBossHull` refusal.
|
||||
- `A_boss_hull_two_makers_sell_is_a_boss_of_each` is a real fixture defect. Its `BossHullsOf`
|
||||
rule survives. Its last assertion (the boss flies "Shared Boss") becomes "flies its Loadout".
|
||||
- **BH13. The Loadout record already carries the key from a boss hull to its gear.** `Loadout`
|
||||
(`Loadout.cs:14-22`, schema `aetheria.loadout` v1) has `Name` (Key 0), `Hull` (Key 1),
|
||||
`Slots` (Key 2: `Position`, `Rotation`, `Design`, optional `Product`), `WeaponGroups` (Key 3)
|
||||
and `HullProduct` (Key 4, optional). `HullProduct` names the product, and therefore the maker,
|
||||
of the hull. So "the Loadout whose `HullProduct` is the faction's boss hull product" needs no new
|
||||
field on `Loadout`, `Faction`, `FactionProductData` or `HullData`. It also tells apart two makers
|
||||
of one shared Boss hull.
|
||||
- **BH14. `Loadouts.Materialize` is already the builder a fixed boss needs.** It is all or
|
||||
nothing, pins a set product as named, and builds a design-only slot through the passed chooser.
|
||||
Its doc says a game spawner passes `LoadoutGenerator.ChooseProduct`. It builds a Ship from a ship
|
||||
hull with the given zone, null included, as `ShipLoadout` did, and pads to `WeaponGroupCount`.
|
||||
It adds no pickup, consumables or outfit. Its callers today are `ScenarioStage.cs:179` (fits)
|
||||
and the capture round trip in tests.
|
||||
- **BH15. A catalog Loadout cannot name a package hull.** `Loadouts.Commit` (`Loadout.cs:78-98`)
|
||||
is the only writer of Loadout records, through the in-game capture command
|
||||
(`ActionGameManager.cs:643`). It refuses any loadout whose hull or slot design is a mod key.
|
||||
Package hulls and products are keyed `mod-hull:`, `mod-product:` and `mod-ship:`
|
||||
(`ShipModCatalog.cs:23-30`), and "the shipped catalog ... never reference[s] one". A package
|
||||
carries only its HullData, its ShipAuthoring and its products (`ShipModCatalog.Package`,
|
||||
`:13-21`). So a boss Loadout for a package Boss hull has to ride in the package. That is the
|
||||
next cut line below, not r4.
|
||||
- **BH16. Boss-dependent tests.** `RunGoalTests.cs` holds 18 tests. Those that read the demo's
|
||||
fallback boss are `Boss_spawns_once_in_boss_zone`, `Boss_spawns_in_a_zone_that_is_not_ambient`,
|
||||
`Exit_sealed_until_boss_dies`, `Boss_mark_survives_continue` and
|
||||
`Exit_open_after_save_of_a_slain_boss`. `Exit_sealed_when_no_ship_hull_is_on_offer` pins the
|
||||
fallback's null branch. No other test file references `IsBoss`, `BossSpawned` or `ExitOpen`.
|
||||
- **BH17. CultCache variants are not available to Aetheria yet.** CultLib `src` has variant
|
||||
records (`CultCache.cs:59-60`, `CultVariantDelta`, "a variant record (slot 4): its base and
|
||||
overrides"). Aetheria pins CultLib `45c2f4006f5f` (`Directory.Build.props:6`), and
|
||||
`git grep CultVariantDelta 45c2f40` finds nothing. An interim Boss hull copied from a shipped
|
||||
hull would be a plain copy until the pin moves. "Variant" is that record kind, never a loadout
|
||||
(ruling `boss-gear-is-a-loadout`).
|
||||
|
||||
### Rationale
|
||||
### Rationale (r4)
|
||||
|
||||
- **One owner of which hull is a faction's boss:** `LoadoutGenerator.BossHullsOf(CultCache,
|
||||
Faction)`. It returns the faction's products whose design is a `HullType.Ship` HullData with
|
||||
`Boss` set, ordered by hull Price descending, then hull Name ordinal. The flag is authored on
|
||||
the hull, and the faction comes from the product's maker. Nothing stores the pairing.
|
||||
`GenerateBossLoadout`, `Galaxy.PlaceFactionsMain` and AetherDb `factions` all call it.
|
||||
- **One owner of "a Boss hull is never on offer":** `AvailableProducts`' candidate filter (BH6).
|
||||
r2 put the exclusion on `RandomHull` and the stock filter, and gave `RandomHull` a boss bool.
|
||||
That made two sites and one mode flag, and it missed the fallback's own
|
||||
`AvailableProducts` call. r3 uses one clause instead. The boss is built straight from its
|
||||
product through a new `ShipLoadout(FactionProductData)` builder, which is the body of
|
||||
`GenerateShipLoadout` after its draw. So the boss path never asks the availability rule to
|
||||
make an exception.
|
||||
- **The "named but unsold" branch goes.** It existed because BossHull named a design. A
|
||||
derivation that starts from products cannot name an unsold hull, so its log line and its test
|
||||
(`Boss_hull_nobody_sells_falls_back`) are deleted. They are not rewritten.
|
||||
- **A boss is not a market draw.** `ShipLoadout` from the boss product does not apply
|
||||
`AvailableProducts`' `Price > 0` rule. A price-0 Boss hull is still a boss.
|
||||
- **Kept:** a sold Boss hull's hardpoints still count in `SoldHulls`, so gear only it can mount has
|
||||
a home. That gear is loot. `ScenarioStage.Generated("<boss hull>")` fails like any unavailable
|
||||
hull. `ChooseProduct` is untouched.
|
||||
- **Prior art (from memory, medium confidence; no Eyes pass this round):** Starsector tags unique
|
||||
and boss hulls so markets never sell them. They are spawned by script, outside a faction's
|
||||
known-ships draw. EVE's officer spawns fly hulls that no market sells. Both match the exclusive
|
||||
reading. Question `boss-hull-exclusive` asks the operator to confirm it.
|
||||
- **One owner of what a boss flies:** `Loadouts.BossOf(CultCache, Faction, out BossRefusal)`, in
|
||||
`Loadout.cs` beside the other owners of Loadout records. It takes `BossHullsOf(items, faction)[0]`
|
||||
and returns the Loadout whose `HullProduct` key is that product's key, first by Name ordinal.
|
||||
When there is none, it returns null with `NoBossHull` or `NoLoadout`. It reads no `Random`, no
|
||||
model and no availability rule. `ZoneGenerator`'s boss block, `Galaxy.PlaceFactionsMain` and
|
||||
AetherDb `factions` all call it. `LoadoutGenerator.BossHullsOf` stays the one owner of which hull
|
||||
is a boss.
|
||||
- **Keyed on the product, not the hull** (BH13). A Boss hull two makers sell is a boss of each,
|
||||
and each maker's boss is the Loadout on its own product. No field on Faction or the product
|
||||
points at a Loadout, so no second record can disagree with the Loadout.
|
||||
- **No generator on the boss path.** `GenerateBossLoadout`, its priciest-hull fallback and
|
||||
`ShipLoadout(product)` are deleted. `GenerateShipLoadout` gets back its single body, because the
|
||||
boss was `ShipLoadout`'s only other caller. The boss is built by `Loadouts.Materialize`, the one
|
||||
builder from a Loadout (BH14). It carries exactly its cells: no tractor pickup, no consumables
|
||||
and no outfit pass.
|
||||
- **The chooser is not a draw of gear.** Materialize needs a chooser only for a design-only slot.
|
||||
Ruling `loadout-product-optional-context-chooses` says that slot's product is chosen by context,
|
||||
and `ChooseProduct` is that rule's owner. It never picks a design, a cell or the hull. The
|
||||
negative check forbids every generator draw on the boss path (`GenerateShipLoadout`,
|
||||
`RandomHull`, `RandomProduct(s)`, `OutfitEntity`, `EquipPickup`, `StockConsumables`). An agent
|
||||
who wants the brand fixed pins the slot's product.
|
||||
- **A typed refusal, never a fallback.** `BossRefusal { None, NoBossHull, NoLoadout }`.
|
||||
`Loadouts.Describe` names the missing field (`HullData.Boss`, `Loadout.HullProduct`) and echoes
|
||||
no product name or record key. The zone logs `<faction> has no boss: it <describe>.`, spawns
|
||||
nothing and leaves `BossSpawned` false, so the exit gate fails closed through boss-gate's
|
||||
existing rule. A Loadout that does not fit is logged with Materialize's failures and also spawns
|
||||
nothing.
|
||||
- **Galaxy picks factions that have a boss to fly.** In the main galaxy, a faction with a Boss
|
||||
hull but no Loadout gets no boss zone. That keeps a placed boss zone from being empty by
|
||||
construction.
|
||||
- **Authoring** (ruling `boss-loadouts-agent-authored`). AetherDb `loadout-commit <name> <hull
|
||||
product> [<x>,<y>,<rotation>,<design>[@<product>]]... [--group i,j]... [--replace]` writes one
|
||||
Loadout through `Loadouts.Commit`, which stays the only writer. The command first resolves every
|
||||
name exactly and test-materializes the Loadout, so a typo or a misfit refuses before anything is
|
||||
written. The operator adjusts the record in Studio afterwards. The command is argument-driven,
|
||||
not a coded table like `product-lines tiers`. A coded table replayed at merges would fight her
|
||||
Studio edits. The record in the catalog is the authority, and the commit message records the
|
||||
command line for a merge replay.
|
||||
- **Vocabulary.** `Loadout.cs` says "Loadout" throughout. "Preset" and "variant" go from its
|
||||
comments and messages. The wider sweep (the `capturepreset` console command, `ScenarioStage`,
|
||||
`Provenance`, `LoadoutGenerator` and `ActionGameManager` comments) is follow-up
|
||||
`loadout-vocabulary-sweep`.
|
||||
- **Kept from r3 (h1, landed):** Faction.BossHull is deleted with key 10 retired, `HullData.Boss`
|
||||
Key(33) stays, `BossHullsOf` stays, the exclusive clause in `AvailableProducts` stays (ruling
|
||||
`boss-hulls-exclusive`), and `ship-authoring boss`, the compose test and the no-authored-model
|
||||
test stay (ruling `hull-programmer-art-reads-as-stand-in`).
|
||||
- **Revoked from r3:** `GenerateBossLoadout` through `BossHullsOf` and `ShipLoadout(product)`, the
|
||||
AetherDb `loadout` boss branch, `Boss_without_a_boss_hull_falls_back`, and the "makes no boss
|
||||
hull" log.
|
||||
- **Prior art (from memory, medium confidence; no Eyes pass this round):** Starsector's unique
|
||||
bosses (for example the Ziggurat and the Omega flagships) fly hand-authored variant files
|
||||
attached to a scripted fleet, never the faction's autofit. EVE's officer and faction-commander
|
||||
spawns have fixed fittings per NPC type. Both match a fixed record keyed to the boss, with no
|
||||
generator.
|
||||
|
||||
### Model page rows
|
||||
|
||||
Replace the two rows in "Demo scope, Model page rows" (`Boss hull`, `Faction.BossHull`) with:
|
||||
Replace the rows in "Demo scope, Model page rows" (`Boss hull`, `Faction.BossHull`), and add the
|
||||
boss Loadout row:
|
||||
|
||||
| Kind | Identity | Lifecycle | Authority |
|
||||
|---|---|---|---|
|
||||
| Boss hull | `HullData.Boss` (key 33, bool) on any HullData, whether shipped, package, or agent-built stopgap | Set by `ship-authoring boss <ship.cc> on\|off` on packages, or in Studio's inspector on shipped hulls. Derived per generation; stored nowhere | The hull's author sets the flag. `LoadoutGenerator.BossHullsOf` derives the faction from the product's maker |
|
||||
| Boss hull | `HullData.Boss` (key 33, bool) on any HullData: shipped, package, or agent-built stopgap | Set by `ship-authoring boss <ship.cc> on\|off` on packages, or in Studio's inspector on catalog hulls. Derived per generation; stored nowhere | The hull's author sets the flag. `LoadoutGenerator.BossHullsOf` derives the faction from the product's maker |
|
||||
| Boss Loadout | A `Loadout` record (`aetheria.loadout` v1) whose `HullProduct` is a faction's Boss hull product, at `Loadouts.KeyOf(name)` | Written by AetherDb `loadout-commit` (agents) or the in-game capture, both through `Loadouts.Commit`. Studio edits after that. Read at zone generation by `Loadouts.BossOf` | `Loadouts.BossOf` alone decides what a boss flies, and `Loadouts.Materialize` builds it. No generator draw |
|
||||
| `Faction.BossHull` (key 10) | Retired; never reuse | Deleted by `boss-hull-derived`. The shipped records keep `''` in the slot (BH5) | none |
|
||||
|
||||
### Cut sequence
|
||||
|
||||
Remove the `boss-hull-derived` line. Change the `demo-cast-hulls` line's "boss-hull-derived" in
|
||||
"Waits on" to "boss-hull-derived (spec cut-boss-hull-derived.r3)" until it merges.
|
||||
- `boss-hull-derived` (spec `cut-boss-hull-derived.r4`, branch at `095baf2f`). Its merge waits on
|
||||
question `demo-boss-before-its-hull`.
|
||||
- If `interim-catalog-boss` is chosen: `zhestokost-interim-boss`, a sibling content cut that lands
|
||||
with r4. Through AetherDb it writes a catalog Boss hull copied from the hull today's demo boss
|
||||
flies (BH10), plus its Zhestokost product and its Loadout (`loadout-commit`). It is replayed at
|
||||
merges and replaced when the real Zhestokost Boss hull lands.
|
||||
- `package-boss-loadout`: a package carries its boss Loadout, as it carries its product (ruling
|
||||
`package-carries-product`, BH15). `ship.cc` gains Loadout records, Compose writes them under a
|
||||
`mod-loadout:` key that `IsModKey` knows, and `ship-authoring loadout` writes them. It is needed
|
||||
before the first package Boss hull can be a boss. `BossOf` already matches it unchanged, by
|
||||
`HullProduct` key in the composed cache.
|
||||
- `demo-cast-hulls`'s "Waits on" reads "boss-hull-derived (spec cut-boss-hull-derived.r4)" until it
|
||||
merges.
|
||||
|
||||
## Product tiers
|
||||
|
||||
|
|
@ -11186,3 +11268,196 @@ with the wider scope taken when the FusedRounds premise is fixed.
|
|||
**Old saves.** A pre-cut save reads `Seed == 0` and continues with name-only drops. Under ruling
|
||||
`no-save-compatibility-before-players` the cut may refuse it, but a refusal gate would be code
|
||||
written for nobody.
|
||||
|
||||
## Stand-in hulls (SI)
|
||||
|
||||
Imagination pass `imagination-ag6-hull-art`, session `self-2026-10-18-ag6`, 2026-10-18. Pinned to Aetheria
|
||||
`origin/master` 6d10331c (worktree `C:\aeth-wt\ag6-hull-art`). Serves ruling
|
||||
`hull-programmer-art-reads-as-stand-in`, which supersedes `hull-programmer-art-stopgap`.
|
||||
Research input: `docs/research/2026-10-18-agent-built-hull-art-prior-art.md`. That file surveys making agent-built
|
||||
art look good or deliberate. This ruling asks for the opposite, so the research's §B3 (low poly that reads as
|
||||
deliberate) is evidence about what to avoid. The greybox "dev texture" convention below comes from general practice,
|
||||
not from that file (one of the gaps listed at the end).
|
||||
|
||||
Scratch root `S` = `C:\Users\Meta\AppData\Local\Temp\claude\F--Projects-CultLib\1b477360-19e3-41e2-bde4-8932ace0b8fd\scratchpad\imagination-ag6-hull-art`.
|
||||
|
||||
### Renders for the operator
|
||||
|
||||
- `S\probe\out\longinus_top_standin_vs_existing.png`: top view, stand-in (left) and the shipped `Longinus.fbx` (right), each at its own scale.
|
||||
- `S\probe\out\longinus_iso_standin_vs_existing.png`: three-quarter view, same pair.
|
||||
- `S\probe\out\longinus_combat_zoom_proxy.png`: both top views at ship lengths of 48, 80 and 128 px.
|
||||
- `S\probe\out\longinus_top_same_metres.png`: both in one 40 m frame. The stand-in uses 2 m cells and the shipped mesh about 1 m per cell (SI-B5).
|
||||
|
||||
All four come from one scratch rasterizer (`S\probe\render.py`) with the same camera and light. The stand-in side is
|
||||
read back from the `ship.glb` the package path accepted, so it is not a separate preview.
|
||||
|
||||
### What the game reads (body facts)
|
||||
|
||||
- **SI-B1. Package shape.** A mod hull is `GameData/Mods/<id>/ship.cc` plus `ship.glb`. The `.cc` holds `HullData`
|
||||
(`mod-hull:<id>`) and `ShipAuthoring` (`mod-ship:<id>`; `ModelAsset`, `Anchors`, `SchematicLines`, `Joints`).
|
||||
Source: `ShipAuthoring.cs:12-21`, `docs/moddable-ship-authoring.md:160-200`.
|
||||
- **SI-B2. Anchors the game needs.** Exactly one each of `map-icon`, `hull-collider`, `shield` and `tractor`
|
||||
(`ShipAuthoring.cs:288-290`). Each visible hardpoint has a mount anchor whose id is its mount id: `thruster-emitter`,
|
||||
`radiator-mesh` or `weapon-mount`. Each weapon needs at least one `weapon-muzzle` whose ParentId is the mount
|
||||
(`:325-356`). These nodes must carry a mesh: thruster-emitter, radiator-mesh, map-icon and hull-collider. The
|
||||
shield node needs three positive scale radii (`ShipModCatalog.cs:256-262`). Weapon mounts and the tractor may be
|
||||
empties. Internal hardpoints have no anchor. Every hardpoint needs a non-empty mount id.
|
||||
- **SI-B3. Frame.** Blender Ship Root has the nose toward -Y, starboard toward -X and Z up. Cell (x, y) is centred at
|
||||
`origin - 2(x, y)`, schematic +x is starboard and +y is forward, so row 0 is the stern
|
||||
(`tools/blender/aetheria_ships/hull_grid.py:3-20`). Package exports with `export_yup` (`__init__.py:791`), so the
|
||||
glTF nose is +Z. Hardpoint `Rotation` gives the facing: 0 nose, 1 port, 2 aft, 3 starboard
|
||||
(`hull_grid.py:141-145`). A footprint shape is stored as placed, and validation reads it at `Position` without
|
||||
rotating it (`ShipAuthoring.cs:310-320`).
|
||||
- **SI-B4. Longinus HullData** (decoded from `GameData/Aetheria.cc` with cultcache-py, `S\grid.py`). It is 6 x 17
|
||||
cells, symmetric, with 13 hardpoints. Visible: Th.L and Th.R (2x2, rows 0-1, aft), Th.CW and Th.CCW (1x2, row 14,
|
||||
port and starboard), En.L and En.R (1x2, row 8), La.L and La.R (1x3, flanks, rows 5-7), Ra.L and Ra.R (1x2,
|
||||
row 2). Internal: Reactor, ControlModule, and Sensors, which has a **null mount id**. Because of that null id, a
|
||||
package copy of the shipped Longinus is refused until the Sensors hardpoint is given an id (`ship_cc.replace_layout`
|
||||
raised "needs 7 typed fields and a stable mount ID"). The probe gave it the id `Sensors`.
|
||||
- **SI-B5. Scale.** The add-on authors at 2 m per cell (`hull_grid.py:11`, "Settings.SchematicCellSize"; New Ship
|
||||
sizes "this many 2 m cells"). The shipped `Longinus.fbx` body is 5.2 x 16.8 m for 6 x 17 cells, about 1 m per cell
|
||||
(`S\fbx.py`, mesh `Cube.043`). Ruling `schematic-and-model-are-separate-spaces` already allows the two to differ.
|
||||
The stand-in uses the add-on's 2 m.
|
||||
- **SI-B6. Materials today.** `ShipModVisual` imports the GLB with glTFast and keeps its glTF materials. The only
|
||||
material it adds is the schematic line material (`ShipModVisual.cs:55-64`). `Aetheria/Hull` and the UV0 livery mask
|
||||
belong to `ships-hull-material`, which has a spec but has not landed. So a stand-in's look today is whatever its
|
||||
glTF materials say.
|
||||
- **SI-B7. `ship-authoring create --like`** copies a shipped hull's stats with an empty shape and no hardpoints
|
||||
(`ShipAuthoringCommands.cs:57-75`, `HullLike` clears `Shape` and `Hardpoints`). The layout comes from Blender or
|
||||
from `ship_cc.replace_layout`.
|
||||
- **SI-B8. OpenSCAD over LevelDsl, decided from source.** The Geometry.Csg `LevelDsl` has box, cut box, oriented box,
|
||||
cylinder_z, dome_cap_z and floret_arm, with no hull or free-axis cone (research, Internal fit), and it needs a
|
||||
cargo build. OpenSCAD nightly with Manifold is installed, and brush-tree's `openscad-target` already rules
|
||||
OpenSCAD-shaped text. LevelDsl was not probed.
|
||||
|
||||
### The probe (Longinus)
|
||||
|
||||
The stand-in source has three files (`S\probe\`):
|
||||
- `standin.scad`: the vocabulary. Glyph per kind, kind colours, cell frame, part dispatch.
|
||||
- `longinus.layout.scad`: generated from HullData by `gen_layout.py`, and never edited by hand. It holds the visible
|
||||
mounts: id, kind, position, placed footprint and rotation.
|
||||
- `longinus.standin.scad`: agent-authored, 25 lines. Four `hull()` segments make the body (stern block, flared
|
||||
weapons section, spine, nose), plus a `deck()` height function.
|
||||
|
||||
`build.py` renders each part separately with `openscad.com --backend Manifold --render all -D part="…"` (one process
|
||||
at a time, about 22 runs). It then writes `ship.glb`: a flat-faceted mesh per part, UV0 as a top-down projection in
|
||||
cell units, and nodes tagged `extras.aetheria.id`. It also writes `anchors.json`.
|
||||
|
||||
- **SI-P1. Measurements** (`S\probe\build.py` output):
|
||||
- The body has 104 triangles. 0 of its 60 vertices lie outside the hull cells, and the worst miss of the mirror
|
||||
test is 0.0000 m (the body is symmetric).
|
||||
- The GLB is 84 KB, with 28 nodes and 18 anchors.
|
||||
- All 10 visible mounts project into their own footprint: weapons and radiators at the footprint centre, thrusters
|
||||
on the footprint edge they face.
|
||||
- The probe checked vertices, not edges, against the cells. Each segment is convex inside a rectangular run of
|
||||
cells, which is why the vertex check suffices here; a stepped outline needs an edge check.
|
||||
- **SI-P2. Package path, on Yggdrasil.** `ygg-verify` ran on a scratch clone at `2329782244d3`. That clone is
|
||||
6d10331c plus `standin-probe/`; its commits are throwaway and were never pushed to origin. Image
|
||||
`mcr.microsoft.com/dotnet/sdk:10.0`, with pins `/pins/cultlib-git-45c2f40` and `/pins/cultlib-9c1b06cd` and the
|
||||
`/lfs` catalog. Log: `S\ygg-standin-6.log`, GREEN. The steps were:
|
||||
1. `ship-authoring create … --like Longinus`.
|
||||
2. `ship_cc.replace_layout` with Longinus' shape and hardpoints, then `ship_cc.replace_visual` with the 18 anchors
|
||||
(the add-on's own Python).
|
||||
3. `validate`, which reported "13 hardpoints, 18 anchors, 0 lines (records and model)", meaning
|
||||
`ShipModCatalog.ReadPackage` judged the GLB.
|
||||
4. `compose`, which reported "Composed 1 mod ships", with no exclusions.
|
||||
|
||||
Runs 1-5 were red on job plumbing: LFS pointer, CultLib pins, empty layout from `--like`, and the null Sensors id.
|
||||
- **SI-P3. Not proven.** The Unity side was not run: `ShipModPreview.Smoke -assemble` and `ShipModPlaySmoke.Run`
|
||||
(thrusters, weapons with muzzles, collider, map icon). The probe's GLB writer is scratch Python, not the add-on's
|
||||
Package Ship. Blender was not in the loop, because no current Blender is on Starfire (only 2.77 and 2.78 under
|
||||
`D:\Applications`). Glyph proportions were checked only in the proxy renders, never at the real combat camera.
|
||||
- **SI-P4. What the renders show.**
|
||||
- From above, the stand-in states where every gun, launcher, engine and radiator is, by colour. At 48 px the
|
||||
shipped mesh reads only as a silhouette.
|
||||
- The body reads as an unfinished blockout: slab facets, a cell grid, no surface detail. Nobody would take it for
|
||||
the shipped hull.
|
||||
- The grid is texture noise at 48 px. In engine, mips average it away at distance.
|
||||
- The first radiator fins were edge-on from above and vanished. They became panels tilted 30° outward. Energy
|
||||
barrels were thickened from 0.14 to 0.28 m for the same reason.
|
||||
|
||||
### Visual grammar (proposal; the look is question `standin-visual-mark`)
|
||||
|
||||
1. **Body.** A few convex `hull()` segments that follow the hull's outline and its concept (Concept Mill image or
|
||||
description). Flat facets at `$fn = 8`. No bevels, panel lines, greebles or invented surface detail. One neutral
|
||||
grey, carrying a dark line every 2 m cell on UV0, which is projected top-down in cell units. The grid is the
|
||||
greybox mark, and because the cells are the schematic's, it also states size class.
|
||||
2. **Mount glyphs by kind,** each in one saturated colour, on its footprint, facing its Rotation:
|
||||
- energy: a puck and one long barrel;
|
||||
- ballistic: a puck and two short thick barrels;
|
||||
- launcher: a box with tube mouths on its facing face;
|
||||
- thruster: a bell opening toward its facing, whose mouth disc is the `thruster-emitter` mesh;
|
||||
- radiator: a panel tilted outward, which is itself the `radiator-mesh`.
|
||||
|
||||
Internal hardpoints are not drawn, because the schematic UI owns them.
|
||||
3. **Size.** Model metres are cells x 2 m (the add-on's rule).
|
||||
4. **Faction.** None in the mesh. Once `ships-hull-material` lands, the body's UV0 is where the livery mask goes
|
||||
(primary channel on the body). Until then the body is neutral grey.
|
||||
|
||||
### Model (SKILL step 0b): rows for the persistent kinds this adds
|
||||
|
||||
| Kind | Identity | Lifecycle | Authority |
|
||||
|---|---|---|---|
|
||||
| Stand-in source, `GameData/Mods/<id>/standin.scad` | The package directory, which equals `ShipAuthoring.Id`, names it. One per package, at a fixed name. Injective because package ids are. | Created by an agent pass for a hull with no operator art. Revised by agents. Deleted in the same change that lands operator art for that package. | The agent pass (Imagination for content, Hands for the cut) authors the body. The operator decides when it dies, by packaging her art. Forbidden writer: nothing hand-edits the layout. |
|
||||
| Layout, generated | Derived from that package's HullData. | Regenerated on every build. Not committed, never edited. | HullData. Owner only under `standin-layout-follows-schematic` = follow-schematic. Under free-like-real-art this row disappears and the source holds mount placement. |
|
||||
| Shared vocabulary, `tools/blender/aetheria_ships/standin/standin.scad` | Single file. | Revised by cuts; a revision re-renders every stand-in at its next build. | The ship add-on (Aetheria). |
|
||||
| `ship.glb` | `ShipAuthoring.ModelAsset`: one slot per package. | While `standin.scad` exists, a build output of it. Afterwards, the output of the operator's Package. | Exactly one writer at a time. The slot holds one mesh, so two meshes can never both decide. |
|
||||
| "This hull is a stand-in" | The presence of `standin.scad` in the package. **No new field.** | Ends when the file is deleted. | Derived. Build Stand-in refuses a package without the file, so it can never overwrite operator art. Any "which hulls are still stand-ins" view is a directory listing. |
|
||||
| Anchors | `ShipAuthoring.Anchors`, ids = mount ids. | Rewritten by Package on each build, stand-in or art. Ids are stable across the swap. | Package Ship (`ship_cc.replace_visual`). The same anchor ids keep loadouts and saves valid across the swap. |
|
||||
|
||||
Replacement when real art arrives: the operator packages her collection into the same package. Package writes
|
||||
`ship.glb` and the anchors, and the same change deletes `standin.scad`. That is one act and one owner.
|
||||
Open choice inside the cut, not an operator fork: whether Package deletes `standin.scad` itself or refuses until it is
|
||||
deleted. Recommendation: Package deletes it when the bound collection was not built by Build Stand-in, and says so.
|
||||
|
||||
`standin.scad` ships inside the mod directory, so it may reach player builds. That is harmless, and it doubles as an
|
||||
example for modders. The alternative is `tools/standin/hulls/<id>.scad`, which breaks "presence in the package is the
|
||||
status".
|
||||
|
||||
### Rationale
|
||||
|
||||
- **Why not Tripo or "deliberate low poly".** The ruling's test is "never mistaken for final art". Every element of
|
||||
the grammar is chosen to fail the polish test on purpose: the grid, flat facets, kind colours, and no surface
|
||||
detail. Each element also carries information (scale, mount kind, facing), so the look is utility.
|
||||
- **Why the layout is generated.**
|
||||
- Readability: the mounts the player sees agree with the schematic UI.
|
||||
- Upkeep: a schematic edit re-renders the stand-in with no agent pass.
|
||||
- Opus's weak spot: spatial placement is the failure mode the prior art documents most (LL3M §5; CAD
|
||||
valid-but-wrong), and generation leaves only the body to judgment.
|
||||
|
||||
This is model geometry derived from cells, against `schematic-and-model-are-separate-spaces`, hence question
|
||||
`standin-layout-follows-schematic`.
|
||||
- **Why Blender for the target, not the probe's Python.** Package Ship already writes `ship.glb` and the anchors,
|
||||
and runs validate. A second GLB writer would be a second authority over the same file. The cut imports OpenSCAD
|
||||
part meshes into the bound collection with role tags, then calls Package. Brush-tree's `bake-only` and
|
||||
`lowered-hull-through-package` point the same way.
|
||||
|
||||
### Cut sequence (one line each; no spec admitted, because `standin-build` waits on both questions)
|
||||
|
||||
1. `standin-build`. Add-on operator Build Stand-in (headless-capable): checks that `standin.scad` exists, generates
|
||||
the layout from the bound `ship.cc`, runs `openscad.com` per part, imports the parts into the bound collection with
|
||||
`aetheria.role`/`aetheria.id` tags, and calls Package Ship. It carries the vocabulary file and Longinus as its test
|
||||
hull, gives the shipped Longinus Sensors hardpoint a mount id (SI-B4), and adds a `smoke_standin.py` beside
|
||||
`smoke_package.py`. Verification:
|
||||
- `ShipModPreview.Smoke -assemble` and `ShipModPlaySmoke.Run` on the package;
|
||||
- the in-cells, mirror and in-footprint checks from SI-P1 (the last only under follow-schematic).
|
||||
|
||||
Deploy owner: OpenSCAD in whatever image runs Blender smokes on Yggdrasil. Not established, because the Blender
|
||||
smoke's host was not found.
|
||||
2. `standin-package-retire`. Package Ship, on a package that still has `standin.scad`, deletes the file when the
|
||||
collection is the operator's (model row above). It can fold into cut 1 if small.
|
||||
3. `standin-role-hulls` (content). Stand-ins for the demo role hulls as their HullData land (follow-up
|
||||
`demo-role-hulls`), and for the cast flyables until her art arrives. Each body is authored from the hull's
|
||||
Concept Mill keeper or description (follow-up `hulls-through-concept-mill`).
|
||||
4. After `ships-hull-material`: the stand-in mask, with primary channel = body, so faction liveries colour stand-ins.
|
||||
A line in that spec's next revision, not a cut of its own.
|
||||
|
||||
### Gaps
|
||||
|
||||
- Combat camera distance and angle were not read, so the zoom panel is a proxy.
|
||||
- The greybox dev-texture convention is general practice, not evidence in the Eyes file.
|
||||
- Where a current Blender runs headless for agents is not established on Starfire. The Package smoke's host was
|
||||
not checked.
|
||||
- Settings.SchematicCellSize = 2 m is cited from `hull_grid.py`, not read from `Settings.asset`.
|
||||
- Scope reading, not a ruling: stand-ins are for hulls without operator art (demo role hulls; cast flyables until
|
||||
hers land). Shipped legacy hulls keep their meshes.
|
||||
|
|
|
|||
Loading…
Reference in a new issue