Commit graph

2,015 commits

Author SHA1 Message Date
Meta Vens
e4df73e4fe Fix F1: PersistedBehaviors as a pairs array, per Cut 0's ruling
Dictionary<int2, PersistentBehaviorData[]> has no hash-resistant comparer
under CultCache's UntrustedData security, so any run store holding an
entity threw MessagePackSerializationException on reopen, even when the
dictionary was empty. EntityPack.PersistedBehaviors (key 5, unchanged)
becomes (int2 position, PersistentBehaviorData[] data)[], matching
Equipment/CargoBays/DockingBays. Pack writes pairs; Unpack builds a local
lookup before restoring behaviors.

PersistentBehaviorData had no concrete leaf and no Union, so nothing could
ever exercise its wire format; added MarkerPersistentBehaviorData as the
one placeholder leaf so ReopenedPackKeepsPersistedBehaviors can round-trip
real data ahead of a real IPersistentBehavior implementer landing.

Tests: RunSaveTests.BarePack uses an empty pairs array.
Added ReopenedPackKeepsPersistedBehaviors (a hand-built pack with two
positions, each with one persisted behavior, round-trips through commit
and reopen) and ReopenSurvivesAGeneratedShipWithEntities (a real ship from
LoadoutGenerator against a catalog fixture, committed into a SavedZone and
reopened, pinning the defect at the layer Soul's probe found it).
MaterializedLotsSurviveSaveAndReload and CommitKeepsOnlyReachableLots go
green; 36 of 36 tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 04:58:15 +02:00
Meta Vens
562cdcf2b8 Record the subsystem-reveal targeting ruling
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 04:50:14 +02:00
Meta Vens
df4056123a Record the fire-control ruling: hits are rolled, not detected
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 04:49:13 +02:00
Meta Vens
1eb01a4277 Record the reopen-defect ruling: persisted behaviors as a pairs array
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 04:48:14 +02:00
Meta Vens
559db068b2 Map the headless playground cut
Imagination pass. Cut 0 makes runs reopenable; Cut 1 moves the run lifecycle
into a ServerShared Run owner; Cut 2 moves damage, death, loot and pickup out
of Unity; Cut 3 adds AetherDb play; Cut 4 adds smoke scenarios. Forks R, P, H,
D, S, N await rulings.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 04:46:12 +02:00
Meta Vens
122769ff59 Record provenance Cut B landed and Soul's findings
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 04:45:45 +02:00
Meta Vens
1e647953e5 Add mutation tests proving Cut B's new rules fail under their own mutation
tests/mutation_tests.py applies each named mutation from the cut map's
Cut B table to an exact, unique source anchor, runs the one test that
must catch it, restores the file byte-for-byte, and reports kill/survive.
A no-op control (anchor rewritten to itself) proves the read/replace/
restore path is transparent. Covers the four rules whose tests currently
pass: MissingLotIsLoud (indexer and Reachable throw on an absent lot),
MatchingHardpointsShareALot (same lot, not a fresh mint), Brand's and
Materialize's record-key order, and StatsReadTheLot (Evaluate reads the
lot's role fills, QualityForRole's fallback). All 8 mutations (7 rules
+ control) behave as expected.

MaterializedLotsSurviveSaveAndReload and CommitKeepsOnlyReachableLots
are not yet reached: they fail before any mutation is applied, blocked
by the CultLib int2 dictionary-key gap reported in the previous commit
and the landing report.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 04:22:29 +02:00
Meta Vens
76c331e9bc Test the lot ledger, its GC, and stats reading through it
Every existing ItemManager/RunSave.Commit call site now threads a
ProvenanceLedger. Five new tests and one rewrite cover the rulings this
cut lands: a materialized ship's lots survive a real save and reload
with unchanged quality and Attributed provenance; RunSave.Commit keeps
only lots reachable from a save's packed entities, closing over
Produced.Facility/Inputs, while the live ledger keeps everything;
ProvenanceLedger's indexer and Reachable are loud on a missing lot;
matching hardpoints mint from the same lot; Evaluate/GetTier read a
lot's Quality and per-role fills; and Brand's first-in-key-order rule
is asserted directly instead of through the old Product field.

