Blacksite/docs/adr/0031-animation-authoring-runtime-contract.md
Rbanh 0553a85220
Some checks are pending
CI / Format, lint, test, build (push) Waiting to run
Build production-ready editor authoring workflows
2026-07-11 12:41:04 -04:00

5.5 KiB

ADR 0031: Animation Authoring And Runtime Contract

Status

Accepted

Context

Blacksite currently records animation and skin counts while importing glTF and FBX models, but the authored scene contract contains only a path-based ModelRef. Scene-instance hydration loads a model world without selecting or driving its clips, and the editor has no stable clip references, controller inspector, preview, or compatibility validation.

Bevy 0.19 already exposes complete glTF scene, skin, AnimationClip, AnimationGraph, and AnimationPlayer primitives. The patched FBX loader does not: it parses some skin metadata but does not build the source node hierarchy, attach Bevy skinned-mesh bindings, or convert animation curves into clips. Treating its metadata counts as playback support would create scenes that appear valid but remain static at runtime.

Animation authoring also needs a boundary between persistent intent and transient Bevy player state. Clip references must survive project asset moves, preview must agree with PIE, and a game extension must be able to request authored states without depending on editor types.

Decision

Animation authoring v1 supports glTF 2.0 sources (.gltf and .glb) through Bevy's native loader. FBX remains a supported static-model source only. Import or validation of an FBX containing animation stacks or skin deformers must produce an actionable blocking diagnostic until the loader can construct hierarchy, Bevy skinned meshes, and animation clips.

Each animated model has a versioned generated manifest under assets/animations/generated/, keyed by the model's stable asset-registry UUID. The manifest records the source path and fingerprint, skeletons and their exact signatures, stable clip sub-asset IDs, clip label/index/duration, target skeleton signature, normalized event markers, and import diagnostics. Registry UUID plus sub-asset ID is authoritative; cached ModelRef and EditorAssetRef source paths are only runtime-resolution and repair hints. UUID-keyed generated manifests provide the current source path after a uniquely reconciled move.

shared owns reflected, serializable AnimationControllerDesc and its named state/event value types. A controller sits beside the model scene reference and uses EditorAssetRef for skeleton and clip references. It contains one layer of named states, one default state, a default crossfade duration, and per-state playback range, loop, and speed. State IDs are stable authored identifiers; display labels are not runtime keys.

Retargeting v1 requires an exact skeleton-signature match. The signature is a deterministic hash of ordered joint target paths and bind-pose structure. For glTF, each path is hashed as a length-prefixed vector of the exact UTF-8 node-name bytes Bevy 0.19 uses, including whitespace and path separators; unnamed nodes use Bevy's GltfNode{index} fallback. The readable normalized paths stored in the manifest are display metadata and never compatibility keys. Blacksite does not infer humanoid roles, rename joints, or perform proportional remapping in this version. This exact node identity contract is animation-manifest schema v2; v1 artifacts must be reimported.

The runtime adapter waits for the model WorldAssetRoot instance to report readiness, finds its animation player, and derives Bevy graph/player state from the controller. Generated instance, graph, player, transition, and playback bookkeeping is transient and excluded from authored scene serialization. Rehydration owns a dedicated generated instance root and must not despawn authored children attached to the actor.

game exposes a small request API that changes an animated actor to a named state and applies the authored/default crossfade. Invalid state requests are rejected without disturbing the active state. The editor's preview and scrub controls use the same runtime adapter; starting, stopping, or scrubbing a preview never changes the authored descriptor, history, or scene dirty state.

Interactive diagnostics, headless validation, and packaging resolve the same manifest and enforce source availability, stable reference resolution, supported formats, valid playback ranges/events, and exact skeleton compatibility.

Animation v1 supports one top-level animated hierarchy per glTF asset. Import scans every animation channel using the same top-level roots Bevy uses for AnimationPlayer placement. A source requiring multiple players is marked runtime-unsupported with a blocking repair diagnostic; artists must split the rigs into separate assets or export all clips beneath one common root.

Consequences

  • glTF/GLB is the only animation interchange format promised by v1; artists must convert animated FBX sources before import.
  • Stable registry and sub-asset identities allow source moves without rewriting every controller, while ambiguous or removed clips become explicit validation failures.
  • One-layer named states provide a deterministic gameplay integration point without committing the editor to a visual graph format prematurely.
  • Exact-signature retargeting is intentionally restrictive but cannot silently deform the wrong rig.
  • Rejecting multiple animation roots prevents the runtime adapter from selecting an arbitrary first player and driving only part of a source.
  • A future visual graph, blend trees, root motion, IK, advanced retargeting, or animated FBX support must extend or supersede this contract explicitly rather than leaking runtime Bevy components into authored scenes.
  • Implementation and production acceptance are tracked in Gitea issue #46.