# 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 ```mermaid 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 ```mermaid 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 ```mermaid 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 ```mermaid 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 ```mermaid 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.gd` covers 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 | ## Related decisions and references - [`m2-build-dispatch-planner.md`](m2-build-dispatch-planner.md) - [`m2-build-batch-planner.md`](m2-build-batch-planner.md) - [`m2-build-queue.md`](m2-build-queue.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)