Two of these (MaterializedLotsSurviveSaveAndReload,
CommitKeepsOnlyReachableLots) currently fail: committing any EntityPack
into the run store and reopening it throws
`TypeAccessException: No hash-resistant equality comparer available
for type: CultMath.int2`, because EntityPack.PersistedBehaviors is a
Dictionary<int2, ...> and CultMessagePackSecurity registers a
collision-resistant comparer only for CultRecordRef<T>. This predates
this cut (PersistedBehaviors is untouched here) and is a CultLib gap,
not a defect in this schema; see the landing report.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 04:17:13 +02:00
Meta Vens
415f8e1152 Add the lot ledger and mint every crafted instance through it
Quality, Ingredients and Product leave CraftedItemInstance; a unit now
carries only a LotId into the run-scoped ProvenanceLedger. ItemManager
gains the one lot-minting path (CreateLot/CreateInstance(int)), GetLot,
and Brand(), which derives a maker and product from provenance instead
of a stored field. RunSave persists the ledger pruned to what the saved
zones' entities still reach; the live ledger is never pruned, so a
floor pickup collected after a save still resolves. CopyBuild is gone:
matching hardpoints now share the same lot instead of copying values.
PropertiesPanel shows the derived brand where the design's manufacturer
used to be. AetherDb's census flags duplicate (maker, design) products
and its save command checks every crafted instance's lot against a real
run store.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 04:16:23 +02:00
Meta Vens
d8c4ec4dd8 Record provenance Cut A landed and verified
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 03:46:43 +02:00
Meta Vens
69801bbb2c Delete the re-mint path and the dead debug menu
Cut A of docs/item-provenance-cut.md. TradeMenuDebug.cs was dead (its
script GUID appears in no scene/prefab/asset and no other .cs names the
type). ItemManager.Instantiate and CreateInstance(SimpleCommodityData,
int) re-rolled quality and dropped provenance on every load; nothing
outside EntitySerializer called them once the instantiate parameter is
gone. EntitySerializer.Unpack/Restore no longer thread an instantiate
flag, so Temperature and Armor are restored unconditionally like every
other caller already got. The starting ship and spawnturret now keep
what the generator built instead of re-minting.

Verification: Aetheria.Shared, tools/AetherDb build green against
CultLib a0813c6; tests/Aetheria.Shared.Tests 29 passed (unchanged); the
four negative greps in the map are empty. Unity compile is still owed
(the editor is open on this project; batchmode was not run per
instructions).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 03:42:12 +02:00
Meta Vens
7425ff1be4 Map the item provenance schema cut
Imagination pass. Cut A deletes the re-mint path and TradeMenuDebug; Cut B
moves quality and product onto lots in a run-store ledger with a placeholder
origin and derived branding; Cut C removes ItemData.Manufacturer and rewrites
the catalog. N1 (Produced.Station type) defaults to a run-scoped integer id.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 03:38:04 +02:00
Meta Vens
b0df689d55 Record F4 as ruled: quality stored per lot per role
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 03:20:18 +02:00
Meta Vens
605d3af60a Record F1 as ruled: one run-store lot ledger
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 02:58:10 +02:00
Meta Vens
30c316a303 Record the placeholder origin and in-place factory construction rulings
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 02:54:48 +02:00
Meta Vens
ec1d961bef Map the item provenance substrate and its forks
Imagination pass. Body map of items, mint sites, stations, persistence and
quality readers; identity/lifecycle/authority table; schema-cut forks F0, F1,
F4 and future forks F2-F10.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 02:45:58 +02:00
Meta Vens
130df11240 Put the provenance schema in Terminus scope and generation after it
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 02:43:10 +02:00
Meta Vens
c25e8cefd3 Record synthesis down to mining, consumer goods and routes
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 02:40:00 +02:00
Meta Vens
e875b08d31 Record that Terminus synthesizes provenance backwards from products
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 02:36:38 +02:00
Meta Vens
dbe1dd8319 Record the item provenance rulings as a target
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 02:35:37 +02:00
Meta Vens
bf822e9646 Record the Studio click-through and the dropped grouping features
The click-through passed with the AmmoType refs repointed and the schematic
underlay fixed. It also surfaced that the cutover dropped the Database Tools'
record grouping and drag-and-drop without recording them; CultLib's
studio-grouping cut map owns restoring them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 02:08:41 +02:00
Meta Vens
37f513e5dc Keep the schematic underlay under the shape grid
The port dropped the legacy drawer's FlexibleSpace, so the grid's group
stretched to the inspector width and the texture was fitted there instead.
Checked by the operator in Studio on LonginusX.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 02:08:40 +02:00
Meta Vens
515859cf24 Repoint the three dangling WeaponData.AmmoType refs in Studio
AetherDb dangling now reports 0 refs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 02:01:49 +02:00
Meta Vens
f9d21e28f6 Say npm publication is parked by choice, not held on a token
The cut map and postmortem read as if a cultcache-ts publish were blocked
and imminent. The operator parked registry publication for a later pass
(CultLib QUIC map, Q5 C, 2026-09-16); no token exists and none is planned.
Same facts, honest framing.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 15:49:40 +02:00
Meta Vens
84e012a6b3 Write the CultCache migration postmortem
Covers the timeline, the structural delta against the ledger (including the
CultLib core size miss), the defects Soul caught that green tests passed
(29 of 32 passes found real defects), operator corrections with the context
each one lacked, incidents, and the pipeline changes carried into the Eureka
skill. The evidence comes from commits, the cut map and the session
transcript.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 17:59:26 +02:00
Meta Vens
98a0b977d0 Stop claiming spawners already materialize loadout presets
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 17:49:43 +02:00
Meta Vens
58b2769017 Record preset capture through its own cache in the cut map
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 23:33:50 +02:00
Meta Vens
20f0f3d775 Describe preset capture through its own cache in the target
The Loadouts section no longer says the editor opens the catalog writable. It
describes the read-only process cache, the short-lived capture cache, the
refusals for a missing catalog and a same name under another key, the console's
character refusal, and the need to reopen CultCache Studio after capturing,
since a Studio save overwrites captured presets.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 23:32:53 +02:00
Meta Vens
d90a65a908 Stop describing a preset materializer that does not exist
LoadoutGenerator.IsAvailable's comment claimed a caller passes it to
Loadouts.Materialize. No game path materializes a preset yet. The comment now
says a spawner is to pass it, and that none does yet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 23:32:53 +02:00
Meta Vens
d503f076e1 Capture presets through their own cache; keep the catalog read-only
Opening the process cache's catalog writable in the editor was the wrong cut.
It made catalog read-only only by convention, so a stray catalog write in play
succeeded and a later flush could land play-mutated instances in Aetheria.cc.

