9.0 KiB
WMO Placement Resolver
Metadata
| Field | Value |
|---|---|
| Status | Implemented |
| Target/work package | M03 / M03-RND-WMO-PLACEMENT-RESOLVER-001 |
| Owners | Pure WMO cache-key, placement-identity and world-transform rules |
| Last verified | Worktree work/sindo-main-codex/m03-wmo-placement-resolver, 2026-07-17 |
| Profiles/capabilities | Existing ADT/WDT WMO placement paths |
Purpose
Provide one scene-free boundary for normalized WMO cache keys, MODF placement identity and world-space Godot transforms across render-cache, cached-scene and live-prototype instance paths.
Non-goals
- Parse ADT/WDT or convert canonical WoW coordinates.
- Own WMO registry references, tile state, jobs, queues or retries; reference
ownership belongs to
WmoPlacementRegistry. - Load/validate cached scenes or lightweight render resources.
- Create Nodes, Mesh/MultiMesh, materials or RIDs.
- Implement portals, rooms, visibility, materials, doodads or WMO parity.
Context and boundaries
flowchart LR
Parsed[ADT/WDT WMO placement] --> Loader[StreamingWorldLoader adapter]
Loader --> Resolver[WmoPlacementResolver]
Resolver --> CacheKey[Normalized cache key]
Resolver --> Identity[Registry unique key]
Resolver --> Transform[World Transform3D]
CacheKey --> Cache[Loader WMO caches/requests]
Identity --> Registry[WmoPlacementRegistry]
Transform --> RenderRoot[Lightweight render root]
Transform --> Scene[Cached scene instance]
Transform --> Live[Live prototype instance]
Allowed dependencies are Dictionary/String values and Godot Vector3, Basis
and Transform3D math. Node, SceneTree, RenderingServer, ResourceLoader,
WorkerThreadPool, mutexes, files, gameplay, network and editor UI are forbidden.
Public API
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|---|---|---|---|---|
normalize_relative_path(relative_wmo_path) |
Pure query | Return lowercase slash-normalized cache/lookup key | Any thread; value result | Empty input returns empty |
resolve_unique_key(placement, tile_key, placement_index) |
Pure query | Return positive UID identity or tile/index fallback | Any thread; value result | Missing/non-positive UID uses fallback |
resolve_world_transform(placement) |
Pure query | Compose world-space position, Godot Euler rotation and uniform scale | Any thread; value result | Missing fields use zero/zero/one defaults; scale is not clamped |
Inputs and outputs
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|---|---|---|---|---|---|
| Input | Relative WMO path | WDT/ADT name table | Path normalizer | Caller String | One call |
| Input | Raw placement Dictionary | WDT global or ADT MODF parser result | Identity/transform queries | Read-only shallow value | One call |
| Input | Tile key and placement index | Loader build job | Synthetic identity fallback | Copied scalar/String | Registry entry lifetime |
| Output | Normalized relative path | Resolver | Render/scene cache and load-request maps | New String value | Request/cache lookup |
| Output | uid:* or tile:*:* key |
Resolver | WmoPlacementRegistry and loader ref arrays |
New String value | Until unregister/reset |
| Output | World Transform3D |
Resolver | Three WMO instance adapters | Value copy | Instance lifetime after assignment |
The resolver retains no source Dictionary, output or engine resource.
Data flow
flowchart TD
Path[relative path] --> Slash[replace backslash with slash]
Slash --> Lower[lowercase entire key]
Placement[placement Dictionary] --> UID{unique_id > 0?}
UID -->|yes| UidKey[uid decimal]
UID -->|no| TileKey[tile key + placement index]
Placement --> Defaults[position/rotation/scale defaults]
Defaults --> Basis[Godot Basis.from_euler]
Basis --> Scale[unclamped uniform scale]
Scale --> World[world Transform3D]
Lifecycle/state
The resolver is stateless. Construction, world reset, map switch, cancellation and shutdown require no resolver operation.
Main sequence
sequenceDiagram
participant Loader as StreamingWorldLoader
participant Resolver as WmoPlacementResolver
participant Registry as WmoPlacementRegistry
participant Instance as Render/cached/live instance
Loader->>Resolver: normalize_relative_path(path)
Resolver-->>Loader: cache key
Loader->>Resolver: resolve_unique_key(placement, tile, index)
Resolver-->>Registry: identity adopted by loader
Loader->>Resolver: resolve_world_transform(placement)
Resolver-->>Loader: value Transform3D
Loader->>Instance: assign transform and attach/build
Ownership, threading and resources
- The resolver owns only call-local scalar/value calculations.
WmoPlacementRegistryowns placement-key reference sets. The loader owns its key-to-Node map, cache/load-request state, jobs/queues, resource fallback and cancellation.- The loader and builders own every Node/Mesh/MultiMesh/material/RID lifecycle.
- Pure calls are thread-safe; current consumers execute on the main thread.
Errors, cancellation and recovery
| Failure | Detection | Behavior | Diagnostic | Recovery |
|---|---|---|---|---|
| Empty path | Empty String | Empty normalized key | Contract fixture | Loader cache path rejects/degrades |
| Missing/non-positive UID | Integer rule | Tile/index key prevents same-tile collisions | Contract fixture | Rebake old ADT cache to restore cross-tile UID dedupe |
| Missing transform fields | Dictionary defaults | Zero position/rotation; scale one | Contract fixture | Repair parser/cache input |
| Zero/negative scale | No historical clamp | Preserve zero/mirrored basis | Contract fixture | Preserve source behavior |
| Resource missing/pending | Outside resolver | Loader retry/fallback path unchanged | Existing loader diagnostics | Restore cache/raw asset |
| Tile unregister/reset | Outside resolver | Loader releases refs/nodes/jobs | Shutdown/registry regressions | New placements resolve afresh |
Configuration and capabilities
The resolver introduces no setting or profile branch. WMO radii, visibility, shadow/occlusion flags, cache size limits and build budgets remain loader-owned.
Persistence, cache and migration
No format is written. Existing WMO .tscn/lightweight render cache versions and
ADT/WDT formats are unchanged. Older placement caches without positive UID retain
the existing per-tile fallback and require no migration for this extraction.
Diagnostics and observability
The resolver emits no logs. Its verifier records path/identity/transform/source contracts and bounded 20,000-triple timing. WMO queue/cache/instance metrics remain in the facade/loader diagnostic snapshot.
Verification
verify_wmo_placement_resolver.gd: slash/case/empty path, positive UID, missing/zero/negative UID fallback, default and rotated/scaled transform, unclamped scale, historical Node3D property equivalence, stateless results, all loader adapters, dependency boundary and bounded timing.- Existing WMO material/cache/shutdown and renderer baseline regressions remain required alongside M2/terrain/facade/internal-access/coordinate gates.
- Fidelity evidence is exact extraction of current placement rules. No new original-client WMO visual/portal/material parity claim is made.
Extension points
WmoPlacementRegistryconsumes these stable keys; later packages may extract WMO build/resource state without changing the resolver.- Render-group build cursor and resource-selection state require separate lifecycle/cancellation contracts.
Capability status
| Capability | Status | Evidence | Gap/next step |
|---|---|---|---|
| Consistent WMO placement identity/transform | Implemented extraction | Contract/source/timing verifier | Asset-backed placement comparison pending |
| WMO placement registry | Implemented extraction | Ownership/source/timing verifier | Asset-backed cross-tile corpus pending |
| WMO build services | Remains in loader | Existing runtime regressions | Stateful extraction pending |
| Portals/rooms/material fidelity | Partial | M00 checkpoint/material evidence | Broader WMO implementation pending |
Known gaps and risks
- Raw placement Dictionaries remain the parser/cache boundary.
- Old caches without UID cannot dedupe one WMO across ADT tile boundaries.
- WMO scale remains intentionally unclamped, including zero/negative input.
- Asset-backed visual/p95/p99 evidence is unavailable in this package.
Source map
| Path | Responsibility |
|---|---|
src/render/wmo/wmo_placement_resolver.gd |
Path, identity and transform rules |
src/render/wmo/wmo_placement_registry.gd |
Placement reference-set ownership |
src/scenes/streaming/streaming_world_loader.gd |
Node map/cache/build/resource ownership |
src/tools/verify_wmo_placement_resolver.gd |
Contract, source and timing regression |