# WMO Runtime Scene Preparer ## Metadata | Field | Value | |---|---| | Status | Implemented | | Target | M03 Renderer Facade and Safe Extraction | | Work package | `M03-RND-WMO-RUNTIME-SCENE-PREPARER-001` | | Owner | Render | | Last verified | 2026-08-01 | ## Purpose `WmoRuntimeScenePreparer` applies the existing post-instantiation rules to a borrowed WMO subtree on the renderer main thread. Cached scenes receive recursive Mesh finalization before render policy; live-built duplicates receive render policy only. ## Non-goals - load, validate, instantiate, duplicate, place or attach a WMO scene; - change `WmoRuntimeMeshFinalizer` material/version rules; - own the supplied subtree or decide Editor persistence; - change cache formats, queue progress, permits or shutdown; - implement portal/room visibility or recursive occluder discovery. ## Context and boundaries The loader distinguishes validated cached PackedScene instances from duplicated live-built prototypes. That distinction remains explicit through two public methods so the live path does not gain cached-Mesh refresh behavior. ```mermaid flowchart LR Cached[Validated cached WMO instance] --> Loader[StreamingWorldLoader] Live[Duplicated live-built WMO instance] --> Loader Loader -->|prepare_cached_instance| Preparer[WmoRuntimeScenePreparer] Loader -->|prepare_live_instance| Preparer Preparer -->|cached only| MeshFinalizer[WmoRuntimeMeshFinalizer] Preparer --> Policy[Direct Occluders policy + recursive shadow enabling] Policy --> Borrowed[Borrowed WMO Node3D subtree] Loader --> Placement[Placement/attachment/registry lifecycle] ``` ## Public API | Symbol | Role | Thread/lifetime | Failure behavior | |---|---|---|---| | `prepare_cached_instance(instance, extracted_directory, enable_occlusion_culling, cast_shadows)` | Finalize cached subtree Meshes, then apply render policy | Renderer main thread; stateless after return | Null/freed root returns false | | `prepare_live_instance(instance, enable_occlusion_culling, cast_shadows)` | Apply render policy without Mesh finalization | Renderer main thread; stateless after return | Null/freed root returns false | Both methods borrow the root and return a success flag. Disabled occlusion removes only the direct child named `Occluders`, matching the previous loader lookup. Enabled shadows set all descendant `GeometryInstance3D` nodes to ON. Disabled shadows preserve every existing value rather than forcing OFF. ## Inputs and outputs | Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime | |---|---|---|---|---|---| | Input | Cached or live-built WMO `Node3D` | Loader scene/prototype adapter | Preparer | Borrowed exact subtree | One main-thread call | | Input | Extracted directory | Loader configuration | Runtime Mesh finalizer via preparer | Borrowed String | Cached preparation call | | Input | Occlusion/shadow policy | Loader quality configuration | Preparer | Scalar values | One call | | Internal | Mesh/MultiMesh Mesh reference | Borrowed subtree traversal | `WmoRuntimeMeshFinalizer` | Exact Resource reference; not retained by preparer | One cached call | | Output | Success flag | Preparer | Loader | Value | Immediate | | Side effect | Mesh refresh, optional child removal and shadow mutation | Preparer | Borrowed subtree | SceneTree remains loader/placement-owned | Until subtree release | No filesystem, ResourceLoader, worker, RID, cache, queue, placement, attachment, Editor-owner or diagnostic side effect is introduced. ## Data flow ```mermaid flowchart LR Input[Borrowed root + path kind + policies] --> Valid{Root valid?} Valid -->|no| False[Return false] Valid -->|yes cached| Walk[Parent-before-children traversal] Valid -->|yes live| Policy Walk --> Mesh{MeshInstance or non-null MultiMesh?} Mesh -->|yes| Finalize[WmoRuntimeMeshFinalizer.finalize_mesh] Mesh -->|no| Next[Continue] Finalize --> Next Next --> Policy[Apply direct Occluders and shadow policies] Policy --> True[Return true] ``` ## Main sequence ```mermaid sequenceDiagram participant Loader as StreamingWorldLoader participant Preparer as WmoRuntimeScenePreparer participant Finalizer as WmoRuntimeMeshFinalizer participant Root as Borrowed WMO subtree alt cached scene Loader->>Preparer: prepare_cached_instance(root, directory, policies) loop parent-before-children Mesh traversal Preparer->>Finalizer: finalize_mesh(exact Mesh, directory) end else live-built duplicate Loader->>Preparer: prepare_live_instance(root, policies) end Preparer->>Root: optional direct Occluders removal Preparer->>Root: optional recursive shadow ON Preparer-->>Loader: true/false Loader->>Loader: place, attach, register and own lifetime ``` ## Dependency diagram ```mermaid flowchart TB Loader[StreamingWorldLoader] --> Preparer[WmoRuntimeScenePreparer] Preparer --> Finalizer[WmoRuntimeMeshFinalizer] Preparer --> Engine[Node3D / GeometryInstance3D / Mesh / MultiMesh] Preparer -. no dependency .-> IO[ResourceLoader / FileAccess] Preparer -. no dependency .-> Queue[WMO queues / RenderBudgetScheduler] Preparer -. no dependency .-> Placement[WmoPlacementResolver / Registry] ``` ## Ownership, threading and resources - Calls are main-thread only because Mesh Resources and SceneTree nodes mutate. - The loader/placement registry retains ownership of the root and descendants. - Mesh finalization receives exact borrowed Resource identities. - A removed direct `Occluders` child is detached and `queue_free()`d as before. - The service retains no Node, Resource, RID, path, collection or state after return. ## Errors, cancellation and recovery | Failure/state | Detection | Behavior | Recovery | |---|---|---|---| | Null/freed root | Guard | Return false, no traversal | Caller drops/retries stale placement | | Missing finalizer composition | Null dependency | Skip Mesh refresh but apply render policy | Fix renderer composition before cached use | | Null Mesh | Exact finalizer call for MeshInstance; finalizer handles null | Continue traversal | Cache rebuild/finalizer diagnostics | | Null MultiMesh or its Mesh | Guard/finalizer contract | Skip null MultiMesh; finalizer handles null Mesh | Continue safely | | Occlusion disabled | Direct-child lookup | Detach and queue-free `Occluders` | Reinstantiate to restore subtree | | Shadows disabled | Policy branch | Preserve current node settings | Re-run with enabled policy if required | | Placement cancellation/shutdown | Loader lifecycle | Service has no retained work to cancel | Existing subtree release/drain order | ## Configuration and capabilities The service introduces no setting. It receives existing `enable_occlusion_culling` and `wmo_cast_shadows` values. The direct-child name `Occluders` and shadow-ON behavior are compatibility rules, not new capabilities. ## Persistence, cache and migration No serialized format or cache version changes. Cached Mesh refresh metadata continues to belong to `WmoRuntimeMeshFinalizer`; no rebake or migration is needed. ## Diagnostics and observability The preparer emits no log or metric. Loader placement/build metrics and the Mesh finalizer contracts remain the diagnostic surfaces. ## Verification - `verify_wmo_runtime_scene_preparer.gd` covers cached parent-before-children Mesh/MultiMesh identity, directory forwarding, null MultiMesh, direct versus nested `Occluders`, enabled/preserved shadows, live finalizer suppression, missing dependency, invalid roots, source boundaries and 1,000 traversals. - Adjacent WMO finalizer/cache/queue/shutdown and checkpoint checks protect the unchanged lifecycle and presentation behavior. - Fidelity evidence is exact behavior-preserving extraction; no asset-backed or original-client 3.3.5a visual-parity claim is added. The synthetic budget is 1,000 two-Mesh cached preparations under one second. Asset-backed CPU/GPU p95/p99, traversal and leak measurements remain pending. ## Extension points - Asset-backed traversal can validate material identity and subtree lifetime without expanding this service contract. - Portal/room visibility requires its own documented WMO service and evidence; it must not be hidden inside generic subtree preparation. ## Capability status | Capability | Status | Evidence | Gap/next step | |---|---|---|---| | Cached WMO subtree Mesh finalization | Implemented extraction | Exact traversal/resource/directory contract | Asset-backed visual/GPU p95/p99 pending | | Live/cached render policy | Implemented extraction | Direct occluder and recursive shadow contract | Portal/room and asset-backed traversal pending | | Placement/attachment/lifetime | Loader-owned | Existing registry/shutdown regressions | Further orchestration extraction pending | ## Known gaps and risks - Recursive Mesh finalization and shadow mutation remain synchronous main-thread work. - The missing-finalizer branch degrades safely for isolated tests but production composition must always inject `WmoRuntimeMeshFinalizer`. - No private WMO corpus, portal/room behavior, long traversal, leak/GPU timing or paired original-client capture is included. ## Source map | Path | Responsibility | |---|---| | `src/render/wmo/wmo_runtime_scene_preparer.gd` | Cached/live subtree traversal and render policy | | `src/render/wmo/wmo_runtime_mesh_finalizer.gd` | Borrowed Mesh refresh/version/material rules | | `src/scenes/streaming/streaming_world_loader.gd` | Composition, instantiation, placement, attachment and lifetime | | `src/tools/verify_wmo_runtime_scene_preparer.gd` | Synthetic traversal/policy/boundary/timing regression | ## Related decisions and references - [`wmo-runtime-mesh-finalizer.md`](wmo-runtime-mesh-finalizer.md) - [`wmo-scene-resource-finalizer.md`](wmo-scene-resource-finalizer.md) - [`wmo-render-group-materializer.md`](wmo-render-group-materializer.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)