# 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).
