# WMO Placement Registry

## Metadata

| Field | Value |
|---|---|
| Status | Implemented extraction |
| Target/work package | M03 / `M03-RND-WMO-PLACEMENT-REGISTRY-001` |
| Owners | WMO placement-key to tile/global reference-set ownership |
| Last verified | Worktree `work/sindo-main-codex/m03-wmo-placement-registry`, 2026-07-17 |
| Profiles/capabilities | Existing ADT/WDT WMO placement paths; profile-independent |

## Purpose

Own the reference sets that keep one resolved WMO placement active across one
or more ADT tiles, or across the global WDT placement lifetime. This separates
value-only placement ownership from scene and render-resource ownership.

## Non-goals

- Resolve WMO paths, placement identity or world transforms.
- Own Nodes, Resources, RIDs, roots, jobs, queues, tasks or caches.
- Load, instantiate, build or free WMO content.
- Change duplicate order, budgets, retries or global-WMO behavior.
- Claim original-client WMO visual, portal, room or material parity.

## Context and boundaries

```mermaid
flowchart LR
    Parsed[ADT/WDT placement] --> Resolver[WmoPlacementResolver]
    Resolver --> Key[Placement key]
    Tile[Tile/global reference key] --> Loader[StreamingWorldLoader]
    Key --> Loader
    Loader --> Registry[WmoPlacementRegistry]
    Registry --> Decision[Existing or final-release decision]
    Decision --> Loader
    Loader --> NodeMap[Loader key-to-Node map]
    Loader --> Jobs[Loader jobs/queues/resources]
```

The registry depends only on `RefCounted`, `Dictionary`, `Array` and `String`.
SceneTree, Node, Resource, RID, files, loaders, threads, locks, network,
gameplay and editor UI are forbidden dependencies.

## Public API

| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|---|---|---|---|---|
| `add_reference(placement_key, reference_key)` | Command/query | Idempotently add a reference; return true only when creating the placement key | Renderer main thread; map session | Empty key returns false and changes nothing |
| `release_reference(placement_key, reference_key)` | Command/query | Remove an owned reference; return true only on final removal | Renderer main thread; map session | Empty, unknown or non-owner input returns false |
| `contains(placement_key)` | Query | Test whether any reference keeps a placement active | Renderer main thread; map session | Empty key returns false |
| `active_count()` | Query | Count active placement keys, not references | Renderer main thread; map session | None |
| `diagnostic_snapshot()` | Query | Return detached, placement/reference-sorted records | Renderer main thread; caller-owned result | None |
| `clear()` | Command | Remove all ownership records idempotently | Renderer main thread; reset/shutdown | None |

## Inputs and outputs

| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|---|---|---|---|---|---|
| Input | Resolved `uid:*`, `tile:*:*` or `global` placement key | `WmoPlacementResolver` / loader global adapter | Registry | Copied String dictionary key | Until final release/clear |
| Input | ADT tile key or `__global__` reference key | Loader | Registry | Copied String set key | Until release/clear |
| Output | First-placement Boolean | Registry | Loader creation path | Value | One call |
| Output | Final-release Boolean | Registry | Loader cancellation/free path | Value | One call |
| Output | Active count and sorted diagnostic records | Registry | Renderer diagnostics/verifier | Detached values | Caller lifetime |

## Data flow

```mermaid
flowchart TD
    Add[add placement + reference] --> Valid{Both non-empty?}
    Valid -->|no| Reject[false; unchanged]
    Valid -->|yes| Existing{Placement exists?}
    Existing -->|no| Create[Create reference set; true]
    Existing -->|yes| Retain[Idempotently add reference; false]
    Release[release placement + reference] --> Owner{Known owner reference?}
    Owner -->|no| Reject
    Owner -->|yes| Erase[Erase reference]
    Erase --> Empty{Set empty?}
    Empty -->|no| Retained[false; placement remains]
    Empty -->|yes| Final[Erase placement; true]
```

## Lifecycle and state

The states for one placement are `Absent`, `Referenced` and back to `Absent`.
The first valid add enters `Referenced`; further tile/global adds remain there.
Only release of the final owned reference returns to `Absent`. Map reset and
shutdown call `clear`, which is safe from either state.

```mermaid
stateDiagram-v2
    [*] --> Absent
    Absent --> Referenced: first valid add / true
    Referenced --> Referenced: idempotent/shared add / false
    Referenced --> Referenced: non-owner/non-final release / false
    Referenced --> Absent: final owned release / true
    Referenced --> Absent: clear
```

