Files
open-wc/docs/modules/m2-animation-playback-controller.md
T

10 KiB

M2 Animation Playback Controller

Metadata

Field Value
Status Implemented extraction
Target/work package M03 / M03-RND-M2-ANIMATION-PLAYBACK-001
Owners Per-instance AnimationPlayer/native animator playback mutation
Last verified Worktree work/sindo-main-codex/m03-m2-animation-playback, 2026-07-18
Profiles/capabilities Imported GLB and native experimental animated M2 instances

Purpose

Apply deterministic playback to one already duplicated M2 instance: copy native animator runtime fields, derive a stable phase, prepare/phase native animators, select an imported animation and configure linear looping/playback/seek.

Non-goals

  • Duplicate, name, transform or attach instances/batch roots.
  • Apply visibility, shadows or Editor ownership.
  • Load/finalize/cache animated scenes or decide eligibility.
  • Change animation priorities, phase formula or native animator implementation.

Context and boundaries

flowchart LR
    Prototype[Animated prototype] --> Materializer[M2AnimatedInstanceMaterializer]
    Materializer -->|source and duplicate| Playback[M2AnimationPlaybackController]
    Finalizer[M2AnimatedSceneFinalizer player inventory] --> Playback
    Playback --> Native[M2NativeAnimator mutation]
    Playback --> Imported[AnimationPlayer mutation]

Allowed dependencies are supplied Nodes, Script identity, AnimationPlayer and Animation resources. ResourceLoader, files, workers, caches, scheduler, MultiMesh, SceneTree attachment and application layers are forbidden.

Public API

Symbol Kind Purpose Thread/lifetime Errors
copy_native_animator_data(source_root, target_root, script) Command Copy five runtime fields in depth-first pair order Main thread; borrowed nodes Null/script mismatch yields no pairs
start_instance_playback(root, path, index, script, players, collect_diagnostics) Command/query Start native/imported playback and optionally return native state Main thread; one instance Null/empty inventories no-op
native_animators_in_subtree(root, script) Query Exact-script depth-first inventory Main thread; borrowed nodes Null root/script returns empty
phase_for_instance(path, index) Pure query Stable phase in [0, 1) Any thread; scalar Historical hash behavior retained
choose_default_animation(player, path) Pure engine query Apply exact ordinary/fish/bird/fallback priority Main thread; borrowed player Null/no animations returns empty

Inputs and outputs

Direction Contract/data Producer Consumer Ownership Thread/lifetime
Input Prototype and duplicate subtrees M2 animated instance materializer Native field copy Borrowed Nodes One duplicate
Input Exact native animator Script Loader composition Native inventory Borrowed Script Call-local
Input Relative path and instance index Build batch adapter Phase/selection Copied values One start
Input AnimationPlayer inventory Animated scene finalizer traversal Imported playback Borrowed players One start
Output Mutated native/imported playback Controller Rendered instance Nodes retain state/resources Instance lifetime
Output Detached native diagnostic records Controller Loader log adapter Caller-owned Dictionaries Debug call

Side effects are native field assignment, prepare/phase calls, animation loop mutation, play and seek. The service retains no inputs.

Data flow

flowchart TD
    Identity[Path and index] --> Phase[Stable hash phase]
    NativeInventory[Exact-script native inventory] --> Prepare[prepare runtime if available]
    Phase --> NativePhase[set native phase]
    Players[AnimationPlayers] --> Select[Choose path-specific default]
    Select --> Loop[Set every animation LOOP_LINEAR]
    Loop --> Play[Play selected name]
    Phase --> Seek[Seek positive-length selection]
    NativePhase --> Diagnostics{Debug requested?}
    Diagnostics -->|yes| Snapshot[Detached runtime state]

Lifecycle/state

stateDiagram-v2
    [*] --> Unstarted
    Unstarted --> Prepared: copy native data and start
    Prepared --> Playing: native phase and/or AnimationPlayer play
    Playing --> Playing: repeated deterministic start
    Playing --> [*]: instance owner releases subtree

The controller itself is stateless; lifecycle labels describe borrowed instance state.

Main sequence

sequenceDiagram
    participant M as M2AnimatedInstanceMaterializer
    participant F as M2AnimatedSceneFinalizer
    participant P as M2AnimationPlaybackController
    participant N as M2NativeAnimator
    participant A as AnimationPlayer
    M->>P: copy_native_animator_data(prototype, duplicate, script)
    M->>F: animation_players_in_subtree(duplicate)
    F-->>M: ordered players
    M->>P: start_instance_playback(path, index, players, debug)
    P->>N: prepare_runtime and set_phase
    P->>A: choose, loop, play and seek
    P-->>M: optional detached native diagnostics
    M-->>M: tag states with instance index

