Work-Package: M03-RND-WMO-RUNTIME-SCENE-PREPARER-001 Agent: sindo-main-codex Tests: 64/65 headless verifiers passed; proprietary ADT probe unavailable; checkpoint dry-run 7/7; docs and coordination passed Fidelity: preserves cached/live preparation distinction, direct occluder policy and shadow semantics
10 KiB
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
WmoRuntimeMeshFinalizermaterial/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.
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
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
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
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
Occluderschild is detached andqueue_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.gdcovers cached parent-before-children Mesh/MultiMesh identity, directory forwarding, null MultiMesh, direct versus nestedOccluders, 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 |