Files
open-wc/docs/modules/m2-native-animation-resource-observer.md
T

11 KiB

M2 Native Animation Resource Observer

Metadata

Field Value
Status Implemented extraction
Target/work package M03 / M03-RND-M2-NATIVE-ANIMATION-RESOURCE-OBSERVER-001
Owners Native animation candidate policy, raw read, build and cache outcome
Last verified Worktree work/sindo-main-codex/m03-m2-native-animation-resource-observer, 2026-07-18
Profiles/capabilities Existing native GryphonRoost animated M2 path

Purpose

Resolve the historical native animated M2 path as one synchronous observation: select a GryphonRoost candidate, reuse cached state, read animated raw data, build a detached prototype and publish either the prototype or static-only result.

Non-goals

  • Add native animation candidates or change the gryphonroost predicate.
  • Observe/request cached GLB animation or poll/finalize ResourceLoader work.
  • Duplicate, attach, animate or free accepted instances in the SceneTree.
  • Consume render permits or change build queue, batching and dispatch policy.
  • Change native parsing/building, cache formats, profiles or visible output.

Context and boundaries

flowchart LR
    Loader[StreamingWorldLoader] --> Observer[M2NativeAnimationResourceObserver]
    Observer --> Repository[M2RawModelRepository]
    Observer --> Builder[M2NativeAnimatedBuilder]
    Observer --> Cache[M2PrototypeCacheState]
    Observer --> Prototype[Detached animated prototype]
    Prototype --> Snapshot[M2BuildResourceSnapshot]
    Observer -->|no prototype| Cached[M2CachedAnimationResourceObserver]

The observer is the native-first resource boundary. The loader supplies an already-normalized path and remains responsible for fallback ordering and for constructing the snapshot consumed by dispatch.

Public API

Symbol Kind Purpose Failure behavior
is_native_animation_candidate(path) Pure query Apply the historical case-insensitive GryphonRoost substring policy Empty/unmatched path returns false
observe(path, extracted_dir, repository, cache, debug) Command/query Return an exact cached/new native prototype or record static-only fallback Invalid/non-candidate input returns null without mutation
build_animated_prototype(data, extracted_dir) Adapter command Call the retained native animated builder dependency Missing builder or invalid result returns null

The optional constructor dependency exists for deterministic tests; production uses M2NativeAnimatedBuilder without a new abstraction or runtime setting.

Inputs and outputs

Direction Data Producer Consumer Ownership/lifetime
Input Normalized relative M2 path Loader path adapter Observer Copied String; one call
Input Extracted directory Loader configuration Repository/builder Copied String; one call
Input Raw animated Dictionary Repository Observer/builder Call-local value
Input Prototype cache state Loader composition Observer Borrowed service; loader session
Output Cached or adopted prototype Cache/observer Loader snapshot Borrowed exact Node3D reference
Output Static-only transition Observer Prototype cache Copied path; loader session
Output Debug line Observer Runtime log Emitted only when enabled and build succeeds

Data flow

flowchart TD
    Start[Observe normalized path] --> Candidate{Valid native candidate?}
    Candidate -->|no| None[Return null]
    Candidate -->|yes| Cached{Cached prototype?}
    Cached -->|yes| Return[Return exact prototype]
    Cached -->|no| Static{Marked static-only?}
    Static -->|yes| None
    Static -->|no| Read[Read animated raw data]
    Read --> Surfaces{Data and animated surfaces?}
    Surfaces -->|no| Mark[Mark static-only]
    Surfaces -->|yes| Build[Build detached prototype]
    Build --> Children{Prototype has children?}
    Children -->|no| Mark
    Children -->|yes| Adopt[Adopt first cache prototype]
    Adopt --> Log{Debug enabled?}
    Log -->|yes| Emit[Emit exact native cache fields]
    Log -->|no| Return
    Emit --> Return
    Mark --> None

Lifecycle/state

stateDiagram-v2
    [*] --> Observing
    Observing --> Rejected: invalid or non-candidate
    Observing --> Cached: positive cache hit
    Observing --> StaticOnly: negative cache hit
    Observing --> Reading: uncached candidate
    Reading --> StaticOnly: no animated surfaces
    Reading --> Building: usable raw data
    Building --> StaticOnly: null or childless result
    Building --> Adopted: valid result
    Adopted --> [*]
    Cached --> [*]
    StaticOnly --> [*]
    Rejected --> [*]

Main sequence

