# WMO Scene Resource Cache State

## Metadata

| Field | Value |
|---|---|
| Status | Implemented |
| Target/work package | M03 / `M03-RND-WMO-SCENE-RESOURCE-CACHE-001` |
| Owners | Validated cached-WMO PackedScenes, negative cache and pending request paths |
| Last verified | Worktree `work/sindo-main-codex/m03-wmo-scene-resource-cache`, 2026-07-17 |
| Profiles/capabilities | Cached WMO `.tscn` fallback; profile-independent state |

## Purpose

Own mutually exclusive cached, missing and pending states for normalized WMO
scene paths. The state accepts only caller-validated `PackedScene` references and
records `.tscn` paths for threaded requests whose I/O, size limit and cache
metadata validation remain in `StreamingWorldLoader`.

## Non-goals

- Normalize WMO paths, build `.tscn` paths or measure files.
- Call ResourceLoader/FileAccess, instantiate scenes or validate cache metadata.
- Own live prototypes, placement/build jobs, instantiated Nodes or RIDs.
- Change missing, oversize, request-error, retry or fallback behavior.
- Persist, migrate or build WMO caches.

## Context and boundaries

```mermaid
flowchart LR
    Lookup[Normalized WMO scene lookup] --> Loader[StreamingWorldLoader]
    Loader --> State[WmoSceneResourceCacheState]
    Loader --> Size[File existence and size limit]
    Size --> ResourceLoader[Godot ResourceLoader]
    ResourceLoader --> Loader
    Loader --> Probe[Instantiate, metadata/version check, free]
    Probe -->|accepted PackedScene or missing| State
    Loader --> Live[Live-prototype fallback]
```

The module retains validated PackedScenes and copied paths only. ResourceLoader,
FileAccess, WMOBuilder, Node, RID, worker, gameplay, network and editor concerns
remain outside it.

## Public API

| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|---|---|---|---|---|
| `scene_for(path)` / `contains_scene(path)` | Query | Borrow/test validated PackedScene | Renderer main thread; until full clear | Empty/unknown returns null/false |
| `is_missing(path)` / `has_request(path)` | Query | Test negative/pending state | Renderer main thread | Empty returns false |
| `remember_request(path, cache_path)` | Command/query | Record loader-started request | Until terminal/reset | Invalid/occupied returns false |
| `mark_missing(path)` | Command/query | Record absent/oversize/request-start failure without pending state | Until transient/full clear | Invalid/occupied returns false |
| `request_paths_snapshot()` | Query | Copy pending normalized/cache-path mapping | Poll/shutdown drain | Detached Dictionary |
| `complete_request_with_scene(path, scene)` | Command/query | Remove pending and adopt validated PackedScene | Terminal poll | Unknown/null returns false |
| `complete_request_as_missing(path)` | Command/query | Remove pending and adopt negative state | Terminal poll | Unknown returns false |
| `clear_transient_state()` | Command | Clear pending/missing and retain scenes | Map reset/request drain | Idempotent |
| `clear_all()` | Command | Release scenes and transient state | Final runtime-cache release | Idempotent |
| `pending_request_count()` | Query | Preserve renderer queue metric | Diagnostics | None |
| `diagnostic_snapshot()` | Query | Return sorted detached scalar keys | Tests/diagnostics | No PackedScene exposure |

## Inputs and outputs

| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|---|---|---|---|---|---|
| Input | Normalized WMO path | Placement resolver through loader | State | Copied String | Cache lifetime |
| Input | `.tscn` path after accepted request start | Loader | Pending state | Copied String | Until terminal/reset |
| Input | Metadata/version-validated PackedScene | Loader probe adapter | Scene cache | Strong reference | Until full clear |
| Input | Missing decision | Loader file/size/request/load/validation adapters | Negative cache | Boolean membership | Until transient/full clear |
| Output | Borrowed PackedScene | State | Loader scene instantiation | State retains reference | Lookup/instantiate call |
| Output | Detached request snapshot | State | Loader polling/shutdown | Caller-owned Dictionary | One drain pass |

## Data flow

```mermaid
flowchart TD
    Lookup --> Known{Cached, missing or pending?}
    Known -->|cached| Return[Return PackedScene]
    Known -->|missing/pending| Null[Return null]
    Known -->|absent| Check[Loader checks file and size]
    Check -->|missing/oversize| Mark[mark_missing]
    Check -->|allowed| Request[Start threaded request]
    Request -->|error| Mark
    Request -->|accepted| Remember[remember_request]
    Remember --> Poll[Loader polls snapshot]
    Poll -->|failure| CompleteMissing[complete as missing]
    Poll -->|loaded| Validate[Instantiate and validate cache metadata]
    Validate -->|valid| CompleteScene[complete with scene]
    Validate -->|invalid| CompleteMissing
```

## Lifecycle/state

