# 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 ```mermaid 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 ```mermaid 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 ```mermaid 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 ```mermaid 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 ```mermaid 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 | ## Related decisions and references - [`m2-cached-animation-resource-observer.md`](m2-cached-animation-resource-observer.md) - [`m2-build-resource-snapshot.md`](m2-build-resource-snapshot.md) - [`m2-raw-model-repository.md`](m2-raw-model-repository.md) - [`m2-prototype-cache-state.md`](m2-prototype-cache-state.md) - [`world-renderer.md`](world-renderer.md) - [`../../RENDER.md`](../../RENDER.md) - [`../../targets/roadmap/02-rendering-and-graphics.md`](../../targets/roadmap/02-rendering-and-graphics.md)