Aetheria/docs/addressables-cut.md
Meta Vens 482229d1e3 Record where the Addressables cuts landed, misattributed moves included
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 05:08:53 +02:00

28 KiB

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<T> 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\|\.AssetPath\b".

Landed (2026-09-18). Cut 1 491811bd, Cut 2 67cbf34b, harness restore 2d348d64. The 587 asset moves out of Resources into Assets/Content are in 1fdf8151, whose message is about the shield map: Self committed a doc while Hands' git mvs were staged in the same index, in the same working tree. History is pushed and shared, so it stands as is. Catalog rewrite: 30 references, all resolved, 0 unresolved; EngineAssetCheck.Run reports 30 stored references OK.

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<Texture2D> with inline prefix strip binding ctor
Gameplay/ActionBarSlot.cs:153 same binding ctor
Gameplay/EntityInstance.cs:195 UnityHelpers.LoadAsset<InstantWeaponEffectManager> SetEntity, then Instantiate
Gameplay/EntityInstance.cs:224 UnityHelpers.LoadAsset<ConstantWeaponEffectManager> same
Gameplay/ShipInstance.cs:53 UnityHelpers.LoadAsset<ParticleSystem> same, Instantiate
Gameplay/ShipInstance.cs:67 same LINQ select, Instantiate
Zone Display/ZoneRenderer.cs:295,304 UnityHelpers.LoadAsset<GameObject> LoadEntity, Instantiate
Editor/CultCacheDrawers.cs:104 AssetDatabase.LoadAssetAtPath<Texture2D> 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<T>(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<T>(string guid) where T : Object. Empty GUID returns null. A Component type loads the GameObject and returns GetComponent<T>().
  • Loads with Addressables.LoadAssetAsync<T>(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 <repo>/<path>.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<Texture2D>(...).
    • EntityInstance.cs:195, :224: EngineAssets.Load<InstantWeaponEffectManager> / <ConstantWeaponEffectManager>(data.EffectPrefab); the error text at :200, :229 says GUID, not path.
    • ShipInstance.cs:53, :67: EngineAssets.Load<ParticleSystem>.
    • ZoneRenderer.cs:295, :304: EngineAssets.Load<GameObject>.
    • Editor/CultCacheDrawers.cs:104: LoadAssetAtPath<Texture2D>(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 Objects 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=<CultLib 1.0.60 scratch worktree>; 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.