Each normalized path is `Absent`, `Pending`, `Cached` or `Missing`. Direct file,
size and request-start rejection moves `Absent` directly to `Missing`; terminal
loads move `Pending` to `Cached` or `Missing`. Transient clear retains `Cached`,
while full clear returns every path to `Absent`.

```mermaid
stateDiagram-v2
    [*] --> Absent
    Absent --> Pending: accepted request
    Absent --> Missing: absent, oversize or request error
    Pending --> Cached: validated PackedScene
    Pending --> Missing: load or validation failure
    Pending --> Absent: transient/full clear
    Missing --> Absent: transient/full clear
    Cached --> Absent: full clear
```

## Main sequence

```mermaid
sequenceDiagram
    participant Loader as StreamingWorldLoader
    participant State as WmoSceneResourceCacheState
    participant RL as ResourceLoader
    Loader->>State: cached/missing/pending queries
    Loader->>Loader: exists and wmo_max_runtime_scene_mb check
    Loader->>RL: load_threaded_request(.tscn)
    Loader->>State: remember_request(normalized, path)
    loop renderer tick
        Loader->>State: request_paths_snapshot()
        Loader->>RL: load_threaded_get_status(path)
    end
    Loader->>RL: load_threaded_get(path)
    Loader->>Loader: instantiate, validate WMO metadata, free probe
    Loader->>State: complete with scene or as missing
```

## Ownership, threading and resources

- State owns three Dictionaries and strong references to accepted PackedScenes.
- Loader owns paths, file measurement, requests, validation and all Node lifetime.
- All mutation is serialized on the renderer main thread; no mutex is required.
- Detached request snapshots allow terminal removal while polling.
- Scene instantiation borrows the PackedScene and does not transfer cache ownership.

## Errors, cancellation and recovery

| Failure/state | Detection | Behavior | Recovery |
|---|---|---|---|
| Missing `.tscn` | Loader ResourceLoader existence check | Direct missing state | Transient reset permits later retry |
| Oversize `.tscn` | Loader byte limit | Missing plus unchanged debug log | Raise limit/rebuild, then reset |
| Request-start/load failure | Loader error/status | Direct or terminal missing | Reset and retry later |
| Wrong type/stale metadata | Loader PackedScene/probe validation | Terminal missing; probe freed | Rebuild cache and reset |
| Shutdown while pending | Loader drains snapshot | Clear transient, then full cache release | New loader starts absent |

## Configuration and capabilities

No setting is added. `wmo_cache_dir`, `wmo_max_runtime_scene_mb`, request timing,
quality profiles and WMO build budgets stay loader-owned.

## Persistence, cache and migration

The module is runtime-only. Existing WMOBuilder cache metadata/version checks
remain unchanged and execute before state adoption. No cache format, migration or
rebuild is introduced.

## Diagnostics and observability

Pending count preserves the existing `wmobuild` metric contribution. The snapshot
exposes sorted scene/missing/request keys only. Existing oversize logging remains
in the loader; the state emits no logs.

## Verification

- `verify_wmo_scene_resource_cache_state.gd`: invalid input, direct missing,
  exclusivity, request completion, partial/full reset, diagnostics, boundaries
  and bounded timing.
- `verify_render_runtime_cache_shutdown.gd`: loader full release of scenes,
  negative and pending state.
- Adjacent WMO/renderer regressions verify unchanged lookup/fallback behavior.
  This extraction makes no visual or build-12340 parity claim.

## Extension points

- A later I/O adapter may consume request snapshots while retaining this contract.
- Scene instantiation/material preparation can be extracted separately because it
  owns Nodes and main-thread side effects rather than cache state.

## Capability status

| Capability | Status | Evidence | Gap/next step |
|---|---|---|---|
| Cached WMO PackedScene state | Implemented extraction | Lifecycle/source/timing and shutdown verifiers | Asset-backed traversal/leak evidence pending |
| Size and cache metadata validation | Preserved in loader | Source boundary and WMO regressions | Oversize/stale asset fixtures could follow |
| ResourceLoader I/O and live fallback | Partial/loader-owned | Existing runtime behavior | Separate extraction if justified |

## Known gaps and risks

- PackedScenes remain strongly referenced until final shutdown cache release.
- Negative caching suppresses retries until transient reset, matching prior behavior.
- No proprietary WMO corpus, stale/oversize asset fixture, leak run, traversal
  p95/p99 or paired original-client capture is included.

## Source map

| Path | Responsibility |
|---|---|
| `src/render/wmo/wmo_scene_resource_cache_state.gd` | Scene/missing/request state and resets |
| `src/scenes/streaming/streaming_world_loader.gd` | Paths, file limit, requests, validation, fallback and Node lifetime |
| `src/tools/verify_wmo_scene_resource_cache_state.gd` | State, boundary and timing regression |
| `src/tools/verify_render_runtime_cache_shutdown.gd` | Loader final cache ownership regression |

## Related decisions and references

- [`wmo-render-resource-cache-state.md`](wmo-render-resource-cache-state.md)
- [`wmo-render-build-queue.md`](wmo-render-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)
