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

88 lines
5.5 KiB
Markdown

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