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