Files
open-wc/docs/modules/m2-build-resource-snapshot.md
T

11 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
    Loader[StreamingWorldLoader observations] --> Snapshot[M2BuildResourceSnapshot]
    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 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 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: resolve/request animated prototype
    L->>S: construct(path, prototype, pending)
    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; loader owns observation order.
  • 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

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.

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
Animated lookup/request execution Loader-owned Animation regressions 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.
  • 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/scenes/streaming/streaming_world_loader.gd Animated observation/request execution and materializer borrowing
src/tools/verify_m2_build_resource_snapshot.gd Identity/adoption/lifetime/boundary/timing regression