- The process cache opens the catalog read-only in every build again, so the
  missing-global check runs for it as it did before db83cd29. The writable
  empty-seed exemption from ab4ced0f stays for AetherDb and fixtures.
- Loadouts.Commit(catalogPath, loadout, replace) is the only preset writer. It
  opens its own short-lived cache over the catalog, writable, commits the one
  preset conditionally on its key and disposes it. No other cache or instance
  is touched. The running game sees the preset only after the catalog reloads.
  CultLib at a0813c6 allows this: a read-only single-file store takes no lock
  and holds no handle, and the writer's commit takes the exclusive sidecar
  <path>.lock and replaces the file atomically.
- Capture refuses, writing nothing, when Aetheria.cc is missing (it never
  creates the catalog), when a preset of that name exists under another key
  (CultCache does not reject duplicate names on write), or when the key holds a
  preset and replace is not given.
- The console refuses a command with any character outside letters, digits,
  spaces and hyphens instead of stripping it, so "Mk.II" can no longer become
  "MkII". capturepreset refuses a second argument other than replace, so an
  unquoted capturepreset My Ship refuses instead of saving "My".
- The capture log line says the session sees the preset after a reload, and
  that a Studio session opened earlier overwrites captured presets on save.
- Tests: capture while the process cache holds the catalog read-only leaves it
  unchanged; a missing catalog refuses and creates nothing; a same name under a
  Studio-minted key refuses, with or without replace; a game-shaped read-only
  open missing a global throws. The existing preset tests now commit through
  the catalog path.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 23:32:52 +02:00
Meta Vens
11902b23cf Record loadouts as authored catalog presets in the cut map
The operator redefined loadouts as ship presets (variants) for spawners,
hidden from players in the MVP. The header and Q7 route now describe the
live design, and the older player-store Cut 10 text is marked as history.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 23:14:40 +02:00
Meta Vens
ab4ced0f53 Keep a missing catalog global loud for writable opens
Globals are owned by lifecycle, so the cache must never run without one,
however the catalog is opened. Before this, opening the catalog writable
skipped the check entirely. The editor now opens the catalog writable, so a
missing global went silent in play mode.

