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

Static and skinned renderers need the same Unity-like authoring contract without sharing their mesh
or hydration components. A project material must be reusable, an instance must express explicit
overrides over one base material, and each renderer 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.
- `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.
- `StaticMeshRenderer` and `SkinnedMeshRenderer` each own a `RendererMaterialSet`. They do not share
  a mesh component or hydration path. Each `RendererMaterialSlot` has a stable ID, a presentation
  name, a read-only imported `source_material`, and an optional explicit `material` assignment.
  Explicit assignment wins; clearing it returns to the imported source material.
- 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 every material
  slot on each selected static or skinned renderer. Renderer inspectors provide per-slot
  Browse/Select/Locate/Clear controls and expose orphan warnings.
- `HydratedRendererMaterialBinding` identifies each runtime draw's owner, slot, and effective
  material. 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.
- Scene schema v4 migrates legacy renderer assignments into `RendererMaterialSet`. Project material,
  material-instance, shader-schema, and scene rewrites are performed by the explicit transactional
  `cargo upgrade-project` command; ordinary loading remains read-only.

## Consequences

- Static and skinned renderers have a consistent 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.
- Legacy primitive/actor `MaterialDesc` paths remain compatibility paths until their consumers move
  to renderer slots; new mesh-renderer work uses shared references.
- `MaterialPropertyBlocks` currently define the runtime-only schema and persistence exclusion, but
  renderer application and the editor promote-to-instance transaction are not implemented. Gitea
  #53 owns that complete workflow; callers must not treat the component as visibly applied yet.
- Material graph authoring, nested instances, blended/transmissive materials, and a generalized
  per-renderer property-block inspector are outside this decision.
