# M2 Cached Animation Resource Observer ## Metadata | Field | Value | |---|---| | Status | Implemented extraction | | Target/work package | M03 / `M03-RND-M2-CACHED-ANIMATION-RESOURCE-OBSERVER-001` | | Owners | Cached animated M2 eligibility, request admission and initial snapshot | | Last verified | Worktree `work/sindo-main-codex/m03-m2-cached-animation-resource-observer`, 2026-07-18 | | Profiles/capabilities | Existing optional cached-GLB animated M2 path | ## Purpose Produce the cached-animation phase of `M2BuildResourceSnapshot`: reuse an exact animated prototype, wait for a pending request, admit the first safe GLB request, or record a terminal static-only animation outcome without changing behavior. ## Non-goals - Load/build native GryphonRoost animation or emit its diagnostics. - Poll/finalize threaded Resources or instantiate/fix imported scenes. - Own/free prototype Nodes, snapshots, pipeline entries or cache state. - Materialize instances, consume permits or change animation policy defaults. - Change GLB layout, cache format, batching, profiles or visible output. ## Context and boundaries ```mermaid flowchart LR Loader[StreamingWorldLoader] --> Native[Native animation attempt] Native -->|no prototype| Observer[M2CachedAnimationResourceObserver] Prototype[M2PrototypeCacheState] --> Observer Pipeline[M2AnimationLoadPipelineState] --> Observer Resource[ResourceLoader and GLB JSON] --> Observer Observer --> Snapshot[M2BuildResourceSnapshot] Snapshot --> Dispatch[M2BuildDispatchPlanner] ``` ## Public API | Symbol | Kind | Purpose | Failure behavior | |---|---|---|---| | `observe(path, cache_dir, max_primitives, allow, deny, prototype_state, pipeline_state, debug)` | Command/query | Produce cached prototype/pending/static-only snapshot | Invalid input returns empty non-pending snapshot | | `find_eligible_glb_cache_path(...)` | I/O query | Select first existing safe historical candidate | Returns empty String | | `is_animation_path_allowed(path, allow, deny)` | Pure query | Apply stripped case-insensitive substring policy | Empty path/allowlist rejects; deny wins | | `cache_resource_paths(cache_dir, path)` | Pure query | Return nested/lowercase/basename GLB candidates | Empty values yield fewer candidates | | `glb_cache_is_safe_for_runtime_animation(path, max, debug)` | I/O query | Validate animations, primitive limit and schema | Invalid/missing GLB rejects | | `read_glb_json(path)` | I/O query | Read version-2 GLB JSON chunk | Returns empty Dictionary | | `glb_primitive_count(gltf)` | Pure query | Count primitives across meshes | Invalid entries contribute zero | | `glb_animation_schema(gltf)` | Pure query | Read OpenWC schema marker | Missing marker returns empty String | ## Inputs and outputs | Direction | Data | Producer | Consumer | Ownership/lifetime | |---|---|---|---|---| | Input | Normalized M2 path/cache directory | Loader | Observer | Copied Strings; one observation | | Input | Primitive cap and allow/deny patterns | Loader exports | Observer | Copied/read-only call values | | Input | Prototype and request states | M2 state services | Observer | Borrowed services; map/session | | Input | Existing GLB cache file | Offline cache pipeline | Observer | Read-only file access | | Output | Resource snapshot | Observer | Loader/dispatch/materializer | Caller-owned snapshot; one build step | | Output | Borrowed exact prototype | Prototype state | Snapshot/materializer | No ownership transfer | | Output | Threaded request record | Observer | Animation pipeline | Pipeline-owned copied paths | | Output | Static-only transition | Observer | Prototype state | State-owned copied path | ## Data flow ```mermaid flowchart TD Start[Observe normalized path] --> Valid{Valid composition?} Valid -->|no| Empty[Empty non-pending snapshot] Valid -->|yes| Cached{Prototype cached?} Cached -->|yes| Ready[Snapshot with exact prototype] Cached -->|no| Static{Already static-only?} Static -->|yes| Empty Static -->|no| Pending{Request exists?} Pending -->|yes| Wait[Pending snapshot] Pending -->|no| Policy{Allow and not deny?} Policy -->|no| Mark[Mark static-only] Policy -->|yes| Candidate[Historical GLB candidates] Candidate --> Safe{Animations, primitive cap, schema safe?} Safe -->|no usable| Mark Safe -->|yes| Request[Threaded request] Request -->|OK or busy| Remember[Remember and return pending snapshot] Request -->|error| Mark Mark --> Empty ``` ## Lifecycle/state ```mermaid stateDiagram-v2 [*] --> Observing Observing --> Cached Observing --> Pending Observing --> Requested Observing --> StaticOnly Observing --> Rejected Cached --> [*] Pending --> [*] Requested --> [*] StaticOnly --> [*] Rejected --> [*] ``` ## Main sequence ```mermaid sequenceDiagram participant L as StreamingWorldLoader participant O as CachedAnimationObserver participant C as PrototypeCacheState participant P as AnimationLoadPipelineState participant R as ResourceLoader participant S as M2BuildResourceSnapshot L->>L: attempt native candidate alt no native prototype L->>O: observe(path, cache/policy/state) O->>C: find animated / static-only O->>P: has request opt eligible cache candidate O->>R: exists + GLB JSON + threaded request O->>P: remember request end O-->>L: snapshot else native prototype L->>S: construct native snapshot end ``` ## Dependency diagram ```mermaid flowchart TB Observer[M2CachedAnimationResourceObserver] --> Snapshot[M2BuildResourceSnapshot] Observer --> Prototype[M2PrototypeCacheState] Observer --> Pipeline[M2AnimationLoadPipelineState] Observer --> ResourceLoader Observer --> FileAccess[GLB header and JSON] Observer -. no dependency .-> Native[M2 native builder/repository] Observer -. no dependency .-> Finalizer[M2AnimatedSceneFinalizer] Observer -. no dependency .-> Scheduler[RenderBudgetScheduler] Observer -. no dependency .-> SceneTree ``` ## Ownership, threading and resources - Observer retains no service, request, snapshot, Resource or Node reference. - Prototype state retains accepted Nodes and static-only paths until shutdown. - Pipeline state retains copied request paths until terminal polling/finalize. - Snapshot borrows the exact cached prototype; observer never frees it. - Resource existence/request and synchronous GLB JSON inspection run on the renderer main thread, matching the previous loader behavior. - Loader remains owner of native animation build, terminal polling/finalize, permits, materialization and every SceneTree mutation. ## Errors, cancellation and recovery | Failure/state | Behavior | Recovery | |---|---|---| | Empty path/dependency | Return empty non-pending snapshot without mutation | Correct composition | | Cached prototype | Return exact reference immediately | None | | Existing request | Return pending without duplicate admission | Retry after terminal drain | | Disallowed/denied path | Mark static-only | New loader session/configuration | | Missing/invalid/unsafe GLB | Continue candidates, then mark static-only | Repair/rebake cache and restart session | | Request error | Mark static-only | New loader session/cache repair | | Tile cancellation | Observer retains nothing | Loader queue remains authoritative | | Shutdown | Loader drains requests before state clear | New loader starts empty | ## Configuration and capabilities The service consumes existing `m2_cache_dir`, `m2_animated_max_primitives`, `m2_animated_allowlist_patterns`, `m2_animated_denylist_patterns` and debug flag. It adds no setting and preserves runtime mutability and defaults. ## Persistence, cache and migration No persistence or cache format changes are introduced. Existing `.glb` candidate layout and OpenWC schema markers are read unchanged; no rebake is required. ## Diagnostics and observability Unsupported non-empty schemas preserve the historical optional `M2_ANIM_REJECT` debug line. Snapshot and pipeline diagnostics remain detached; queue and hitch metrics are unchanged. ## Verification - Dedicated verifier covers invalid/cached identity/static/pending/missing lifecycle, allow/deny semantics, exact path order, generated GLB header/JSON, animation presence, primitive cap, accepted/rejected schemas, source boundaries and bounded timing. - Snapshot, dispatch, pipeline, prototype, shutdown, facade, internal-access and checkpoint regressions protect adjacent behavior. - Fidelity evidence is exact policy/I/O extraction. No original-client visual or animation parity claim is made. ## Extension points Native animation observation can become a sibling service without changing this cached-GLB contract. Terminal ResourceLoader polling/finalization may later move behind a dedicated adapter while retaining the pipeline state. ## Capability status | Capability | Status | Evidence | Gap/next step | |---|---|---|---| | Cached animation observation/request | Implemented extraction | Lifecycle/policy/GLB/source/timing verifier | Asset-backed traversal pending | | Native animation observation | Existing loader-owned | Native renderer regression | Separate extraction | | Animated finalize/preparation | Loader/finalizer split | Pipeline/finalizer regressions | Polling adapter optional | ## Known gaps and risks - A successful asynchronous request is not started by the unit fixture because leaving it undrained leaks process work; source and pipeline tests cover admission. - Generated GLB metadata fixtures validate policy, not Godot import fidelity. - Private traversal, leak/descriptor pressure, p95/p99 and paired visuals remain unavailable without private assets. ## Source map | Path | Responsibility | |---|---| | `src/render/m2/m2_cached_animation_resource_observer.gd` | Cached GLB policy, request and snapshot production | | `src/render/m2/m2_build_resource_snapshot.gd` | Typed per-step observation value | | `src/render/m2/m2_animation_load_pipeline_state.gd` | Pending request/finalize state | | `src/render/m2/m2_prototype_cache_state.gd` | Prototype and static-only outcomes | | `src/scenes/streaming/streaming_world_loader.gd` | Native attempt, composition, finalize and execution | | `src/tools/verify_m2_cached_animation_resource_observer.gd` | Lifecycle/policy/GLB/boundary/timing regression | ## Related decisions and references - [`m2-build-resource-snapshot.md`](m2-build-resource-snapshot.md) - [`m2-animation-load-pipeline-state.md`](m2-animation-load-pipeline-state.md) - [`m2-prototype-cache-state.md`](m2-prototype-cache-state.md) - [`m2-animated-scene-finalizer.md`](m2-animated-scene-finalizer.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)