12 KiB
M2 Build Resource Snapshot
Metadata
| Field | Value |
|---|---|
| Status | Implemented extraction |
| Target/work package | M03 / M03-RND-M2-BUILD-RESOURCE-SNAPSHOT-001 |
| Owners | Per-build-step observed M2 resource references and outcome flags |
| Last verified | Worktree work/sindo-main-codex/m03-m2-build-resource-snapshot, 2026-07-18 |
| Profiles/capabilities | Existing static MultiMesh and animated-instance build paths |
Purpose
Carry the resource observations for one M2 build operation as a typed contract: normalized path, optional animated prototype, pending-animation state, optional static Mesh and terminal missing-model state. The snapshot never owns engine lifetime.
Non-goals
- Locate, request, load, validate or cache M2 resources.
- Decide dispatch action, batch count, queue order or permit consumption.
- Create, attach or free Nodes, Meshes, MultiMeshes or animated instances.
- Change animation allowlists, native candidate rules or cache paths/formats.
- Persist observations beyond one build operation.
Context and boundaries
flowchart LR
Native[M2NativeAnimationResourceObserver] --> Snapshot[M2BuildResourceSnapshot]
Cached[M2CachedAnimationResourceObserver] --> Snapshot
Static[M2StaticBuildResourceObserver] --> Snapshot
Snapshot --> Dispatch[M2BuildDispatchPlanner]
Snapshot --> Loader
Loader --> Animated[M2AnimatedInstanceMaterializer]
Loader --> Static[M2StaticBatchMaterializer]
The snapshot depends only on RefCounted, String, booleans and borrowed Node3D/
Mesh references. ResourceLoader, cache/pipeline state, SceneTree mutation,
RenderingServer/RIDs, files, workers, mutexes, gameplay, network and Editor APIs
are forbidden.
Public API
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|---|---|---|---|---|
Constructor (normalized_path, animated_prototype, animation_pending) |
Command | Capture first-phase animation observation | Main thread; one build operation | Values retained exactly |
normalized_relative_path() |
Query | Return observed normalized path | Snapshot lifetime | None |
animated_prototype() |
Query | Borrow exact animated prototype | Main thread; no ownership transfer | May be null |
has_animated_prototype() |
Query | Report prototype availability | Any serialized caller | None |
animation_request_pending() |
Query | Report pending animation request | Any serialized caller | None |
adopt_static_observation(mesh, missing) |
Command | Replace second-phase static observation | Main thread | Values retained exactly, including contradictory inputs |
static_mesh() |
Query | Borrow exact prepared Mesh | Main thread; no ownership transfer | May be null |
has_static_mesh() |
Query | Report static Mesh availability | Any serialized caller | None |
static_model_missing() |
Query | Report terminal missing outcome | Any serialized caller | None |
diagnostic_snapshot() |
Query | Return detached path/availability flags | Any serialized caller | Omits engine references |
Inputs and outputs
| 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 | 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 |
| Output | Borrowed prototype or Mesh | Snapshot | Loader materializer adapter | No ownership transfer | One materialization call |
| Output | Detached diagnostics | Snapshot | Tests/future metrics | Caller-owned Dictionary | Call result |
The snapshot is released after the build-loop operation. Releasing or replacing it never frees the borrowed Node3D or Mesh.
Data flow
flowchart TD
Path[Normalized M2 path] --> Construct[Create snapshot]
Animated[Animated prototype or null] --> Construct
Pending[Animation request pending] --> Construct
Construct --> PendingCheck{Animation pending?}
PendingCheck -->|yes| Dispatch[Dispatch planner]
PendingCheck -->|no| Batch[Batch planner]
Batch --> StaticNeeded{Positive static batch?}
StaticNeeded -->|yes| Adopt[Adopt Mesh and missing observation]
StaticNeeded -->|no| Dispatch
Adopt --> Dispatch
Dispatch --> Borrow[Loader borrows selected resource]
Lifecycle/state
stateDiagram-v2
[*] --> AnimationObserved: construct
AnimationObserved --> AnimationObserved: animation pending or animated path
AnimationObserved --> StaticObserved: adopt static result
StaticObserved --> StaticObserved: replace static result
AnimationObserved --> Released: operation ends
StaticObserved --> Released: operation ends
Released --> [*]
Main sequence
sequenceDiagram
participant L as StreamingWorldLoader
participant S as M2BuildResourceSnapshot
participant B as M2BuildBatchPlanner
participant D as M2BuildDispatchPlanner
participant M as M2 materializer
L->>L: delegate native animated prototype observation
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
L->>L: lookup/request static Mesh
L->>S: adopt_static_observation
end
end
L->>D: plan_step(batch count, snapshot)
D-->>L: action plan
opt materialization action
L->>S: borrow prototype or Mesh
L->>M: materialize selected slice
end
Dependency diagram
flowchart TB
Loader[StreamingWorldLoader] --> Snapshot[M2BuildResourceSnapshot]
Dispatch[M2BuildDispatchPlanner] --> Snapshot
Snapshot --> Values[String and booleans]
Snapshot --> Borrowed[Borrowed Node3D and Mesh]
Snapshot -. no dependency .-> IO[ResourceLoader / FileAccess]
Snapshot -. no dependency .-> State[Cache / pipeline / queue / scheduler]
Snapshot -. no dependency .-> Mutation[SceneTree / RenderingServer]
Ownership, threading and resources
- Snapshot owns only copied scalar state and temporary references.
- Prototype/cache services retain resource state; the native observer owns its phase while loader owns native-first ordering among resource observers.
- 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.
Errors, cancellation and recovery
| Failure/state | Detection | Behavior | Diagnostic | Recovery |
|---|---|---|---|---|
| Empty path | Exact String retained | No validation or hidden normalization | Contract fixture | Loader already normalizes input |
| Null prototype/Mesh | Availability accessor false | Dispatch uses pending/missing state | Contract fixture | Loader request/cache path retries |
| Mesh and missing both true | Exact adoption | Both retained; dispatch gives ready Mesh priority | Contradiction fixture | Loader normally prevents combination |
| Static observation replaced | Explicit adoption | Latest Mesh/missing values win | Replacement fixture | None required |
| Borrowed Node externally freed | Outside snapshot | Loader validity/lifecycle gates remain authoritative | Shutdown regressions | Tile may cancel/requeue |
| Tile cancellation | Outside snapshot | Ephemeral snapshot releases references | Queue/shutdown regressions | Eligible tile may rebuild |
Configuration and capabilities
The snapshot adds no setting. Existing animation enable/allow/deny rules, batch limits, cache paths, render settings and scheduler permits remain external.
Persistence, cache and migration
No observation is serialized. No ADT/M2 cache version changes and no rebake or migration are required.
Diagnostics and observability
diagnostic_snapshot() exposes normalized path and four availability/outcome
flags without Node/Mesh references. The module emits no logs. Existing loader
queue/hitch metrics remain unchanged.
Verification
verify_m2_build_resource_snapshot.gdcovers exact path/reference identity, defaults, static adoption/replacement/clear, contradictory flags, detached diagnostics, engine lifetime, loader/dispatch boundaries and bounded timing.- Dispatch, queue, batch, materializer, cache/pipeline, shutdown, facade, internal-access and checkpoint regressions protect adjacent behavior.
- Fidelity evidence is exact observation transfer only; no private asset or original-client visual claim is made.
Extension points
M2CachedAnimationResourceObserver produces the cached animated phase and
M2StaticBuildResourceObserver produces the static phase. Native animation is
produced by M2NativeAnimationResourceObserver. The value stays independent of all
producers and of generic callback frameworks.
Capability status
| Capability | Status | Evidence | Gap/next step |
|---|---|---|---|
| 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 |
| Cached animated lookup/request execution | Implemented separately | Cached observer verifier | Asset-backed traversal pending |
| Native animated lookup/build execution | Implemented observer extraction | Native observer lifecycle/source verifier | Asset-backed traversal 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.
- Native parsing/build remains synchronous; loader still owns terminal polling.
- Synthetic timing does not measure resource, Node or GPU work.
Source map
| Path | Responsibility |
|---|---|
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/render/m2/m2_cached_animation_resource_observer.gd |
Cached animated snapshot production and requests |
src/render/m2/m2_native_animation_resource_observer.gd |
Native prototype observation producer |
src/scenes/streaming/streaming_world_loader.gd |
Observer ordering and materializer borrowing |
src/tools/verify_m2_build_resource_snapshot.gd |
Identity/adoption/lifetime/boundary/timing regression |