From 78fd617cddea0dd9fb0ee8e8f471e768e5f8e43d Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Thu, 17 Sep 2026 19:33:24 +0200 Subject: [PATCH] Restore the escaped dot in the corrected grep Co-Authored-By: Claude Opus 5 --- docs/addressables-cut.md | 812 +++++++++++++++++++-------------------- 1 file changed, 406 insertions(+), 406 deletions(-) diff --git a/docs/addressables-cut.md b/docs/addressables-cut.md index 9f9359f70..2e2f25cc8 100644 --- a/docs/addressables-cut.md +++ b/docs/addressables-cut.md @@ -1,406 +1,406 @@ -# Engine Asset References Through Addressables: Cut Map - -Date: 2026-09-17 - -Status: Imagination pass, cut map. Nothing here has landed. Anchors are against -`codex/item-provenance` HEAD `f8396313` (settings Cut 0 in progress; none of its commits -touch a file named here, checked with `git diff --name-only f13a3356 f8396313`). Ends are -owned by the ruling in `docs/settings-globals-cut.md` (header); this document owns the means. - -Claims marked **(probe)** were run, not read off names: -- a scratch console over `Aetheria.Shared` (sparse scratch worktree of `f13a3356`, CultLib - `a0813c6` as a detached scratch worktree, both removed afterwards; the catalog was a copy) - that opened `GameData/Aetheria.cc` through `AetheriaStores.Open` read-only and walked every - stored document for string members carrying `[CultInspectorAssetPath]`; -- a Python pass resolving each stored path against the main tree and its `.meta` guid, and a - guid closure over the referenced prefabs' YAML; -- reads of `Library/PackageCache/com.unity.addressables@8460f1c9c927` (2.9.1) source and its - `Documentation~`. - -Operator ruling (2026-09-17): engine asset references from CultCache data are Unity -Addressables, stored as the asset's `.meta` GUID and loaded through Addressables by GUID. -One rule for every engine asset reference from data. No dual Resources/Addressables path. - -Rulings: Q1 (A), one `Assets/Content` folder entry (operator, 2026-09-17: "I take your recommendation"). Open: none. -Ruling S (A), sub-assets (operator, 2026-09-17: "A"): a stored reference is the Addressables key, -the bare GUID for a main asset and `guid[subAssetName]` when a sub-asset (e.g. one sprite of a -sheet) is picked. It is what `AssetReference` keeps (GUID plus sub-object name), and -Addressables loads both forms. The drawer writes and reads both; `EngineAssets.Load` passes the -key through. Found by Soul on CultLib `0155ba8`, before release. -Correction: Cut 0's negative grep `AssetPath` also matches Unity's `GUIDToAssetPath`/ -`AssetPathToGUID`; the check is `git grep -n "CultInspectorAssetPath\|\.AssetPathb"`. - -## 0. Target - -Ends: -- A CultCache field that names an engine asset stores that asset's GUID. No stored path. -- One Unity-side loader turns a GUID into an asset. No runtime code calls `Resources.Load` - for data-named assets, and nothing strips an `Assets/Resources/` prefix. -- Every stored GUID resolves to an addressable asset of the type its consumer loads, and a - check proves it. - -Invariants kept: -- ServerShared stays engine-free (`Aetheria.Shared.csproj` builds it without Unity), so the - data carries a string, never a Unity type. -- Catalog globals are authored, never invented; the migration rewrites values in existing - records only. -- One home store per type, one-store commits. - -## 1. Census - -### 1.1 Stored references **(probe)** - -8 attributed members exist (`git grep CultInspectorAssetPath`). No other engine asset string -exists in ServerShared: the only other `*Path` strings are `InputLayout.cs:68,92` (input -control paths) and `WwiseMetadata.cs:37,91` (Wwise object paths), neither a Unity asset. - -| Member (`file:line`) | Stored | Non-empty | Resolves | Dangling | Runtime reader | -|---|---|---|---|---|---| -| `ConsumableItemData.Icon` (`ItemData.cs:352`) | 0 | 0 | 0 | 0 | `ActionBarSlot.cs:114` | -| `EquippableItemData.Schematic` (`ItemData.cs:362`) | 51 | 1 | 1 | 0 | editor only, `Editor/CultCacheDrawers.cs:104` | -| `EquippableItemData.ActionBarIcon` (`ItemData.cs:392`) | 51 | 0 | 0 | 0 | `ActionBarSlot.cs:153` | -| `HullData.Prefab` (`ItemData.cs:499`) | 3 | 3 | 3 | 0 | `ZoneRenderer.cs:295,304` | -| `WeaponData.EffectPrefab` (`Behaviors/Weapon.cs:43`) | 18 | 18 | 18 | 0 | `EntityInstance.cs:195,224` | -| `ThrusterData.ParticlesPrefab` (`Behaviors/Thruster.cs:26`) | 2 | 2 | 2 | 0 | `ShipInstance.cs:67` | -| `AetherDriveData.Particles` (`Behaviors/AetherDrive.cs:50`) | 1 | 1 | 1 | 0 | `ShipInstance.cs:53` | -| `Faction.Logo` (`Corporations.cs:25`) | 12 | 5 | 5 | 0 | none | - -Totals: 138 stored values in 180 documents, 30 non-empty, 27 distinct paths, all 30 resolve -to an existing file with a `.meta` guid, **0 dangling**. Every one of the 27 distinct paths is -under `Assets/Resources/`: 21 prefabs under `Prefabs/`, 1 png under `Schematics/`, 5 pngs under -`Sprites/Icons/{Logos,Tech}` (3 paths are shared by two records each). -The value-by-value list (record, member, path, guid) is the migration's expected report; -Cut 2 regenerates it rather than trusting this copy. - -### 1.2 Load sites (all synchronous, all on the main thread) - -| Site | Loads | Context | -|---|---|---| -| `Gameplay/ActionBarSlot.cs:114` | `Resources.Load` with inline prefix strip | binding ctor | -| `Gameplay/ActionBarSlot.cs:153` | same | binding ctor | -| `Gameplay/EntityInstance.cs:195` | `UnityHelpers.LoadAsset` | `SetEntity`, then `Instantiate` | -| `Gameplay/EntityInstance.cs:224` | `UnityHelpers.LoadAsset` | same | -| `Gameplay/ShipInstance.cs:53` | `UnityHelpers.LoadAsset` | same, `Instantiate` | -| `Gameplay/ShipInstance.cs:67` | same | LINQ select, `Instantiate` | -| `Zone Display/ZoneRenderer.cs:295,304` | `UnityHelpers.LoadAsset` | `LoadEntity`, `Instantiate` | -| `Editor/CultCacheDrawers.cs:104` | `AssetDatabase.LoadAssetAtPath` | Studio drawer | - -`UnityHelpers.cs:7-10` is the only helper and has no other caller. Four sites load a -**component** type. Addressables cannot do that: "You can't load a component of a GameObject -directly through Addressables" (`Documentation~/load-assets.md:60`). The loader must load the -GameObject and take the component. - -### 1.3 `Resources.Load` not tied to data - -One: `Gameplay/ActionGameManager.cs:420` (`:425` before Cut 0) loads -`Sprites/Input/{controlName}` by naming convention. No data names it; it is outside the ruling. -**Default: out of scope**, and `Assets/Resources/Sprites/Input/` stays in Resources. It goes to -follow-ups: if Resources is ever emptied, this becomes a player-store or catalog map of control -name to GUID. - -### 1.4 Resources dependencies of the referenced assets **(probe)** - -The guid closure of the 27 referenced assets is 145 assets; 40 sit in `Assets/Resources`: the -27 seeds, plus 13 dependencies. 8 of those are inside the subtrees Cut 2 moves (`Prefabs/` -top-level Influence, Lightning, PingEffect, Shield, Tractor Beam; `Sprites/Icons/Stroked/` -Satellite, Ship, orbital). 5 stay behind: `Gradients/Ironbow.png` (written there by -`GradientMapper.cs:94-96`), `Sprites/Mote.png`, `Sprites/ringThick.png`, -`Sun Albedo/sunsurface1map 1.png`, `Sun Flow/sunsurface1offset 1.png`. Nothing loads them by -name; left in Resources they ship twice (Resources data and the bundle). That is size, not -correctness. **Default: follow-up**, reported by the Addressables Analyze rule "Check Resources -to Addressable Duplicate Dependencies" (`Editor/Build/AnalyzeRules/CheckResourcesDupeDependencies.cs`). - -## 2. Ownership decisions - -### 2.1 The picker and the attribute belong to CultLib - -`[CultInspectorAssetPath]` is CultLib's (`src/GameCult.Caching/CultInspectorAttributes.cs:65-77`), -read into `CultInspectorMetadata.AssetPath` (`CultInspectorModel.cs:44,56`) and drawn by CultLib's -Studio, not by Aetheria: `src/GameCult.Unity/Assets/Caching/Editor/CultCacheStudioDrawers.cs:168-176` -turns an `ObjectField` into `AssetDatabase.GetAssetPath`. Aetheria's -`Assets/Scripts/Editor/CultCacheDrawers.cs` claims only its own attributes. No CultLib path -under `src/GameCult.Caching`, `src/GameCult.Unity` or `unity/` changed between `a0813c6` and -CultLib `main` `244154e`. Aetheria is the only consumer in `F:\Projects` (`git grep` over every -repo; the other hits are CultLib's README and voidbot memory dumps). - -Decision: the owner fills the gap. CultLib replaces the path attribute with -`CultInspectorAssetGuidAttribute(Type? assetType = null)`, "a string member holding an engine -asset's stable identity; in Unity, the `.meta` GUID", and its Studio drawer stores -`AssetDatabase.AssetPathToGUID` and reads back through `GUIDToAssetPath`. Reusing the name -`AssetPath` for GUIDs would make the attribute lie, and keeping both leaves a dead path form -for the next field to pick up. Addressables stays out of CultLib: whether an asset is -addressable is Aetheria's content rule (2.3), so the Studio package gains no Addressables -dependency. `AssetType` stays optional; Aetheria cannot pass it (ServerShared is engine-free, -recorded in `docs/cultcache-migration-cut.md:2003`), so the Studio falls back to `Object`. - -Cost: a CultLib release (plugins rebuilt with `scripts/build-unity-package.ps1`), tags -`cultlib-unity-v1.0.60` and `caching-unity-v1.4.0` (removing a public attribute is breaking), -and an Aetheria bump of `Packages/manifest.json` (both URLs), `Packages/packages-lock.json` -and `Directory.Build.props` `CultLibRevision`. Coordination: CultLib `codex/studio-grouping` -(mapped, blocked on the Aetheria lot schema) also edits `CultInspectorModel.cs` and the Studio -drawers; whichever releases second rebases onto the first release. The older CultLib worktree branches -(`CultLib-aetheria-authority`, `CultLib-delvehold-isosurface`, `CultLib-document-variants`) carry -the attribute in its pre-move location (`Runtime/CultCacheInspectorAttributes.cs`); they are -CultLib branches, not consumers, and meet the rename only if rebased. - -### 2.2 Addressables package **(probe)** - -- `com.unity.addressables` **2.9.1** is already resolved and compiled, at depth 1, pulled by - `com.unity.localization` 1.5.8 (`Packages/packages-lock.json:18-33,117-126`). Its - `package.json` needs Unity 2023.1+; the project is `6000.3.24f1`. `Library/ScriptAssemblies` - holds `Unity.Addressables.dll`, `Unity.Addressables.Editor.dll`, `Unity.ResourceManager.dll`. - No script uses Localization and no Localization settings exist (grep of `Assets`, - `ProjectSettings`); that is a follow-up, not this cut. -- No settings exist yet: no `Assets/AddressableAssetsData`, no `com.unity.addressableassets` - in `ProjectSettings/EditorBuildSettings.asset` `m_configObjects`. -- Creating settings (`AddressableAssetSettings.Create`, `:2023-2056`, and `Validate`, - `:1946-1976`) writes: `Assets/AddressableAssetsData/AddressableAssetSettings.asset`, - `DefaultObject.asset`, `AssetGroups/Default Local Group.asset` with - `AssetGroups/Schemas/Default Local Group_BundledAssetGroupSchema.asset` and - `_ContentUpdateGroupSchema.asset`, `AssetGroupTemplates/Packed Assets.asset`, - `DataBuilders/BuildScriptFastMode.asset`, `BuildScriptPackedPlayMode.asset`, - `BuildScriptPackedMode.asset`, each with a `.meta`, plus the config object entry in - `EditorBuildSettings.asset`. It sets `BuildAddressablesWithPlayerBuild = PreferencesValue`, - a per-user preference. -- **GUID keys.** `BundledAssetGroupSchema.IncludeGUIDInCatalog` defaults to `true` - (`BundledAssetGroupSchema.cs:155`); the player catalog then adds each entry's guid as a key - (`AddressableAssetEntry.CreateKeyList`, `:254-261`). Folder entries expand into sub-entries - carrying their own guid (`AddressableAssetEntry.cs:554`). In editor play mode ("Use Asset - Database"), `AddressableAssetSettingsLocator` keys direct entries by guid (`:155`) and resolves - a guid under an addressable folder entry by walking parent folders (`:262-283`). A guid that is - not addressable yields no location and the load fails. So `LoadAssetAsync(guid)` works in - editor and player for direct and folder entries, and fails for non-addressable assets. -- **Resources.** An asset under a `Resources` folder cannot be an entry: the Inspector and Groups - window move it to `Resources_moved` (`AddressableAssetUtility.SafeMoveResourcesToGroup`, - `:309-339`), and moving an entry into Resources unmarks it (`AddressableAssetSettings.cs:2900-2906`). - The programmatic `CreateOrMoveEntry` (`:2541`) does not check, so a script could create an - entry the GUI would refuse; the cut must move assets out of Resources rather than rely on that. - Moving a file together with its `.meta` keeps the guid, so stored GUIDs survive the move. -- **Sync.** `AsyncOperationHandle.WaitForCompletion` (`AsyncOperationHandle.cs:178`) completes a - local load in place; `SynchronousAddressables.md:21` puts the cost near `Resources.Load` for - local bundles and warns that it completes every pending load. -- **Player build.** Bundles build with the player only when `BuildAddressablesWithPlayerBuild` - resolves to build (`build-player-builds.md:5`). Cut 1 sets `BuildWithPlayer` in the project - settings so the rule is committed, not a preference. - -### 2.3 How an asset becomes addressable (Q1) - -Recommended (Q1 A): one folder entry, `Assets/Content`, in `Default Local Group`. Addressable -means "lives under `Assets/Content`". Cut 2 moves `Assets/Resources/Prefabs/`, -`Assets/Resources/Schematics/` and `Assets/Resources/Sprites/Icons/` there with their `.meta` -files, keeping relative paths. `Sprites/Icons/` includes the 33 settings icons settings Cut 1 -will reference, so that cut only stores GUIDs. The 7 `CelestialBodySettings` presets under -`Assets/Plugins/Celestial Body/Solar System/` move into `Assets/Content/Celestial Bodies/` in -settings Cut 1 (that map's old fork B(a) move, retargeted). - -### 2.4 Loading owner - -`Assets/Scripts/EngineAssets.cs` (Assembly-CSharp, replaces `UnityHelpers.cs`), about 30 lines: -- `public static T Load(string guid) where T : Object`. Empty GUID returns `null`. A - `Component` type loads the GameObject and returns `GetComponent()`. -- Loads with `Addressables.LoadAssetAsync(guid).WaitForCompletion()`. All call sites are - synchronous construction paths that `Instantiate` at once. Making them async would reorder - entity construction for 30 local assets. **Default: sync.** -- Caches one handle per `(guid, type)` for the process lifetime and never releases, which is - the lifetime `Resources.Load` has today. It owns every handle; no caller holds or releases one. - A failed handle logs the GUID and returns `null`; callers keep their existing null handling. - -### 2.5 Validation owner - -`Assets/Scripts/Editor/EngineAssetCheck.cs` (Assembly-CSharp-Editor). It needs `AssetDatabase`, -the Addressables editor settings and the consumer component types, so it cannot be a headless -`AetherDb` command or a `Tests.asmdef` test (that assembly cannot reference Assembly-CSharp). -It has a menu item and a batchmode entry (`-executeMethod EngineAssetCheck.Run`, exit 1 on -failure). It opens the catalog read-only through `AetheriaStores.Open`, walks every stored -document for `[CultInspectorAssetGuid]` string members, and for each non-empty value checks: -1. it is 32 lowercase hex characters; -2. `AssetDatabase.GUIDToAssetPath` names an existing file; -3. `AddressableAssetSettingsDefaultObject.Settings.FindAssetEntry(guid, includeImplicit: true)` is non-null; -4. the asset satisfies its member's consumer contract, from one table with one row per member: - `Icon`, `ActionBarIcon`, `Schematic`, `Logo` are `Texture2D`; `HullData.Prefab` is a - GameObject with `EntityInstance`; `WeaponData.EffectPrefab` has `InstantWeaponEffectManager` - on `InstantWeaponData` records and `ConstantWeaponEffectManager` on `ConstantWeaponData` - records; `ThrusterData.ParticlesPrefab` and `AetherDriveData.Particles` have `ParticleSystem`. - An attributed member missing from the table fails the check, so a new field cannot skip it. - -It reports record name, member, GUID and path per failure. The check does not write, and no -repair path exists. - -## 3. Data migration (Cut 2, First) - -- Scratch-only program over `Aetheria.Shared` at the pre-Cut-2 commit (the fork K (a) - precedent: no landed bypass, no one-shot command left behind). It opens a **copy** of the - catalog writable, walks every stored document for `[CultInspectorAssetPath]` string members, - and maps each non-empty path to the `guid:` of `/.meta`. It writes through the - catalog's own types and commits once. -- Report, one line per value: record type, record name, member, old path, GUID. Unresolved - paths are listed and **fail the run with nothing written**; none are expected (1.1). Empty - values stay empty. -- No record is added or removed, and no global is touched. Member names, keys and `JsonProperty` - names are unchanged: the member still names the asset, and the attribute says the form. -- The rewritten `GameData/Aetheria.cc` (LFS) lands in the same commit as the code. A commit - that holds GUIDs with path-loading code, or paths with GUID-loading code, loads nothing. -- Before/after: `AetherDb census` and `dangling` outputs are byte-identical (same capture - method both sides); the report has 30 lines; a scratch re-read finds 30 GUIDs, 108 empty - strings and no string starting with `Assets/`. - -## 4. Cuts - -### Cut 0. CultLib: asset GUID attribute and Studio picker - -- **Repo/branch:** CultLib, a `codex/` branch from `main` `244154e`. Releases before Cut 2. -- **Deletes first:** `CultInspectorAssetPathAttribute` (`src/GameCult.Caching/CultInspectorAttributes.cs:65-77`); - `CultInspectorMetadata.AssetPath` (`CultInspectorModel.cs:44,56`); the path branch in - `CultCacheStudioDrawers.cs:170-176`. -- **Adds:** `CultInspectorAssetGuidAttribute(Type? assetType = null)` with the identity comment; - `CultInspectorMetadata.AssetGuid`; the drawer branch: `GUIDToAssetPath` then - `LoadAssetAtPath(path, assetType)`, then `ObjectField`, storing `AssetPathToGUID(next)` or - `string.Empty`. README `src/GameCult.Unity/Assets/Caching/README.md:153,164`. -- **Release:** rebuild plugins, bump `unity/org.gamecult.cultlib/package.json` to 1.0.60 and - `src/GameCult.Unity/Assets/Caching/package.json` to 1.4.0 (depending on cultlib 1.0.60), tag both. -- **Verification:** - - builds: `GameCult.Caching`, the CultLib test project that covers `CultInspectorModel`, the - Unity package build script. - - tests: a model test pins that a member carrying the GUID attribute exposes `AssetGuid` and - its `AssetType`. - - negative: `git grep -n "AssetPath" -- src unity` finds nothing (checked: today the only hits - are the three files above and the README). - - operator (Unity, after Cut 2's bump): pick a prefab in Studio, the stored value is its GUID, - and reopening shows the same asset. - -### Cut 1. Aetheria: Addressables settings - -- **Repo/branch:** Aetheria `codex/item-provenance`, after settings Cut 0 lands. Independent of Cut 0. -- **First:** Unity closed (operator), because batchmode refuses an open project. -- **Adds:** `"com.unity.addressables": "2.9.1"` as a direct dependency in `Packages/manifest.json`, - since Aetheria code now uses it; the lock entry goes from depth 1 to 0. Settings created by a - scratch-only batchmode `-executeMethod` script, which is not landed: `Create`, then set - `BuildAddressablesWithPlayerBuild = BuildWithPlayer`, create `Assets/Content/` (+`.meta`) and - `CreateOrMoveEntry(guid of Assets/Content, DefaultGroup)`. Operator alternative: *Window > - Asset Management > Addressables > Groups > Create Addressables Settings*, then the same three - edits by hand. -- **Intentional Unity churn (the whole list):** `Packages/manifest.json`, - `Packages/packages-lock.json`, `ProjectSettings/EditorBuildSettings.asset` (one - `com.unity.addressableassets` config object), `Assets/Content.meta`, and under - `Assets/AddressableAssetsData/`: `AddressableAssetSettings.asset`, `DefaultObject.asset`, - `AssetGroups/Default Local Group.asset`, `AssetGroups/Schemas/Default Local Group_BundledAssetGroupSchema.asset`, - `AssetGroups/Schemas/Default Local Group_ContentUpdateGroupSchema.asset`, - `AssetGroupTemplates/Packed Assets.asset`, `DataBuilders/BuildScriptFastMode.asset`, - `DataBuilders/BuildScriptPackedPlayMode.asset`, `DataBuilders/BuildScriptPackedMode.asset`, - and every folder and file `.meta`. Nothing else: the modified materials and render textures - in the worktree today are pre-existing Unity churn and stay unstaged. -- **Harmless alone:** no code loads through Addressables yet, and `Assets/Content` is empty. -- **Verification:** - - operator: Unity opens without Addressables errors; the Groups window shows `Assets/Content` - under `Default Local Group`; the ARPG play smoke is unchanged. - - negative: `git show --stat` lists only the files above. - -### Cut 2. Aetheria: switch every data asset reference to GUIDs - -One commit, or a series whose intermediate commits Soul accepts as unplayable. See section 3 -for why it is atomic. - -- **Depends on:** Cuts 0 and 1, and Q1. -- **First:** the migration report (section 3) against the pre-cut catalog; `AetherDb census` and - `dangling` captures; `git grep -n "Assets/Resources/" -- Assets/Scripts` capture. -- **Deletes first:** - - `Assets/Scripts/UnityHelpers.cs` (+`.meta`), 11 lines. - - `Gameplay/ActionBarSlot.cs:114`, `:153`: the inline `Substring("Assets/Resources/".Length).Split('.').First()` - loads (and `using System.Linq` if nothing else uses it). - - All 8 `CultInspectorAssetPath` usages (`ItemData.cs:352,362,392,499`, `Weapon.cs:43`, - `Thruster.cs:26`, `AetherDrive.cs:50`, `Corporations.cs:25`) become `CultInspectorAssetGuid`. -- **Moves (git mv, file and `.meta` together, guids unchanged):** `Assets/Resources/Prefabs/` to - `Assets/Content/Prefabs/`, `Assets/Resources/Schematics/` to `Assets/Content/Schematics/`, - `Assets/Resources/Sprites/Icons/` to `Assets/Content/Sprites/Icons/`, plus their folder `.meta` files. -- **Adds:** `Assets/Scripts/EngineAssets.cs` (2.4); `Assets/Scripts/Editor/EngineAssetCheck.cs` (2.5); - the migrated `GameData/Aetheria.cc`; the CultLib bump (manifest URLs `#cultlib-unity-v1.0.60` - and `#caching-unity-v1.4.0`, lock, `Directory.Build.props:5` `CultLibRevision`). -- **Per-file changes (against `f8396313`):** - - `ActionBarSlot.cs:114`, `:153`: `EngineAssets.Load(...)`. - - `EntityInstance.cs:195`, `:224`: `EngineAssets.Load` / - `(data.EffectPrefab)`; the error text at `:200`, `:229` says GUID, not path. - - `ShipInstance.cs:53`, `:67`: `EngineAssets.Load`. - - `ZoneRenderer.cs:295`, `:304`: `EngineAssets.Load`. - - `Editor/CultCacheDrawers.cs:104`: `LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(item.Schematic))`. -- **Authority map:** - - Owner: the catalog field holds the GUID. `EngineAssets` owns turning a GUID into a loaded - asset and holding its handle. The `Assets/Content` folder entry owns addressability. - - Inputs: GUID strings from catalog records; the Addressables catalog (editor locator or player catalog). - - Outputs: loaded `Object`s or components, or `null` with a logged GUID. - - Derived state: asset paths are display-only (Studio, check reports). Group entries are - derived from folder location. - - Forbidden writers: `Resources.Load` for data-named assets; any path-prefix stripping; any - runtime or editor code that writes a path into an asset member; per-asset entries created by - scripts outside `Assets/Content`. - - Shared paths: game runtime, editor play mode and player builds all load through - `EngineAssets` and the same GUID keys. Studio authoring and the migration both store GUIDs. - The check reads through the same `AetheriaStores.Open`. - - Deletion line: `UnityHelpers.cs`, the two inline strips, and the path attribute are gone - before `EngineAssets` exists. -- **Verification:** - - builds: `Aetheria.Shared` headless with `-p:CultLibRoot=`; - `tests/Aetheria.Shared.Tests`; Unity batchmode compile. - - tests: `EngineAssetCheck.Run` in batchmode exits 0 on the migrated catalog. Mutation: on a - scratch catalog copy, set one `HullData.Prefab` to a Texture2D GUID, one to a GUID outside - `Assets/Content`, one to a random 32-hex, and one to an old path. Each must fail with its - record named. Also add a scratch attributed member missing from the table; it must fail. - - data: section 3 before/after checks. - - negative (checked against `f8396313` for collisions; each has hits only at the sites above today): - - `git grep -n "Resources.Load" -- Assets/Scripts` gives exactly `ActionGameManager.cs:420`. - - `git grep -n "Assets/Resources/" -- '*.cs'` gives only the generated comments at - `AetheriaInput.cs:5,19`, `GradientMapper.cs:94,96`, and `tools/AetherDb/AuthoredSettings.cs:13` - until settings Cut 1 deletes that file. - - `git grep -n "UnityHelpers\|CultInspectorAssetPath"` gives nothing outside `docs/`. - - `git ls-files Assets/Resources/Prefabs Assets/Resources/Schematics Assets/Resources/Sprites/Icons` is empty. - - operator (Unity): - 1. Addressables Play Mode Script "Use Asset Database": ARPG play smoke. Ships, turret and - Zenith station spawn; thruster and aether drive particles show; fire an instant weapon - (e.g. Autocannon) and a charged one (ChargeBlast SG); the action bar shows icons where - the settings fallback applies. - 2. Play Mode Script "Use Existing Build" after *Build > New Build > Default Build Script*: - the same smoke. This proves the player catalog carries GUID keys. - 3. Addressables *Analyze*: run "Check Resources to Addressable Duplicate Dependencies" and - record the result. The 5 known leftovers (1.4) are expected. - 4. Studio: `LonginusX` schematic still underlays the shape drawer. - -## 5. Subtraction ledger (estimate) - -| Cut | Removed | Added | Dependencies, formats, targets | -|---|---|---|---| -| 0 | path attribute ~12, metadata 2, drawer branch 6 | GUID attribute ~12, metadata 2, drawer branch ~7, test ~15 | CultLib release; no targets | -| 1 | none | Addressables settings assets (Unity YAML, ~12 files) | `com.unity.addressables` becomes direct (already installed); Addressables build step joins player builds | -| 2 | `UnityHelpers.cs` 11, 2 inline strips, 283 assets (584 files with `.meta`) leave `Assets/Resources` (moved, not deleted) | `EngineAssets.cs` ~30, `EngineAssetCheck.cs` ~80, 30 catalog values rewritten | removes the Resources path convention for data; no targets | - -Net code is positive (~+110). It buys the ruled capability (GUID identity that survives moves, -and one loader) and a check that did not exist: today a mistyped path fails only at spawn. - -## 6. Operator forks - -**Q1. How assets become addressable.** Blocks Cut 1. -- (A) One folder entry `Assets/Content`. Addressable means under that folder. Cut 2 moves - `Prefabs/`, `Schematics/` and `Sprites/Icons/` out of Resources into it, and settings Cut 1 - moves the 7 body presets there. New content is addressable by placement, and the group file - does not change per asset. Unreferenced assets in those folders ship too, as they do from - Resources today. -- (B) One entry per referenced asset, assets kept in place, except that the 27 in Resources must - still move (2.2). Only referenced assets ship. Each new pick needs a manual *Addressable* tick - (the check catches a miss), and the group asset changes with every new reference. -- (C) Per-asset entries generated from the catalog by an editor step before play and build. The - group becomes derived state, but a sync step runs at play and build boundaries, and a stale - group between steps fails loads in play mode. -- **Recommend (A).** It makes the invariant structural (location) with no per-asset bookkeeping - and no sync step, and it matches how Resources behaves today. - -## Follow-ups outside this migration - -- `ActionGameManager.cs:420` loads input prompt sprites from `Resources/Sprites/Input` by control - name (1.3). -- 5 Resources textures referenced by moved prefabs stay in Resources and ship twice (1.4). -- `com.unity.localization` 1.5.8 is a direct dependency with no script use and no settings; it - was the only reason Addressables was installed. -- `Faction.Logo` (5 values) has no reader; `EquippableItemData.ActionBarIcon` and - `ConsumableItemData.Icon` hold no values (1.1). -- Doc sweep for Self: `docs/settings-globals-cut.md` still carries path-based text in 2.1 - (`:238`, "Engine asset paths (forks I and B...)"), 3.2 (`:328`), Cut 1, and forks I and B - (`:522-541`). Those become history once this map is ruled. Settings Cut 1's icon and preset - fields use `[CultInspectorAssetGuid]` and `EngineAssets`. +|.AssetPathb"# Engine Asset References Through Addressables: Cut Map +|.AssetPathb" +|.AssetPathb"Date: 2026-09-17 +|.AssetPathb" +|.AssetPathb"Status: Imagination pass, cut map. Nothing here has landed. Anchors are against +|.AssetPathb"`codex/item-provenance` HEAD `f8396313` (settings Cut 0 in progress; none of its commits +|.AssetPathb"touch a file named here, checked with `git diff --name-only f13a3356 f8396313`). Ends are +|.AssetPathb"owned by the ruling in `docs/settings-globals-cut.md` (header); this document owns the means. +|.AssetPathb" +|.AssetPathb"Claims marked **(probe)** were run, not read off names: +|.AssetPathb"- a scratch console over `Aetheria.Shared` (sparse scratch worktree of `f13a3356`, CultLib +|.AssetPathb" `a0813c6` as a detached scratch worktree, both removed afterwards; the catalog was a copy) +|.AssetPathb" that opened `GameData/Aetheria.cc` through `AetheriaStores.Open` read-only and walked every +|.AssetPathb" stored document for string members carrying `[CultInspectorAssetPath]`; +|.AssetPathb"- a Python pass resolving each stored path against the main tree and its `.meta` guid, and a +|.AssetPathb" guid closure over the referenced prefabs' YAML; +|.AssetPathb"- reads of `Library/PackageCache/com.unity.addressables@8460f1c9c927` (2.9.1) source and its +|.AssetPathb" `Documentation~`. +|.AssetPathb" +|.AssetPathb"Operator ruling (2026-09-17): engine asset references from CultCache data are Unity +|.AssetPathb"Addressables, stored as the asset's `.meta` GUID and loaded through Addressables by GUID. +|.AssetPathb"One rule for every engine asset reference from data. No dual Resources/Addressables path. +|.AssetPathb" +|.AssetPathb"Rulings: Q1 (A), one `Assets/Content` folder entry (operator, 2026-09-17: "I take your recommendation"). Open: none. +|.AssetPathb"Ruling S (A), sub-assets (operator, 2026-09-17: "A"): a stored reference is the Addressables key, +|.AssetPathb"the bare GUID for a main asset and `guid[subAssetName]` when a sub-asset (e.g. one sprite of a +|.AssetPathb"sheet) is picked. It is what `AssetReference` keeps (GUID plus sub-object name), and +|.AssetPathb"Addressables loads both forms. The drawer writes and reads both; `EngineAssets.Load` passes the +|.AssetPathb"key through. Found by Soul on CultLib `0155ba8`, before release. +|.AssetPathb"Correction: Cut 0's negative grep `AssetPath` also matches Unity's `GUIDToAssetPath`/ +|.AssetPathb"`AssetPathToGUID`; the check is `git grep -n "CultInspectorAssetPath\|.AssetPath\b"`. +|.AssetPathb" +|.AssetPathb"## 0. Target +|.AssetPathb" +|.AssetPathb"Ends: +|.AssetPathb"- A CultCache field that names an engine asset stores that asset's GUID. No stored path. +|.AssetPathb"- One Unity-side loader turns a GUID into an asset. No runtime code calls `Resources.Load` +|.AssetPathb" for data-named assets, and nothing strips an `Assets/Resources/` prefix. +|.AssetPathb"- Every stored GUID resolves to an addressable asset of the type its consumer loads, and a +|.AssetPathb" check proves it. +|.AssetPathb" +|.AssetPathb"Invariants kept: +|.AssetPathb"- ServerShared stays engine-free (`Aetheria.Shared.csproj` builds it without Unity), so the +|.AssetPathb" data carries a string, never a Unity type. +|.AssetPathb"- Catalog globals are authored, never invented; the migration rewrites values in existing +|.AssetPathb" records only. +|.AssetPathb"- One home store per type, one-store commits. +|.AssetPathb" +|.AssetPathb"## 1. Census +|.AssetPathb" +|.AssetPathb"### 1.1 Stored references **(probe)** +|.AssetPathb" +|.AssetPathb"8 attributed members exist (`git grep CultInspectorAssetPath`). No other engine asset string +|.AssetPathb"exists in ServerShared: the only other `*Path` strings are `InputLayout.cs:68,92` (input +|.AssetPathb"control paths) and `WwiseMetadata.cs:37,91` (Wwise object paths), neither a Unity asset. +|.AssetPathb" +|.AssetPathb"| Member (`file:line`) | Stored | Non-empty | Resolves | Dangling | Runtime reader | +|.AssetPathb"|---|---|---|---|---|---| +|.AssetPathb"| `ConsumableItemData.Icon` (`ItemData.cs:352`) | 0 | 0 | 0 | 0 | `ActionBarSlot.cs:114` | +|.AssetPathb"| `EquippableItemData.Schematic` (`ItemData.cs:362`) | 51 | 1 | 1 | 0 | editor only, `Editor/CultCacheDrawers.cs:104` | +|.AssetPathb"| `EquippableItemData.ActionBarIcon` (`ItemData.cs:392`) | 51 | 0 | 0 | 0 | `ActionBarSlot.cs:153` | +|.AssetPathb"| `HullData.Prefab` (`ItemData.cs:499`) | 3 | 3 | 3 | 0 | `ZoneRenderer.cs:295,304` | +|.AssetPathb"| `WeaponData.EffectPrefab` (`Behaviors/Weapon.cs:43`) | 18 | 18 | 18 | 0 | `EntityInstance.cs:195,224` | +|.AssetPathb"| `ThrusterData.ParticlesPrefab` (`Behaviors/Thruster.cs:26`) | 2 | 2 | 2 | 0 | `ShipInstance.cs:67` | +|.AssetPathb"| `AetherDriveData.Particles` (`Behaviors/AetherDrive.cs:50`) | 1 | 1 | 1 | 0 | `ShipInstance.cs:53` | +|.AssetPathb"| `Faction.Logo` (`Corporations.cs:25`) | 12 | 5 | 5 | 0 | none | +|.AssetPathb" +|.AssetPathb"Totals: 138 stored values in 180 documents, 30 non-empty, 27 distinct paths, all 30 resolve +|.AssetPathb"to an existing file with a `.meta` guid, **0 dangling**. Every one of the 27 distinct paths is +|.AssetPathb"under `Assets/Resources/`: 21 prefabs under `Prefabs/`, 1 png under `Schematics/`, 5 pngs under +|.AssetPathb"`Sprites/Icons/{Logos,Tech}` (3 paths are shared by two records each). +|.AssetPathb"The value-by-value list (record, member, path, guid) is the migration's expected report; +|.AssetPathb"Cut 2 regenerates it rather than trusting this copy. +|.AssetPathb" +|.AssetPathb"### 1.2 Load sites (all synchronous, all on the main thread) +|.AssetPathb" +|.AssetPathb"| Site | Loads | Context | +|.AssetPathb"|---|---|---| +|.AssetPathb"| `Gameplay/ActionBarSlot.cs:114` | `Resources.Load` with inline prefix strip | binding ctor | +|.AssetPathb"| `Gameplay/ActionBarSlot.cs:153` | same | binding ctor | +|.AssetPathb"| `Gameplay/EntityInstance.cs:195` | `UnityHelpers.LoadAsset` | `SetEntity`, then `Instantiate` | +|.AssetPathb"| `Gameplay/EntityInstance.cs:224` | `UnityHelpers.LoadAsset` | same | +|.AssetPathb"| `Gameplay/ShipInstance.cs:53` | `UnityHelpers.LoadAsset` | same, `Instantiate` | +|.AssetPathb"| `Gameplay/ShipInstance.cs:67` | same | LINQ select, `Instantiate` | +|.AssetPathb"| `Zone Display/ZoneRenderer.cs:295,304` | `UnityHelpers.LoadAsset` | `LoadEntity`, `Instantiate` | +|.AssetPathb"| `Editor/CultCacheDrawers.cs:104` | `AssetDatabase.LoadAssetAtPath` | Studio drawer | +|.AssetPathb" +|.AssetPathb"`UnityHelpers.cs:7-10` is the only helper and has no other caller. Four sites load a +|.AssetPathb"**component** type. Addressables cannot do that: "You can't load a component of a GameObject +|.AssetPathb"directly through Addressables" (`Documentation~/load-assets.md:60`). The loader must load the +|.AssetPathb"GameObject and take the component. +|.AssetPathb" +|.AssetPathb"### 1.3 `Resources.Load` not tied to data +|.AssetPathb" +|.AssetPathb"One: `Gameplay/ActionGameManager.cs:420` (`:425` before Cut 0) loads +|.AssetPathb"`Sprites/Input/{controlName}` by naming convention. No data names it; it is outside the ruling. +|.AssetPathb"**Default: out of scope**, and `Assets/Resources/Sprites/Input/` stays in Resources. It goes to +|.AssetPathb"follow-ups: if Resources is ever emptied, this becomes a player-store or catalog map of control +|.AssetPathb"name to GUID. +|.AssetPathb" +|.AssetPathb"### 1.4 Resources dependencies of the referenced assets **(probe)** +|.AssetPathb" +|.AssetPathb"The guid closure of the 27 referenced assets is 145 assets; 40 sit in `Assets/Resources`: the +|.AssetPathb"27 seeds, plus 13 dependencies. 8 of those are inside the subtrees Cut 2 moves (`Prefabs/` +|.AssetPathb"top-level Influence, Lightning, PingEffect, Shield, Tractor Beam; `Sprites/Icons/Stroked/` +|.AssetPathb"Satellite, Ship, orbital). 5 stay behind: `Gradients/Ironbow.png` (written there by +|.AssetPathb"`GradientMapper.cs:94-96`), `Sprites/Mote.png`, `Sprites/ringThick.png`, +|.AssetPathb"`Sun Albedo/sunsurface1map 1.png`, `Sun Flow/sunsurface1offset 1.png`. Nothing loads them by +|.AssetPathb"name; left in Resources they ship twice (Resources data and the bundle). That is size, not +|.AssetPathb"correctness. **Default: follow-up**, reported by the Addressables Analyze rule "Check Resources +|.AssetPathb"to Addressable Duplicate Dependencies" (`Editor/Build/AnalyzeRules/CheckResourcesDupeDependencies.cs`). +|.AssetPathb" +|.AssetPathb"## 2. Ownership decisions +|.AssetPathb" +|.AssetPathb"### 2.1 The picker and the attribute belong to CultLib +|.AssetPathb" +|.AssetPathb"`[CultInspectorAssetPath]` is CultLib's (`src/GameCult.Caching/CultInspectorAttributes.cs:65-77`), +|.AssetPathb"read into `CultInspectorMetadata.AssetPath` (`CultInspectorModel.cs:44,56`) and drawn by CultLib's +|.AssetPathb"Studio, not by Aetheria: `src/GameCult.Unity/Assets/Caching/Editor/CultCacheStudioDrawers.cs:168-176` +|.AssetPathb"turns an `ObjectField` into `AssetDatabase.GetAssetPath`. Aetheria's +|.AssetPathb"`Assets/Scripts/Editor/CultCacheDrawers.cs` claims only its own attributes. No CultLib path +|.AssetPathb"under `src/GameCult.Caching`, `src/GameCult.Unity` or `unity/` changed between `a0813c6` and +|.AssetPathb"CultLib `main` `244154e`. Aetheria is the only consumer in `F:\Projects` (`git grep` over every +|.AssetPathb"repo; the other hits are CultLib's README and voidbot memory dumps). +|.AssetPathb" +|.AssetPathb"Decision: the owner fills the gap. CultLib replaces the path attribute with +|.AssetPathb"`CultInspectorAssetGuidAttribute(Type? assetType = null)`, "a string member holding an engine +|.AssetPathb"asset's stable identity; in Unity, the `.meta` GUID", and its Studio drawer stores +|.AssetPathb"`AssetDatabase.AssetPathToGUID` and reads back through `GUIDToAssetPath`. Reusing the name +|.AssetPathb"`AssetPath` for GUIDs would make the attribute lie, and keeping both leaves a dead path form +|.AssetPathb"for the next field to pick up. Addressables stays out of CultLib: whether an asset is +|.AssetPathb"addressable is Aetheria's content rule (2.3), so the Studio package gains no Addressables +|.AssetPathb"dependency. `AssetType` stays optional; Aetheria cannot pass it (ServerShared is engine-free, +|.AssetPathb"recorded in `docs/cultcache-migration-cut.md:2003`), so the Studio falls back to `Object`. +|.AssetPathb" +|.AssetPathb"Cost: a CultLib release (plugins rebuilt with `scripts/build-unity-package.ps1`), tags +|.AssetPathb"`cultlib-unity-v1.0.60` and `caching-unity-v1.4.0` (removing a public attribute is breaking), +|.AssetPathb"and an Aetheria bump of `Packages/manifest.json` (both URLs), `Packages/packages-lock.json` +|.AssetPathb"and `Directory.Build.props` `CultLibRevision`. Coordination: CultLib `codex/studio-grouping` +|.AssetPathb"(mapped, blocked on the Aetheria lot schema) also edits `CultInspectorModel.cs` and the Studio +|.AssetPathb"drawers; whichever releases second rebases onto the first release. The older CultLib worktree branches +|.AssetPathb"(`CultLib-aetheria-authority`, `CultLib-delvehold-isosurface`, `CultLib-document-variants`) carry +|.AssetPathb"the attribute in its pre-move location (`Runtime/CultCacheInspectorAttributes.cs`); they are +|.AssetPathb"CultLib branches, not consumers, and meet the rename only if rebased. +|.AssetPathb" +|.AssetPathb"### 2.2 Addressables package **(probe)** +|.AssetPathb" +|.AssetPathb"- `com.unity.addressables` **2.9.1** is already resolved and compiled, at depth 1, pulled by +|.AssetPathb" `com.unity.localization` 1.5.8 (`Packages/packages-lock.json:18-33,117-126`). Its +|.AssetPathb" `package.json` needs Unity 2023.1+; the project is `6000.3.24f1`. `Library/ScriptAssemblies` +|.AssetPathb" holds `Unity.Addressables.dll`, `Unity.Addressables.Editor.dll`, `Unity.ResourceManager.dll`. +|.AssetPathb" No script uses Localization and no Localization settings exist (grep of `Assets`, +|.AssetPathb" `ProjectSettings`); that is a follow-up, not this cut. +|.AssetPathb"- No settings exist yet: no `Assets/AddressableAssetsData`, no `com.unity.addressableassets` +|.AssetPathb" in `ProjectSettings/EditorBuildSettings.asset` `m_configObjects`. +|.AssetPathb"- Creating settings (`AddressableAssetSettings.Create`, `:2023-2056`, and `Validate`, +|.AssetPathb" `:1946-1976`) writes: `Assets/AddressableAssetsData/AddressableAssetSettings.asset`, +|.AssetPathb" `DefaultObject.asset`, `AssetGroups/Default Local Group.asset` with +|.AssetPathb" `AssetGroups/Schemas/Default Local Group_BundledAssetGroupSchema.asset` and +|.AssetPathb" `_ContentUpdateGroupSchema.asset`, `AssetGroupTemplates/Packed Assets.asset`, +|.AssetPathb" `DataBuilders/BuildScriptFastMode.asset`, `BuildScriptPackedPlayMode.asset`, +|.AssetPathb" `BuildScriptPackedMode.asset`, each with a `.meta`, plus the config object entry in +|.AssetPathb" `EditorBuildSettings.asset`. It sets `BuildAddressablesWithPlayerBuild = PreferencesValue`, +|.AssetPathb" a per-user preference. +|.AssetPathb"- **GUID keys.** `BundledAssetGroupSchema.IncludeGUIDInCatalog` defaults to `true` +|.AssetPathb" (`BundledAssetGroupSchema.cs:155`); the player catalog then adds each entry's guid as a key +|.AssetPathb" (`AddressableAssetEntry.CreateKeyList`, `:254-261`). Folder entries expand into sub-entries +|.AssetPathb" carrying their own guid (`AddressableAssetEntry.cs:554`). In editor play mode ("Use Asset +|.AssetPathb" Database"), `AddressableAssetSettingsLocator` keys direct entries by guid (`:155`) and resolves +|.AssetPathb" a guid under an addressable folder entry by walking parent folders (`:262-283`). A guid that is +|.AssetPathb" not addressable yields no location and the load fails. So `LoadAssetAsync(guid)` works in +|.AssetPathb" editor and player for direct and folder entries, and fails for non-addressable assets. +|.AssetPathb"- **Resources.** An asset under a `Resources` folder cannot be an entry: the Inspector and Groups +|.AssetPathb" window move it to `Resources_moved` (`AddressableAssetUtility.SafeMoveResourcesToGroup`, +|.AssetPathb" `:309-339`), and moving an entry into Resources unmarks it (`AddressableAssetSettings.cs:2900-2906`). +|.AssetPathb" The programmatic `CreateOrMoveEntry` (`:2541`) does not check, so a script could create an +|.AssetPathb" entry the GUI would refuse; the cut must move assets out of Resources rather than rely on that. +|.AssetPathb" Moving a file together with its `.meta` keeps the guid, so stored GUIDs survive the move. +|.AssetPathb"- **Sync.** `AsyncOperationHandle.WaitForCompletion` (`AsyncOperationHandle.cs:178`) completes a +|.AssetPathb" local load in place; `SynchronousAddressables.md:21` puts the cost near `Resources.Load` for +|.AssetPathb" local bundles and warns that it completes every pending load. +|.AssetPathb"- **Player build.** Bundles build with the player only when `BuildAddressablesWithPlayerBuild` +|.AssetPathb" resolves to build (`build-player-builds.md:5`). Cut 1 sets `BuildWithPlayer` in the project +|.AssetPathb" settings so the rule is committed, not a preference. +|.AssetPathb" +|.AssetPathb"### 2.3 How an asset becomes addressable (Q1) +|.AssetPathb" +|.AssetPathb"Recommended (Q1 A): one folder entry, `Assets/Content`, in `Default Local Group`. Addressable +|.AssetPathb"means "lives under `Assets/Content`". Cut 2 moves `Assets/Resources/Prefabs/`, +|.AssetPathb"`Assets/Resources/Schematics/` and `Assets/Resources/Sprites/Icons/` there with their `.meta` +|.AssetPathb"files, keeping relative paths. `Sprites/Icons/` includes the 33 settings icons settings Cut 1 +|.AssetPathb"will reference, so that cut only stores GUIDs. The 7 `CelestialBodySettings` presets under +|.AssetPathb"`Assets/Plugins/Celestial Body/Solar System/` move into `Assets/Content/Celestial Bodies/` in +|.AssetPathb"settings Cut 1 (that map's old fork B(a) move, retargeted). +|.AssetPathb" +|.AssetPathb"### 2.4 Loading owner +|.AssetPathb" +|.AssetPathb"`Assets/Scripts/EngineAssets.cs` (Assembly-CSharp, replaces `UnityHelpers.cs`), about 30 lines: +|.AssetPathb"- `public static T Load(string guid) where T : Object`. Empty GUID returns `null`. A +|.AssetPathb" `Component` type loads the GameObject and returns `GetComponent()`. +|.AssetPathb"- Loads with `Addressables.LoadAssetAsync(guid).WaitForCompletion()`. All call sites are +|.AssetPathb" synchronous construction paths that `Instantiate` at once. Making them async would reorder +|.AssetPathb" entity construction for 30 local assets. **Default: sync.** +|.AssetPathb"- Caches one handle per `(guid, type)` for the process lifetime and never releases, which is +|.AssetPathb" the lifetime `Resources.Load` has today. It owns every handle; no caller holds or releases one. +|.AssetPathb" A failed handle logs the GUID and returns `null`; callers keep their existing null handling. +|.AssetPathb" +|.AssetPathb"### 2.5 Validation owner +|.AssetPathb" +|.AssetPathb"`Assets/Scripts/Editor/EngineAssetCheck.cs` (Assembly-CSharp-Editor). It needs `AssetDatabase`, +|.AssetPathb"the Addressables editor settings and the consumer component types, so it cannot be a headless +|.AssetPathb"`AetherDb` command or a `Tests.asmdef` test (that assembly cannot reference Assembly-CSharp). +|.AssetPathb"It has a menu item and a batchmode entry (`-executeMethod EngineAssetCheck.Run`, exit 1 on +|.AssetPathb"failure). It opens the catalog read-only through `AetheriaStores.Open`, walks every stored +|.AssetPathb"document for `[CultInspectorAssetGuid]` string members, and for each non-empty value checks: +|.AssetPathb"1. it is 32 lowercase hex characters; +|.AssetPathb"2. `AssetDatabase.GUIDToAssetPath` names an existing file; +|.AssetPathb"3. `AddressableAssetSettingsDefaultObject.Settings.FindAssetEntry(guid, includeImplicit: true)` is non-null; +|.AssetPathb"4. the asset satisfies its member's consumer contract, from one table with one row per member: +|.AssetPathb" `Icon`, `ActionBarIcon`, `Schematic`, `Logo` are `Texture2D`; `HullData.Prefab` is a +|.AssetPathb" GameObject with `EntityInstance`; `WeaponData.EffectPrefab` has `InstantWeaponEffectManager` +|.AssetPathb" on `InstantWeaponData` records and `ConstantWeaponEffectManager` on `ConstantWeaponData` +|.AssetPathb" records; `ThrusterData.ParticlesPrefab` and `AetherDriveData.Particles` have `ParticleSystem`. +|.AssetPathb" An attributed member missing from the table fails the check, so a new field cannot skip it. +|.AssetPathb" +|.AssetPathb"It reports record name, member, GUID and path per failure. The check does not write, and no +|.AssetPathb"repair path exists. +|.AssetPathb" +|.AssetPathb"## 3. Data migration (Cut 2, First) +|.AssetPathb" +|.AssetPathb"- Scratch-only program over `Aetheria.Shared` at the pre-Cut-2 commit (the fork K (a) +|.AssetPathb" precedent: no landed bypass, no one-shot command left behind). It opens a **copy** of the +|.AssetPathb" catalog writable, walks every stored document for `[CultInspectorAssetPath]` string members, +|.AssetPathb" and maps each non-empty path to the `guid:` of `/.meta`. It writes through the +|.AssetPathb" catalog's own types and commits once. +|.AssetPathb"- Report, one line per value: record type, record name, member, old path, GUID. Unresolved +|.AssetPathb" paths are listed and **fail the run with nothing written**; none are expected (1.1). Empty +|.AssetPathb" values stay empty. +|.AssetPathb"- No record is added or removed, and no global is touched. Member names, keys and `JsonProperty` +|.AssetPathb" names are unchanged: the member still names the asset, and the attribute says the form. +|.AssetPathb"- The rewritten `GameData/Aetheria.cc` (LFS) lands in the same commit as the code. A commit +|.AssetPathb" that holds GUIDs with path-loading code, or paths with GUID-loading code, loads nothing. +|.AssetPathb"- Before/after: `AetherDb census` and `dangling` outputs are byte-identical (same capture +|.AssetPathb" method both sides); the report has 30 lines; a scratch re-read finds 30 GUIDs, 108 empty +|.AssetPathb" strings and no string starting with `Assets/`. +|.AssetPathb" +|.AssetPathb"## 4. Cuts +|.AssetPathb" +|.AssetPathb"### Cut 0. CultLib: asset GUID attribute and Studio picker +|.AssetPathb" +|.AssetPathb"- **Repo/branch:** CultLib, a `codex/` branch from `main` `244154e`. Releases before Cut 2. +|.AssetPathb"- **Deletes first:** `CultInspectorAssetPathAttribute` (`src/GameCult.Caching/CultInspectorAttributes.cs:65-77`); +|.AssetPathb" `CultInspectorMetadata.AssetPath` (`CultInspectorModel.cs:44,56`); the path branch in +|.AssetPathb" `CultCacheStudioDrawers.cs:170-176`. +|.AssetPathb"- **Adds:** `CultInspectorAssetGuidAttribute(Type? assetType = null)` with the identity comment; +|.AssetPathb" `CultInspectorMetadata.AssetGuid`; the drawer branch: `GUIDToAssetPath` then +|.AssetPathb" `LoadAssetAtPath(path, assetType)`, then `ObjectField`, storing `AssetPathToGUID(next)` or +|.AssetPathb" `string.Empty`. README `src/GameCult.Unity/Assets/Caching/README.md:153,164`. +|.AssetPathb"- **Release:** rebuild plugins, bump `unity/org.gamecult.cultlib/package.json` to 1.0.60 and +|.AssetPathb" `src/GameCult.Unity/Assets/Caching/package.json` to 1.4.0 (depending on cultlib 1.0.60), tag both. +|.AssetPathb"- **Verification:** +|.AssetPathb" - builds: `GameCult.Caching`, the CultLib test project that covers `CultInspectorModel`, the +|.AssetPathb" Unity package build script. +|.AssetPathb" - tests: a model test pins that a member carrying the GUID attribute exposes `AssetGuid` and +|.AssetPathb" its `AssetType`. +|.AssetPathb" - negative: `git grep -n "AssetPath" -- src unity` finds nothing (checked: today the only hits +|.AssetPathb" are the three files above and the README). +|.AssetPathb" - operator (Unity, after Cut 2's bump): pick a prefab in Studio, the stored value is its GUID, +|.AssetPathb" and reopening shows the same asset. +|.AssetPathb" +|.AssetPathb"### Cut 1. Aetheria: Addressables settings +|.AssetPathb" +|.AssetPathb"- **Repo/branch:** Aetheria `codex/item-provenance`, after settings Cut 0 lands. Independent of Cut 0. +|.AssetPathb"- **First:** Unity closed (operator), because batchmode refuses an open project. +|.AssetPathb"- **Adds:** `"com.unity.addressables": "2.9.1"` as a direct dependency in `Packages/manifest.json`, +|.AssetPathb" since Aetheria code now uses it; the lock entry goes from depth 1 to 0. Settings created by a +|.AssetPathb" scratch-only batchmode `-executeMethod` script, which is not landed: `Create`, then set +|.AssetPathb" `BuildAddressablesWithPlayerBuild = BuildWithPlayer`, create `Assets/Content/` (+`.meta`) and +|.AssetPathb" `CreateOrMoveEntry(guid of Assets/Content, DefaultGroup)`. Operator alternative: *Window > +|.AssetPathb" Asset Management > Addressables > Groups > Create Addressables Settings*, then the same three +|.AssetPathb" edits by hand. +|.AssetPathb"- **Intentional Unity churn (the whole list):** `Packages/manifest.json`, +|.AssetPathb" `Packages/packages-lock.json`, `ProjectSettings/EditorBuildSettings.asset` (one +|.AssetPathb" `com.unity.addressableassets` config object), `Assets/Content.meta`, and under +|.AssetPathb" `Assets/AddressableAssetsData/`: `AddressableAssetSettings.asset`, `DefaultObject.asset`, +|.AssetPathb" `AssetGroups/Default Local Group.asset`, `AssetGroups/Schemas/Default Local Group_BundledAssetGroupSchema.asset`, +|.AssetPathb" `AssetGroups/Schemas/Default Local Group_ContentUpdateGroupSchema.asset`, +|.AssetPathb" `AssetGroupTemplates/Packed Assets.asset`, `DataBuilders/BuildScriptFastMode.asset`, +|.AssetPathb" `DataBuilders/BuildScriptPackedPlayMode.asset`, `DataBuilders/BuildScriptPackedMode.asset`, +|.AssetPathb" and every folder and file `.meta`. Nothing else: the modified materials and render textures +|.AssetPathb" in the worktree today are pre-existing Unity churn and stay unstaged. +|.AssetPathb"- **Harmless alone:** no code loads through Addressables yet, and `Assets/Content` is empty. +|.AssetPathb"- **Verification:** +|.AssetPathb" - operator: Unity opens without Addressables errors; the Groups window shows `Assets/Content` +|.AssetPathb" under `Default Local Group`; the ARPG play smoke is unchanged. +|.AssetPathb" - negative: `git show --stat` lists only the files above. +|.AssetPathb" +|.AssetPathb"### Cut 2. Aetheria: switch every data asset reference to GUIDs +|.AssetPathb" +|.AssetPathb"One commit, or a series whose intermediate commits Soul accepts as unplayable. See section 3 +|.AssetPathb"for why it is atomic. +|.AssetPathb" +|.AssetPathb"- **Depends on:** Cuts 0 and 1, and Q1. +|.AssetPathb"- **First:** the migration report (section 3) against the pre-cut catalog; `AetherDb census` and +|.AssetPathb" `dangling` captures; `git grep -n "Assets/Resources/" -- Assets/Scripts` capture. +|.AssetPathb"- **Deletes first:** +|.AssetPathb" - `Assets/Scripts/UnityHelpers.cs` (+`.meta`), 11 lines. +|.AssetPathb" - `Gameplay/ActionBarSlot.cs:114`, `:153`: the inline `Substring("Assets/Resources/".Length).Split('.').First()` +|.AssetPathb" loads (and `using System.Linq` if nothing else uses it). +|.AssetPathb" - All 8 `CultInspectorAssetPath` usages (`ItemData.cs:352,362,392,499`, `Weapon.cs:43`, +|.AssetPathb" `Thruster.cs:26`, `AetherDrive.cs:50`, `Corporations.cs:25`) become `CultInspectorAssetGuid`. +|.AssetPathb"- **Moves (git mv, file and `.meta` together, guids unchanged):** `Assets/Resources/Prefabs/` to +|.AssetPathb" `Assets/Content/Prefabs/`, `Assets/Resources/Schematics/` to `Assets/Content/Schematics/`, +|.AssetPathb" `Assets/Resources/Sprites/Icons/` to `Assets/Content/Sprites/Icons/`, plus their folder `.meta` files. +|.AssetPathb"- **Adds:** `Assets/Scripts/EngineAssets.cs` (2.4); `Assets/Scripts/Editor/EngineAssetCheck.cs` (2.5); +|.AssetPathb" the migrated `GameData/Aetheria.cc`; the CultLib bump (manifest URLs `#cultlib-unity-v1.0.60` +|.AssetPathb" and `#caching-unity-v1.4.0`, lock, `Directory.Build.props:5` `CultLibRevision`). +|.AssetPathb"- **Per-file changes (against `f8396313`):** +|.AssetPathb" - `ActionBarSlot.cs:114`, `:153`: `EngineAssets.Load(...)`. +|.AssetPathb" - `EntityInstance.cs:195`, `:224`: `EngineAssets.Load` / +|.AssetPathb" `(data.EffectPrefab)`; the error text at `:200`, `:229` says GUID, not path. +|.AssetPathb" - `ShipInstance.cs:53`, `:67`: `EngineAssets.Load`. +|.AssetPathb" - `ZoneRenderer.cs:295`, `:304`: `EngineAssets.Load`. +|.AssetPathb" - `Editor/CultCacheDrawers.cs:104`: `LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(item.Schematic))`. +|.AssetPathb"- **Authority map:** +|.AssetPathb" - Owner: the catalog field holds the GUID. `EngineAssets` owns turning a GUID into a loaded +|.AssetPathb" asset and holding its handle. The `Assets/Content` folder entry owns addressability. +|.AssetPathb" - Inputs: GUID strings from catalog records; the Addressables catalog (editor locator or player catalog). +|.AssetPathb" - Outputs: loaded `Object`s or components, or `null` with a logged GUID. +|.AssetPathb" - Derived state: asset paths are display-only (Studio, check reports). Group entries are +|.AssetPathb" derived from folder location. +|.AssetPathb" - Forbidden writers: `Resources.Load` for data-named assets; any path-prefix stripping; any +|.AssetPathb" runtime or editor code that writes a path into an asset member; per-asset entries created by +|.AssetPathb" scripts outside `Assets/Content`. +|.AssetPathb" - Shared paths: game runtime, editor play mode and player builds all load through +|.AssetPathb" `EngineAssets` and the same GUID keys. Studio authoring and the migration both store GUIDs. +|.AssetPathb" The check reads through the same `AetheriaStores.Open`. +|.AssetPathb" - Deletion line: `UnityHelpers.cs`, the two inline strips, and the path attribute are gone +|.AssetPathb" before `EngineAssets` exists. +|.AssetPathb"- **Verification:** +|.AssetPathb" - builds: `Aetheria.Shared` headless with `-p:CultLibRoot=`; +|.AssetPathb" `tests/Aetheria.Shared.Tests`; Unity batchmode compile. +|.AssetPathb" - tests: `EngineAssetCheck.Run` in batchmode exits 0 on the migrated catalog. Mutation: on a +|.AssetPathb" scratch catalog copy, set one `HullData.Prefab` to a Texture2D GUID, one to a GUID outside +|.AssetPathb" `Assets/Content`, one to a random 32-hex, and one to an old path. Each must fail with its +|.AssetPathb" record named. Also add a scratch attributed member missing from the table; it must fail. +|.AssetPathb" - data: section 3 before/after checks. +|.AssetPathb" - negative (checked against `f8396313` for collisions; each has hits only at the sites above today): +|.AssetPathb" - `git grep -n "Resources.Load" -- Assets/Scripts` gives exactly `ActionGameManager.cs:420`. +|.AssetPathb" - `git grep -n "Assets/Resources/" -- '*.cs'` gives only the generated comments at +|.AssetPathb" `AetheriaInput.cs:5,19`, `GradientMapper.cs:94,96`, and `tools/AetherDb/AuthoredSettings.cs:13` +|.AssetPathb" until settings Cut 1 deletes that file. +|.AssetPathb" - `git grep -n "UnityHelpers\|CultInspectorAssetPath"` gives nothing outside `docs/`. +|.AssetPathb" - `git ls-files Assets/Resources/Prefabs Assets/Resources/Schematics Assets/Resources/Sprites/Icons` is empty. +|.AssetPathb" - operator (Unity): +|.AssetPathb" 1. Addressables Play Mode Script "Use Asset Database": ARPG play smoke. Ships, turret and +|.AssetPathb" Zenith station spawn; thruster and aether drive particles show; fire an instant weapon +|.AssetPathb" (e.g. Autocannon) and a charged one (ChargeBlast SG); the action bar shows icons where +|.AssetPathb" the settings fallback applies. +|.AssetPathb" 2. Play Mode Script "Use Existing Build" after *Build > New Build > Default Build Script*: +|.AssetPathb" the same smoke. This proves the player catalog carries GUID keys. +|.AssetPathb" 3. Addressables *Analyze*: run "Check Resources to Addressable Duplicate Dependencies" and +|.AssetPathb" record the result. The 5 known leftovers (1.4) are expected. +|.AssetPathb" 4. Studio: `LonginusX` schematic still underlays the shape drawer. +|.AssetPathb" +|.AssetPathb"## 5. Subtraction ledger (estimate) +|.AssetPathb" +|.AssetPathb"| Cut | Removed | Added | Dependencies, formats, targets | +|.AssetPathb"|---|---|---|---| +|.AssetPathb"| 0 | path attribute ~12, metadata 2, drawer branch 6 | GUID attribute ~12, metadata 2, drawer branch ~7, test ~15 | CultLib release; no targets | +|.AssetPathb"| 1 | none | Addressables settings assets (Unity YAML, ~12 files) | `com.unity.addressables` becomes direct (already installed); Addressables build step joins player builds | +|.AssetPathb"| 2 | `UnityHelpers.cs` 11, 2 inline strips, 283 assets (584 files with `.meta`) leave `Assets/Resources` (moved, not deleted) | `EngineAssets.cs` ~30, `EngineAssetCheck.cs` ~80, 30 catalog values rewritten | removes the Resources path convention for data; no targets | +|.AssetPathb" +|.AssetPathb"Net code is positive (~+110). It buys the ruled capability (GUID identity that survives moves, +|.AssetPathb"and one loader) and a check that did not exist: today a mistyped path fails only at spawn. +|.AssetPathb" +|.AssetPathb"## 6. Operator forks +|.AssetPathb" +|.AssetPathb"**Q1. How assets become addressable.** Blocks Cut 1. +|.AssetPathb"- (A) One folder entry `Assets/Content`. Addressable means under that folder. Cut 2 moves +|.AssetPathb" `Prefabs/`, `Schematics/` and `Sprites/Icons/` out of Resources into it, and settings Cut 1 +|.AssetPathb" moves the 7 body presets there. New content is addressable by placement, and the group file +|.AssetPathb" does not change per asset. Unreferenced assets in those folders ship too, as they do from +|.AssetPathb" Resources today. +|.AssetPathb"- (B) One entry per referenced asset, assets kept in place, except that the 27 in Resources must +|.AssetPathb" still move (2.2). Only referenced assets ship. Each new pick needs a manual *Addressable* tick +|.AssetPathb" (the check catches a miss), and the group asset changes with every new reference. +|.AssetPathb"- (C) Per-asset entries generated from the catalog by an editor step before play and build. The +|.AssetPathb" group becomes derived state, but a sync step runs at play and build boundaries, and a stale +|.AssetPathb" group between steps fails loads in play mode. +|.AssetPathb"- **Recommend (A).** It makes the invariant structural (location) with no per-asset bookkeeping +|.AssetPathb" and no sync step, and it matches how Resources behaves today. +|.AssetPathb" +|.AssetPathb"## Follow-ups outside this migration +|.AssetPathb" +|.AssetPathb"- `ActionGameManager.cs:420` loads input prompt sprites from `Resources/Sprites/Input` by control +|.AssetPathb" name (1.3). +|.AssetPathb"- 5 Resources textures referenced by moved prefabs stay in Resources and ship twice (1.4). +|.AssetPathb"- `com.unity.localization` 1.5.8 is a direct dependency with no script use and no settings; it +|.AssetPathb" was the only reason Addressables was installed. +|.AssetPathb"- `Faction.Logo` (5 values) has no reader; `EquippableItemData.ActionBarIcon` and +|.AssetPathb" `ConsumableItemData.Icon` hold no values (1.1). +|.AssetPathb"- Doc sweep for Self: `docs/settings-globals-cut.md` still carries path-based text in 2.1 +|.AssetPathb" (`:238`, "Engine asset paths (forks I and B...)"), 3.2 (`:328`), Cut 1, and forks I and B +|.AssetPathb" (`:522-541`). Those become history once this map is ruled. Settings Cut 1's icon and preset +|.AssetPathb" fields use `[CultInspectorAssetGuid]` and `EngineAssets`.