AetheriaStores.Open now skips the check only for a writable catalog that
has no records: a seed store being authored from nothing, as the test
fixtures do. A populated catalog is always checked, and so is a read-only
empty one. Nothing else relied on the old skip: the game has no catalog
globals, AetherDb opens the populated Aetheria.cc, and every writable test
reopen already holds its fixture global.

WritablePopulatedCatalogMissingGlobalIsLoud removes the global from a
populated catalog and checks that a writable reopen throws. It fails when
the old writable-skip condition is restored.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 23:13:22 +02:00
Meta Vens
0c44aacb5b Describe loadouts as authored ship presets in the target and README
The target's Loadouts section now describes catalog presets. They are
authored in Studio or captured by the editor command, and spawners
materialize them under availability, all-or-nothing, with no charge. It
keeps the weapon-group rules and records hardpoint-configuration variants as
future direction. The README no longer lists loadouts as player state.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 23:09:56 +02:00
Meta Vens
db83cd290d Make loadouts authored catalog presets with an editor capture command
Loadouts are ship presets (variants) for spawners, not a player feature.

- Route Loadout to the catalog; the player store no longer holds it.
- Materialize takes no credits: affordability, the charge and Loadouts.Price
  are deleted, since only the player menu priced loadouts. Availability,
  key-order product choice, all-or-nothing failures and the weapon-group
  rules stay.
- Delete the InventoryPanel Save Loadout and Restore Loadout entries and
  RestoreLoadout, which also removes its docking-bay overwrite.
- The editor opens the catalog writable in the one process cache, and the
  editor-only console command capturepreset "<name>" [replace] captures the
  piloted ship. Loadouts.Commit keys a preset by name, refuses a same-name
  replace unless asked, and commits conditionally on that key. The single-file
  store then lands only that record onto the file as it is on disk. An
  unconditional commit would re-serialize every live catalog instance,
  including any that play has mutated.
- Tests: capture -> commit -> fresh reopen round-trip, explicit replace with
  no duplicate, and a preset commit that does not persist a mutated live
  Faction. The money-only tests are removed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 23:09:43 +02:00
Meta Vens
e7dd62d02a Record the loadout ruling in the migration target
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 22:51:48 +02:00
Meta Vens
a786767bac Point the README at Aetheria.cc, CultCache Studio and AetherDb
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 22:51:43 +02:00
Meta Vens
0702e78cda Refuse non-weapon groups, fix the group count, and widen loadout price (Cut 10 fix)
Materialize now reports a weapon group index whose slot design has no
WeaponData, and a loadout with more groups than WeaponGroupCount. The ship
always gets exactly WeaponGroupCount groups, and a shorter loadout is padded
with empty groups. Both checks run before anything is built.

Price is summed as long, so the hull add can no longer wrap to a negative
price that pays the player. A price beyond int credits is simply unaffordable.

Deleted Capture's index >= 0 filter and its test. Every production writer of
WeaponGroups items (WeaponGroupAssignment, GenerateWeaponGroups, Unpack replay
of Pack, Materialize) groups only weapons, and TryUnequip removes weapons from
groups, so nothing produces an ungrouped index any more.

The Materialize comment now says a failed fit may already have advanced
itemManager.Random.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 22:51:37 +02:00
Meta Vens
bb3aae4998 Record Cut 10 landed and correct its negative greps
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 22:39:56 +02:00
Meta Vens
1cd6170d7a Decide loadout affordability in Materialize and test weapon-group unequip (Cut 10 fix)
Materialize, which already charges, now also owns affordability: when credits
are below Price it lists "cannot afford <price> credits" and builds nothing.
The restore menu's separate price < Credits check is deleted; the menu still
shows Loadouts.Price and lists Materialize's failures.

