7e97b19095
Work-Package: M03-RND-WMO-SCENE-INSTANCE-FACTORY-001 Agent: sindo-main-codex Tests: 65/66 headless verifiers; proprietary ADT probe unavailable; checkpoint dry-run 7/7; docs and coordination passed Fidelity: valid cached/live naming, placement, validation and Resource identity preserved; invalid-root leak fixed
220 lines
10 KiB
Markdown
220 lines
10 KiB
Markdown
# WMO Scene Instance Factory
|
|
|
|
## Metadata
|
|
|
|
| Field | Value |
|
|
|---|---|
|
|
| Status | Implemented |
|
|
| Target | M03 Renderer Facade and Safe Extraction |
|
|
| Work package | `M03-RND-WMO-SCENE-INSTANCE-FACTORY-001` |
|
|
| Owner | Render |
|
|
| Last verified | 2026-08-01 |
|
|
|
|
## Purpose
|
|
|
|
`WmoSceneInstanceFactory` creates detached WMO `Node3D` roots from validated
|
|
cached `PackedScene` resources or live-built prototypes. It owns cache-currentness
|
|
validation, basename assignment and canonical placement-resolver delegation.
|
|
|
|
## Non-goals
|
|
|
|
- look up/load/cache PackedScenes or build live WMO prototypes;
|
|
- apply Mesh/material/occluder/shadow runtime preparation;
|
|
- attach nodes, assign Editor ownership or manage placement references;
|
|
- own queues, permits, cache versions or world teardown;
|
|
- define WMO placement formulas or scene-cache currentness rules.
|
|
|
|
## Context and boundaries
|
|
|
|
The loader selects cached versus live sources. The factory creates a detached
|
|
instance and applies identity/placement. `WmoRuntimeScenePreparer` then applies
|
|
path-specific presentation policy before the loader attaches/registers the root.
|
|
|
|
```mermaid
|
|
flowchart LR
|
|
Cache[WMO PackedScene cache] --> Loader[StreamingWorldLoader]
|
|
Prototype[Live WMO prototype cache/build] --> Loader
|
|
Loader --> Factory[WmoSceneInstanceFactory]
|
|
Validator[WMOBuilder scene-cache validator] --> Factory
|
|
Resolver[WmoPlacementResolver] --> Factory
|
|
Factory --> Detached[Detached WMO Node3D]
|
|
Detached --> Preparer[WmoRuntimeScenePreparer]
|
|
Preparer --> Loader
|
|
Loader --> Scene[Attachment and placement registry]
|
|
```
|
|
|
|
## Public API
|
|
|
|
| Symbol | Role | Thread/lifetime | Failure behavior |
|
|
|---|---|---|---|
|
|
| `is_cached_node_current(node)` | Delegate one Node to the injected cache validator | Renderer main thread; no retention | Null/missing validator returns false |
|
|
| `instantiate_cached_scene(relative_path, scene, placement)` | Instantiate, type-check, validate, name and place a cached scene | Renderer main thread; detached result caller-owned | Invalid input/root/stale/dependency returns null; created rejected roots freed |
|
|
| `duplicate_live_prototype(relative_path, prototype, placement)` | Duplicate, name and place a live prototype | Renderer main thread; detached result caller-owned | Null/missing resolver/unexpected duplicate returns null |
|
|
|
|
The cached path validates before placement. The live path deliberately skips the
|
|
scene-cache validator. Both paths use `get_file().get_basename()` and the exact
|
|
`WmoPlacementResolver.resolve_world_transform` result.
|
|
|
|
## Inputs and outputs
|
|
|
|
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|
|
|---|---|---|---|---|---|
|
|
| Input | Cached `PackedScene` or live prototype `Node3D` | Loader cache/build adapters | Factory | Borrowed Resource/Node | One main-thread call |
|
|
| Input | Relative WMO path and placement Dictionary | Loader placement job | Factory | Borrowed values | One call |
|
|
| Internal | Candidate root | PackedScene instantiate/prototype duplicate | Validator/factory | Factory-owned until accepted | One call |
|
|
| Output | Detached named/placed `Node3D` | Factory | Runtime scene preparer/loader | Ownership transfers to caller | Until attachment/release |
|
|
| Output | Currentness bool | Validator via factory | Loader cache admission | Value | Immediate |
|
|
|
|
Side effects are limited to scene instantiation/duplication, candidate name and
|
|
transform mutation, and synchronous free of rejected candidates. No attachment,
|
|
filesystem, ResourceLoader, worker, RID, queue, cache or Editor-owner mutation.
|
|
|
|
## Data flow
|
|
|
|
```mermaid
|
|
flowchart LR
|
|
Source[PackedScene or live prototype] --> Create{Cached or live?}
|
|
Create -->|cached| Instantiate[PackedScene.instantiate]
|
|
Create -->|live| Duplicate[prototype.duplicate]
|
|
Instantiate --> Type{Node3D?}
|
|
Duplicate --> Type
|
|
Type -->|no| Free[Free created candidate and return null]
|
|
Type -->|yes cached| Current{Cache current?}
|
|
Type -->|yes live| Identity[Apply basename]
|
|
Current -->|no| Free
|
|
Current -->|yes| Identity
|
|
Identity --> Resolve[WmoPlacementResolver]
|
|
Resolve --> Return[Return detached Node3D]
|
|
```
|
|
|
|
## Main sequence
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant Loader as StreamingWorldLoader
|
|
participant Factory as WmoSceneInstanceFactory
|
|
participant Validator as WMOBuilder
|
|
participant Resolver as WmoPlacementResolver
|
|
alt cached source
|
|
Loader->>Factory: instantiate_cached_scene(path, scene, placement)
|
|
Factory->>Factory: instantiate and require Node3D
|
|
Factory->>Validator: is_scene_cache_current(root)
|
|
Validator-->>Factory: current/stale
|
|
else live source
|
|
Loader->>Factory: duplicate_live_prototype(path, prototype, placement)
|
|
Factory->>Factory: duplicate and require Node3D
|
|
end
|
|
Factory->>Factory: assign basename
|
|
Factory->>Resolver: resolve_world_transform(placement)
|
|
Resolver-->>Factory: exact Transform3D
|
|
Factory-->>Loader: detached Node3D or null
|
|
```
|
|
|
|
## Dependency diagram
|
|
|
|
```mermaid
|
|
flowchart TB
|
|
Loader[StreamingWorldLoader] --> Factory[WmoSceneInstanceFactory]
|
|
Factory --> Validator[Injected WMO scene-cache validator]
|
|
Factory --> Resolver[Injected WmoPlacementResolver]
|
|
Factory --> Engine[PackedScene / Node3D / Transform3D]
|
|
Factory -. no dependency .-> Preparation[WmoRuntimeScenePreparer]
|
|
Factory -. no dependency .-> IO[ResourceLoader / FileAccess]
|
|
Factory -. no dependency .-> Queue[WMO queues / scheduler]
|
|
```
|
|
|
|
## Ownership, threading and resources
|
|
|
|
- Calls are renderer-main-thread only because PackedScene/Node APIs mutate.
|
|
- The source scene/prototype remains caller/cache-owned.
|
|
- The factory owns a newly created root until rejection or successful return.
|
|
- Successful return transfers detached-root ownership to the caller.
|
|
- Descendant Mesh/Material Resources retain engine duplicate/instantiate identity.
|
|
- The factory retains only injected stateless dependencies, never Nodes/Resources.
|
|
|
|
## Errors, cancellation and recovery
|
|
|
|
| Failure/state | Detection | Behavior | Recovery |
|
|
|---|---|---|---|
|
|
| Null scene/prototype | Guard | Return null without allocation | Correct caller source |
|
|
| Missing validator | Currentness guard | Cached candidate rejected/freed | Fix composition |
|
|
| Missing resolver | Guard before creation | Return null without allocation | Fix composition |
|
|
| Non-Node3D root | Runtime type check | Free candidate and return null | Rebuild invalid cache/source |
|
|
| Stale cached root | Injected validator | Free candidate; skip placement | Rebuild cache/current metadata |
|
|
| Placement cancellation | Loader lifecycle | Detached/attached result released by caller | Existing retry path |
|
|
| Shutdown | No retained candidates | Nothing to drain | Existing loader teardown |
|
|
|
|
The non-Node3D cached rejection now frees the created invalid root synchronously.
|
|
Normal admitted caches already enforce Node3D through the scene finalizer, so this
|
|
closes an error-path lifetime leak without changing valid rendered output.
|
|
|
|
## Configuration and capabilities
|
|
|
|
No new settings. Cache-currentness rules belong to the injected WMOBuilder
|
|
boundary; placement formulas belong to `WmoPlacementResolver`.
|
|
|
|
## Persistence, cache and migration
|
|
|
|
No format/version change and no rebake. The factory reads no files and writes no
|
|
metadata. Existing cache validator version policy remains authoritative.
|
|
|
|
## Diagnostics and observability
|
|
|
|
The factory emits no logs or metrics. Loader cache/placement metrics and
|
|
synthetic rejection contracts remain the diagnostic surfaces.
|
|
|
|
## Verification
|
|
|
|
- `verify_wmo_scene_instance_factory.gd` covers cached validation-before-placement,
|
|
exact accepted root/descendant Resource identity, stale-root free, non-Node3D
|
|
rejection, live validator suppression, detached ownership, dependencies,
|
|
basename/Transform3D application, source boundaries and 1,000 duplicates.
|
|
- Adjacent scene finalizer, placement resolver, runtime preparer, shutdown and
|
|
checkpoint regressions protect lifecycle and visible output.
|
|
- Fidelity evidence is behavior-preserving extraction for valid inputs. The
|
|
invalid non-Node3D free is a lifetime fix, not a visual 3.3.5a change.
|
|
|
|
The synthetic budget requires 1,000 simple live duplicates in under one second.
|
|
Asset-backed CPU/GPU p95/p99 and long-traversal evidence remain pending.
|
|
|
|
## Extension points
|
|
|
|
- Asset-backed cached/live instances can compare placement and lifetime without
|
|
changing the factory API.
|
|
- New source kinds should be separate explicit methods only when their validation
|
|
and identity semantics differ materially.
|
|
|
|
## Capability status
|
|
|
|
| Capability | Status | Evidence | Gap/next step |
|
|
|---|---|---|---|
|
|
| Cached WMO instantiation | Implemented extraction | Type/currentness/name/placement/lifetime contract | Serialized asset-backed cache corpus pending |
|
|
| Live prototype duplication | Implemented extraction | Identity/name/placement/validator-suppression contract | Asset-backed traversal/leak evidence pending |
|
|
| Runtime preparation | Separate implemented service | Runtime scene preparer regression | Visual/GPU p95/p99 pending |
|
|
| Attachment/registry lifetime | Loader-owned | Existing WMO placement/shutdown regressions | Further orchestration extraction pending |
|
|
|
|
## Known gaps and risks
|
|
|
|
- Scene instantiation/duplication remains synchronous main-thread work.
|
|
- 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_scene_instance_factory.gd` | Cached/live creation, validation, identity and placement |
|
|
| `src/render/wmo/wmo_placement_resolver.gd` | Canonical WMO placement Transform3D |
|
|
| `src/render/wmo/wmo_runtime_scene_preparer.gd` | Post-factory cached/live render preparation |
|
|
| `src/scenes/streaming/streaming_world_loader.gd` | Source selection, cache/prototype lookup, attachment and lifetime |
|
|
| `src/tools/verify_wmo_scene_instance_factory.gd` | Synthetic type/identity/lifetime/boundary/timing regression |
|
|
|
|
## Related decisions and references
|
|
|
|
- [`wmo-placement-resolver.md`](wmo-placement-resolver.md)
|
|
- [`wmo-scene-resource-finalizer.md`](wmo-scene-resource-finalizer.md)
|
|
- [`wmo-runtime-scene-preparer.md`](wmo-runtime-scene-preparer.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)
|