render: extract WMO runtime scene preparer
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
This commit is contained in:
@@ -0,0 +1,211 @@
|
||||
# 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)
|
||||
Reference in New Issue
Block a user