Dependency diagram

flowchart TB
    Loader[StreamingWorldLoader] --> Materializer[M2AnimatedInstanceMaterializer]
    Materializer --> Playback[M2AnimationPlaybackController]
    Materializer --> Finalizer[M2AnimatedSceneFinalizer]
    Playback --> Engine[Node / Script / AnimationPlayer / Animation]
    Materializer --> Batch[SceneTree batch materialization]
    Loader --> NativeScript[M2NativeAnimator script]
    Playback -. no dependency .-> ResourceLoader
    Playback -. no dependency .-> Cache[M2PrototypeCacheState]
    Playback -. no dependency .-> Scheduler[RenderBudgetScheduler]

Ownership, threading and resources

  • Materializer owns prototype duplication, instance/batch roots and SceneTree attachment.
  • Materializer uses finalizer traversal for borrowed AnimationPlayer references.
  • Controller borrows Nodes/Script/players only for the synchronous call.
  • Native arrays are assigned by reference exactly as before extraction.
  • Diagnostic Dictionaries are deep-duplicated before return.
  • Main thread performs all engine-object mutation; pure phase math is thread-safe.

Errors, cancellation and recovery

Failure Detection Behavior Diagnostic Recovery
Missing roots/script Inventory guard No native copy/start Contract fixture Correct composition
Fewer target animators Pair-count minimum Copy available pairs only Native copy fixture Rebuild import/prototype
No matching name Ordered fallback Substring, first name, then empty Selection fixtures Add compatible animation
Empty animation selection Empty result Skip loop/play/seek for that player Selection fixture Static/native path continues
Zero-length selected animation Length guard Play without seek Playback contract Imported timing remains engine-owned
Cancellation/shutdown Outside service No retained state N/A Loader owns subtree release

Configuration and capabilities

Setting/capability Default Profile Runtime mutable Effect
Phase buckets 1000 All No Stable per path/index desynchronization
Loop mode LOOP_LINEAR All No Every available imported animation loops
Native diagnostics debug_streaming Debug Yes Samples state only when requested

Persistence, cache and migration

No state or format is serialized. GLB/native cache formats, prototype lifetime and material versions are unchanged; no rebake is required.

Diagnostics and observability

  • Optional native states remain detached and contain existing prepared, processing, mesh, bone, surface and length fields.
  • Loader retains M2_NATIVE_ANIMATOR log formatting and path normalization.
  • Controller emits no logs or metrics.

Verification

  • verify_m2_animation_playback_controller.gd covers exact phase, ordinary/ fish/bird priorities, substring/first fallback, loop/play/seek, native exact- script order, five-field copy, phase, detached diagnostics and boundaries.
  • Finalizer/build/prototype/material/shutdown regressions protect adjacent behavior.
  • Fidelity evidence is exact policy/mutation extraction; no private asset or original-client animation comparison is claimed.
  • Performance budget: 20,000 phase-and-selection pairs under one second.

Extension points

Server-driven animation state may later replace default selection through a separate presentation contract; this fallback controller must remain available for world doodads and compatibility fixtures.

Capability status

Capability Status Evidence Gap/next step
Imported default selection/playback Implemented extraction Synthetic priority/play/seek verifier Asset-backed animation-name corpus pending
Native data/phase startup Implemented extraction Exact-reference/native state verifier Native visual fidelity remains experimental
Instance/batch materialization Implemented extraction Materializer contract verifier Asset-backed traversal pending

Known gaps and risks

  • Hash phase intentionally depends on existing Godot String hashing behavior.
  • Default-name heuristics are not a complete WoW animation-state mapping.
  • No proprietary traversal, animation timing comparison, p95/p99 or paired-client run exists.

Source map

Path Responsibility
src/render/m2/m2_animation_playback_controller.gd Phase, selection, native copy/start and imported playback
src/render/m2/m2_animated_scene_finalizer.gd Accepted prototype/player inventory
src/scenes/streaming/m2_native_animator.gd Experimental native deformation runtime
src/render/m2/m2_animated_instance_materializer.gd Instance duplication/attachment and playback composition
src/scenes/streaming/streaming_world_loader.gd Build adapter, diagnostic formatting and Editor ownership
src/tools/verify_m2_animation_playback_controller.gd Policy/mutation/boundary/timing regression