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

3.5 KiB

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.