40 KiB
Settings Into CultCache Globals: Cut Map
Date: 2026-09-17
Status: Imagination pass, cut map. Nothing here has landed. Anchors are against
codex/item-provenance HEAD 00ebe46a (which carries the separate mouse-sensitivity
move). Claims marked (probe) were run, not read off names:
- a Python walk of every
MonoBehaviour/ScriptableObjectfield graph inAssets/Scripts(attribute-prefixed fields included), and of the scene, prefab and asset YAML; - a Python parse of
Settings.assetmapped key by key onto the C# classes; - a headless build of
tools/AetherDbin a sparse scratch worktree of00ebe46a, with CultLib ata0813c6as a detached scratch worktree. Both were removed afterwards, and the main tree got nobin/objwrites. The probe sources were scratch-only and are not kept; each result below says what it ran.
Operator ruling, 2026-09-17: "We don't care for Unity's serializer, we have CultCache sitting right there. All settings should be in globals."
Rulings (2026-09-17):
- Engine asset references: Addressables, stored as asset GUIDs (operator: "you got it",
to that proposal). This supersedes forks I and B: nothing moves under
Resources, and the existing[CultInspectorAssetPath]fields (ItemData.Icon/ActionBarIcon,Weaponeffect,Thruster/AetherDriveparticles,Corporationslogo) convert too, so there is one rule. That conversion is its own cut, mapped indocs/addressables-cut.md, and lands before Cut 1; Cut 1's icon and body-preset fields then use it. - K (a), D (a), P (a), X (a), M (a): Self's defaults, stated to the operator, who did not object. Reopen any of them on request.
- Open: none blocking Cut 0.
Doc sweep for Self: this map supersedes the mechanism in
docs/headless-playground-cut.mdfork S ("a text edit ofSettings.asset",:1151-1159,:404-410,:693-694). It also changes theRun.New(CultCache, GameplaySettings, ZoneGenerationSettings, PlanetSettings, ...)signature (:415), and that map's use ofAuthoredSettings(:176,:224) goes away. Once fork P is ruled, those settings come from the cache.
0. Target
Ends:
- Every authored setting lives in a CultCache global. The catalog holds design data and the player store holds preferences. Unity's serializer holds no setting.
- One owner per setting. The Unity game, the headless sim, the tests and
AetherDbread the same record through the same port. No reader rebuilds the values by hand, and none parses them out of YAML. - Nothing authored is lost, and no authored value loads silently as zero.
Invariants:
- Catalog globals are authored, never invented.
AetheriaStores.Openkeeps refusing a populated catalog that is missing one, however it is opened (AetheriaStores.cs:14-15, ruling behindab4ced0f). - A catalog global is never written at runtime. Runtime variations are copies owned by the run or the entity.
- One home store per type, and a commit lands in one store.
Not settings (the census below gives the evidence):
- Engine configuration:
ProjectSettings/*,Assets/Resources/InputSystem.inputsettings.asset. - Per-component presentation tuning on scene and prefab instances (fork M).
1. Census
1.1 GameSettings / Settings.asset, field by field
GameSettings (Assets/Scripts/Gameplay/GameSettings.cs:11) is the only
ScriptableObject settings type. It is referenced from 15 YAML fields:
Assets/Scenes/ARPG.unity x12, Assets/Scenes/Main Menu.unity x2 and
Assets/Prefabs/UI/Main Menu Canvas.prefab x1. The asset guid is
cd2344e2001a37544aab0960c164d4ff.
(probe) YAML-to-class mapping: every key under the MonoBehaviour maps to a field,
and no key is unplaced. The asset holds 266 scalar leaves, 15 float3 components, 16
Color components, 58 Gradient keys and 53 object references. One field is missing
from the YAML: ZoneGenerationSettings.NeutralWandererCount, which Unity loads as its
initializer, 2. The existing AetherDb settings command agrees: 0 unplaced, 0 missing.
Classes: C is a catalog global (design data). CP is a catalog global holding
presentation data: authored and not a preference, but only Unity reads it. E is an
engine asset reference (forks I and B). Dead means no code reads it (checked with
rg over Assets/Scripts, tools and tests).
Field (GameSettings.cs line) |
Type | Authored (Settings.asset line) |
Readers (HEAD) | Class |
|---|---|---|---|---|
DefaultOverworldSoundbank, DefaultCombatSoundbank, DefaultBossSoundbank, AmbienceSoundBank (13-16) |
string | :15-18 |
none | Dead (fork X) |
StartingHullName (17) |
string | LonginusX (the code default is Longinus; LonginusX is in the catalog) |
ActionGameManager.cs:776 |
C |
PickupLifetime (18) |
float | 30 | ZoneRenderer.cs:572 |
C |
LootDropProbability, LootDropVelocity (19-20) |
float | .25, 25 | EntityInstance.cs:413,417,428 |
C |
HeatstrokePhasingFloor, HeatstrokePhasingFrequency (21-22) |
float | 0, 5 | ActionGameManager.cs:1236 |
CP |
WormholeDistanceRatio (23) |
float | .75 | ZoneRenderer.cs:230 (a game rule, per the headless map) |
C |
DefaultViewDistance, MinimapZoneGravityRange (24-25) |
float | 4096, .45 | ZoneRenderer.cs:178,526 |
CP |
MinimapZoomLevels, DefaultMinimapZoom (26-27) |
float[], int | [250,500,1000,2000,4000], 3 | ActionGameManager.cs:280-284, ZoneRenderer.cs:179 |
CP |
IconSize (28) |
ExponentialCurve |
.15/10/25 | ZoneRenderer.cs:412 |
CP |
AsteroidMeshCount, MinimapAsteroidSize, MinimapIconSize, PlanetRotationSpeed (29-32) |
int, float | 4, 3, .125, .1 | ZoneRenderer.cs:342,361,147,497 |
CP |
NameGeneratorSettings (33) |
ServerShared | :43-46 |
MainMenu.cs:132,158 -> Galaxy |
C |
TutorialGenerationSettings (34) |
ServerShared | :47-56 |
MainMenu.cs:156, ActionGameManager.cs:772 |
C |
TutorialBackgroundSettings, SectorBackgroundSettings (35-36) |
ServerShared | :57-74 (NoisePosition at :63,72) |
MainMenu.cs:125-157 (writes, see 1.4) |
C |
SectorGenerationSettings (37) |
ServerShared | :75-79 |
MainMenu.cs:130 |
C |
BodySettingsCollections (38) |
{float MinimumMass; CelestialBodySettings[]} |
:80-99: 6 collections, 7 refs, all under Assets/Plugins/Celestial Body/Solar System/ |
ZoneRenderer.cs:397 |
E (fork B) |
PlanetSettings (39) |
ServerShared | :100-155 |
ActionGameManager.cs:690 -> Zone, ZoneRenderer.cs:391-536 |
C |
ZoneSettings (40) |
ZoneGenerationSettings |
:156-217 |
ActionGameManager.cs:685, SectorRenderer.cs:71-72 |
C |
GameplaySettings (41) |
ServerShared | :218-326 |
ItemManager (ctor at ActionGameManager.cs:263), plus Unity readers listed in Cut 1 |
C |
HullHitColor (42) |
Color | :327 |
none | Dead (fork X) |
ArmorHitColor, HardpointHitColor, GearHitColor (43-45) |
Color | :328-330, every a=1 |
InventoryPanel.cs:497-499 |
CP -> float3 |
ArmorGradient, DurabilityGradient (46-47) |
Gradient | :331-390 |
InventoryPanel.cs:790,792 |
CP -> float4[] |
DefaultEnvironment (48) |
ZoneEnvironment |
:391-425 |
ActionGameManager.cs:203, VolumeSampling.cs:54 |
C |
ItemIcons, WeaponTypeIcons, WeaponCaliberIcons, WeaponRangeIcons, WeaponFireTypeIcons, WeaponModifierIcons (49-54) |
Sprite[] | :426-477 |
GetIcon/GetIcons (:56-84) from ActionBarSlot.cs:155-156, SchematicListElement.cs:27-44; edited by Editor/GameplaySettingsEditor.cs |
E (fork I) |
(probe) Icon references, resolved guid to path through every .meta under
Assets:
- 33 resolve under
Assets/Resources/Sprites/Icons{,/Weapons,/Tech}. - 3 are intentional
{fileID: 0}slots:ItemIcons[13],WeaponFireTypeIcons[0]andWeaponModifierIcons[0](the None slot). - 9 point at assets that do not exist:
WeaponTypeIcons[1](dc8c6f7a…),WeaponTypeIcons[9](9ae7f8b4…) and every realWeaponModifierIcons[1..7]. This bug predates the cut. It is recorded here, not fixed, and not invented over.
Gradients convert without loss (read, not built). Both gradients are m_Mode: 0
(Blend) with m_NumAlphaKeys: 2, and alpha is 1 at 0 and 1 at 65535. That is exactly
what the existing UnityExtensions.ToGradient(float4[], sharp: false)
(CultCache/UnityExtensions.cs:29-36) rebuilds. Color key time is a ushort, so w = ctime/65535f goes back to the same ushort. The values are:
ArmorGradient:(.2244,.66,.2244, 0),(1,1,1, 1).DurabilityGradient:(.7264151,0,0, 0),(1,1,.19999999, 29613/65535),(.2244,.66,.2244, 1).
1.2 Other Assets/Resources/*.asset
(probe) None of these five has a live script. For each, the m_Script guid matches
no .meta in Assets or Library/PackageCache, and no scene, prefab, material, asset
or .cs references the asset's own guid. git log -S on the script guids finds where
each script was deleted:
| Asset | Script deleted | What it held | Class |
|---|---|---|---|
Atmosphere Settings.asset |
AtmosphereConfig.cs, d61d2f19 "Raymarching Cleanup" (2022-07-13) |
atmosphere scattering constants | dead presentation |
Volume Settings.asset |
the clouds renderer, d61d2f19 |
cloud texture refs and tiling | dead presentation |
Elysium.asset, Test Galaxy.asset |
Galaxy.cs and GalaxyEditor.cs, 0f33e541 "Sector Constellations" (2021-03-11) |
the old galaxy generator's MapData (arms, twist, masses) |
dead content (fork X) |
Icons.asset |
UI/Icons.cs, 81acaf2a (2021-05-08) |
plus and minus textures | dead presentation |
InputSystem.inputsettings.asset is engine configuration and stays.
1.3 CultMath fields Unity serializes (these load as zero)
(probe) A walk of the field graph from every MonoBehaviour/ScriptableObject
through [Serializable] types, looking for CultMath floatN/intN/boolN/quaternion
leaves, finds these. A Vector3 or Vector2 field is not affected: the inline
{x: …} YAML values belong to those.
| Field | Stored data | Loss |
|---|---|---|
ActionGameManager.Sensitivity (float2, cf60fd07 :101) |
ARPG.unity:16548-16550 (-.001, .001) |
Real. Fixed separately in 00ebe46a (PlayerInputSettings.Sensitivity, PlayerSettings.cs:76). The scene key is now stale. |
GameSettings.GameplaySettings.Tiers[].Color (RarityTier.Color, Settings.cs:226) |
Settings.asset:221-250: 5 tiers |
Real. Rarity colours render black in TradeMenu.cs:193, PropertiesPanel.cs:436 and ZoneRenderer.cs:567. Cut 1 recovers them. |
MapRenderer.Position (float2, MapRenderer.cs:30) |
ARPG.unity:10236-10238 (0, 0) |
None. The stored value was zero, and the field is runtime pan state. |
FieldDriver.Push (float2, FieldDriver.cs:17) |
no key in FieldShieldTest.unity |
None. It is runtime state, set at :179. |
FieldShieldTest.unity:1047-1049 also holds a stale Throttle: (0,0) for a field that
was deleted. It is harmless. (probe) No [Serializable] type on a Unity-reachable
path lost the attribute during the migration: the YAML map above placed every key.
1.4 Writers into settings (forbidden once they are globals)
MainMenu.cs:125and:148assignSettings.{Sector,Tutorial}BackgroundSettings.NoisePosition = Random.value * 1000on the shared authored object. The authored values91.29439and536.5106(Settings.asset:72,63) look like play-mode residue from this writer. Run state already has a home:Galaxy.Backgroundis saved asSavedGame.Background(SavedGame.cs:37).- Entity settings aliasing.
Loadout.cs:131andLoadoutGenerator.cs:46,61,76passGameplaySettings.DefaultEntitySettingsitself intoEntity, andEntity.cs:340stores it without copying.InventoryMenu.cs:175then writesCurrentEntity.Settings.ShutdownPerformance. That write changes the default for every generated entity, and once settings are a global it changes the cached catalog record. Two other places copy:ActionGameManager.cs:228-232(NewEntitySettings, used byTradeMenu.cs:377) andEntitySerializer.cs:51-52. Three copies and four aliases make split authority.
1.5 Component tuning on scene and prefab instances
(probe) This lists every scalar, non-reference serialized field stored in .unity
and .prefab files for scripts in Assets/Scripts, grouped by script:
- Presentation timing, layout and VFX: 45 scripts. Examples:
ShieldAnimationcurves,ConstantLaser/Laser/HitscanEffectcurves,InputDisplayLayoutcolours,SectorMap,ZoneRenderertour,TextButton,PropertiesList,MenuPaneltab colours,ItemPickuplabel fades. - Behaviour on prefabs that affects play:
Projectile(Gravity,Drag,DirectHitDamageMultiplier),GuidedProjectile(Thrust,TopSpeed, split and airburst),Mine(Lifetime,BlastRange,ActivationDelay),TractorBeam,GridObject. These are per-prefab instance data.docs/headless-playground-cut.mdalready moves simulation ownership of hits, loot and fields. - Run state misfiled as a component field:
ActionGameManager.Credits, 100000000 inARPG.unityagainst a code default of 15000000. It is the player's wallet (TradeMenu.cs:344-385). It is not saved, so it resets every scene load.
Fork M decides whether any of this counts as "settings".
2. Global shape
Superseded in part (2026-09-17): wherever this map stores an engine asset as an
Assets/Resources/...path or loads it withUnityHelpers.LoadAsset/Resources.Load, read instead: the asset's GUID under[CultInspectorAssetGuid], loaded withEngineAssets.Load<T>, the asset living underAssets/Content(docs/addressables-cut.md, which lands first). That coversItemIcons/WeaponTypeIcons/... (2.1), the 3.2 import's{fileID, guid}resolution (store the guid directly, no path), the body presets (the 7CelestialBodySettingsassets move toAssets/Content, notResources), and Cut 1's picker check. Forks I and B are history.
2.1 Document
This is fork D, recommended (a). One catalog global in a new file,
Assets/Scripts/ServerShared/GameSettings.cs. The name is freed by deleting the Unity
ScriptableObject. Both classes cannot coexist: Assembly-CSharp would see two global
GameSettings types (CS0433). So the type change and the switch of every reader must be
one commit.
[CultDocument("aetheria.gamesettings", "1"), CultGlobal, MessagePackObject]
public class GameSettings
{
// Simulation and generation
[Key(0)] public GameplaySettings GameplaySettings;
[Key(1)] public PlanetSettings PlanetSettings;
[Key(2)] public ZoneGenerationSettings ZoneSettings;
[Key(3)] public SectorGenerationSettings SectorGenerationSettings;
[Key(4)] public TutorialGenerationSettings TutorialGenerationSettings;
[Key(5)] public NameGeneratorSettings NameGeneratorSettings;
[Key(6)] public SectorBackgroundSettings SectorBackgroundSettings;
[Key(7)] public SectorBackgroundSettings TutorialBackgroundSettings;
[Key(8)] public ZoneEnvironment DefaultEnvironment;
[Key(9)] public string StartingHullName;
[Key(10)] public float PickupLifetime;
[Key(11)] public float LootDropProbability;
[Key(12)] public float LootDropVelocity;
[Key(13)] public float WormholeDistanceRatio;
// Presentation (authored; read only by Unity)
[Key(14)] public float HeatstrokePhasingFloor;
[Key(15)] public float HeatstrokePhasingFrequency;
[Key(16)] public float DefaultViewDistance;
[Key(17)] public float MinimapZoneGravityRange;
[Key(18)] public float[] MinimapZoomLevels;
[Key(19)] public int DefaultMinimapZoom;
[Key(20)] public ExponentialCurve IconSize;
[Key(21)] public int AsteroidMeshCount;
[Key(22)] public float MinimapAsteroidSize;
[Key(23)] public float MinimapIconSize;
[Key(24)] public float PlanetRotationSpeed;
[Key(25)] public float3 ArmorHitColor;
[Key(26)] public float3 HardpointHitColor;
[Key(27)] public float3 GearHitColor;
[Key(28)] public float4[] ArmorGradient; // xyz colour, w time; lowered by ToGradient()
[Key(29)] public float4[] DurabilityGradient;
// Engine asset paths (forks I and B, recommended (a)): "Assets/Resources/..." like ItemData.Icon
[Key(30)] public string[] ItemIcons; // [CultInspectorAssetPath] on each, indexed by enum as today
[Key(31)] public string[] WeaponTypeIcons;
[Key(32)] public string[] WeaponCaliberIcons;
[Key(33)] public string[] WeaponRangeIcons;
[Key(34)] public string[] WeaponFireTypeIcons;
[Key(35)] public string[] WeaponModifierIcons;
[Key(36)] public BodySettingsCollection[] BodySettingsCollections; // { [Key(0)] float MinimumMass; [Key(1)] string[] BodySettings }
}
(probe) A top-level Cult document must put an integer [Key] on every public member.
The CultLib contract check threw …member GameplaySettings has no [Key] on a
keyAsPropertyName probe document. Nested [MessagePackObject(keyAsPropertyName: true)]
classes are accepted as they are. A probe global with [Key(0)] GameplaySettings, filled
from the asset, wrote to a catalog and read back read-only with all 5 tiers. Legendary
came back (1, .4, 0), and a re-serialize was byte-identical (1841 bytes). The existing
settings classes are kept unchanged.
StartingHullName, LootDrop* and WormholeDistanceRatio stay top-level. Headless
fork S wanted them inside GameplaySettings so there would be one reader. Once
everything reads one global, the nesting buys nothing.
2.2 Readers
This is fork P, recommended (a).
- Simulation.
ItemManageralready holds the catalog cache, so it owns the read.ItemManager(CultCache itemData, ProvenanceLedger lots, Action<string> logger)setsSettings = itemData.GetGlobal<GameSettings>() ?? throw …. It exposesGameSettings SettingsandGameplaySettings GameplaySettings => Settings.GameplaySettings, so the 55GameplaySettingsreads in ServerShared do not change.Zonedrops itsPlanetSettingsparameter (Zone.cs:46) and readsitemManager.Settings.PlanetSettings. - Unity before a run (
MainMenu,VolumeSampling): one static,ActionGameManager.GameSettings => CultCache.GetGlobal<GameSettings>(), on the same composition asActionGameManager.PlayerSettings(:66). Inside a run the same object is also reachable asItemManager.Settings. Both paths read the one record, so they cannot disagree. - Icons.
GetIcon/GetIconsmove to Unity-side extension methods onGameSettingsinCultCache/UnityExtensions.cs. They take the path throughUnityHelpers.LoadAsset<Sprite>and cache the sprite by path. An empty path returns null, the same as today's missing sprite. - Tests and
AetherDb. Nonew GameplaySettingsremains outside one test seeding helper.AetherDb loadoutreads the real catalog's global. Test fixtures seed aGameSettingsinto their temp catalogs, which they must do anyway:AetheriaStoresrefuses a populated catalog without one, writable or not (probe).
3. Data migration
3.1 Bootstrap of the first catalog global
(probe) The catalog cannot gain its first global through AetheriaStores:
- Open
GameData/Aetheria.cc(a copy) with a build whoseGameSettingstype is[CultGlobal]and routed to the catalog. The writable open threw…has no aetheria.probe.settings record; catalog globals are authored, never invented., and so did the read-only one. This is the deliberateab4ced0frule. - With
[CultGlobal]removed from the type, a scratch build opened the same copy writable. It upserted with an explicit key,UpsertAsync(typeof(T), doc, new CultRecordKey("global:aetheria.probe.settings")), and flushed. - The build with
[CultGlobal]restored then opened that file read-only and returned the record fromGetGlobal, under keyglobal:aetheria.probe.settings, with the colours intact. CultCache classifies a global by descriptor when it hydrates (CultCache.cs:1854), not by key. The explicit key matches the one a global write mints (:1540). - Record diff between the original and the written copy: 180 records before, 181 after; 0 removed, 0 changed (a SHA-256 of each record's MessagePack), and 1 added. The file grew by 2501 bytes.
This is fork K, recommended (a): the authoring build exists only in scratch, and the landed type differs from it by the one attribute.
3.2 Steps (Cut 1, First)
- Close Unity;
GameData/Aetheria.cc.lockmust be free. Capture the "before" state:dotnet run --project tools/AetherDb -p:CultLibRoot=<a0813c6 worktree> -- settings > before-settings.txt(the method:dotnet runstdout, redirected by Git Bash, no BOM);- a per-record SHA-256 dump of
Aetheria.ccthroughAetheriaStores.Open(read-only works before the type exists).
- Create a scratch worktree of the Cut 0 landing commit and a CultLib worktree at
a0813c6. AddGameSettings.csas in 2.1 without[CultGlobal], and add it toCatalogTypes. Add a scratchimport-settingscommand overAuthoredSettingsthat fills the document through the catalog's own types:- existing classes by the current
Read<T>(subtree); Color{r,g,b,a}tofloat3, refusing whena != 1;Gradienttofloat4[]fromkey{i}/ctime{i}fori < m_NumColorKeys, refusing unlessm_Mode == 0and every alpha key is 1;{fileID, guid}to anAssets/...path through a.metaguid index.{fileID: 0}and unresolved guids become"". Every unresolved one is printed, and the run fails unless the printed set equals the 9 in 1.1.BodySettingsCollectionsper fork B.float.Parse(…, CultureInfo.InvariantCulture), throwing on failure. Today'sTryParse(...) ? f : 0f(AuthoredSettings.cs:131) is the silent-zero shape this cut exists to kill. The probe host culture wasen-150.- Write with key
global:aetheria.gamesettingsinto the realGameData/Aetheria.cc.
- existing classes by the current
- Value check, in the same scratch build:
- Reopen the file and walk the YAML again. Every scalar leaf (266, less the 4
soundbank strings Cut 0 deletes), every
float3component (15) and every live colour component (9) must equal the document value by path (==on the invariant parse). Every gradient key and time must match, and every icon and body path must match its guid's.metapath. - Print the counts compared. Exit non-zero on any mismatch or unplaced key.
NeutralWandererCount == 2(absent from YAML, carried by the initializer).
- Reopen the file and walk the YAML again. Every scalar leaf (266, less the 4
soundbank strings Cut 0 deletes), every
- Record diff (
beforeagainstafter): exactly one record added,global:aetheria.gamesettings; 0 changed; 0 removed. - Copy the written
Aetheria.ccinto the main tree. It lands in the Cut 1 commit together with the type (with[CultGlobal]). Remove the scratch worktrees.
4. Cuts
Cut 0. Cut the settings writers and the dead settings
- Repo/branch: Aetheria
codex/item-provenancefrom00ebe46a. No dependencies. Unity is closed for the asset edits. - First:
rg -n "Settings\.\w*BackgroundSettings\.NoisePosition" Assets/Scriptsreturns 2 hits at HEAD. Rundotnet test tests/Aetheria.Shared.Testsfor a baseline. - Deletes first:
GameSettings.cs:13-16(soundbanks) and:42(HullHitColor);Settings.asset:15-18,327.ActionGameManager.cs:228-232(NewEntitySettings).EntitySerializer.cs:51-52(the copy;pack.Settings ??= itemManager.GameplaySettings.DefaultEntitySettings).- Fork X (a):
Assets/Resources/{Atmosphere Settings,Volume Settings,Elysium,Test Galaxy,Icons}.assetand their.metafiles.
- Per-file changes:
Entity.cs:340:Settings = MessagePackSerializer.Deserialize<EntitySettings>(MessagePackSerializer.Serialize(settings)). The entity owns its copy, so every construction path is safe, includingLoadout.cs:131,LoadoutGenerator.cs:46,61,76,EntitySerializer.UnpackandTradeMenu.cs:377.TradeMenu.cs:377: passGameManager.ItemManager.GameplaySettings.DefaultEntitySettings.MainMenu.cs:125-157: take a run-owned copy of the background (the same MessagePack round trip), setNoisePositionon the copy, search the copy (:146-150), and pass the copy toGalaxy. The authored object is never assigned.
- Authority map:
- Owner:
Entityowns itsEntitySettingsinstance.Galaxy(and soSavedGame) owns the run's background position. - Inputs: the authored defaults, read only.
- Outputs: per-entity and per-run copies.
- Derived state:
DefaultEntitySettingsand*BackgroundSettingsare template-only. - Forbidden writers:
InventoryMenu.cs:175may write only the entity's own copy.MainMenunever writes the settings. - Shared paths: new ship (
LoadoutGenerator), preset (Loadout.Build), bought ship (TradeMenu) and Continue (EntitySerializer.Unpack) all go through theEntityctor. - Deletion line:
NewEntitySettingsand the serializer copy are gone before the ctor copy lands.
- Owner:
- Verification:
- builds:
Aetheria.Shared,tests/Aetheria.Shared.Tests(-p:CultLibRoot=); Unity batchmode compile. - tests:
EntitiesDoNotShareDefaultEntitySettings(LoadoutTests fixture) pins "no entity aliases the template". Generate two ships, writeShutdownPerformanceon one, then assert that the other andGameplaySettings.DefaultEntitySettingsare unchanged. It fails whenEntity.cs:340goes back toSettings = settings. - negative:
rg -n "Settings\.\w*BackgroundSettings\.NoisePosition" Assets/Scriptsreturns 0.rg -n "NewEntitySettings|Soundbank|SoundBank|HullHitColor" Assets/Scriptsreturns 0 (at HEAD it hits only the lines deleted here).git ls-files "Assets/Resources/*.asset"lists onlySettings.assetandInputSystem.inputsettings.asset.
- operator: open the Inventory ship settings and change Shutdown Threshold. A generated NPC ship's threshold must not move.
- builds:
Cut 1. Move the settings into the catalog global
- Repo/branch: Aetheria
codex/item-provenancefrom the Cut 0 landing. Depends on forks K, I, B, D and P. It is one commit, because the type rename forces the reader switch (2.1). - First: section 3.2, steps 1-4. The written
Aetheria.ccis in hand before any main-tree edit. - Deletes first:
Assets/Scripts/Gameplay/GameSettings.cs(+.meta;BodySettingsCollectionmoves per fork B).Assets/Resources/Settings.asset(+.meta).Assets/Scripts/Editor/GameplaySettingsEditor.cs(+.meta; 100 lines; Studio edits the record).- The 7 serialized
public GameSettings Settings;fields:ActionGameManager.cs:99,SchematicDisplay.cs:14,SchematicListElement.cs:9,MainMenu.cs:20,InventoryPanel.cs:26,VolumeSampling.cs:22,ZoneRenderer.cs:27. Also their 15 YAML reference lines (rg -n "cd2344e2001a37544aab0960c164d4ff" Assets -g "*.unity" -g "*.prefab"), text-edited with Unity closed, and the staleSensitivityblock atARPG.unity:16548-16550. tools/AetherDb/AuthoredSettings.cs(174 lines);Program.cs:30-31,33(thesettingsandsettings-dumpcommands and help text) and:174-228; theYamlDotNetreference (AetherDb.csproj:16);Program.cs:437-442(the hand-built settings).ItemManager.cs:28,32(the settings parameter);Zone.cs:46(thePlanetSettingsparameter).- Tests:
RunSaveTests.cs:313-319andIffAndCombatTests.cs:62-70(TestSettingscopies);AetheriaStoresTests.cs:11-14(TestCatalogGlobal, the stand-in for "the game has none yet").
- Adds:
ServerShared/GameSettings.cs(2.1).AetheriaStores.cs:9: addtypeof(GameSettings)toCatalogTypes.- The icon extensions in
CultCache/UnityExtensions.cs(2.2). - One test seeding helper. It builds
GameSettings { GameplaySettings = {DefaultEntitySettings, one Common tier, QualityPriceModifier}, PlanetSettings = new() }with an optional tweak, and each fixture upserts it where it seeds its temp catalog:LoadoutTests.cs:27,IffAndCombatTests.cs:23-24,AetheriaStoresTests.cs:27-28, andRunSaveTests.cs:23. LoadoutTests.StatsReadTheLot(:495-503) seeds its two tiers through the helper instead of mutating a live settings object.IffAndCombatTestsseedsTargetDetectionInfoThreshold = .5fthe same way.GameData/Aetheria.cc: +1 record.
- Per-file changes (reader switch; the member paths after
Settings.do not change):ActionGameManager.cs:203,280-284,313,671,685,772,776,847,1208-1292useItemManager.Settingsinside the run.:230goes with Cut 0.:263becomesnew ItemManager(CultCache, RunSave.Lots(CultCache), Debug.Log).:690becomesnew Zone(ItemManager, galaxyZone.PackedContents, …). Add the staticGameSettingsaccessor next toPlayerSettings(:66).ZoneRenderer.cs:147-572(22 lines) useItemManager.Settings(ZoneRenderer.ItemManager,:95).:397follows fork B.:497and:412are unchanged apart from the source.EntityInstance.cs:413,417,428:ZoneRenderer.Settings.LootDrop*becomesEntity.ItemManager.Settings.LootDrop*.MainMenu.cs:125-158andVolumeSampling.cs:54useActionGameManager.GameSettings.InventoryPanel.cs:497-499:.ToColor()on thefloat3.:790,792: buildToGradient()once per panel and keep it, instead of one gradient per call.SchematicDisplay.cs:169,SectorRenderer.cs:71-72,WeaponGroupAssignment.cs:25,ActionBarSlot.cs:155-156andSchematicListElement.cs:27-44read throughActionGameManager.Instance.ItemManager.Settings.Zone.cs:46-…:Settings = itemManager.Settings.PlanetSettings. The callers areIffAndCombatTests.cs:58andRunSaveTests.cs:93.tools/AetherDb/Program.cs:444:new ItemManager(db.Cache, new ProvenanceLedger(), log.Add). The loadout command now runs on the authored tiers and price curve.AetheriaStoresTests: the refusal tests (includingWritablePopulatedCatalogMissingGlobalIsLoud) remove and expectGameSettingsinstead ofTestCatalogGlobal.
- Authority map:
- Owner: the catalog record
global:aetheria.gamesettings, authored in CultCache Studio. - Inputs: none at runtime. It is authored data.
- Outputs:
ItemManager.Settings(sim, in-run Unity) andActionGameManager.GameSettings(pre-run Unity). - Derived state: Unity
Gradient,ColorandSpriteobjects are lowering-only, built from the record. Entity and run copies come from Cut 0. - Forbidden writers: any runtime assignment into the record. The Unity serializer
(asset deleted).
GameSettingsEditor.AuthoredSettingsand YAML. Hand-builtGameplaySettingsinAetherDb. - Shared paths: new game, Continue, the tutorial, the headless tests and
AetherDball constructItemManagerover a cache holding the record, so they all read the same one. - Deletion line: the
ScriptableObject, the asset, its editor, the YAML reader and the settings constructor parameters are gone in the same commit that adds the global.
- Owner: the catalog record
- Verification:
- builds:
Aetheria.Shared;tools/AetherDb;tests/Aetheria.Shared.Tests(-p:CultLibRoot=ata0813c6,--artifacts-pathin scratch); Unity batchmode compile (Assembly-CSharp and Editor). - data: the 3.2 value check prints its compared counts with 0 mismatches, and the record diff is +1/0/0.
- tests:
ItemManagerReadsTiersFromTheCatalogGlobal: two temp catalogs whoseGameSettingsdiffer only inTiersgive differentGetTierfor the same lot. Pins one owner. It cannot be written against a constructor that takes settings.ItemManagerRefusesACacheWithoutGameSettings: a bareCultCachemakes the constructor throw. Pins loud absence, never defaults.MissingCatalogGlobalIsLoud,WritablePopulatedCatalogMissingGlobalIsLoudandGameShapedOpenMissingGlobalIsLoud(AetheriaStoresTests.cs:116-143, retargeted). Pins "authored, never invented".ShippedCatalogSettingsAreNotZero: opensGameData/Aetheria.ccread-only (root found as inAetherDb.FindRoot) and asserts everyRarityTier.Colorhas a nonzero component, the hit colours are nonzero, each gradient has at least 2 keys,MinimapZoomLevelsis non-empty, andPlanetSettings.GravityStrength > 0. Pins "no authored value silently loads as zero" (this cut's triggering defect). Exact values belong to the one-time 3.2 check, not to a test that future authoring would break.
- negative (hit counts at
00ebe46ain brackets; each must be 0 after):rg -n "\bGameSettings Settings\b" Assets/Scripts[7]rg -l "0d957fc3cdc9310499a3a494a317fe0a|cd2344e2001a37544aab0960c164d4ff" Assets -g "*.unity" -g "*.prefab" -g "*.asset"[4]rg -n "AuthoredSettings|YamlDotNet" tools -g "!**/obj/**" -g "!**/bin/**"[9]rg -n "Settings\.asset" -g "*.cs" .[2]rg -n "TestCatalogGlobal" tests[7]rg -n "new GameplaySettings\b" tools Assets/Scripts[1]. Intests, exactly 1 (the helper) [2 now].rg -n "ScriptableObject" Assets/Scripts/ServerSharedmust stay 0.
- operator:
- Play smoke: main menu, then New Game (tutorial). The tutorial galaxy generates, and the starting ship is LonginusX.
- In a station trade menu and the properties panel, item names show tier colours: Legendary orange, Uncommon green. None is black.
- The inventory shows armour and durability gradients, and hit flashes use the authored colours: armour white, hardpoint pale blue, gear pale red.
- The minimap cycles 5 zoom levels. Planets render with body presets. Loot drops and pickups expire.
- Action bar and schematic weapon icons appear. Modifier icons stay blank, as they are today (1.1).
- Continue after save.
- Studio click-through: the
aetheria.gamesettingsrecord opens, nested curves and tiers edit, icon fields show the asset-path picker, and the edit persists.
- builds:
- Operator questions: forks K, I, B, D and P (section 6).
5. Subtraction ledger (estimate)
| Cut | Removed | Added | Dependencies, formats, targets |
|---|---|---|---|
| 0 | ~12 C# lines; 5 dead fields; 5 orphan assets (~18 KB YAML); 1 dead copy path | ~12 C# lines; 1 test | none |
| 1 | GameSettings.cs 88, GameplaySettingsEditor.cs 100, AuthoredSettings.cs 174, Program.cs ~65, Settings.asset 477 YAML lines, 7 serialized fields plus 16 scene YAML lines, 2 TestSettings copies ~15, TestCatalogGlobal 4, 2 settings ctor parameters |
ServerShared/GameSettings.cs ~45, icon extensions ~30, test helper ~15, tests ~70, +1 catalog record (~2.5 KB) |
removes the YamlDotNet dependency and one Unity asset format (settings YAML); no targets added |
6. Operator forks (ordered by how much they block)
Superseded in part (2026-09-17): wherever this map stores an engine asset as an
Assets/Resources/...path or loads it withUnityHelpers.LoadAsset/Resources.Load, read instead: the asset's GUID under[CultInspectorAssetGuid], loaded withEngineAssets.Load<T>, the asset living underAssets/Content(docs/addressables-cut.md, which lands first). That coversItemIcons/WeaponTypeIcons/... (2.1), the 3.2 import's{fileID, guid}resolution (store the guid directly, no path), the body presets (the 7CelestialBodySettingsassets move toAssets/Content, notResources), and Cut 1's picker check. Forks I and B are history.
K. How the first catalog global gets written. Blocks Cut 1.
- (a) A scratch-only authoring build without
[CultGlobal]writes the record under the explicitglobal:key, and the landed type adds the attribute (probed, 3.1). No landed code bypasses the refusal. - (b) Loosen
AetheriaStoresso writable opens skip the refusal. This reverses theab4ced0freasoning ("a missing global went silent in play mode"). - (c) Land a one-shot
import-settingscommand with a bypass flag onOpen, then delete both. A guardrail hole ships, even if briefly. - Recommend (a).
I. Icon sprites (engine references) in the global. Blocks Cut 1.
- (a) Asset-path strings with
[CultInspectorAssetPath], loaded throughUnityHelpers.LoadAsset<Sprite>. This is whatItemData.Icon/ActionBarIcon,Weapon,ThrusterandAetherDriveparticles already do, and all 33 live sprites already sit underAssets/Resources. The 9 dangling refs become empty strings. - (b) Keep a slim Unity
ScriptableObjectholding only the sprite arrays. This keeps Unity serialization for this data, against the ruling's direction. - (c) Put the sprite arrays on each consuming component. Several prefab instances would each duplicate them.
- Recommend (a). Separately, the operator may want to know that modifier icons are already broken (9 missing assets).
B. BodySettingsCollections (mass thresholds plus CelestialBodySettings plugin assets). Blocks Cut 1.
- (a) Thresholds and asset paths go in the global, like fork I. The 7 referenced assets
move, with their
.metafiles so guids are kept, fromAssets/Plugins/Celestial Body/Solar System/…toAssets/Resources/Celestial Bodies/, becauseResources.Loadneeds them there. Their dependencies follow by guid. - (b) Serialized field on
ZoneRenderer, the only reader. Thresholds and references stay inARPG.unity, with no asset moves. Unity keeps authoring a setting. - Recommend (a). One rule ("engine assets are referenced from globals by path") is cheaper to explain than an exception. The 7 moves are a one-time cost.
D. Document shape. Blocks Cut 1.
- (a) One catalog global
GameSettings(2.1). It keeps today's single authoring surface, every existing settings class, and everySettings.Xmember path. - (b) Split by consumer:
GameplaySettings,GenerationSettingsandPresentationSettingsglobals. It gives narrower ports, but nothing needs isolating yet, and it means three records and a wider reader diff. - Recommend (a). A later split is a mechanical re-key if a consumer ever needs one.
P. Simulation port. Blocks Cut 1.
- (a)
ItemManagerandZoneread the global from the cache they already hold, and their settings constructor parameters go (2.2). - (b) Keep the parameters, with callers passing
cache.GetGlobal<GameSettings>().…. The sim could then run on settings the catalog does not hold, and tests andAetherDbcould keep hand-building them. That is the duplication this cut exists to delete. - Recommend (a). The headless-playground
Run.Newsignature shrinks with it.
M. Is component tuning on scene and prefab instances "settings"? Does not block Cut 0 or Cut 1.
- (a) No. It is instance data of presentation components (1.5) and stays Unity-serialized.
Gameplay-affecting prefab values (
Projectile,Mine,GuidedProjectile) move when the headless-playground map moves hit and loot ownership into the sim.ActionGameManager.Creditsis run state, not a setting, so it goes to follow-ups. - (b) Yes. Each component's tuning becomes a catalog global or a per-prefab catalog record. That is a second authoring surface per component and a much larger cut.
- Recommend (a).
X. Dead authored data. Low blocking; affects Cut 0 only.
- (a) Delete the 4 soundbank names,
HullHitColorand the 5 orphanResourcesassets. Their bytes stay in git history (1.2 names the commits), andElysium/Test Galaxyhold parameters of a generator deleted in 2021. - (b) Migrate them into the global or a catalog record anyway.
- Recommend (a). Nothing reads them. They are not pre-breach content in the sense of
GameData; they are Unity assets whose scripts are gone.
Follow-ups outside this migration
ActionGameManager.Creditsis not saved and resets per scene load (ActionGameManager.cs:101,ARPG.unity: 100000000). It belongs in the run store withSavedGame.NoisePositionis run state inside a settings class (Settings.cs:62). After Cut 0 the authored value is only an unused seed. It could move toSavedGame/Galaxy.StartingHullNameand the tutorial faction names are name strings, notCultRecordRefs, soAetherDb danglingcannot see them.GameplaySettingsmixes presentation (LockIndicator*,MessageDuration) with sim rules.TargetArmorInfoThresholdandTargetGearInfoThresholdare authored and unread (recorded in the headless map).- 9 dangling icon references (1.1).