Tests: one credit short builds nothing, leaves credits unchanged and lists the
failure. A fixture gun with an InstantWeapon behavior proves TryUnequip removes
the weapon and its item from every weapon group.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 22:38:16 +02:00
Meta Vens
bc3ce8fc26 Pin loadout product order against the live cache's enumeration order (Cut 10 fix)
The key-order test wrote its products and then reopened the store, whose
snapshot already returned them in key order, so removing the explicit OrderBy
still passed. The products are now committed into the open cache in z-then-a
order, the test asserts that enumeration order first, and Materialize must
still pick lamp-a.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 22:35:16 +02:00
Meta Vens
890063b8bb Remove an unequipped weapon from its weapon groups (Cut 10 fix)
TryUnequip removed a weapon from _weapons but left it in WeaponGroups, so an
unequipped weapon could still be fired from its action-bar group and
EntitySerializer.Pack wrote -1 for it. The removal mirrors the group toggle in
WeaponGroupAssignment.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 22:30:40 +02:00
Meta Vens
45f2a97862 Correct the CultCache migration target's status and save and legacy claims (Cut 10 fix)
Status is landed and awaiting the operator's play smoke and Studio
click-through. Quit and zone transition share the run save commit and death
uses RunSave.Clear. The importer was deleted in Cut 10, so the target states
that no legacy reader exists.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 22:29:58 +02:00
Meta Vens
89667ca4e7 Offer Save Loadout only for a displayed ship (Cut 10 fix)
The option was added for any displayed entity, stations included, and a null
displayed entity threw when chosen. It is now added only when the displayed
entity is a Ship, the same conditional shape as the Restore Loadout guard.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 22:29:58 +02:00
Meta Vens
61fc863e6b Stop loadouts from saving or trusting dangling weapon-group indices (Cut 10 fix)
Entity.TryUnequip does not clear WeaponGroups, so Capture saved -1 for a
grouped item that was no longer equipped, and Materialize later threw
IndexOutOfRangeException after every other check passed. Capture now skips
group items that are not equipped. Materialize reports every weapon-group index
outside the loadout's slots as a failure before building anything, so a stale
or hand-edited record builds nothing and charges nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 22:28:24 +02:00
Meta Vens
5e5c304e37 Make loadout availability a port and move the charge into Materialize (Cut 10 fix)
Materialize takes isAvailable as a Predicate<FactionProductData>; the restore
menu passes LoadoutGenerator.IsAvailable for the docked station, so generation
and loadouts still share one availability rule. Candidate products are ordered
by record key, so "first available product" no longer depends on dictionary
order. Materialize charges Price through ref credits only when it returns a
ship; RestoreLoadout no longer subtracts on its own.

Tests: a slot that resolves but does not fit builds nothing, charges nothing and
is listed; Price is the sum of design prices and is charged on success; the
first available product in key order builds a slot, a later one is used when
the first is unavailable, and none available fails the slot.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 22:27:03 +02:00
Meta Vens
453ee9fba2 Mark the CultCache migration target done (Cut 10)
docs/cultcache-migration-target.md's status names the Aetheria range: Cut 8a
(4f4beb0f) through Cut 10 (4a545469 to this commit) on
codex/cultcache-cutover. The cut map's status header is left for the root
pass that records Cuts 9 and 10.

Builds: docs only.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 22:09:39 +02:00
Meta Vens
1951b39a95 Restore the schematic-shape drawer's texture, aspect and hardpoint tints (Cut 10)
On Studio 1.3.1, drawers are handed the owning record read-only as
CultInspector.Record. InspectableSchematicShapeDrawer reads the item from it
and restores what the Database Tools drawer did (d3db1730,
AetheriaInspectors.cs ShapeSchematicInspector):
- the item's schematic texture drawn under the toggle grid;
- the height derived from the texture's aspect when a texture exists;
- hull hardpoint cells tinted with HardpointData.TintColor.

The drawer never writes to the record. The comment saying these waited on
the Studio exposing the open record is gone.

Verified by the Unity batchmode compile (no error CS). The visual check is
in the operator's Studio click-through.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 22:09:38 +02:00
Meta Vens
65a007e509 Seed zones from a stable name hash instead of string.GetHashCode (Cut 10)
Closes the 8a follow-up. string.GetHashCode is stable on Mono but randomized
per process on .NET Core, so a headless sim that generates zones would get
different zones on every run.

- Extensions.StableHash folds a string's UTF-8 bytes through CultMath's pcg
  from the FNV-1a offset basis. No new CultMath API.
- ZoneGenerator seeds from `Name.StableHash() ^ pcg3d(Position).x`.
- Zone seeds from `Name.StableHash() ?? 1337u`. CultMath's Random maps
  seed 0 to its default, and the abs/Convert.ToUInt32 pair that threw on
  int.MinValue is gone.

Old runs are discarded, so a different galaxy per seed costs nothing.

