# Renderer, Material, and Component Foundation Evaluation

Date: 2026-07-11  
Branch: `codex/renderer-material-component-foundation`

This record captures implementation evidence for the dedicated skinned renderer, shared Material
and Material Instance workflow, registry-driven authoring components, and custom Surface contract.
The permanent design contracts live in [ADR 0033](../../../adr/0033-dedicated-skinned-mesh-renderer.md),
[ADR 0034](../../../adr/0034-registry-driven-authoring-components.md),
[ADR 0035](../../../adr/0035-shared-material-assets-and-renderer-slots.md), and
[ADR 0036](../../../adr/0036-surface-abi-and-solari-parity.md). The user workflow lives in the
[material-system guide](../../material-system.md).

## Live editor evidence

All images below are native Wayland captures from the running editor. The temporary actors and
assignments used for visual verification were not saved into the authored scene.

### Dedicated skinned renderer and stable material slots

![RobotExpressive in its explicit Standing edit pose with a dedicated Skinned Mesh Renderer and stable named material slots](skinned-mesh-renderer-material-slots.png)

The actor is hydrated through `SkinnedMeshRenderer`, not `StaticMeshRenderer`. Its imported
hierarchy remains intact, the configured default Standing clip is sampled in edit mode, and every
draw binding exposes a stable slot ID plus source-material fallback.

### Shared custom Surface assigned through a Material Instance

![Custom Surface Material Instance assigned across the skinned renderer slots](custom-surface-on-skinned-renderer.png)

Applying `surface_tint_instance` updates the existing hydrated draw bindings in place. The visible
blue Surface evaluation does not respawn the hierarchy or reset the sampled pose.

### Sparse Material Instance authoring

![Material Instance editor showing sparse shader and texture overrides](material-instance-sparse-overrides.png)

Instances reference one direct base Material. Unchecked values inherit; selecting or dropping a
texture creates only that override, and **Clear** removes the binding so inheritance resumes.

### Registry-driven component composition

![One actor composed from Static Mesh Renderer and Light components](mesh-light-component-composition.png)

The actor combines renderer and light components without an `ActorKind` exclusivity rule. The
registry owns add/remove/reset/copy/paste/history/persistence behavior; `ActorKind` remains a
derived compatibility and presentation hint.

### Larger material thumbnail previews

![Material thumbnails with tightly framed sphere previews](larger-material-thumbnail-previews.png)

Material spheres now fill roughly 78-81% of the preview while retaining edge padding. Model and
mesh-subasset framing is unchanged.

Additional captures:

- [Material editor and Create Instance action](material-editor-create-instance.png)
- [Registry-driven Add Component categories](component-registry-add-menu.png)

## Acceptance results

| Area | Result | Evidence |
|------|--------|----------|
| Dedicated static/skinned ownership | Pass | Separate authored/hydrated renderer types; rigged/animated manifests are rejected by static hydration |
| Pose lifecycle | Pass | Explicit stable default clip; transform and morph-weight baseline restore on stop, controller removal, preview reset, and PIE exit |
| Renderer material slots | Pass | Stable named IDs, imported fallback, explicit assignment, orphan retention, and in-place material patching |
| Material assets/instances | Pass | Versioned shared assets, direct-base sparse instances, transient property blocks, Apply/Revert/Create Instance |
| Texture parameters | Pass | Project-wide picker includes imported texture subassets; base/schema/stored/instance slots share browse, drop, reject, and clear behavior |
| Component lifecycle | Pass | Stable registry IDs, reflected transactions/history, persistence allowlist, independent active state, and compatible composition |
| Surface raster/Solari semantics | Pass for eligible geometry | Shared ABI packing, evaluator, textures, normal/emissive/unlit values, and cutout predicate |
| Project migration | Pass | Transactional schema-v4 upgrader, backup/rollback path, canonical slots and active-state split; second dry run is empty |
| Project validation | Pass | 59 dependencies, 5 non-blocking findings, 0 blocking errors |

## Automated verification

| Command/suite | Result |
|---------------|--------|
| `cargo test -p shared --offline` | 84 passed |
| `cargo test -p scene --offline` | 64 passed, 1 ignored migration fixture |
| `cargo test -p game --lib --offline` | 37 passed |
| `cargo test -p game_hot --lib --offline` | 16 passed |
| `cargo test -p editor --lib --offline` | 171 passed |
| `cargo test -p blacksite_surface --offline` | 3 passed plus doc tests |
| `cargo test -p bevy_solari --lib --offline` | 1 passed |
| `cargo --locked --offline validate-levels` | 0 blocking errors |
| `cargo --offline upgrade-project --project . --json` | No pending changes or warnings |
| `cargo fmt --all -- --check` / `git diff --check` | Pass |

## Deliberate boundary

Bevy 0.19's maintained Solari path does not expose dynamic post-morph/post-skin vertex buffers and
BLAS updates. Skinned and morph-deformed actors therefore remain visible in raster but are omitted
from ray tracing with an actionable diagnostic. This is safer than tracing a stale bind-pose ghost;
dynamic deformed BLAS support remains future work and the corresponding Gitea acceptance item stays
open.
