Blacksite/docs/adr/0033-dedicated-skinned-mesh-renderer.md
Rbanh 0798aa5d57 Build renderer and material component foundations
Add dedicated skinned rendering, pose restoration, shared Material and Material Instance slots, registry-driven components, Surface/Solari integration, transactional schema upgrades, navigation authoring, documentation, and evaluation evidence.
2026-07-12 00:24:06 -04:00

61 lines
3.5 KiB
Markdown

# ADR 0033: Dedicated Skinned Mesh Renderer
## Status
Accepted
## Context
The normalized model artifact originally exposed every imported primitive to
`StaticMeshRenderer`, including primitives with joint indices and weights. Whole-model static
placement could therefore detach rigged geometry from its joints and render it in bind pose. The
animation workflow avoided individual skinned-part placement, but it still represented animated
geometry as generic `ImportedModel + ModelRef`, leaving no authoring boundary between an arbitrary
source scene and geometry that requires a skeleton hierarchy.
Static, generic scene-instance, and skinned rendering have different ownership rules. Static parts
can be flattened into independent slots and triangle-mesh colliders. A skinned mesh must retain the
source joints, inverse bind poses, animation player, and hierarchy. A generic scene instance may
contain cameras, lights, or other source data and does not imply an animation contract.
## Decision
Rigged geometry uses `ActorKind::SkinnedMesh + SkinnedMeshRenderer`. The renderer stores stable
asset identity, cached source path, and source scene index. Its dedicated hydration system creates a
`HydratedSkinnedMeshRoot` and instantiates the complete source hierarchy so Bevy owns the runtime
`SkinnedMesh`, joints, inverse bind poses, and animation-player placement. The animation runtime
binds only to this dedicated root. `AnimationControllerDesc` is valid only beside a
`SkinnedMeshRenderer`.
Renderer hydration itself remains animation-agnostic. The animation adapter captures the imported
hierarchy baseline when that root becomes ready and optionally drives the explicit default clip
recorded by animation-manifest schema v3. Without a controller, the dedicated renderer remains at
the captured imported pose.
Static-mesh artifact schema v2 marks every part as skinned or unskinned. Static renderer creation
filters out skinned parts and all geometry from an animated source; static hydration enforces the
same rule even if a scene contains a hand-authored slot. Automatic renderable-model placement
chooses `SkinnedMeshRenderer` when any source part is skin-bound or the source contains animation,
and otherwise chooses `StaticMeshRenderer`. Placing a skinned or animated subasset
places its owning model through the dedicated renderer; it never creates a detached static slot.
`ImportedModel + ModelRef` remains the explicit generic scene-instance path. It is not the animation
renderer. Scene schema v3 migrates v2 entities containing both `ModelRef` and
`AnimationControllerDesc` to `SkinnedMeshRenderer + ActorKind::SkinnedMesh`; unrelated imported
scene instances remain unchanged.
Automatic triangle-mesh collider generation applies only to static renderers. Animated collision
must be authored explicitly with stable gameplay shapes or hitboxes rather than a deforming render
surface.
## Consequences
- Static renderer slots can no longer silently accept newly imported skinned or animated geometry.
- Rigged models retain the exact hierarchy Bevy needs for skinning and animation playback.
- Generic scene instances, static meshes, and skinned meshes have distinct persisted components,
actor kinds, hydration markers, validation, and editor presentation.
- A source containing both rigid and skinned nodes is placed as one skinned actor so the imported
hierarchy remains coherent; splitting it requires separate source assets.
- FBX skinning and animation remain blocked by ADR 0031 until the FBX loader can build the same
runtime hierarchy and clip contract as glTF.