# Skeletal Animation Import, Preview, And Authoring

Working implementation plan for Gitea issue
[`#46`](https://git.spacetrainclubhouse.com/Falling-Metal-Interactive/Blacksite/issues/46).
This is the minimum production animation loop required by the M7 content-production milestone.

## Status

Implementation and production acceptance are complete. The persistent/runtime contract, generated
manifest, stable model/clip identity, runtime adapter, browser drag workflow, typed inspector, and
headless validation are implemented. Live acceptance covers import, placement, preview, scrub,
save/reload hydration, PIE playback, source relocation, development packaging, and standalone
runtime playback with the committed RobotExpressive fixture.

## Outcome

An author can import a supported skinned model, inspect its skeleton and clips, place an animated
actor, preview and scrub a clip without dirtying the scene, save a small named state set, and drive
the same state changes in PIE and a packaged game. Missing content and skeleton mismatches fail with
owner-attributed repair guidance before packaging.

## Format Policy

- Animation authoring v1 supports glTF 2.0 (`.gltf` and `.glb`) through Bevy's native scene,
  skinning, and animation assets.
- FBX remains supported for the existing static mesh import path. An FBX containing animation stacks
  or skin deformers is not a supported animated source and must produce a blocking, actionable
  diagnostic.
- Animated FBX support cannot be enabled until the loader constructs the source hierarchy, Bevy
  skinned-mesh bindings, and `AnimationClip` assets. Metadata counts alone do not qualify as support.

## Architecture

- The asset registry UUID remains the parent model identity. A generated animation manifest under
  `assets/animations/generated/` records the source fingerprint, skeleton signatures, stable clip
  sub-asset IDs, clip metadata, and animation events.
- `shared::AnimationControllerDesc` is reflected and serializable. It references imported skeletons
  and clips through stable `EditorAssetRef` values and owns one layer of named states, a default
  state, playback range/loop/speed, and default crossfade duration.
- `game` owns the runtime adapter. It waits for the model `WorldAssetRoot` instance to become ready,
  attaches transient animation graph/player state, and exposes a request API for changing an actor's
  named state.
- `editor` owns import presentation, clip/skeleton shelves, drag assignment/placement, inspector
  controls, and non-dirty preview/scrubbing. Preview uses the runtime adapter rather than a separate
  interpretation of the authored contract.
- `scene` and package validation resolve the same manifests and stable references used by editor and
  runtime hydration.

## Authoritative Data

The generated manifest is keyed by the model's stable registry UUID and includes:

- schema version and source path/fingerprint;
- scenes and exact skeleton signatures;
- stable skeleton and clip sub-asset IDs;
- clip label, source index, duration, target skeleton signature, and event markers;
- import diagnostics, including unsupported animated FBX content.

An exact skeleton signature is a deterministic hash of normalized ordered joint target paths and
bind-pose structure. Retargeting v1 accepts only an exact signature match. No implicit joint-name,
humanoid, or proportional remapping is allowed.

## Implementation Sequence

1. Add the versioned animation manifest schema, stable sub-asset identity rules, glTF extraction,
   FBX blocking diagnostics, and focused importer tests.
2. Add `AnimationControllerDesc`, named state/event types, registration, migration defaults,
   actor validation, history snapshots, scene round-trip coverage, and runtime-only strip policy.
3. Replace broad imported-model child cleanup with a dedicated generated instance root, then hydrate
   the ready scene instance into a transient graph/player and implement the game state-request API.
4. Extend Asset Browser model shelves with skeleton and clip rows, compatibility status, preview,
   scrub/range/loop controls, and clip drag placement or assignment.
5. Add the typed Animation Controller inspector, undoable state edits, PIE pause/stop reconciliation,
   owner-attributed project validation, and package dependency collection.
6. Commit a small redistributable animated GLB and sample scene, then run import, round-trip,
   headless, live editor/PIE, and packaged-runtime acceptance.

## Acceptance Gates

- A committed skinned GLB and its clips import, preview, save, hydrate, and play in PIE and a
  packaged runtime.
- Moving or renaming the source inside the project preserves controller clip references through the
  registry UUID and stable sub-asset IDs.
- Missing manifests/clips and mismatched skeleton signatures produce actor/property-attributed
  diagnostics with a concrete repair action.
- Gameplay can request a valid named state through the public game API; invalid requests leave the
  current state intact and report a diagnostic.
- Preview follows the authored loop/range/speed settings, stops cleanly, and never mutates the scene
  descriptor, history, or dirty state.
- Generated graph/player/instance components never serialize into authored scenes, and model
  rehydration never deletes authored hierarchy children.
- Animated or skinned FBX content blocks animation import with a diagnostic that explains the glTF
  v1 requirement.
- Focused importer, validation, hydration, request-API, and editor operator tests pass alongside
  workspace tests, `cargo validate-levels`, package validation, and live smoke testing.

## Deliberate Boundaries

- V1 provides one animation layer with named states, one default state, and a default crossfade. It
  does not include a visual state graph, blend trees, transition-condition authoring, inverse
  kinematics, motion matching, or root-motion extraction.
- Event markers are normalized data and runtime notifications; V1 has no timeline scripting system.
- Retargeting is exact signature match only.
- FBX static mesh import remains available, but animated FBX conversion is separate loader work and
  cannot silently fall back to bind-pose or metadata-only playback.
