# M2 Static Build Resource Observer ## Metadata | Field | Value | |---|---| | Status | Implemented extraction | | Target/work package | M03 / `M03-RND-M2-STATIC-BUILD-RESOURCE-OBSERVER-001` | | Owners | Static build Mesh lookup, request selection and missing transition | | Last verified | Worktree `work/sindo-main-codex/m03-m2-static-build-resource-observer`, 2026-07-18 | | Profiles/capabilities | Existing static MultiMesh M2 build path | ## Purpose Produce the static phase of `M2BuildResourceSnapshot`: reuse a prepared Mesh, wait for an existing request, start the first eligible cache request, or record a terminal missing model while preserving historical path and GLB rules. ## Non-goals - Observe animated/native prototypes or finalize threaded loads. - Own/free Meshes, Nodes, cache entries or snapshot lifetime. - Materialize instances, plan batches, rotate queues or consume permits. - Change cache format, candidate order, profiles or visible output. ## Context and boundaries ```mermaid flowchart LR Loader[StreamingWorldLoader] --> Observer[M2StaticBuildResourceObserver] MeshCache[M2MeshResourceCacheState] --> Observer Missing[M2PrototypeCacheState] --> Observer Pipeline[M2MeshLoadPipelineState] --> Observer Observer --> Snapshot[M2BuildResourceSnapshot] Snapshot --> Dispatch[M2BuildDispatchPlanner] ``` ## Public API | Symbol | Kind | Purpose | Errors | |---|---|---|---| | `observe(snapshot, path, cache_dir, mesh_cache, prototype_cache, pipeline)` | Command/query | Adopt cached/pending/requested/missing static result | Invalid inputs return `rejected` | | `cache_resource_paths(cache_dir, path, extensions)` | Pure query | Return unique historical nested/lowercase/basename candidates | Empty values yield fewer/empty candidates | | `glb_animation_schema(path)` | Read query | Read OpenWC schema marker from GLB JSON | Invalid/missing GLB returns empty String | | `OUTCOME_*` | Constants | Stable diagnostic transition identifiers | None | ## Inputs and outputs | Direction | Data | Producer | Consumer | Ownership/lifetime | |---|---|---|---|---| | Input | Normalized M2 path/cache directory | Loader | Observer | Copied Strings; one observation | | Input | Mesh/missing/request states | M2 state services | Observer | Borrowed services; map/session | | Input/output | Resource snapshot | Loader | Observer/dispatch/materializer | Borrowed; one build operation | | Output | Cached Mesh or missing/pending values | Observer | Snapshot | Exact borrowed Mesh/scalars | | Output | Threaded request record | Observer | Mesh pipeline | Pipeline-owned path record | | Output | Outcome StringName | Observer | Tests/future diagnostics | Immutable scalar | ## Data flow ```mermaid flowchart TD Start[Observe static path] --> Cached{Mesh cached?} Cached -->|yes| AdoptMesh[Adopt exact Mesh; cached] Cached -->|no| Missing{Already missing?} Missing -->|yes| AdoptMissing[Adopt missing] Missing -->|no| Pending{Request pending?} Pending -->|yes| AdoptPending[Adopt unresolved; pending] Pending -->|no| Candidates[Generate tscn then glb candidates] Candidates --> Eligible{Exists and not pivot-prefix GLB?} Eligible -->|yes| Request[Threaded request] Request -->|OK or busy| Remember[Remember request; requested] Eligible -->|none/error| MarkMissing[Mark/adopt missing] ``` ## Lifecycle/state ```mermaid stateDiagram-v2 [*] --> Observing Observing --> Cached Observing --> Pending Observing --> Requested Observing --> Missing Observing --> Rejected Cached --> [*] Pending --> [*] Requested --> [*] Missing --> [*] Rejected --> [*] ``` ## Main sequence ```mermaid sequenceDiagram participant L as StreamingWorldLoader participant O as StaticBuildResourceObserver participant C as Mesh/prototype cache state participant P as MeshLoadPipelineState participant R as ResourceLoader participant S as M2BuildResourceSnapshot L->>O: observe(snapshot, path, cache_dir, states) O->>C: cached/missing lookup alt cache hit or missing O->>S: adopt static observation else pending O->>P: has_request O->>S: adopt unresolved observation else request candidate O->>R: exists + load_threaded_request O->>P: remember_request O->>S: adopt unresolved observation end ``` ## Dependency diagram ```mermaid flowchart TB Observer[M2StaticBuildResourceObserver] --> ResourceLoader Observer --> FileAccess[GLB header/JSON read] Observer --> MeshCache[M2MeshResourceCacheState] Observer --> PrototypeCache[M2PrototypeCacheState] Observer --> Pipeline[M2MeshLoadPipelineState] Observer --> Snapshot[M2BuildResourceSnapshot] Observer -. no dependency .-> SceneTree Observer -. no dependency .-> Materializers Observer -. no dependency .-> Scheduler ``` ## Ownership, threading and resources - Observer retains no request, cache, snapshot or engine reference. - Cache/pipeline services retain their existing state and resource ownership. - Snapshot borrows the exact cached Mesh; observer never frees it. - Resource existence/request and GLB inspection run on renderer main thread. - Finalization and Mesh preparation remain in the loader drain path. ## Errors, cancellation and recovery | State | Behavior | Recovery | |---|---|---| | Invalid dependency/path | Return `rejected`; no mutation | Correct composition | | Existing request | Return `pending`; no duplicate request | Retry when queue rotates | | Pivot-prefix GLB | Skip as static candidate | Continue next candidate | | Request error | Stop historical search and mark missing | Later reset/new session | | No candidate | Mark/adopt terminal missing | Cache content becomes available in new session | | Tile cancellation | Observer retains nothing | Loader/queue cancellation remains authoritative | ## Configuration and capabilities The observer consumes existing `m2_cache_dir`. It adds no profile, cache version, batch, visibility, shadow or scheduler setting. ## Persistence, cache and migration No new persistence is introduced. Existing `.tscn/.glb` layout, pipeline records and missing-cache lifetime are unchanged; no rebake is required. ## Diagnostics and observability Returned outcomes distinguish rejected/cached/pending/requested/missing without logging. Existing queue and hitch metrics remain unchanged. ## Verification - Dedicated verifier covers invalid, cached identity, missing, pending, path order/deduplication, absent GLB schema, source ownership and bounded timing. - Snapshot/dispatch/pipeline/cache/shutdown/facade/internal-access/checkpoint regressions protect adjacent behavior. - No original-client visual claim is made; this is bookkeeping/I/O extraction. ## Extension points Animated/native observation may become a separate producer of the animation phase. Shared cache candidate/GLB inspection can later be reused without adding a generic resource framework. ## Capability status | Capability | Status | Evidence | Gap | |---|---|---|---| | Static build lookup/request production | Implemented extraction | Lifecycle/path/source/timing verifier | Asset-backed traversal pending | | Static finalize/preparation | Existing loader-owned | Pipeline/finalizer regressions | Later extraction | | Animated observation | Existing loader-owned | Animation regressions | Separate producer pending | ## Known gaps and risks - Successful request behavior is protected by source plus existing pipeline tests; starting an undrained asynchronous request in the unit fixture would leak. - GLB schema parser reads synchronously, matching the previous loader behavior. - Private traversal, p95/p99, leak and visual evidence remain unavailable. ## Source map | Path | Responsibility | |---|---| | `src/render/m2/m2_static_build_resource_observer.gd` | Static cache/request/missing observation | | `src/render/m2/m2_build_resource_snapshot.gd` | Typed observation result | | `src/scenes/streaming/streaming_world_loader.gd` | Composition, finalize drain and action execution | | `src/tools/verify_m2_static_build_resource_observer.gd` | Lifecycle/path/boundary/timing regression | ## Related decisions and references - [`m2-build-resource-snapshot.md`](m2-build-resource-snapshot.md) - [`m2-mesh-load-pipeline-state.md`](m2-mesh-load-pipeline-state.md) - [`m2-mesh-resource-cache-state.md`](m2-mesh-resource-cache-state.md) - [`world-renderer.md`](world-renderer.md) - [`../../RENDER.md`](../../RENDER.md)