240 lines
11 KiB
Markdown
240 lines
11 KiB
Markdown
# 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)
|