# 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)