## Main sequence

```mermaid
sequenceDiagram
    participant Loader as StreamingWorldLoader
    participant Registry as WmoPlacementRegistry
    participant Nodes as Loader Node map
    Loader->>Registry: contains(placement key)
    alt existing placement
        Loader->>Registry: add_reference(key, tile)
    else new placement built successfully
        Loader->>Nodes: store key-to-Node
        Loader->>Registry: add_reference(key, tile/global)
    end
    Loader->>Registry: release_reference(key, tile)
    alt final reference
        Registry-->>Loader: true
        Loader->>Loader: cancel render build
        Loader->>Nodes: queue-free and erase Node
    else shared/unknown reference
        Registry-->>Loader: false
    end
```

## Ownership, threading and resources

- The registry owns only placement-key Dictionaries and reference-key sets.
- The loader owns the parallel placement-key-to-Node map and every Node action.
- The loader also owns WMO roots, tile state, caches, requests, jobs, queues,
  retries, cancellation and render-resource lifetime.
- Current calls are serialized on the renderer main thread. The registry has no
  lock and must not be mutated concurrently.

## Errors, cancellation and recovery

| Failure | Detection | Behavior | Diagnostic | Recovery |
|---|---|---|---|---|
| Empty placement/reference key | API guard | Reject without mutation | Contract verifier | Repair caller input |
| Duplicate add | Existing set member | Idempotent retain | Sorted snapshot | No recovery required |
| Unknown/non-owner release | Missing placement/reference | Ignore without mutation | Contract verifier | Release from actual owner or reset |
| Tile build cancellation | Loader-owned condition | Loader cancels job, then releases recorded refs | Existing queue metrics | Retry when tile becomes wanted |
| Final release | Empty set after erase | Return true and erase registry record | Active count | Loader cancels/free Node exactly once |
| Map reset/shutdown | Loader lifecycle | Loader frees Nodes/jobs and clears registry | Shutdown regression | Next map begins empty |

## Configuration and capabilities

There is no setting, profile branch or capacity knob. WMO tile radius, cache
limits, render budgets and visibility settings remain loader-owned.

## Persistence, cache and migration

No data is serialized and no cache/schema/version changes. Placement and
reference keys exist only for the current renderer map session.

## Diagnostics and observability

`active_count` backs existing WMO instance metrics. `diagnostic_snapshot`
returns deterministic detached records for tests and future read-only
diagnostics; mutating the returned arrays cannot alter registry state.

## Verification

- `verify_wmo_placement_registry.gd`: invalid/first/idempotent/shared/non-owner/
  final/global/clear behavior, deterministic detached diagnostics, source
  ownership boundaries and bounded 100-by-256 shared-reference cycles.
- WMO resolver, material, runtime-cache shutdown and the renderer regression
  suite verify the unchanged loader adapters and surrounding lifecycle.
- Fidelity evidence is exact preservation of the repository's prior shared-ref
  and final-release semantics. Asset-backed 3.3.5a comparison is still pending.

## Extension points

- WMO build/resource state can be extracted separately without moving Nodes
  into this registry.
- A future diagnostic adapter may expose the detached snapshot through the
  facade without exposing mutable loader internals.

## Capability status

| Capability | Status | Evidence | Gap/next step |
|---|---|---|---|
| WMO placement reference ownership | Implemented extraction | Contract/source/timing verifier | Asset-backed cross-tile corpus pending |
| WMO build/resource scheduling | Remains in loader | Existing runtime regressions | Separate state/cancellation package |
| WMO visual/portal/material fidelity | Partial | Renderer baseline/material checks | Original-client paired evidence pending |

## Known gaps and risks

- Correctness assumes the loader records each successful placement reference in
  the tile `wmo_refs` array before unregister.
- Old caches without positive UID retain resolver-defined per-tile identity.
- No proprietary WMO corpus or original-client paired capture is in this package.

## Source map

| Path | Responsibility |
|---|---|
| `src/render/wmo/wmo_placement_registry.gd` | Placement-key/reference-set ownership |
| `src/render/wmo/wmo_placement_resolver.gd` | Stable path, identity and transform rules |
| `src/scenes/streaming/streaming_world_loader.gd` | Node map, instances, resources, jobs, queues and lifecycle reactions |
| `src/tools/verify_wmo_placement_registry.gd` | Contract, boundary and timing regression |

## Related decisions and references

- [`wmo-placement-resolver.md`](wmo-placement-resolver.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)
