Work-Package: M03-RND-WMO-RENDER-GROUP-MATERIALIZER-001 Agent: sindo-main-codex Tests: 63/64 autonomous verifiers passed; proprietary ADT probe unavailable; baseline dry-run 7/7; documentation and coordination gates passed Fidelity: exact behavior-preserving node materialization extraction; no new 3.3.5a parity claim
9.9 KiB
WMO Render Group Materializer
Metadata
| Field | Value |
|---|---|
| Status | Implemented |
| Target | M03 Renderer Facade and Safe Extraction |
| Work package | M03-RND-WMO-RENDER-GROUP-MATERIALIZER-001 |
| Owner | Render |
| Last verified | 2026-08-01 |
Purpose
WmoRenderGroupMaterializer creates and attaches one lightweight cached WMO
render group on the renderer main thread. It owns the duplicated
MeshInstance3D/MultiMeshInstance3D presentation rules that previously lived
inside StreamingWorldLoader.
Non-goals
- select a build step, advance/cancel a job or consume a render permit;
- load, cache, validate or finalize WMO Resources;
- resolve placement transforms or own the placement root;
- choose Editor persistence policy or serialize generated nodes;
- change WMO cache formats, materials, visibility or shadow policy.
Context and boundaries
The loader obtains a queue-owned WMO root and render Resource, asks
WmoRenderBuildStepPlanner for one operation, and finalizes the selected Mesh.
The materializer then performs only indexed node presentation and attachment.
The loader retains scheduler, queue and Editor composition responsibilities.
flowchart LR
Queue[WmoRenderBuildQueue] --> Loader[StreamingWorldLoader]
Planner[WmoRenderBuildStepPlanner] --> Loader
Loader --> Finalizer[WmoRuntimeMeshFinalizer]
Loader --> Materializer[WmoRenderGroupMaterializer]
Materializer --> MeshNode[MeshInstance3D]
Materializer --> MultiMeshNode[MultiMeshInstance3D]
MeshNode --> Root[Queue-owned WMO Node3D root]
MultiMeshNode --> Root
Loader --> EditorOwner[Optional Editor owner assignment]
Public API
| Symbol | Role | Thread/lifetime | Failure behavior |
|---|---|---|---|
materialize_mesh_group(...) |
Create, configure and attach one indexed Mesh group | Renderer main thread; stateless after return | Null/invalid parent, null Mesh or negative index returns null |
materialize_multimesh_group(...) |
Create, configure and attach one indexed MultiMesh doodad group | Renderer main thread; stateless after return | Null/invalid parent, null MultiMesh or negative index returns null |
Both methods preserve exact Resource identity. group_index selects the optional
name and transform; missing names use the historical indexed fallback and a
missing transform leaves Transform3D.IDENTITY. Positive visibility range
applies its caller-supplied margin. Shadow mode is always applied explicitly.
Inputs and outputs
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|---|---|---|---|---|---|
| Input | Queue-owned WMO Node3D root |
WmoRenderBuildQueue via loader |
Materializer | Borrowed; not retained | One main-thread call |
| Input | Selected Mesh or MultiMesh |
WMO render Resource via loader | Materializer | Borrowed exact Resource | Parent-node lifetime after attach |
| Input | Names, transforms and selected index | WMO render Resource/planner via loader | Materializer | Borrowed value collections | One call |
| Input | Visibility end/margin and shadow flag | Loader quality profile | Materializer | Scalar values | One call |
| Output | Attached MeshInstance3D or MultiMeshInstance3D |
Materializer | Loader/SceneTree | Parent root owns node and Resource reference | Until placement release/world teardown |
Side effects:
- allocates exactly one Godot geometry node for valid input;
- applies name, optional transform, shadow and optional visibility settings;
- attaches the node exactly once to the supplied WMO root.
It performs no filesystem, ResourceLoader, worker, RenderingServer RID, cache, queue, permit, logging or direct Editor-owner mutation.
Data flow
flowchart LR
Input[Root + Resource + indexed metadata + render settings] --> Validate{Valid root/resource/index?}
Validate -->|no| Null[Return null; no attachment]
Validate -->|yes| Kind{Mesh or MultiMesh method}
Kind --> Mesh[Create MeshInstance3D]
Kind --> Multi[Create MultiMeshInstance3D]
Mesh --> Configure[Name + optional transform + render settings]
Multi --> Configure
Configure --> Attach[Attach once to WMO root]
Attach --> Return[Return borrowed attached node]
Main sequence
sequenceDiagram
participant Loader as StreamingWorldLoader
participant Queue as WmoRenderBuildQueue
participant Finalizer as WmoRuntimeMeshFinalizer
participant Materializer as WmoRenderGroupMaterializer
participant Root as WMO Node3D root
Loader->>Queue: read front job and cursors
Loader->>Finalizer: finalize selected Mesh
Loader->>Materializer: materialize selected group
Materializer->>Root: add_child(geometry instance)
Materializer-->>Loader: attached node or null
Loader->>Loader: optional Editor ownership
Loader->>Queue: adopt planned cursors
Loader->>Loader: consume one group permit
Dependency diagram
flowchart TB
Loader[StreamingWorldLoader] --> Materializer[WmoRenderGroupMaterializer]
Materializer --> Engine[Node3D / GeometryInstance3D / Mesh / MultiMesh]
Materializer -. no dependency .-> IO[ResourceLoader / FileAccess]
Materializer -. no dependency .-> Queue[WmoRenderBuildQueue]
Materializer -. no dependency .-> Scheduler[RenderBudgetScheduler]
Materializer -. no dependency .-> Finalizer[WmoRuntimeMeshFinalizer]
Ownership, threading and resources
- Both public methods are main-thread only because they mutate the SceneTree.
- The caller owns the parent root; after attachment the root owns the new node.
- The node retains the exact input Mesh or MultiMesh Resource reference.
- The service retains no Node, Resource, RID, collection or per-group state.
- Loader-owned optional recursive Editor ownership runs after successful return.
Errors, cancellation and recovery
| Failure/state | Detection | Behavior | Recovery |
|---|---|---|---|
| Null/freed parent | Guard | Return null; allocate/attach nothing | Caller validates current queue job |
| Null Resource | Guard | Return null | Loader advances the historically selected cursor |
| Negative index | Guard | Return null | Planner supplies non-negative selected indices |
| Missing name | Bounds check | Use Group_N or DoodadGroup_N |
Rebuild cache metadata if desired |
| Missing transform | Bounds check | Retain identity transform | Rebuild cache metadata if desired |
| Placement cancellation | Loader/queue | Parent and children released by existing lifecycle | Re-request placement later |
| Shutdown | Loader lifecycle | Service has no retained state to drain | New loader composes a new service |
Configuration, capabilities and profiles
The service introduces no configuration or capability. It accepts the existing
wmo_visibility_range, CHUNK_SIZE margin and wmo_cast_shadows values chosen
by the loader quality profile. Blizzlike and Enhanced selection remains outside.
Persistence, cache and migrations
No persisted data or cache format changes. The service neither reads nor writes WMO cache files and requires no migration or rebake.
Diagnostics and observability
The service emits no logs or metrics. Existing wmo_groups queue depth, build
permits and loader lifecycle diagnostics remain authoritative.
Verification and fidelity evidence
verify_wmo_render_group_materializer.gdcovers exact Mesh/MultiMesh identity, indexed and fallback names, optional transforms, shadows, positive/disabled visibility, attachment, invalid input, source boundaries and 1,000 groups.- Adjacent WMO queue/planner/finalizer and checkpoint regressions protect the unchanged orchestration and visible output.
- This is an exact code-motion extraction of existing Godot presentation rules; it adds no original-client 3.3.5a visual parity claim.
Performance budgets
The synthetic contract requires 1,000 simple Mesh group materializations in under one second. Production work remains limited to one group per scheduler permit. Asset-backed CPU/GPU p95/p99 and long traversal remain required evidence.
Extension points
- Asset-backed WMO traversal can measure group attachment and lifetime without changing this API.
- Additional render settings belong here only when they apply equally to both lightweight group-node kinds and have fidelity evidence.
Capability status
| Capability | Status | Evidence | Gap/next step |
|---|---|---|---|
| Mesh group materialization | Implemented extraction | Identity/name/transform/render/attachment contract | Asset-backed visual/GPU p95/p99 pending |
| MultiMesh doodad group materialization | Implemented extraction | Identity/name/transform/render/attachment contract | Asset-backed traversal/leak evidence pending |
| Build planning/queue progress | Loader-owned | Existing planner/queue regressions | Further orchestration extraction pending |
| Editor persistence ownership | Loader-owned | Source-boundary contract | Editor scene-save integration evidence pending |
Known gaps and risks
- SceneTree node creation remains synchronous main-thread work by design.
- The synthetic fixture does not measure private WMO assets, GPU upload, portal visibility, original-client visuals, long traversal or leak behavior.
Source map
| Path | Responsibility |
|---|---|
src/render/wmo/wmo_render_group_materializer.gd |
Indexed geometry-node creation, settings and attachment |
src/scenes/streaming/streaming_world_loader.gd |
Composition, finalization, queue, permits, Editor ownership and lifecycle |
src/tools/verify_wmo_render_group_materializer.gd |
Synthetic contract, ownership boundary and timing regression |