Blacksite/docs/adr/0035-shared-material-assets-and-renderer-slots.md

93 lines
6.5 KiB
Markdown

# ADR 0035: Shared Material Assets and Renderer Slots
## Status
Accepted
## Context
Renderer material authoring previously mixed copied `MaterialDesc` values, imported source-material
defaults, and static-mesh draw data. That made a renderer's ownership unclear, encouraged material
copies per actor, and left rigged geometry without the same assignment model as static geometry.
Matching imported materials by a display name was also unsafe: names are neither unique nor stable
when a model is reimported.
Primitives, static renderers, and skinned renderers need the same Unity-like authoring contract
without sharing their geometry or hydration components. A project material must be reusable, an
instance must express explicit overrides over one base material, and every renderable surface must
expose stable material slots independently from its draw or skeleton representation.
## Decision
- Project materials use versioned `MaterialAsset` documents. A `MaterialInstanceAsset` references
one direct base `MaterialAsset` and stores parameter and texture overrides. Nested material
instances are invalid. Schema-driven input storage and processed Texture bindings are defined by
[ADR 0046](0046-schema-driven-material-inputs-and-processed-textures.md).
- `MaterialRef` is the assignment type. Its registry UUID and subasset ID are authoritative;
`source_path` is a cached loading hint that validation or migration may repair after a move.
- `Primitive`, `StaticMeshRenderer`, and `SkinnedMeshRenderer` own `MaterialSlot` values. Mesh
renderers use a `MaterialSlotSet`; a primitive owns one stable `surface` slot with ID
`slot:primitive:surface`. They do not share a geometry component or hydration path. Each saved
slot has a stable ID, a presentation name, and only an optional actor/prefab `material`
assignment. Model defaults and imported source materials remain asset-owned in model import
metadata and runtime manifests. Clearing an actor assignment returns resolution to those weaker
model-owned layers.
- Static draw parts and imported skinned-hierarchy draws bind to material slots by stable IDs.
Reimport reconciles by ID only. An explicit assignment whose source slot disappears is retained as
an `OrphanedMaterialAssignment`; the editor never silently reconnects it by display name.
- Asset Browser **Apply Material** assigns a shared material or material instance to a selected
primitive surface or every material slot on each selected static/skinned renderer. One inspector
widget provides drag/drop, Browse/Locate/Clear, inherited status, expandable shared parameters,
exact-slot extraction, and orphan diagnostics.
- `HydratedMaterialSlotBinding` identifies each runtime draw's owner and exact slot.
`HydratedMaterialSelection` carries Project Actor, Project Model, Imported Source, or Inherit
ownership without copying model defaults into the scene. A skinned renderer material-only edit
patches the instantiated hierarchy without reloading its source hierarchy or joints.
- `MaterialPropertyBlocks` are runtime-only, per-slot overrides. They do not mutate a shared asset
and are excluded from scene and prefab persistence. Persisting a reusable variation requires an
explicit material instance.
- Model assets may provide a project Material/Instance default between their imported source and a
scene/prefab assignment. The terminal fallback chain and immutable DefaultGrid contract are owned
by [ADR 0045](0045-content-workspace-and-material-fallback-contract.md).
- Effective precedence is runtime MaterialPropertyBlock, scene/prefab assignment, model-asset
default, imported source, project default, then built-in DefaultGrid. A configured but broken
authored reference uses the fallback with diagnostics instead of silently exposing another
authored layer.
- Repository scenes use schema v6, where renderable slots persist actor/prefab assignments only.
This pre-production project updates its fixtures directly. The explicit project upgrader remains
available for legacy primitive `MaterialDesc`, mesh-wide descriptors, and per-slot
`MaterialOverride` values: it deduplicates canonical descriptors as deterministic project
Materials under `assets/materials/migrated/` and rewrites project references transactionally.
Ordinary loading remains non-writing.
## Consequences
- Primitives, static renderers, and skinned renderers have one material-slot workflow while
retaining distinct geometry ownership and hydration behavior.
- An assignment remains a reference rather than a copied material value, so editing or replacing a
shared asset does not create actor-local material copies. Material/base/schema/evaluator
dependency revisions update the shared runtime handle in place across all assigned slots.
- Source reimport cannot silently move an override to the wrong draw. Orphans require an explicit
user decision to reassign or discard.
- Component-form `MaterialDesc` is restricted to the brush fallback compatibility path. It is not
addable to primitives or mesh renderers, does not override their slots, and never causes a
primitive-local runtime material allocation. Unmigrated actor material components produce an
upgrade diagnostic and visible fallback.
- `MaterialPropertyBlocks` define the runtime-only schema and persistence exclusion. Runtime
application clones and caches the resolved Standard or Surface material per owner/slot, above
direct-base Material Instance values. An invalid block leaves the resolved base visible and emits
a deduplicated diagnostic instead of applying a partial override. Promotion to a reusable
instance uses an explicit target-path review and source/target fingerprint guards, assigns one
exact slot through history, and clears the block only after the file, registry, and scene
transaction succeeds. It may never use DefaultGrid as its base. The editor registers the
component with reflection for trusted local BRP runtime injection and native promotion testing;
that registration does not add it to authoring persistence or the Inspector component registry.
- Material graph authoring, nested instances, and blended/transmissive materials are outside this
decision.
Scene schema v6 stores only the actor/prefab assignment in a renderable slot. Model defaults and
imported source selections remain model-owned, and hydration carries their origin through
`HydratedMaterialSelection`. Live project resolution is owned solely by the overlay/cache boundary
in [ADR 0047](0047-editor-authored-asset-documents.md); shared slot presentation follows
[ADR 0048](0048-modular-editor-composition-and-debt-ratchet.md).