# ADR 0045: Content Workspace and Material Fallback Contract ## Status Accepted ## Context Blacksite's Content Browser scans the project `assets/` tree, but authored RON type detection and import destinations still depend on conventional folders. The asset registry is editor-owned and serialized as an unversioned record list, so validation, packaging, runtime hydration, and future headless processing duplicate parts of its contract. Imported model materials are either read-only source defaults or scene-level assignments; a model asset cannot own editable project defaults. Missing renderer materials currently create separate magenta `StandardMaterial` values, while skinned, primitive, brush, terrain, thumbnail, raster, and Solari paths use different fallback rules. A missing or broken material therefore does not have one predictable, production-safe result. ## Decision - User-authored content may live in any normal nested folder beneath the project `assets/` root. Folder names are organizational only. External absolute linked assets are not supported. - `AssetId` is authoritative identity. Project-relative paths and labels are repairable cached metadata. Moves and reimports reconcile by stable IDs and never by display name. - Registry contracts move to `shared::content`: typed asset kinds, versioned registry documents, project content defaults, import settings, model material policies, and runtime catalog records. UI-independent filesystem scanning, classification, transaction planning, import/reimport, and processing live in a `content_pipeline` crate used by the editor, validator, watcher, and xtask. - Registry schema v3 is `AssetRegistryDocument { schema_version, defaults, records }` with typed Model/Texture import settings as extended by [ADR 0046](0046-schema-driven-material-inputs-and-processed-textures.md). The source registry remains editor-managed under `assets/.index/`; packages receive a stripped runtime catalog without editor import provenance or source-only dependencies. - Project content roots normalize to `assets/`. `.index`, `.trash`, thumbnails, recovery data, and implementation-owned derived artifacts are hidden and protected from normal file operations. - Fixed-folder consumers are retired or explicitly constrained as follows: | Consumer | Current contract / migration | |----------|------------------------------| | Project settings and templates | `asset_roots` upgrades to exactly `["assets"]`; the explicit project upgrader performs the write. | | Content Browser and registry scanner | Walk all user-managed descendants and classify by extension plus document schema, never by parent folder. | | Material, shader, model, audio, level, and prefab lookup | Resolve the registry's stable ID to its current path; a cached path is a repair hint only. | | Scene/prefab validation and hydration | Consume registry/runtime-catalog resolution rather than conventional authoring directories. | | Packaging and headless processing | Scan the same content root and publish the same stripped catalog and normalized model manifests. | | Material creation UX | The current browser folder is authoritative; `assets/materials/` remains only a Material Library convenience default. | | Generated mesh, animation, navigation, thumbnail, trash, and index data | Their fixed implementation-owned locations remain hidden managed storage, excluded from user classification and ordinary file operations. | - Model material selections are per stable slot: Source, Project Material/Instance, or Default. Extracting a source material creates an ordinary project Material and then stores a Project selection. Re-extraction may replace a provenance-matched Material only after an explicit diff and fingerprint-guarded Apply decision; Create New remains available and unrelated provenance is never overwritten. Reimport retains removed project assignments as explicit orphans. - Effective material precedence, strongest first, is: runtime MaterialPropertyBlock, scene/prefab assignment, model-asset default, imported source, project default, built-in DefaultGrid. Missing layers inherit downward. A configured but broken authored reference renders the fallback and reports the broken layer rather than silently revealing a different authored layer. - Primitive surfaces participate in that same chain through their saved `slot:primitive:surface`; they no longer hydrate from a separate actor-local descriptor. Static and skinned draws bind their exact saved slot IDs through the same runtime binding contract. - DefaultGrid is immutable engine content compiled into the renderer. It is a shared, UV-independent world-space checker with raster/Solari parity and a neutral StandardMaterial emergency path. A project may choose a Material/Instance fallback; clearing or breaking it returns to DefaultGrid. Terrain retains its specialized layer fallback. - Built-in DefaultGrid is not editable, instanceable, or a valid MaterialPropertyBlock promotion base. Promotion requires a resolved project or imported Material base. - Ordinary editor/runtime loading is read-only. Registry, project-settings, and scene migrations run only through the explicit transactional project upgrader. ## Consequences - Editor, runtime, validation, packaging, watcher, and headless processing share one content model and one stable-ID resolver. - Arbitrary organization no longer changes asset type, runtime behavior, or package inclusion. - The content pipeline becomes a reusable crate boundary instead of an editor implementation detail; this adds one workspace crate and moves registry types out of `editor`. - Older registries, conventional asset roots, global model material policy, and legacy mesh material overrides require the explicit transactional upgrader; normal loading accepts current schemas. - Broken assignments stay visible and diagnosable, but may look different from their previous imported source because failure uses the project/engine fallback deliberately. - One cached DefaultGrid/emergency handle covers repeated primitive and mesh hydration, so missing assignments do not grow the material asset stores. - Thumbnail cache ownership includes the backing Egui image registration. Replacement, retry, and catalog invalidation explicitly release the last registration before a new studio render owns the key, preventing refresh-driven GPU resource growth. - File operations and imports must be staged transactions that update authored references, registry state, derived manifests, and runtime catalogs together. - Ordinary asset-property edits use the explicit dirty-document and two-phase publication boundary defined by [ADR 0047](0047-editor-authored-asset-documents.md); topology transactions remain immediate. - Scene schema v6 carries actor/model/imported/inherit origin explicitly at hydration time, so a broken authored layer uses DefaultGrid instead of accidentally revealing a weaker layer.