# M2 Unique Placement Registry ## Metadata | Field | Value | |---|---| | Status | Implemented extraction | | Target/work package | M03 / `M03-RND-M2-UNIQUE-REGISTRY-001` | | Owners | Cross-tile positive ADT MDDF unique-ID reservations | | Last verified | Worktree `work/sindo-main-codex/m03-m2-unique-registry`, 2026-07-16 | | Profiles/capabilities | Existing static M2 placement path; session-memory only | ## Purpose Ensure a positive ADT `MDDF.uniqueId` has at most one owning streamed tile while allowing unkeyed placements through and returning skipped keys for loader-driven candidate retry after the owner unloads. ## Non-goals - Own tile state, retry queues, worker/group/build jobs or detail radius rules. - Group placements or create MultiMesh, Mesh, Node, material or RID resources. - Validate M2 paths, transforms, animation, materials or build-12340 fidelity. - Persist reservations or define a generic identity/ownership framework. - Change cache formats, quality profiles or placement visibility. ## Context and boundaries ```mermaid flowchart LR ADT[ADT M2 placement dictionaries] --> Loader[StreamingWorldLoader adapter] Loader --> Registry[M2UniquePlacementRegistry] Registry --> Accepted[Filtered placements + owned keys] Registry --> Skipped[Skipped keys] Accepted --> Group[Existing worker grouping/build path] Skipped --> TileState[Loader-owned tile retry state] Unload[Owner tile unload] --> Loader Loader -->|release keys| Registry Loader -->|notify released candidates| Retry[Existing detail sync queue] ``` Allowed dependencies are scalar/container values from the ADT adapter. Node, SceneTree, RenderingServer, ResourceLoader, WorkerThreadPool, gameplay, network, editor UI, files and persistent caches are forbidden. ## Public API | Symbol | Kind | Purpose | Thread/lifetime | Errors | |---|---|---|---|---| | `reserve(tile_key, m2_placements)` | Command/query | Reserve available positive IDs and return fresh filtered/owned/skipped arrays | Renderer owner thread/session | Invalid non-Dictionary entries are ignored; unkeyed entries pass through | | `release(tile_key, unique_keys)` | Command | Release only keys owned by the supplied tile | Renderer owner thread/session | Unknown/non-owner/empty keys are ignored | | `clear()` | Lifecycle command | Drop every reservation on world reset/shutdown | Renderer owner thread | Idempotent | | `active_count()` | Query | Return current reservation count | Renderer owner thread | No failure state | | `diagnostic_snapshot()` | Read model | Return sorted detached key/tile ownership records | Caller-owned result | Exposes no mutable registry reference | ## Inputs and outputs | Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime | |---|---|---|---|---|---| | Input | Tile key | Streamer tile state | Registry reservation owner | Copied string | Reservation lifetime | | Input | Raw M2 placement dictionaries | ADT parser result | Registry | Loader-owned; read only | One reserve call | | Input | Claimed key array | Tile state | Registry release | Loader-owned; read only | One release call | | Output | Filtered placement array | Registry | M2 grouping task adapter | Fresh shallow array; placement dictionaries remain source values | One request | | Output | Owned `uid:` keys | Registry | Loader tile state | Fresh strings/array | Until release | | Output | Skipped keys | Registry | Loader candidate retry state | Fresh strings/array | Until owner release/retry | | Output | Released keys | Registry | Loader candidate notifier | Fresh array in input order | One release | | Output | Detached diagnostics/count | Registry | Metrics/tests | Caller-owned scalars/dictionaries | One query | Only `unique_id > 0` enters the registry. Missing, zero and negative values have an empty key and remain untracked, matching the prior loader behavior. ## Data flow ```mermaid flowchart TD Reserve[reserve tile + placements] --> Placement[Iterate placement order] Placement --> Dict{Dictionary?} Dict -->|no| Placement Dict -->|yes| Positive{unique_id > 0?} Positive -->|no| Pass[Append untracked placement] Positive -->|yes| Owned{Key already owned?} Owned -->|no| Claim[Set owner and append placement/key] Owned -->|same tile + already in this call| Duplicate[Suppress duplicate] Owned -->|same tile + first in this call| PassOwner[Append placement/key] Owned -->|other tile| Skip[Append skipped key once] Pass --> Placement Claim --> Placement Duplicate --> Placement PassOwner --> Placement Skip --> Placement Release[release tile + keys] --> Owner{Tile is current owner?} Owner -->|yes| Erase[Erase and return key] Owner -->|no| Ignore[Leave reservation unchanged] ``` ## Lifecycle/state ```mermaid stateDiagram-v2 [*] --> Empty Empty --> Reserved: reserve positive unowned ID Reserved --> Reserved: same-owner reserve / competing skip / non-owner release Reserved --> Empty: owner releases final ID Reserved --> Empty: clear world/reset Empty --> Empty: unkeyed reserve / unknown release / clear Empty --> [*] Reserved --> [*]: owner releases registry after clear ``` ## Main sequence ```mermaid sequenceDiagram participant TileA as First streamed tile participant Loader as StreamingWorldLoader participant Registry as M2UniquePlacementRegistry participant TileB as Cross-border candidate tile TileA->>Loader: placements include positive unique ID Loader->>Registry: reserve(tile A, placements) Registry-->>Loader: accepted placement + owned key TileB->>Loader: same placement ID Loader->>Registry: reserve(tile B, placements) Registry-->>Loader: skipped key Loader->>Loader: retain skipped key in tile B state TileA->>Loader: unload/remove details Loader->>Registry: release(tile A, owned keys) Registry-->>Loader: released key Loader->>Loader: enqueue eligible tile B detail retry ``` ## Ownership, threading and resources - The registry owns one private key-to-owner-string dictionary. - The loader owns tile-held owned/skipped arrays, retry discovery, task/build lifecycle and every MultiMesh/Node/Mesh/material/RID side effect. - Calls currently occur on the renderer main thread. The registry has no lock and must not be mutated concurrently. - Returned arrays and diagnostic dictionaries are fresh; source placement dictionaries are passed through shallowly and must remain read-only. ## Errors, cancellation and recovery | Failure | Detection | Behavior | Diagnostic | Recovery | |---|---|---|---|---| | Invalid placement variant | Type guard | Ignore entry | Contract test | Repair parser/adapter input | | Missing/non-positive ID | Key rule | Pass placement without ownership | Smoke/contract test | Expected legacy behavior | | Cross-tile duplicate | Owner lookup | Skip placement and return key once | Tile skipped-key state | Owner release triggers eligible candidate retry | | Non-owner release | Owner comparison | Ignore; preserve owner | Contract test | Release from recorded tile | | Reset/shutdown | Loader lifecycle | Clear all reservations | Shutdown regression/count | New world reserves afresh | There is no asynchronous work or cancellation inside the registry. Loader-owned tasks are cancelled independently before/reset around registry clearing. ## Configuration and capabilities The registry introduces no settings. It applies to all positive IDs in the existing static M2 placement path. Detail radius, M2 enablement, build budgets and animation allowlists remain loader/profile configuration. ## Persistence, cache and migration Reservations are runtime-only and are not serialized. M2 `.tscn/.glb`, animation, mesh/material refresh and ADT cache versions remain unchanged. No rebuild or migration is required. ## Diagnostics and observability - `active_count()` continues to feed `m2_active_unique_ids` baseline metrics. - Detached diagnostics sort by unique key for deterministic inspection and expose only strings/counts. - Contract timing covers repeated 256-placement reserve/release cycles. Asset- backed streaming p95/p99 remains milestone acceptance work. ## Verification - `verify_m2_unique_placement_registry.gd`: invalid/unkeyed handling, within-call duplicate suppression, same-owner repeat, cross-tile skip, owner-checked release, retry, idempotent clear, detached/sorted diagnostics, source boundary and timing. - `verify_m2_unique_dedupe.gd`: existing `uid:11785` cross-tile smoke now targets the extracted public contract rather than loader-private methods. - Facade, terrain, internal-access, manifest, shutdown, scheduler, streaming and coordinate regressions remain required. - Fidelity evidence is dedupe behavior preservation only. No new build-12340 visual parity claim is made. ## Extension points - Expose detached ownership diagnostics through facade metrics only if needed. - Extract grouping/build orchestration as separate services with explicit budgets. ## Capability status | Capability | Status | Evidence | Gap/next step | |---|---|---|---| | Cross-tile positive-ID ownership | Implemented extraction | Dedicated contract plus legacy smoke | Asset-backed boundary traversal timing pending | | Candidate retry and M2 build | Remains in loader | Existing release adapter/source boundary | Broader M2 service extraction required | ## Known gaps and risks - Placement dictionaries are shallow pass-through values at the ADT boundary. - Same-owner reservations across separate calls intentionally pass again; the caller's existing root/task guards prevent duplicate build requests. - First owner follows tile processing order, matching prior behavior rather than a canonical geometric owner rule. - Group/build/load/animation caches and render finalization remain monolithic. ## Source map | Path | Responsibility | |---|---| | `src/render/m2/m2_unique_placement_registry.gd` | Positive-ID keying, ownership, release and diagnostics | | `src/scenes/streaming/streaming_world_loader.gd` | Reservation adapters, tile state, candidate retry and M2 rendering | | `src/tools/verify_m2_unique_placement_registry.gd` | Contract, lifecycle, source and timing regression | | `src/tools/verify_m2_unique_dedupe.gd` | Historical cross-tile smoke fixture | ## Related decisions and references - [`world-renderer.md`](world-renderer.md) - [`../../RENDER.md`](../../RENDER.md) - [`../../targets/roadmap/02-rendering-and-graphics.md`](../../targets/roadmap/02-rendering-and-graphics.md)