# ADR 0031: Animation Authoring And Runtime Contract

## Status

Accepted

## Context

Blacksite previously recorded animation and skin counts while importing glTF and FBX models, but the
authored scene contract contained only a path-based `ModelRef`. Generic scene-instance hydration loaded 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 `SkinnedMeshRenderer` 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 dedicated `SkinnedMeshRenderer` 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.

Animation-manifest schema v3 adds an optional stable default clip sub-asset ID sourced from model
import settings. It is an explicit presentation policy: renderable animated-model placement creates
a one-state controller only when this ID resolves. An absent value preserves the imported node pose;
the importer and placement workflow never infer a first clip. Generic scene-instance placement does
not consume this setting.

The runtime adapter waits for the renderer's `HydratedSkinnedMeshRoot` 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.

When a hydrated hierarchy becomes ready, the runtime captures its descendant `Transform` and
`MorphWeights` components before evaluating authored playback. Edit mode samples the configured
default state at its range start and pauses it. Preview stop, controller removal, failed graph
reconfiguration, and PIE exit first stop playback and restore the captured baseline; if a valid
default remains, it is then sampled again without a crossfade. Skin validation checks each imported
skin independently for joint/inverse-bind count and hierarchy ownership, including sources that
reuse one joint hierarchy across multiple skin records.

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.
- Explicit edit-rest clips avoid malformed source presentation poses without coupling rendering to
  animation; assets without a configured clip keep their imported rest pose.
- Baseline restoration prevents stopped previews and mode changes from leaving sampled transforms or
  morph weights behind.
- 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.
- The static/skinned renderer boundary and v2-to-v3 scene migration are defined by
  [ADR 0033](0033-dedicated-skinned-mesh-renderer.md).
