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

11 KiB

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

flowchart LR
    Loader[StreamingWorldLoader] --> Native[M2NativeAnimationResourceObserver]
    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

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

stateDiagram-v2
    [*] --> Observing
    Observing --> Cached
    Observing --> Pending
    Observing --> Requested
    Observing --> StaticOnly
    Observing --> Rejected
    Cached --> [*]
    Pending --> [*]
    Requested --> [*]
    StaticOnly --> [*]
    Rejected --> [*]

Main sequence

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

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.
  • The sibling native observer owns the native build attempt and the animation resource finalizer owns terminal polling/finalize. Loader retains permits, material lookup, materialization and 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 and cached observation remain sibling services. Terminal ResourceLoader polling/finalization uses the dedicated finalizer while retaining 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 Implemented sibling extraction Native observer lifecycle/source verifier Asset-backed traversal pending
Animated finalize/preparation Implemented finalizer extraction Pipeline/finalizer regressions Asset-backed traversal pending

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_animation_resource_finalizer.gd Terminal polling/load/finalize outcomes
src/render/m2/m2_prototype_cache_state.gd Prototype and static-only outcomes
src/render/m2/m2_native_animation_resource_observer.gd Native-first candidate/read/build/cache observation
src/scenes/streaming/streaming_world_loader.gd Composition, fallback order, finalize and execution
src/tools/verify_m2_cached_animation_resource_observer.gd Lifecycle/policy/GLB/boundary/timing regression