Tests: StableHashIsNotProcessRandomized pins "Adrasteia" to 2427892944, the
value an independent Add-Type transcription of CultMath's pcg produced. It
fails with GetHashCode substituted back in.

Builds: headless, 15 tests pass, Unity batchmode compile reports no error CS.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 22:09:37 +02:00
Meta Vens
00f62acc34 Make loadouts player-store design documents built all-or-nothing (Cut 10)
Loadout.cs owns loadouts: the document, capture, save and materialization.

- Loadout ("aetheria.loadout", player store) holds a hull design ref, and
  per slot a hull cell, a rotation and an EquippableItemData design ref,
  plus weapon groups as slot indices. It names no product, manufacturer,
  unit roll, cargo, faction, position or behaviour state.
- Loadouts.Capture records the hull design and one slot per equipped item
  (gear, cargo bays, docking bays). Entity.Equipment also holds the hull's
  own unit (MapEntity adds EquippedHull); that is Loadout.Hull, not a slot.
- Loadouts.Save is the only writer: one Commit to the player store, reusing
  the existing key for the name.
- Loadouts.Materialize is the only builder of a ship from a loadout. For
  the hull and each slot in order it resolves the design and takes the
  first product in catalog order that LoadoutGenerator.IsAvailable (now
  public) accepts, with no any-manufacturer fallback. Every failing slot is
  listed; units are built only after everything resolved, and any resolve
  or fit failure returns null with no ship, no charge and no write.
- Loadouts.Price is the hull design's price plus each slot design's price.
- InventoryPanel: Save Loadout asks for a name (default: the ship's name)
  and saves the capture. Restore Loadout, shown when docked with saved
  loadouts, lists each with its price, debits credits only after
  Materialize succeeds, then docks the ship as before; a failure opens
  "Loadout cannot be built here" with one line per failing slot.

Tests: new LoadoutTests (3): LoadoutRoundTripsThroughAFreshCache,
LoadoutHoldsNoRunOrGalaxyRefs, MissingDesignReports. Each fails with its
rule mutated out. The fixture hull is 3x3, not the spec's 2x2:
HullData.InteriorCells is Shape.Shrink(), so a 2x2 hull has no interior
cell a cargo bay can occupy.

Builds: headless, 14 tests pass, Unity batchmode compile reports no error CS.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 22:06:06 +02:00
Meta Vens
95e164adce Land RunSave and the one-cache run and player lifecycle (Cut 10)
Owner: ActionGameManager composes the process's one cache. Its CultCache
getter attaches the catalog, GameData/run.cc and GameData/player.cc on
first access, in either scene, and nothing reopens it.

- RunSave (SavedGame.cs) replaces the SavedGame constructor:
  - Capture builds the live run as plain documents and writes nothing. Zone
    contents are `zone.Contents?.PackZone() ?? zone.PackedContents`, so a
    zone generated earlier but not loaded this session keeps its contents
    and its orbit and body records instead of being erased by the next save.
  - Commit is the only writer of SavedGame and SavedZone: one Commit with
    zone i at `savedzone-{i}`, SavedGame.Zones set to those refs, and every
    stored SavedZone outside that set removed. Orbits and bodies staged by
    zone generation land in the same single-file write.
  - Clear removes every run record in one Commit.
- Run begins: New Game's first statement is RunSave.Clear. Run saves:
  ActionGameManager.SaveRun at wormhole arrival and quit. Run ends: Die
  clears the run, then saves settings.
- Run exists only as the run store's SavedGame global: MainMenu enables
  Continue from it (and logs when the run store holds records but no
  SavedGame), and StartGame reads it once to pick the new-run or resume
  branch.
- PlayerSettings: the getter returns the player store's global and, on
  first launch, commits GetDefaultPlayerSettings; it is the only creator.
  SavePlayerSettings commits the instance.
- InputDisplayLayout reads its InputLayout from the catalog by the layout
  asset's name and throws when the catalog has none.

Tests: new RunSaveTests (5): RepeatedSavesKeepRecordCountConstant,
SaveRemovesZonesNoLongerInTheRun, SaveWritesOnlyTheRunStore,
ResumeThenSaveKeepsUnloadedZoneContents, ClearRemovesEveryRunRecord. Each
fails with its rule mutated out.

Builds: headless, 11 tests pass, Unity batchmode compile reports no error CS.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 22:00:27 +02:00