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