render: extract cached M2 animation resource observer

This commit is contained in:
2026-07-18 10:23:03 +04:00
parent 3519f183bb
commit df87619220
17 changed files with 1065 additions and 197 deletions
+1
View File
@@ -24,6 +24,7 @@
| M2 build dispatch planner | Implemented extraction | [`m2-build-dispatch-planner.md`](m2-build-dispatch-planner.md) |
| M2 build resource snapshot | Implemented extraction | [`m2-build-resource-snapshot.md`](m2-build-resource-snapshot.md) |
| M2 static build resource observer | Implemented extraction | [`m2-static-build-resource-observer.md`](m2-static-build-resource-observer.md) |
| M2 cached animation resource observer | Implemented extraction | [`m2-cached-animation-resource-observer.md`](m2-cached-animation-resource-observer.md) |
| M2 build queue | Implemented extraction | [`m2-build-queue.md`](m2-build-queue.md) |
| M2 static batch materializer | Implemented extraction | [`m2-static-batch-materializer.md`](m2-static-batch-materializer.md) |
| M2 runtime mesh rebuild classifier | Implemented extraction | [`m2-runtime-mesh-rebuild-classifier.md`](m2-runtime-mesh-rebuild-classifier.md) |
@@ -14,9 +14,10 @@
Own cross-frame bookkeeping between a successful animated M2 ResourceLoader
request, terminal polling and budgeted main-thread scene finalization. This is
an exact state extraction; animation eligibility and loading remain in
`StreamingWorldLoader`, while scene finalization and retained Node lifecycle
belong to `M2AnimatedSceneFinalizer` and `M2PrototypeCacheState`.
an exact state extraction; cached animation eligibility and request admission
belong to `M2CachedAnimationResourceObserver`, while scene finalization and
retained Node lifecycle belong to `M2AnimatedSceneFinalizer` and
`M2PrototypeCacheState`.
## Non-goals
@@ -29,8 +30,10 @@ belong to `M2AnimatedSceneFinalizer` and `M2PrototypeCacheState`.
```mermaid
flowchart LR
Loader[StreamingWorldLoader] --> IO[ResourceLoader]
Loader --> State[M2AnimationLoadPipelineState]
Observer[M2CachedAnimationResourceObserver] --> IO[ResourceLoader request]
Observer --> State[M2AnimationLoadPipelineState]
Loader[StreamingWorldLoader] --> IO
Loader --> State
State -->|detached pending records| Loader
Loader -->|opaque terminal status| State
State -->|completion FIFO| Loader
@@ -100,12 +103,13 @@ stateDiagram-v2
```mermaid
sequenceDiagram
participant O as CachedAnimationObserver
participant L as StreamingWorldLoader
participant R as ResourceLoader
participant S as M2AnimationLoadPipelineState
participant P as M2PrototypeCacheState
L->>R: load_threaded_request(GLB)
L->>S: remember_request(path, GLB)
O->>R: load_threaded_request(GLB)
O->>S: remember_request(path, GLB)
loop frames
L->>S: request_records_snapshot()
L->>R: load_threaded_get_status(GLB)
@@ -128,8 +132,10 @@ sequenceDiagram
```mermaid
flowchart TB
Observer[M2CachedAnimationResourceObserver] --> State[M2AnimationLoadPipelineState]
Observer --> Resource[ResourceLoader request]
Loader[StreamingWorldLoader] --> State[M2AnimationLoadPipelineState]
Loader --> Resource[ResourceLoader]
Loader --> Resource
Loader --> Budget[RenderBudgetScheduler]
Loader --> Prototype[M2PrototypeCacheState]
State -. no dependency .-> Resource
@@ -142,7 +148,7 @@ flowchart TB
| Failure | Detection | Behavior | Diagnostic | Recovery |
|---|---|---|---|---|
| Empty/duplicate request | State guard | Reject unchanged | Contract verifier | Correct caller or request later |
| Request start/cache miss | Loader | No insertion; mark static | Existing loader path | Cache correction/reload |
| Request start/cache miss | Cached observer | No insertion; mark static | Observer contract | Cache correction/reload |
| Non-terminal status | Loader | Keep pending | Existing metric | Poll next frame |
| Failed terminal load | Popped status | Loader marks static | Existing behavior | Future map/session reload |
| Empty defensive path | Loader poll | Discard and mark static | Source contract | Correct producer |
@@ -154,7 +160,7 @@ flowchart TB
|---|---|---|---|---|
| `enable_m2_animated_instances` | `true` | Existing renderer profile | Yes | Enables caller request path |
| `m2_animation_finalize_ops_per_tick` | `1` | Quality/custom | Yes | Bounds caller FIFO drain |
| Animated allow/deny/primitive rules | Existing values | Existing renderer profile | Yes | Filter before state insertion |
| Animated allow/deny/primitive rules | Existing values | Existing renderer profile | Yes | Observer filters before state insertion |
## Persistence, cache and migration
@@ -188,7 +194,8 @@ sibling service.
| Capability | Status | Evidence | Gap/next step |
|---|---|---|---|
| Animated request/finalize state | Implemented extraction | Synthetic contract/source/timing verifier | Asset-backed traversal/leak/p95/p99 pending |
| ResourceLoader and GLB selection | Existing loader-owned | Adjacent renderer tests | I/O adapter extraction optional |
| Cached request admission and GLB selection | Implemented in observer | Policy/GLB/source verifier | Asset-backed traversal pending |
| Terminal ResourceLoader polling | Existing loader-owned | Shutdown/finalizer regressions | I/O adapter extraction optional |
| Animated prototype outcomes | Implemented extraction | Prototype cache verifier | Asset-backed animation fidelity pending |
## Known gaps and risks
@@ -202,9 +209,10 @@ sibling service.
| Path | Responsibility |
|---|---|
| `src/render/m2/m2_animation_load_pipeline_state.gd` | Pending records, completion FIFO and metrics |
| `src/render/m2/m2_cached_animation_resource_observer.gd` | Eligibility, GLB selection and request admission |
| `src/render/m2/m2_animated_scene_finalizer.gd` | Candidate instantiation, material repair and player validation |
| `src/render/m2/m2_prototype_cache_state.gd` | Animated prototype/static-only outcomes |
| `src/scenes/streaming/streaming_world_loader.gd` | Eligibility, I/O, permits, instantiation and adoption |
| `src/scenes/streaming/streaming_world_loader.gd` | Native eligibility/build, polling, permits and adoption |
| `src/tools/verify_m2_animation_load_pipeline_state.gd` | Lifecycle/boundary/timing regression |
## Related decisions and references
+4 -3
View File
@@ -104,7 +104,8 @@ sequenceDiagram
- The planner owns only call-local scalar values and the returned Dictionary.
- `M2BuildQueue` owns typed jobs, FIFO ordering, serial numbers and group/offset
cursors. `M2BuildDispatchPlanner` owns the pure resource-state action decision;
the loader owns tile checks, resource observation/retry and adoption calls.
the loader owns tile checks and native-first orchestration, while static and
cached animation observers own their observation/retry phases.
- Materializers own main-thread Node/MultiMesh construction under loader roots.
- The scheduler owns the frame-local `M2_BUILD` counter.
- Pure planning is thread-safe, though the current adapter calls it on main thread.
@@ -150,8 +151,8 @@ queue depth, build activity and hitch observability.
## Extension points
- A later package may extract resource observation/request orchestration while
retaining the dispatch and typed build-job/FIFO contracts.
- Remaining native resource observation may be extracted while retaining the
dispatch and typed build-job/FIFO contracts.
- Spatial-cell batching must use measured culling/performance evidence and must
not silently change this model-path batch cursor.
+13 -9
View File
@@ -12,9 +12,10 @@
## Purpose
Select one M2 build action after the loader observes animation/static resource
state. The planner makes wait priority, materializer selection and the historical
missing-model serial transition explicit without owning resources or engine work.
Select one M2 build action after renderer observers produce animation/static
resource state. The planner makes wait priority, materializer selection and the
historical missing-model serial transition explicit without owning resources or
engine work.
## Non-goals
@@ -146,7 +147,8 @@ flowchart TB
- Planner owns call-local comparisons and the returned Dictionary only.
- `M2BuildResourceSnapshot` owns the typed call-local observation contract;
dispatch reads availability/pending/missing accessors but no engine references.
- Loader owns resource lookup/request order, action execution and permit use.
- Loader owns native-first observation order, action execution and permit use;
static/cached-animation observers own their lookup/request phases.
- `M2BuildQueue` owns pending jobs, FIFO keys and progress cursors.
- Materializers own main-thread scene construction under loader-owned roots.
- Pure calls are thread-safe; the current loader adapter calls on main thread.
@@ -190,16 +192,17 @@ remain unchanged.
## Extension points
A later package may move resource observation/request execution behind a service
that produces `M2BuildResourceSnapshot`. Generic callbacks, signals and a shared
state-machine framework remain intentionally excluded.
Static and cached animated observation/request execution now live behind sibling
services that produce `M2BuildResourceSnapshot`. Native animation remains an
orchestrator-owned phase. Generic callbacks, signals and a shared state-machine
framework remain intentionally excluded.
## Capability status
| Capability | Status | Evidence | Gap/next step |
|---|---|---|---|
| M2 build action selection | Implemented extraction | Priority/matrix/source/timing verifier | Asset-backed traversal pending |
| Resource observation and requests | Loader-owned | Cache/pipeline regressions | Typed orchestration extraction pending |
| Static/cached animation observation and requests | Implemented separately | Observer/cache/pipeline regressions | Native observation remains loader-owned |
| Queue/cursor state | Implemented separately | M2 build queue verifier | Asset-backed traversal pending |
| Materialization | Implemented separately | Static/animated materializer verifiers | GPU/p95/p99 evidence pending |
@@ -215,7 +218,8 @@ state-machine framework remain intentionally excluded.
|---|---|
| `src/render/m2/m2_build_dispatch_planner.gd` | Pure action priority and transition plan |
| `src/render/m2/m2_build_resource_snapshot.gd` | Typed per-step resource observation contract |
| `src/scenes/streaming/streaming_world_loader.gd` | Resource observation, action execution, permits and engine lifetime |
| `src/render/m2/m2_cached_animation_resource_observer.gd` | Cached animation observation/request phase |
| `src/scenes/streaming/streaming_world_loader.gd` | Native observation order, action execution, permits and engine lifetime |
| `src/render/m2/m2_build_batch_planner.gd` | Batch count and cursor plan |
| `src/render/m2/m2_build_queue.gd` | Pending jobs, FIFO and cursor ownership |
| `src/tools/verify_m2_build_dispatch_planner.gd` | Matrix, boundary and timing regression |
+22 -12
View File
@@ -28,7 +28,9 @@ static Mesh and terminal missing-model state. The snapshot never owns engine lif
```mermaid
flowchart LR
Loader[StreamingWorldLoader observations] --> Snapshot[M2BuildResourceSnapshot]
Native[StreamingWorldLoader native observation] --> Snapshot[M2BuildResourceSnapshot]
Cached[M2CachedAnimationResourceObserver] --> Snapshot
Static[M2StaticBuildResourceObserver] --> Snapshot
Snapshot --> Dispatch[M2BuildDispatchPlanner]
Snapshot --> Loader
Loader --> Animated[M2AnimatedInstanceMaterializer]
@@ -60,8 +62,8 @@ are forbidden.
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|---|---|---|---|---|---|
| Input | Normalized relative path | Loader path adapter | Snapshot/diagnostics | Copied String | Snapshot lifetime |
| Input | Optional animated prototype | Prototype/native animation path | Snapshot/materializer adapter | Borrowed exact Node3D reference | One build operation |
| Input | Animation request pending | Animation pipeline state | Snapshot/dispatch planner | Boolean copy | One build operation |
| Input | Optional animated prototype | Native/cached animation observer | Snapshot/materializer adapter | Borrowed exact Node3D reference | One build operation |
| Input | Animation request pending | Cached animation observer | Snapshot/dispatch planner | Boolean copy | One build operation |
| Input | Optional prepared static Mesh | Mesh cache/request path | Snapshot/materializer adapter | Borrowed exact Mesh reference | One build operation |
| Input | Static model terminally missing | Prototype cache state | Snapshot/dispatch planner | Boolean copy | One build operation |
| Output | Availability/pending/missing values | Snapshot | Dispatch planner | Scalar reads | One dispatch plan |
@@ -110,8 +112,12 @@ sequenceDiagram
participant B as M2BuildBatchPlanner
participant D as M2BuildDispatchPlanner
participant M as M2 materializer
L->>L: resolve/request animated prototype
L->>S: construct(path, prototype, pending)
L->>L: resolve native animated prototype
L->>S: construct native result
opt no native prototype
L->>L: delegate cached animation observer
L->>S: receive cached/pending snapshot
end
alt animation not pending
L->>B: plan batch using snapshot availability
opt positive static batch
@@ -143,7 +149,8 @@ flowchart TB
## Ownership, threading and resources
- Snapshot owns only copied scalar state and temporary references.
- Prototype/cache services retain resource state; loader owns observation order.
- Prototype/cache services retain resource state; loader owns native-first
observation order while cached/static observers own their phases.
- Scene roots remain loader/tile-owned; Mesh lifetime follows cache/resource refs.
- Dispatch planner reads only snapshot accessors and never mutates the snapshot.
- Current construction/adoption occurs on renderer main thread.
@@ -187,9 +194,10 @@ queue/hitch metrics remain unchanged.
## Extension points
`M2StaticBuildResourceObserver` now produces the static phase. A later animated
observer may produce the first phase. The value contract remains independent of
both producers and of generic callback frameworks.
`M2CachedAnimationResourceObserver` produces the cached animated phase and
`M2StaticBuildResourceObserver` produces the static phase. Native animation
observation remains loader-owned. The value contract stays independent of all
producers and of generic callback frameworks.
## Capability status
@@ -198,14 +206,15 @@ both producers and of generic callback frameworks.
| Typed per-step M2 resource observation | Implemented extraction | Identity/adoption/lifetime/source/timing verifier | Asset-backed traversal pending |
| Dispatch decision | Implemented separately | Dispatch planner verifier | Asset-backed traversal pending |
| Static lookup/request execution | Implemented separately | Static observer verifier | Asset-backed traversal pending |
| Animated lookup/request execution | Loader-owned | Animation regressions | Service extraction pending |
| Cached animated lookup/request execution | Implemented separately | Cached observer verifier | Asset-backed traversal pending |
| Native animated lookup/build execution | Loader-owned | Native renderer regression | Service extraction pending |
| Engine lifetime/materialization | Loader/materializer-owned | Lifetime/materializer regressions | GPU/p95/p99 evidence pending |
## Known gaps and risks
- Static observation is mutable during its short two-phase construction.
- Raw Node3D/Mesh references remain necessary for current materializer APIs.
- Loader still owns synchronous/native prototype attempts and ResourceLoader requests.
- Loader still owns synchronous native prototype attempts and terminal polling.
- Synthetic timing does not measure resource, Node or GPU work.
## Source map
@@ -215,7 +224,8 @@ both producers and of generic callback frameworks.
| `src/render/m2/m2_build_resource_snapshot.gd` | Typed per-step resource observations and diagnostics |
| `src/render/m2/m2_build_dispatch_planner.gd` | Snapshot-to-action planning |
| `src/render/m2/m2_static_build_resource_observer.gd` | Static snapshot production and requests |
| `src/scenes/streaming/streaming_world_loader.gd` | Animated observation/request execution and materializer borrowing |
| `src/render/m2/m2_cached_animation_resource_observer.gd` | Cached animated snapshot production and requests |
| `src/scenes/streaming/streaming_world_loader.gd` | Native animation observation and materializer borrowing |
| `src/tools/verify_m2_build_resource_snapshot.gd` | Identity/adoption/lifetime/boundary/timing regression |
## Related decisions and references
@@ -0,0 +1,239 @@
# 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)
+12 -8
View File
@@ -28,7 +28,9 @@ paths and paths whose animation fallback is static-only.
```mermaid
flowchart LR
Loader[StreamingWorldLoader] --> State[M2PrototypeCacheState]
Observer[M2 resource observers] --> State
Resource[ResourceLoader / raw builders] --> Loader
Resource --> Observer
State --> Static[Static prototype Node3D]
State --> Animated[Animated prototype Node3D]
State --> Missing[Missing-model path set]
@@ -62,7 +64,7 @@ Mesh traversal and other application layers are forbidden.
| Input | Normalized non-empty M2 path | Loader normalization | Cache state | Copied String key | Shutdown lifetime |
| Input | Detached static Node3D | Cached scene/raw builder adapter | Cache state | Adopted on success | Until final shutdown |
| Input | Detached animated Node3D | GLB/native animation adapter | Cache state | Adopted on success | Until final shutdown |
| Input | Missing/static-only outcome | Loader failure/fallback adapter | Cache state | Boolean set entry | Until final shutdown |
| Input | Missing/static-only outcome | Loader/observer failure adapter | Cache state | Boolean set entry | Until final shutdown |
| Output | Canonical prototype Node3D | Cache state | Loader instance/material adapter | Borrowed exact reference | One lookup/use |
| Output | Detached path-only snapshot | Cache state | Tests/diagnostics | Fresh caller-owned arrays | One query |
@@ -135,9 +137,10 @@ sequenceDiagram
```mermaid
flowchart TB
Loader[StreamingWorldLoader] --> State[M2PrototypeCacheState]
Observer[M2 resource observers] --> State
State --> Node3D
Loader --> Raw[M2RawModelRepository]
Loader --> ResourceLoader
Observer --> ResourceLoader
Loader --> StaticBuilder[M2Builder]
Loader --> AnimatedBuilder[M2NativeAnimatedBuilder]
State -. no dependency .-> Raw
@@ -165,7 +168,7 @@ flowchart TB
| Unknown positive lookup | Map miss | Return null | Path snapshot | Loader continues existing load/fallback |
| Duplicate candidate | Occupied valid path | Release candidate; return first | Identity fixture | None required |
| Model source/load failure | Loader result | Mark missing outcome | Existing fallback behavior | New loader session/source repair |
| Animation unavailable/unsafe | Loader policy/result | Mark static-only outcome | Existing fallback behavior | New loader session/cache repair |
| Animation unavailable/unsafe | Observer/native result | Mark static-only outcome | Existing fallback behavior | New loader session/cache repair |
| Shutdown | Loader lifecycle | Release positive and clear all state | Shutdown verifier | New loader begins empty |
| Cancellation | Not owned | No state transition inside this service | N/A | Loader drains/cancels before shutdown clear |
@@ -177,8 +180,8 @@ flowchart TB
| Negative cache lifetime | Final loader shutdown | All | No | Preserves historical fallback suppression |
| Eviction capacity | Unbounded historical behavior | All | No | No mid-session Node destruction |
Animation enablement, candidate/allow/deny rules, cache paths and per-frame
permits remain loader configuration.
Animation enablement, candidate/allow/deny rules and cache paths remain loader
configuration consumed by the cached observer. Per-frame permits remain loader-owned.
## Persistence, cache and migration
@@ -188,7 +191,8 @@ and native M2 formats are unchanged; no migration or rebake is introduced.
## Diagnostics and observability
- `diagnostic_snapshot` exposes four sorted path arrays without Node references.
- Existing `M2_ANIM_CACHE` and native animation logs remain loader-owned.
- Cached eligibility rejection logging belongs to the cached observer; native
animation logs remain loader-owned.
- Existing renderer queue metrics remain unchanged because these tables never
contributed work counts.
- Normalized relative path remains the correlation key.
@@ -208,8 +212,8 @@ and native M2 formats are unchanged; no migration or rebake is introduced.
## Extension points
Eviction or byte/count budgets require measured memory evidence and explicit
prototype-user lifetime rules. Animation request-state extraction can consume
this service without moving ResourceLoader or builder ownership into it.
prototype-user lifetime rules. Cached animation observation consumes this
service without moving ResourceLoader or builder ownership into prototype state.
## Capability status
+16 -5
View File
@@ -143,6 +143,7 @@ from externally reading/writing loader-private queue, task, cache and tile-state
| `M2BuildDispatchPlanner.plan_step` | Internal pure M2 service | Selects wait, animated/static materialization or no-Node advancement from observed resource state | Main/any thread; stateless | Pending animation has priority; unresolved static Mesh waits |
| `M2BuildResourceSnapshot` | Internal M2 value contract | Carries per-step normalized path, animated/static references and pending/missing observations | Renderer main thread; one build operation | Values retained exactly; release never frees resources |
| `M2StaticBuildResourceObserver.observe` | Internal M2 I/O service | Produces cached/pending/requested/missing static snapshot phase | Renderer main thread; stateless | Invalid composition rejected; no candidate marks missing |
| `M2CachedAnimationResourceObserver.observe` | Internal M2 I/O service | Produces cached/pending/static-only animated snapshot phase | Renderer main thread; stateless | Invalid composition returns empty snapshot; no safe candidate marks static-only |
| `M2BuildQueue` / `M2BuildJob` | Internal M2 pending-state service | Own typed root/groups/cursor jobs and FIFO/stale tile keys | Renderer main thread; map session | Invalid enqueue rejected; stale keys drain independently of jobs |
| `M2StaticBatchMaterializer.materialize_batch` | Internal M2 scene-materialization service | Builds and attaches one prepared-Mesh MultiMesh transform slice | Renderer main thread; stateless after each call | Invalid/empty input returns null; bounds are caller precondition |
| `M2RuntimeMeshRebuildClassifier` | Internal memoized M2 service | Detects billboard/UV-rotation metadata requiring stale cached-mesh rebuild | Renderer main thread; cached until reset | Invalid variants/indices skipped; first path decision wins |
@@ -193,7 +194,7 @@ loader configuration remains transitional composition data, not a caller API.
| Internal grouping | Tile origin, M2 names and placements | Loader / `M2PlacementGrouper` | Loader worker result/build job | Fresh Dictionary/Transform3D arrays | One grouping task |
| Internal batch plan | Transform count/offset, path kind and limits | Loader / `M2BuildBatchPlanner` | Loader materialization/cursor adapter | Fresh scalar Dictionary | One build operation |
| Internal M2 dispatch plan | Batch count and observed animation/static resource state | Loader / `M2BuildDispatchPlanner` | Loader queue/materializer/progress adapter | Fresh scalar/action Dictionary | One build operation |
| Internal M2 resource observation | Normalized path, optional prototype/Mesh and pending/missing flags | Loader / `M2BuildResourceSnapshot` | Dispatch planner and loader materializer adapter | Snapshot borrows exact engine references | One build operation |
| Internal M2 resource observation | Normalized path, optional prototype/Mesh and pending/missing flags | Loader / M2 observers / `M2BuildResourceSnapshot` | Dispatch planner and loader materializer adapter | Snapshot borrows exact engine references | One build operation |
| Internal M2 pending build | Tile key, M2 root, grouped transforms and cursors | Loader / `M2BuildQueue` | Loader readiness, planner and materializer adapters | Queue-owned job/keys and strong references | Until finish/cancel/clear/replacement |
| Internal static M2 materialization | Parent, prepared Mesh, ordered transform slice and render settings | Loader / `M2StaticBatchMaterializer` | Attached MultiMeshInstance3D | Parent owns node/MultiMesh; exact Mesh reference retained | One main-thread build batch |
| Internal WMO placement | Path, MODF placement, tile/index | Loader / `WmoPlacementResolver` | WMO caches, registry and three instance adapters | Value-only String/Transform3D | Lookup/placement lifetime |
@@ -204,7 +205,7 @@ loader configuration remains transitional composition data, not a caller API.
| Internal WMO scene cache | Normalized path, `.tscn` path and validated PackedScene | Loader / `WmoSceneResourceCacheState` | Loader lookup, request poll and scene instantiation | State-owned PackedScene/path references; detached request snapshots | Until transient/full clear |
| Internal ADT water load | Tile key, ADT path, task ID and parsed Dictionary | Loader/worker / `AdtWaterLoadPipelineState` | Loader task start, budgeted drain and finalization | State-owned records; mutex result mailbox | Request through result completion/reset |
| Internal raw M2 read | Extracted directory and normalized relative path | Loader / `M2RawModelRepository` | Finalizer, static or animated builder adapters | Fresh Dictionary; repository retains nothing | One synchronous native call |
| Internal animated M2 load | Normalized path, cached GLB path and opaque terminal status | Loader / `M2AnimationLoadPipelineState` | Loader poll/finalize adapters | State-owned String/status records; detached pending snapshots | Request through terminal finalize/reset |
| Internal animated M2 load | Normalized path, cached GLB path and opaque terminal status | Cached observer / loader / `M2AnimationLoadPipelineState` | Loader poll/finalize adapters | State-owned String/status records; detached pending snapshots | Request through terminal finalize/reset |
| Internal animated M2 candidate | Loaded PackedScene, static material root and detached candidate | Loader / `M2AnimatedSceneFinalizer` | Loader prototype adoption/static fallback | Finalizer owns until rejection or exact-root transfer | One main-thread finalize permit |
| Internal animated M2 playback | Prototype/instance native animators, path/index and player inventory | Materializer/finalizer / `M2AnimationPlaybackController` | Native and imported animation state | Borrowed Nodes; detached optional diagnostics | One duplicated instance startup |
| Internal animated M2 materialization | Parent, prototype, ordered transform slice and render settings | Loader / `M2AnimatedInstanceMaterializer` | Attached animated batch and indexed diagnostics | Parent owns batch; caller owns detached diagnostics | One main-thread build batch |
@@ -395,9 +396,11 @@ sequenceDiagram
`M2BuildQueue` owns typed pending jobs, FIFO/stale keys, grouped-transform
references and group/offset/serial cursors without freeing engine objects.
`M2StaticBuildResourceObserver` owns static Mesh lookup/request admission and
snapshot adoption. `M2StaticBatchMaterializer` owns static MultiMesh
construction/attachment; the loader retains animated observation/requests,
action execution, cursor adoption, root cleanup, budgets and Editor ownership.
snapshot adoption. `M2CachedAnimationResourceObserver` owns cached animated
prototype lookup, GLB policy/request admission and snapshot production.
`M2StaticBatchMaterializer` owns static MultiMesh construction/attachment; the
loader retains native animation observation, action execution, cursor adoption,
root cleanup, budgets and Editor ownership.
- `WmoPlacementResolver` is stateless and owns only call-local cache-key,
identity and transform values. `WmoPlacementRegistry` owns only placement-key
reference sets. `WmoRenderBuildStepPlanner` owns only a call-local operation
@@ -422,6 +425,11 @@ sequenceDiagram
cache path selection, request admission and initial snapshot adoption. The
loader retains ResourceLoader polling/finalize, permits and terminal adoption;
prototype cache state owns shared missing outcomes.
- `M2AnimationLoadPipelineState` owns animated M2 pending Resource paths, opaque
terminal statuses and completion-order finalize FIFO. The cached animation
observer owns allow/deny/path/GLB selection, request admission and initial
snapshot production. The loader retains native animation build, ResourceLoader
polling/finalize, permits and terminal adoption.
- `M2MeshResourceCacheState` owns prepared static Mesh references and releases
them at the existing final-shutdown site. Prototype state and materialization
belong to the sibling cache service and loader respectively.
@@ -584,6 +592,7 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
| M2 build dispatch planner | Implemented extraction | Scene-free priority/action/transition/source/timing contract | Resource observation/orchestration and asset-backed traversal remain pending |
| M2 build resource snapshot | Implemented extraction | Typed identity/adoption/lifetime/source/timing contract | Resource observation service and asset-backed traversal remain pending |
| M2 static build resource observer | Implemented extraction | Cache/pending/path/request/source/timing contract | Asset-backed traversal and finalize extraction pending |
| M2 cached animation resource observer | Implemented extraction | Cache/pending/policy/GLB/request/source/timing contract | Native observation and asset-backed traversal pending |
| M2 build queue | Implemented extraction | Typed lifecycle/FIFO/progress/lifetime/source/timing contract | Resource dispatch and asset-backed traversal remain pending |
| M2 animation load pipeline state | Implemented extraction | Synthetic lifecycle/FIFO/source/timing contract | Asset-backed traversal/leak/animation-fidelity/p95/p99 pending |
| M2 animated scene finalizer | Implemented extraction | Synthetic type/lifetime/material/player/source/timing contract | Asset-backed GLB traversal/material/animation comparison pending |
@@ -658,6 +667,7 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
| `src/render/m2/m2_build_dispatch_planner.gd` | M2 wait/materializer/advance action priority and transition plan |
| `src/render/m2/m2_build_resource_snapshot.gd` | Typed per-step animated/static observations and borrowed references |
| `src/render/m2/m2_static_build_resource_observer.gd` | Static Mesh lookup/request/missing snapshot production |
| `src/render/m2/m2_cached_animation_resource_observer.gd` | Cached animated GLB policy/request/snapshot production |
| `src/render/m2/m2_build_job.gd` | M2 root/groups references and group/offset/serial progress |
| `src/render/m2/m2_build_queue.gd` | Keyed pending jobs and FIFO/stale tile-key lifecycle |
| `src/render/m2/m2_mesh_load_pipeline_state.gd` | Static M2 pending Resource paths, terminal statuses and finalize FIFO |
@@ -689,6 +699,7 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
| `src/tools/verify_m2_build_dispatch_planner.gd` | M2 dispatch priority/action/transition/boundary/timing regression |
| `src/tools/verify_m2_build_resource_snapshot.gd` | M2 observation identity/adoption/lifetime/boundary/timing regression |
| `src/tools/verify_m2_static_build_resource_observer.gd` | Static lookup/request/path/boundary/timing regression |
| `src/tools/verify_m2_cached_animation_resource_observer.gd` | Cached animation lifecycle/policy/GLB/boundary/timing regression |
| `src/tools/verify_m2_build_queue.gd` | M2 job/FIFO/progress/lifetime/boundary/timing regression |
| `src/tools/verify_m2_mesh_load_pipeline_state.gd` | Static M2 request/terminal/FIFO/boundary/timing regression |
| `src/tools/verify_m2_mesh_resource_cache_state.gd` | Static M2 Mesh cache ownership/lifetime/boundary/timing regression |