sequenceDiagram
    participant L as StreamingWorldLoader
    participant O as NativeAnimationObserver
    participant C as M2PrototypeCacheState
    participant R as M2RawModelRepository
    participant B as M2NativeAnimatedBuilder
    L->>O: observe(path, directory, repository, cache, debug)
    O->>C: find animated / is static-only
    alt uncached native candidate
        O->>R: load_animated_model_data(directory, path)
        R-->>O: raw Dictionary
        O->>B: build(raw, directory)
        alt valid prototype
            O->>C: adopt_animated_prototype(path, prototype)
            O-->>L: canonical prototype
        else unavailable
            O->>C: mark_animation_static(path)
            O-->>L: null
        end
    else cached result
        O-->>L: prototype or null
    end

Dependency diagram

flowchart TB
    Observer[M2NativeAnimationResourceObserver] --> Repository[M2RawModelRepository]
    Observer --> Builder[M2NativeAnimatedBuilder]
    Observer --> Cache[M2PrototypeCacheState]
    Loader[StreamingWorldLoader] --> Observer
    Observer -. no dependency .-> ResourceLoader
    Observer -. no dependency .-> Pipeline[M2AnimationLoadPipelineState]
    Observer -. no dependency .-> Scheduler[RenderBudgetScheduler]
    Observer -. no dependency .-> SceneTree

Ownership, threading and resources

  • Observation and native parsing/building remain synchronous on the renderer main thread, exactly as before extraction; there is no mid-call cancellation.
  • The observer retains no path, raw Dictionary, Node or service reference.
  • Prototype cache owns an accepted detached Node until final shutdown; callers only borrow the canonical reference.
  • The historical childless-builder rejection does not free its detached Node. This deliberately preserved lifetime edge is documented as a remaining leak risk rather than silently changed during architectural extraction.
  • Loader owns snapshots, fallback sequencing, permits, materialization and every SceneTree mutation.

Errors, cancellation and recovery

Failure/state Behavior Recovery
Empty/non-candidate path or missing dependency Return null without state mutation Correct composition/path
Cached prototype Return exact canonical Node immediately None
Existing static-only outcome Return null without repeated I/O New loader session after source repair
Empty raw data or animated surfaces Mark static-only and return null Repair source/parser and start new session
Null/childless build Mark static-only and return null Repair builder/data and start new session
Duplicate successful build Cache releases later candidate and returns first None
Cancellation during native call Not supported; call completes Loader controls whether the call starts
Shutdown Observer retains nothing Cache releases adopted Nodes after drains

Configuration and capabilities

The observer consumes the existing extracted directory and debug_streaming flag. Candidate policy remains the case-insensitive substring gryphonroost. No profile, cache, budget or animation setting is added.

Persistence, cache and migration

No persistence or format changes are introduced. Positive and static-only state continues to use the shutdown-lifetime prototype cache; no asset rebake is needed.

Diagnostics and observability

A successful debug-enabled build emits the unchanged M2_NATIVE_ANIM_CACHE record with path, surface count, bone count, animation id, sequence index, activity score and length. Rejections remain silent as before.

Verification

  • Dedicated synthetic verifier covers candidate policy, invalid dependencies, cache identity, static suppression, raw/surface/build failures, preserved childless lifetime, exact repository/builder arguments, adoption, source ownership and bounded candidate checks.
  • Cached observer, raw repository, prototype cache, snapshot, dispatch, shutdown, facade, internal-access and render-baseline checks protect adjacent behavior.
  • Fidelity evidence is exact control-flow and diagnostic extraction. It is not original-client animation, visual, memory or asset-backed performance evidence.

Extension points

Terminal cached-GLB polling/finalization can move behind a separate adapter without changing this native-first contract. Candidate expansion requires its own fidelity evidence and must not silently change Blizzlike335 behavior.

Capability status

Capability Status Evidence Gap/next step
Native candidate selection Implemented extraction Policy/source/timing verifier Additional candidates require fidelity work
Raw read/build/cache outcome Implemented extraction Failure/identity/adoption verifier Asset-backed traversal pending
Terminal cached ResourceLoader polling Loader-owned Existing pipeline regressions Dedicated finalize adapter

Known gaps and risks

  • Native parsing/building is synchronous and not bounded by a render permit.
  • Rejected childless builder Nodes retain the historical unowned lifetime edge.
  • Private asset traversal, descriptor/leak pressure, p95/p99 and paired visual/ animation evidence remain unavailable in this package.

Source map

Path Responsibility
src/render/m2/m2_native_animation_resource_observer.gd Candidate/cache/raw/build/outcome/logging observation
src/render/m2/m2_raw_model_repository.gd Optional native file/parser boundary
src/render/m2/m2_prototype_cache_state.gd Positive prototype and static-only ownership
addons/mpq_extractor/loaders/m2_native_animated_builder.gd Existing animated prototype construction
src/scenes/streaming/streaming_world_loader.gd Composition, native-first fallback order and execution
src/tools/verify_m2_native_animation_resource_observer.gd Lifecycle/identity/boundary/timing regression