Implement animation playback for imported GLB/GLTF assets in vertical slice order: data model → interaction layer → runtime build → runtime host → serialization/migration → inspector UI → interaction link UI. Each step is immediately integrated; no orphaned code.
- Generate random `ModelInstance` values with optional animation fields; assert `areModelInstancesEqual(original, cloneModelInstance(original))` is true
- Create a document with a model instance that has `animationClipName` and `animationAutoplay` set; assert the built `RuntimeModelInstance` has the same values
- Add `animationMixers: Map<string, AnimationMixer>` and `instanceAnimationClips: Map<string, AnimationClip[]>` private fields to `RuntimeHost` in `src/runtime-three/runtime-host.ts`
- In `rebuildModelInstances`: after creating each render group, check `loadedModelAssets[assetId].animations`; if non-empty, create a mixer, store it, and start autoplay if configured
- In `clearModelInstances`: call `mixer.stopAllAction()` and clear both maps before removing render objects
- In the `render` loop: tick all active mixers with `mixer.update(dt)`
- Add `applyPlayAnimationAction` and `applyStopAnimationAction` private methods
- Extend `createInteractionDispatcher` to wire the two new dispatcher methods to the new private methods
- Create a headless `RuntimeHost` (`enableRendering: false`), load a scene with N animated model instances, assert `animationMixers.size === N`
- **Validates: Requirements 5.1**
- [ ]* 5.2 Write property test: autoplay starts the named clip on scene load
- **Property 4: Autoplay starts the named clip on scene load**
- Load a scene with a model instance with `animationAutoplay: true` and a valid `animationClipName`; assert the mixer has an active action for that clip
- **Validates: Requirements 2.5, 5.4**
- [ ]* 5.3 Write property test: playAnimation action starts the named clip
- **Property 5: playAnimation action starts the named clip**
- Dispatch a `playAnimation` action for a valid instance and clip; assert the mixer has an active action for that clip
- Increment `SCENE_DOCUMENT_VERSION` to `12` and add `ANIMATION_PLAYBACK_SCENE_DOCUMENT_VERSION = 12` in `src/document/scene-document.ts`
- In `src/document/migrate-scene-document.ts`:
- Update `readModelInstance` to read `animationClipName` (optional string, trimmed, empty → undefined) and `animationAutoplay` (optional boolean)
- Update `readInteractionAction` to handle `"playAnimation"` (requires non-empty `targetModelInstanceId` and `clipName`) and `"stopAnimation"` (requires non-empty `targetModelInstanceId`)
- Add a migration branch for v11 → v12 that reads all existing fields and sets `animationClipName: undefined` and `animationAutoplay: undefined` on all model instances
- _Requirements: 8.1, 8.2, 8.3, 8.4, 8.5_
- [ ]* 7.1 Write property test: v11 → v12 migration preserves all existing data
- **Property 11: v11 to v12 migration preserves all existing data**
- Generate random v11 documents; assert migration produces valid v12 documents with animation fields defaulted to `undefined` and all other data unchanged
- **Property 12: Serialization round-trip for v12 documents**
- Generate random v12 documents with animation fields (including `playAnimation` and `stopAnimation` links); assert `parseSceneDocumentJson(serializeSceneDocument(doc))` produces a deeply equal document
- In `src/app/App.tsx`, in the model instance inspector section, add a conditional animation sub-section rendered when `selectedModelAssetRecord.metadata.animationNames.length > 0`
- Render a `<select>` for clip name (options from `animationNames`, plus a "— none —" option) bound to `selectedModelInstance.animationClipName`
- Render a checkbox for `animationAutoplay` bound to `selectedModelInstance.animationAutoplay`
- On change, dispatch `createUpsertModelInstanceCommand` with the updated model instance
- In `src/app/App.tsx`, extend the interaction link action type `<select>` to include `"playAnimation"` and `"stopAnimation"` options
- When `"playAnimation"` is selected, show a model instance picker and a clip name `<select>` (populated from the chosen instance's asset's `animationNames`)
- When `"stopAnimation"` is selected, show only the model instance picker
- Update `getInteractionActionLabel` to return human-readable labels for the new action types
- On save, dispatch `createUpsertInteractionLinkCommand` with the appropriate factory-created link
- Display existing `playAnimation`/`stopAnimation` links with resolved model instance name and clip name