Compare commits

..

5 Commits

12 changed files with 635 additions and 72 deletions
+13
View File
@@ -977,6 +977,19 @@ $exe = Join-Path $env:TEMP 'godot-4.6.1-openwc\Godot_v4.6.1-stable_win64.exe'
- Cache formats, quality profiles and visual rules are unchanged. Asset-backed - Cache formats, quality profiles and visual rules are unchanged. Asset-backed
p95/p99 and visual acceptance remain required. p95/p99 and visual acceptance remain required.
## 2026-07-16 M2 Unique Placement Registry Extraction
- `M2UniquePlacementRegistry` now owns positive ADT `MDDF.uniqueId` reservations
across streamed tiles using the existing `uid:<decimal>` keys.
- Missing/zero/negative IDs remain untracked; duplicates inside one reservation
call are suppressed and another tile records one skipped key until owner release.
- Owner release still lets the loader find eligible waiting tiles and enqueue the
existing detail retry path. First ownership remains tile-processing-order based.
- `StreamingWorldLoader` still owns tile state, retry queues, grouping/build tasks,
M2 caches, MultiMesh/Node/Mesh/material/RID creation and all operation budgets.
- M2/ADT cache formats, transforms, profiles and visible rules are unchanged.
Asset-backed cross-boundary p95/p99 and visual acceptance remain required.
## 2026-07-16 World Environment Snapshot Facade ## 2026-07-16 World Environment Snapshot Facade
- `WorldEnvironmentSnapshot` carries one immutable finite time-of-day value, - `WorldEnvironmentSnapshot` carries one immutable finite time-of-day value,
@@ -0,0 +1,105 @@
# M03-RND-M2-UNIQUE-REGISTRY-001 — M2 unique placement registry
<!-- OPENWC_CLAIM:M03-RND-M2-UNIQUE-REGISTRY-001:sindo-main-codex:2026-07-19 -->
## Ownership
- Target: M03
- Program: RND
- Owner/Agent ID: sindo-main-codex
- Branch: `work/sindo-main-codex/m03-m2-unique-registry`
- Lease expires UTC: 2026-07-19
- Integrator: M03 milestone integrator
## Outcome
Extract cross-tile positive `MDDF.uniqueId` reservation ownership from the
streamer into a scene-free `M2UniquePlacementRegistry`.
## Non-goals
- Move tile state, candidate requeue, group/build tasks or result queues.
- Move MultiMesh/Node/Mesh/material creation, animation or resource loading.
- Change unique-key format, first-owner semantics or unkeyed placement behavior.
- Change placement transforms, detail radius, cache versions or quality profiles.
- Add dependencies or mark the broad M2 service complete.
## Paths
- Exclusive: `src/render/m2/m2_unique_placement_registry.gd`,
`src/tools/verify_m2_unique_placement_registry.gd`,
`docs/modules/m2-unique-placement-registry.md`, this claim
- Shared/hotspots: `src/scenes/streaming/streaming_world_loader.gd`,
`src/tools/verify_m2_unique_dedupe.gd`,
`docs/modules/world-renderer.md`, `docs/modules/README.md`, `RENDER.md`,
`targets/03-renderer-facade.md`
- Generated/ignored: `.godot`, native DLL, generated ADT resources, caches and
proprietary renderer corpus
## Contracts and data
- Input: tile key and raw ADT M2 placement dictionaries
- Reserve output: fresh filtered placements, owned unique keys and skipped keys
- Release input/output: tile key plus claimed keys; only current owner releases
- Positive IDs map to `uid:<decimal>`; zero/negative/missing IDs remain untracked
- Registry is session-memory only and clears on renderer world reset
- Queue/cache/schema/coordinate versions: unchanged
## Dependencies
- Requires: current master `57ae1b8`
- Blocks: broader M2 service extraction and dependency/performance acceptance
- External state: none; tests use synthetic placement dictionaries
## Verification
- Commands: dedicated unkeyed/duplicate/cross-tile/release/clear/detached-
diagnostic and bounded timing verifier; loader source boundary; M2 unique-id
smoke plus facade/terrain/internal-access/manifest/shutdown/scheduler gates
- Fidelity evidence: exact positive-ID key format and first-owner/retry behavior
- Performance budget: dictionary membership over placement count; no Node,
Resource, RID, task, file or persistent cache
## Documentation deliverables
- Inline registry API documentation
- New module spec with inputs/outputs, data-flow, lifecycle, sequence, ownership,
failure, recovery, performance and source map
- World renderer/module registry/`RENDER.md` updates
## Simplicity and naming
- Important name: `M2UniquePlacementRegistry`
- Simplest solution: one private key-to-owner dictionary and explicit operations
- Rejected complexity: generic identity registry, signals, callbacks, Resources,
locks or ownership graph
- Unavoidable complexity: raw placement dictionaries remain at ADT adapter boundary
- Measured optimization evidence: bounded synthetic reserve/release verifier
## Status
- State: accepted
- Done: stateful registry, loader adapters, migrated smoke, regressions and docs
- Next: asset-backed boundary traversal and further M2-service extraction later in M03
- Blocked by:
<!-- OPENWC_HANDOFF:READY:M03-RND-M2-UNIQUE-REGISTRY-001:acb3a80 -->
<!-- OPENWC_INTEGRATION:ACCEPTED:M03-RND-M2-UNIQUE-REGISTRY-001:2342430 -->
## Handoff
- Commit: `acb3a80` (`refactor(M03): extract M2 unique placement registry`)
- Results: dedicated contract PASS (`cases=10`, `iterations=100`, `35.688 ms`);
historical `uid:11785` smoke PASS; terrain/facade/internal-access/manifest/
shutdown/scheduler/streaming/focus/terrain-query/coordinate regressions PASS;
internal inventory remains `42`; editor parse and documentation/coordination/
diff gates PASS; checkpoint dry-run planned `7/7`
- Remaining risks: private Azeroth corpus was absent, so the asset-backed ADT
placement probe could not open the map and is not counted as passed evidence;
first ownership remains processing-order based; M2 grouping/build/tasks/caches/
finalization and asset-backed p95/p99 remain pending
- Documentation updated: `m2-unique-placement-registry.md`, module registry,
`world-renderer.md`, `RENDER.md`, inline API documentation and data-flow,
lifecycle and sequence diagrams
- Integration: accepted in master merge `2342430`; post-merge registry, legacy
smoke, internal-access, facade and manifest gates passed
+1
View File
@@ -15,6 +15,7 @@
| Terrain quality Mesh cache | Implemented extraction | [`terrain-quality-mesh-cache.md`](terrain-quality-mesh-cache.md) | | Terrain quality Mesh cache | Implemented extraction | [`terrain-quality-mesh-cache.md`](terrain-quality-mesh-cache.md) |
| Terrain chunk LOD planner | Implemented extraction | [`terrain-chunk-lod-planner.md`](terrain-chunk-lod-planner.md) | | Terrain chunk LOD planner | Implemented extraction | [`terrain-chunk-lod-planner.md`](terrain-chunk-lod-planner.md) |
| Terrain chunk geometry queue planner | Implemented extraction | [`terrain-chunk-geometry-queue-planner.md`](terrain-chunk-geometry-queue-planner.md) | | Terrain chunk geometry queue planner | Implemented extraction | [`terrain-chunk-geometry-queue-planner.md`](terrain-chunk-geometry-queue-planner.md) |
| M2 unique placement registry | Implemented extraction | [`m2-unique-placement-registry.md`](m2-unique-placement-registry.md) |
| Third-person camera | Implemented | [`third-person-camera.md`](third-person-camera.md) | | Third-person camera | Implemented | [`third-person-camera.md`](third-person-camera.md) |
| Character presentation | Implemented boundary / Partial fidelity | [`character-presentation.md`](character-presentation.md) | | Character presentation | Implemented boundary / Partial fidelity | [`character-presentation.md`](character-presentation.md) |
| Renderer | Partial | [`world-renderer.md`](world-renderer.md), [`../../RENDER.md`](../../RENDER.md) | | Renderer | Partial | [`world-renderer.md`](world-renderer.md), [`../../RENDER.md`](../../RENDER.md) |
@@ -0,0 +1,220 @@
# 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:<decimal>` 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)
+15 -2
View File
@@ -5,9 +5,9 @@
| Field | Value | | Field | Value |
|---|---| |---|---|
| Status | Partial | | Status | Partial |
| Target/work package | M00 baseline; `M01-RND-STREAMING-FOCUS-001`; `M01-QAR-SERVER-SPAWN-RENDERER-001`; M03 facade/planner/scheduler/internal-access/ground/environment/entity packages; M03 terrain cache/LOD/geometry-queue packages | | Target/work package | M00 baseline; `M01-RND-STREAMING-FOCUS-001`; `M01-QAR-SERVER-SPAWN-RENDERER-001`; M03 facade/planner/scheduler/internal-access/ground/environment/entity packages; M03 terrain packages; `M03-RND-M2-UNIQUE-REGISTRY-001` |
| Owners | Renderer workstream / milestone integrator | | Owners | Renderer workstream / milestone integrator |
| Last verified | Worktree `work/sindo-main-codex/m03-terrain-chunk-queue`, 2026-07-16 | | Last verified | Worktree `work/sindo-main-codex/m03-m2-unique-registry`, 2026-07-16 |
| Profiles/capabilities | `Performance`, `Balanced`, `High`, `Custom`; Blizzlike fidelity incomplete | | Profiles/capabilities | `Performance`, `Balanced`, `High`, `Custom`; Blizzlike fidelity incomplete |
## Purpose ## Purpose
@@ -46,6 +46,8 @@ flowchart LR
ChunkLod --> Loader ChunkLod --> Loader
Loader --> ChunkQueue[TerrainChunkGeometryQueuePlanner] Loader --> ChunkQueue[TerrainChunkGeometryQueuePlanner]
ChunkQueue --> Loader ChunkQueue --> Loader
Loader --> M2Registry[M2UniquePlacementRegistry]
M2Registry --> Loader
Native --> Parsed[Parsed tile/model data] Native --> Parsed[Parsed tile/model data]
Parsed --> Loader Parsed --> Loader
Loader --> Scene[SceneTree nodes] Loader --> Scene[SceneTree nodes]
@@ -112,6 +114,7 @@ from externally reading/writing loader-private queue, task, cache and tile-state
| `TerrainQualityMeshCache.store/restore/clear` | Internal terrain service | Owns admitted full-quality revisit Mesh references and LRU | Renderer thread/map session | Rejected/missed entry leaves loader fallback unchanged | | `TerrainQualityMeshCache.store/restore/clear` | Internal terrain service | Owns admitted full-quality revisit Mesh references and LRU | Renderer thread/map session | Rejected/missed entry leaves loader fallback unchanged |
| `TerrainChunkLodPlanner.plan` | Internal pure terrain query | Maps populated parsed chunk indices to desired LOD 0/1/2 | Synchronous per target refresh | Invalid/disabled input returns empty for existing tile-LOD fallback | | `TerrainChunkLodPlanner.plan` | Internal pure terrain query | Maps populated parsed chunk indices to desired LOD 0/1/2 | Synchronous per target refresh | Invalid/disabled input returns empty for existing tile-LOD fallback |
| `TerrainChunkGeometryQueuePlanner.plan` | Internal pure terrain query | Produces chunk create/remove requests from current and desired state | Synchronous per queue rebuild | Invalid focus returns empty arrays | | `TerrainChunkGeometryQueuePlanner.plan` | Internal pure terrain query | Produces chunk create/remove requests from current and desired state | Synchronous per queue rebuild | Invalid focus returns empty arrays |
| `M2UniquePlacementRegistry.reserve/release/clear` | Internal M2 service | Owns positive cross-tile ADT placement-ID reservations | Renderer thread/map session | Invalid/unkeyed/non-owner inputs preserve documented fallback |
Публичным contract не считаются `StreamingWorldLoader` methods/properties, Публичным contract не считаются `StreamingWorldLoader` methods/properties,
внутренние dictionaries, queues, job records и generated node names. Scene-owned внутренние dictionaries, queues, job records и generated node names. Scene-owned
@@ -136,6 +139,7 @@ loader configuration remains transitional composition data, not a caller API.
| Internal cache | Full-quality terrain Mesh/source | Loader terrain upgrade / `TerrainQualityMeshCache` | Revisited tile state | Cache-retained Mesh reference | Map session until eviction/reset | | Internal cache | Full-quality terrain Mesh/source | Loader terrain upgrade / `TerrainQualityMeshCache` | Revisited tile state | Cache-retained Mesh reference | Map session until eviction/reset |
| Internal plan | Parsed chunks, typed focus and immutable chunk-LOD policy | Loader / `TerrainChunkLodPlanner` | Loader desired state | Fresh dictionary; no retained resources | One target refresh | | Internal plan | Parsed chunks, typed focus and immutable chunk-LOD policy | Loader / `TerrainChunkLodPlanner` | Loader desired state | Fresh dictionary; no retained resources | One target refresh |
| Internal plan | Current/desired chunk state and typed focus | Loader / `TerrainChunkGeometryQueuePlanner` | Loader chunk queues | Fresh request arrays; no retained resources | One queue rebuild | | Internal plan | Current/desired chunk state and typed focus | Loader / `TerrainChunkGeometryQueuePlanner` | Loader chunk queues | Fresh request arrays; no retained resources | One queue rebuild |
| Internal registry | Tile key and M2 placement unique IDs | Loader / `M2UniquePlacementRegistry` | Filtered grouping input and tile retry state | Registry-owned strings; fresh result arrays | Map session |
| Output | Desired tile/detail operations | Streamer plan application | Finalize queues | Loader-owned | Cross-frame | | Output | Desired tile/detail operations | Streamer plan application | Finalize queues | Loader-owned | Cross-frame |
| Output | Terrain/M2/WMO/liquid instances | Loader/builders | Godot world/renderer | Loader/world owner | Main-thread attach | | Output | Terrain/M2/WMO/liquid instances | Loader/builders | Godot world/renderer | Loader/world owner | Main-thread attach |
| Output | Detached renderer metrics | `WorldRenderFacade` | Capture/baseline tools | Caller-owned deep copy | On demand | | Output | Detached renderer metrics | `WorldRenderFacade` | Capture/baseline tools | Caller-owned deep copy | On demand |
@@ -179,6 +183,8 @@ flowchart TD
ChunkLod --> DesiredChunkLod[Desired chunk LOD state] ChunkLod --> DesiredChunkLod[Desired chunk LOD state]
DesiredChunkLod --> ChunkQueue[TerrainChunkGeometryQueuePlanner] DesiredChunkLod --> ChunkQueue[TerrainChunkGeometryQueuePlanner]
ChunkQueue --> B ChunkQueue --> B
R --> M2Registry[M2UniquePlacementRegistry reserve]
M2Registry --> M2
R --> B[RenderBudgetScheduler permits] R --> B[RenderBudgetScheduler permits]
B --> Terrain[Terrain attach/upgrade] B --> Terrain[Terrain attach/upgrade]
B --> M2[M2 group/MultiMesh attach] B --> M2[M2 group/MultiMesh attach]
@@ -290,6 +296,9 @@ sequenceDiagram
- `TerrainChunkGeometryQueuePlanner` is stateless and owns only call-local - `TerrainChunkGeometryQueuePlanner` is stateless and owns only call-local
create/remove request calculation and sorting. The loader adopts the fresh create/remove request calculation and sorting. The loader adopts the fresh
arrays and retains removal-first drains, budgets and render side effects. arrays and retains removal-first drains, budgets and render side effects.
- `M2UniquePlacementRegistry` owns only positive unique-key to tile-key strings.
The loader retains owned/skipped tile arrays, candidate retry, grouping/build
tasks, caches and all MultiMesh/Node/Mesh/material/RID side effects.
- Rendered-ground query results and diagnostic snapshots are caller-owned values; - Rendered-ground query results and diagnostic snapshots are caller-owned values;
the facade never returns Mesh, Node, tile-state or queue references. the facade never returns Mesh, Node, tile-state or queue references.
- Loaded-mesh ground sampling is renderer diagnostics, not authoritative gameplay - Loaded-mesh ground sampling is renderer diagnostics, not authoritative gameplay
@@ -372,6 +381,9 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
- Terrain chunk geometry queue contract: current/desired comparison, tile-LOD - Terrain chunk geometry queue contract: current/desired comparison, tile-LOD
takeover, sparse origin fallback, stable removals, nearest creates, loader takeover, sparse origin fallback, stable removals, nearest creates, loader
drain boundary and bounded multi-tile timing without a world scene. drain boundary and bounded multi-tile timing without a world scene.
- M2 unique placement contract: unkeyed pass-through, within-call/cross-tile
dedupe, owner release/retry, clear, detached diagnostics, loader boundary and
bounded reserve/release timing without a world scene.
- Budget scheduler contract: exact lane exhaustion, shared chunk removal/create - Budget scheduler contract: exact lane exhaustion, shared chunk removal/create
priority, independent lanes, frame reset, invalid limits, terminal cancellation, priority, independent lanes, frame reset, invalid limits, terminal cancellation,
dependency boundaries and bounded permit timing without loading a world scene. dependency boundaries and bounded permit timing without loading a world scene.
@@ -401,6 +413,7 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
|---|---|---|---| |---|---|---|---|
| ADT streaming/terrain | Partial | M00 checkpoints and current scenes | Fidelity/performance gaps remain | | ADT streaming/terrain | Partial | M00 checkpoints and current scenes | Fidelity/performance gaps remain |
| Static M2 placement | Partial | MultiMesh/dedupe probes | Full materials/animation/effects | | Static M2 placement | Partial | MultiMesh/dedupe probes | Full materials/animation/effects |
| M2 unique placement registry | Implemented extraction | Scene-free ownership/lifecycle/timing contract and historical `uid:11785` smoke | Group/build/tasks/finalization and asset-backed p95/p99 remain pending |
| WMO rendering | Partial | Cached group rendering | Portals/rooms/material parity | | WMO rendering | Partial | Cached group rendering | Portals/rooms/material parity |
| Liquids | Partial | MH2O/MLIQ paths | LiquidType/depth/shore fidelity | | Liquids | Partial | MH2O/MLIQ paths | LiquidType/depth/shore fidelity |
| Sky/light | Partial | DBC controller/logs | Zone transition paired validation | | Sky/light | Partial | DBC controller/logs | Zone transition paired validation |
@@ -0,0 +1,88 @@
class_name M2UniquePlacementRegistry
extends RefCounted
## Session-memory ownership registry for positive ADT MDDF unique IDs.
var _owner_tile_by_unique_key: Dictionary = {}
## Reserves unowned positive unique IDs for one tile and returns fresh arrays.
## Unkeyed placements pass through without entering registry ownership.
func reserve(tile_key: String, m2_placements: Array) -> Dictionary:
var filtered_placements: Array = []
var unique_keys: Array = []
var skipped_unique_keys: Array = []
for placement_variant in m2_placements:
if not (placement_variant is Dictionary):
continue
var placement: Dictionary = placement_variant
var unique_key := _unique_key_for_placement(placement)
if unique_key.is_empty():
filtered_placements.append(placement)
continue
if _owner_tile_by_unique_key.has(unique_key):
var owner_tile := String(_owner_tile_by_unique_key[unique_key])
if owner_tile == tile_key and unique_keys.has(unique_key):
continue
if owner_tile != tile_key:
if not skipped_unique_keys.has(unique_key):
skipped_unique_keys.append(unique_key)
continue
_owner_tile_by_unique_key[unique_key] = tile_key
if not unique_keys.has(unique_key):
unique_keys.append(unique_key)
filtered_placements.append(placement)
return {
"placements": filtered_placements,
"unique_keys": unique_keys,
"skipped_unique_keys": skipped_unique_keys,
}
## Releases only keys currently owned by the supplied tile and returns them in
## input order so the loader can notify waiting candidate tiles.
func release(tile_key: String, unique_keys: Array) -> Array:
var released_unique_keys: Array = []
for unique_key_variant in unique_keys:
var unique_key := String(unique_key_variant)
if unique_key.is_empty() or not _owner_tile_by_unique_key.has(unique_key):
continue
if String(_owner_tile_by_unique_key[unique_key]) != tile_key:
continue
_owner_tile_by_unique_key.erase(unique_key)
released_unique_keys.append(unique_key)
return released_unique_keys
## Removes all reservations during world reset or renderer shutdown.
func clear() -> void:
_owner_tile_by_unique_key.clear()
## Returns the number of currently owned positive unique IDs.
func active_count() -> int:
return _owner_tile_by_unique_key.size()
## Returns detached, key-sorted scalar diagnostics without registry references.
func diagnostic_snapshot() -> Dictionary:
var sorted_unique_keys: Array = _owner_tile_by_unique_key.keys()
sorted_unique_keys.sort()
var owners: Array = []
for unique_key_variant in sorted_unique_keys:
var unique_key := String(unique_key_variant)
owners.append({
"unique_key": unique_key,
"tile_key": String(_owner_tile_by_unique_key[unique_key]),
})
return {
"active_count": owners.size(),
"owners": owners,
}
func _unique_key_for_placement(placement: Dictionary) -> String:
var unique_id := int(placement.get("unique_id", -1))
if unique_id <= 0:
return ""
return "uid:%d" % unique_id
@@ -0,0 +1 @@
uid://cqdvx0qnpcte1
+13 -54
View File
@@ -24,6 +24,9 @@ const TERRAIN_CHUNK_LOD_PLANNER_SCRIPT := preload("res://src/render/terrain/terr
const TERRAIN_CHUNK_GEOMETRY_QUEUE_PLANNER_SCRIPT := preload( const TERRAIN_CHUNK_GEOMETRY_QUEUE_PLANNER_SCRIPT := preload(
"res://src/render/terrain/terrain_chunk_geometry_queue_planner.gd" "res://src/render/terrain/terrain_chunk_geometry_queue_planner.gd"
) )
const M2_UNIQUE_PLACEMENT_REGISTRY_SCRIPT := preload(
"res://src/render/m2/m2_unique_placement_registry.gd"
)
const STREAMING_TARGET_PLANNER_SCRIPT := preload("res://src/render/streaming/streaming_target_planner.gd") const STREAMING_TARGET_PLANNER_SCRIPT := preload("res://src/render/streaming/streaming_target_planner.gd")
const STREAMING_TARGET_POLICY_SCRIPT := preload("res://src/render/streaming/streaming_target_policy.gd") const STREAMING_TARGET_POLICY_SCRIPT := preload("res://src/render/streaming/streaming_target_policy.gd")
const RENDER_BUDGET_SCHEDULER_SCRIPT := preload("res://src/render/streaming/render_budget_scheduler.gd") const RENDER_BUDGET_SCHEDULER_SCRIPT := preload("res://src/render/streaming/render_budget_scheduler.gd")
@@ -207,7 +210,9 @@ var _m2_group_result_mutex := Mutex.new()
var _m2_group_result_queue: Array = [] var _m2_group_result_queue: Array = []
var _m2_build_jobs: Dictionary = {} var _m2_build_jobs: Dictionary = {}
var _m2_build_queue: Array = [] var _m2_build_queue: Array = []
var _m2_unique_registry: Dictionary = {} var _m2_unique_placement_registry := (
M2_UNIQUE_PLACEMENT_REGISTRY_SCRIPT.new()
)
var _m2_mesh_cache: Dictionary = {} var _m2_mesh_cache: Dictionary = {}
var _m2_mesh_load_requests: Dictionary = {} var _m2_mesh_load_requests: Dictionary = {}
var _m2_mesh_finalize_queue: Array = [] var _m2_mesh_finalize_queue: Array = []
@@ -1011,7 +1016,7 @@ func render_baseline_snapshot() -> Dictionary:
"chunk_remove": _chunk_remove_queue.size(), "chunk_remove": _chunk_remove_queue.size(),
}, },
"cache_entries": _tile_mesh_cache.size(), "cache_entries": _tile_mesh_cache.size(),
"m2_active_unique_ids": _m2_unique_registry.size(), "m2_active_unique_ids": _m2_unique_placement_registry.active_count(),
"wmo_instances": _wmo_registry.size(), "wmo_instances": _wmo_registry.size(),
} }
@@ -2965,7 +2970,7 @@ func _clear_streamed_world() -> void:
for key in _m2_build_jobs.keys(): for key in _m2_build_jobs.keys():
_cancel_m2_build_job(String(key)) _cancel_m2_build_job(String(key))
_m2_build_queue.clear() _m2_build_queue.clear()
_m2_unique_registry.clear() _m2_unique_placement_registry.clear()
_m2_mesh_load_requests.clear() _m2_mesh_load_requests.clear()
_m2_mesh_finalize_queue.clear() _m2_mesh_finalize_queue.clear()
_m2_runtime_rebuild_required_cache.clear() _m2_runtime_rebuild_required_cache.clear()
@@ -4129,7 +4134,10 @@ func _request_tile_m2_assets(
if _m2_group_tasks.has(key) or _m2_build_jobs.has(key): if _m2_group_tasks.has(key) or _m2_build_jobs.has(key):
return result return result
var filtered := _reserve_tile_m2_placements(key, m2_placements) var filtered: Dictionary = _m2_unique_placement_registry.reserve(
key,
m2_placements
)
var filtered_placements: Array = filtered.get("placements", []) var filtered_placements: Array = filtered.get("placements", [])
result["unique_keys"] = filtered.get("unique_keys", []) result["unique_keys"] = filtered.get("unique_keys", [])
result["skipped_unique_keys"] = filtered.get("skipped_unique_keys", []) result["skipped_unique_keys"] = filtered.get("skipped_unique_keys", [])
@@ -4143,46 +4151,6 @@ func _request_tile_m2_assets(
return result return result
func _reserve_tile_m2_placements(tile_key: String, m2_placements: Array) -> Dictionary:
var filtered_placements: Array = []
var unique_keys: Array = []
var skipped_unique_keys: Array = []
for index in m2_placements.size():
var placement_variant = m2_placements[index]
if not (placement_variant is Dictionary):
continue
var placement: Dictionary = placement_variant
var unique_key := _m2_unique_key(placement)
if unique_key.is_empty():
filtered_placements.append(placement)
continue
if _m2_unique_registry.has(unique_key):
var entry: Dictionary = _m2_unique_registry[unique_key]
var owner_tile := String(entry.get("tile", ""))
if owner_tile == tile_key and unique_keys.has(unique_key):
continue
if owner_tile != tile_key:
if not skipped_unique_keys.has(unique_key):
skipped_unique_keys.append(unique_key)
continue
_m2_unique_registry[unique_key] = {"tile": tile_key}
if not unique_keys.has(unique_key):
unique_keys.append(unique_key)
filtered_placements.append(placement)
return {
"placements": filtered_placements,
"unique_keys": unique_keys,
"skipped_unique_keys": skipped_unique_keys,
}
func _m2_unique_key(placement: Dictionary) -> String:
var uid: int = int(placement.get("unique_id", -1))
if uid <= 0:
return ""
return "uid:%d" % uid
func _release_tile_m2_unique_keys(state: Dictionary, notify_candidates: bool = true) -> void: func _release_tile_m2_unique_keys(state: Dictionary, notify_candidates: bool = true) -> void:
var tile_key := String(state.get("key", "")) var tile_key := String(state.get("key", ""))
var unique_keys: Array = state.get("m2_unique_keys", []) var unique_keys: Array = state.get("m2_unique_keys", [])
@@ -4191,16 +4159,7 @@ func _release_tile_m2_unique_keys(state: Dictionary, notify_candidates: bool = t
state["m2_skipped_unique_keys"] = [] state["m2_skipped_unique_keys"] = []
return return
var released_keys: Array = [] var released_keys: Array = _m2_unique_placement_registry.release(tile_key, unique_keys)
for unique_key_variant in unique_keys:
var unique_key := String(unique_key_variant)
if unique_key.is_empty() or not _m2_unique_registry.has(unique_key):
continue
var entry: Dictionary = _m2_unique_registry[unique_key]
if String(entry.get("tile", "")) != tile_key:
continue
_m2_unique_registry.erase(unique_key)
released_keys.append(unique_key)
state["m2_unique_keys"] = [] state["m2_unique_keys"] = []
state["m2_skipped_unique_keys"] = [] state["m2_skipped_unique_keys"] = []
+10 -14
View File
@@ -1,12 +1,14 @@
extends SceneTree extends SceneTree
const STREAMING_WORLD_LOADER := preload("res://src/scenes/streaming/streaming_world_loader.gd") const M2_UNIQUE_PLACEMENT_REGISTRY := preload(
"res://src/render/m2/m2_unique_placement_registry.gd"
)
var _failures := 0 var _failures := 0
func _initialize() -> void: func _initialize() -> void:
var loader = STREAMING_WORLD_LOADER.new() var registry: RefCounted = M2_UNIQUE_PLACEMENT_REGISTRY.new()
var placement := { var placement := {
"name_id": 0, "name_id": 0,
"unique_id": 11785, "unique_id": 11785,
@@ -15,30 +17,24 @@ func _initialize() -> void:
"scale": 1.0, "scale": 1.0,
} }
var first: Dictionary = loader.call("_reserve_tile_m2_placements", "30_49", [placement]) var first: Dictionary = registry.call("reserve", "30_49", [placement])
_expect((first.get("placements", []) as Array).size() == 1, "first tile should reserve unique M2 placement") _expect((first.get("placements", []) as Array).size() == 1, "first tile should reserve unique M2 placement")
_expect((first.get("unique_keys", []) as Array).has("uid:11785"), "first tile should own uid:11785") _expect((first.get("unique_keys", []) as Array).has("uid:11785"), "first tile should own uid:11785")
_expect((first.get("skipped_unique_keys", []) as Array).is_empty(), "first tile should not skip uid:11785") _expect((first.get("skipped_unique_keys", []) as Array).is_empty(), "first tile should not skip uid:11785")
var duplicate: Dictionary = loader.call("_reserve_tile_m2_placements", "31_49", [placement]) var duplicate: Dictionary = registry.call("reserve", "31_49", [placement])
_expect((duplicate.get("placements", []) as Array).is_empty(), "second tile should skip duplicate uid:11785") _expect((duplicate.get("placements", []) as Array).is_empty(), "second tile should skip duplicate uid:11785")
_expect((duplicate.get("skipped_unique_keys", []) as Array).has("uid:11785"), "second tile should remember skipped uid:11785") _expect((duplicate.get("skipped_unique_keys", []) as Array).has("uid:11785"), "second tile should remember skipped uid:11785")
loader.call("_release_tile_m2_unique_keys", { registry.call("release", "30_49", ["uid:11785"])
"key": "30_49", var after_release: Dictionary = registry.call("reserve", "31_49", [placement])
"m2_unique_keys": ["uid:11785"],
"m2_skipped_unique_keys": [],
}, false)
var after_release: Dictionary = loader.call("_reserve_tile_m2_placements", "31_49", [placement])
_expect((after_release.get("placements", []) as Array).size() == 1, "second tile should reserve uid:11785 after owner release") _expect((after_release.get("placements", []) as Array).size() == 1, "second tile should reserve uid:11785 after owner release")
_expect((after_release.get("unique_keys", []) as Array).has("uid:11785"), "second tile should own uid:11785 after release") _expect((after_release.get("unique_keys", []) as Array).has("uid:11785"), "second tile should own uid:11785 after release")
var same_tile_loader = STREAMING_WORLD_LOADER.new() var same_tile_registry: RefCounted = M2_UNIQUE_PLACEMENT_REGISTRY.new()
var same_tile_duplicate: Dictionary = same_tile_loader.call("_reserve_tile_m2_placements", "31_49", [placement, placement]) var same_tile_duplicate: Dictionary = same_tile_registry.call("reserve", "31_49", [placement, placement])
_expect((same_tile_duplicate.get("placements", []) as Array).size() == 1, "same tile should keep only one copy of duplicate uid:11785") _expect((same_tile_duplicate.get("placements", []) as Array).size() == 1, "same tile should keep only one copy of duplicate uid:11785")
loader.free()
same_tile_loader.free()
if _failures > 0: if _failures > 0:
push_error("M2 unique dedupe verification failed: %d issue(s)" % _failures) push_error("M2 unique dedupe verification failed: %d issue(s)" % _failures)
quit(1) quit(1)
@@ -0,0 +1,156 @@
extends SceneTree
## Asset-free ownership, lifecycle, dependency and timing regression for M2 IDs.
const REGISTRY_SCRIPT := preload("res://src/render/m2/m2_unique_placement_registry.gd")
const LOADER_PATH := "res://src/scenes/streaming/streaming_world_loader.gd"
func _initialize() -> void:
var failures: Array[String] = []
_verify_unkeyed_and_invalid_placements(failures)
_verify_same_tile_and_cross_tile_deduplication(failures)
_verify_owner_checked_release_and_retry(failures)
_verify_clear_and_detached_diagnostics(failures)
_verify_loader_boundary(failures)
var elapsed_milliseconds := _verify_bounded_timing(failures)
if not failures.is_empty():
for failure in failures:
push_error("M2_UNIQUE_PLACEMENT_REGISTRY: %s" % failure)
quit(1)
return
print(
"M2_UNIQUE_PLACEMENT_REGISTRY PASS cases=10 iterations=100 elapsed_ms=%.3f"
% elapsed_milliseconds
)
quit(0)
func _verify_unkeyed_and_invalid_placements(failures: Array[String]) -> void:
var registry: RefCounted = REGISTRY_SCRIPT.new()
var result: Dictionary = registry.call("reserve", "tile-a", [
{"unique_id": -1},
{"unique_id": 0},
{"name_id": 7},
"invalid",
])
_expect_equal((result["placements"] as Array).size(), 3, "unkeyed placements pass", failures)
_expect_true((result["unique_keys"] as Array).is_empty(), "unkeyed placements not owned", failures)
_expect_equal(int(registry.call("active_count")), 0, "unkeyed active count", failures)
func _verify_same_tile_and_cross_tile_deduplication(failures: Array[String]) -> void:
var registry: RefCounted = REGISTRY_SCRIPT.new()
var placement := {"unique_id": 11785, "name_id": 0}
var first: Dictionary = registry.call("reserve", "30_49", [placement, placement])
_expect_equal((first["placements"] as Array).size(), 1, "same call duplicate filtered", failures)
_expect_string_array((first["unique_keys"] as Array), ["uid:11785"], "first owner key", failures)
var same_owner_retry: Dictionary = registry.call("reserve", "30_49", [placement])
_expect_equal((same_owner_retry["placements"] as Array).size(), 1, "same owner later call passes", failures)
var competing: Dictionary = registry.call("reserve", "31_49", [placement, placement])
_expect_true((competing["placements"] as Array).is_empty(), "competing tile filtered", failures)
_expect_string_array(
competing["skipped_unique_keys"] as Array,
["uid:11785"],
"competing skip recorded once",
failures
)
func _verify_owner_checked_release_and_retry(failures: Array[String]) -> void:
var registry: RefCounted = REGISTRY_SCRIPT.new()
var placement := {"unique_id": 44}
registry.call("reserve", "owner", [placement])
_expect_true(
(registry.call("release", "other", ["uid:44"]) as Array).is_empty(),
"non-owner release ignored",
failures
)
_expect_equal(int(registry.call("active_count")), 1, "non-owner keeps reservation", failures)
var released: Array = registry.call("release", "owner", ["", "uid:44", "uid:44"])
_expect_string_array(released, ["uid:44"], "owner release order and dedupe", failures)
var retried: Dictionary = registry.call("reserve", "other", [placement])
_expect_equal((retried["placements"] as Array).size(), 1, "waiting tile reserves after release", failures)
func _verify_clear_and_detached_diagnostics(failures: Array[String]) -> void:
var registry: RefCounted = REGISTRY_SCRIPT.new()
registry.call("reserve", "tile-b", [{"unique_id": 20}])
registry.call("reserve", "tile-a", [{"unique_id": 10}])
var snapshot: Dictionary = registry.call("diagnostic_snapshot")
var owners: Array = snapshot["owners"]
_expect_equal(int(snapshot["active_count"]), 2, "diagnostic count", failures)
_expect_string_equal(String(owners[0]["unique_key"]), "uid:10", "diagnostic sorted", failures)
owners[0]["tile_key"] = "mutated"
var fresh_snapshot: Dictionary = registry.call("diagnostic_snapshot")
_expect_string_equal(
String((fresh_snapshot["owners"] as Array)[0]["tile_key"]),
"tile-a",
"diagnostics detached",
failures
)
registry.call("clear")
registry.call("clear")
_expect_equal(int(registry.call("active_count")), 0, "clear idempotent", failures)
func _verify_loader_boundary(failures: Array[String]) -> void:
var source := _read_text(LOADER_PATH, failures)
_expect_true(source.contains("M2_UNIQUE_PLACEMENT_REGISTRY_SCRIPT.new()"), "loader composes registry", failures)
_expect_true(source.contains("_m2_unique_placement_registry.reserve("), "loader delegates reserve", failures)
_expect_true(source.contains("_m2_unique_placement_registry.release("), "loader delegates release", failures)
_expect_true(source.contains("_m2_unique_placement_registry.clear()"), "loader delegates clear", failures)
_expect_true(not source.contains("var _m2_unique_registry"), "loader omits registry dictionary", failures)
_expect_true(not source.contains("func _m2_unique_key("), "loader omits key formula", failures)
func _verify_bounded_timing(failures: Array[String]) -> float:
var placements: Array = []
for placement_index in range(256):
placements.append({"unique_id": placement_index + 1})
var registry: RefCounted = REGISTRY_SCRIPT.new()
var started_microseconds := Time.get_ticks_usec()
for _iteration in range(100):
var reservation: Dictionary = registry.call("reserve", "owner", placements)
registry.call("release", "owner", reservation["unique_keys"])
var elapsed_milliseconds := float(Time.get_ticks_usec() - started_microseconds) / 1000.0
_expect_true(elapsed_milliseconds < 1000.0, "100 reserve/release cycles remain bounded", failures)
return elapsed_milliseconds
func _read_text(path: String, failures: Array[String]) -> String:
var file := FileAccess.open(path, FileAccess.READ)
if file == null:
failures.append("cannot open %s" % path)
return ""
return file.get_as_text()
func _expect_equal(actual_value: int, expected_value: int, label: String, failures: Array[String]) -> void:
if actual_value != expected_value:
failures.append("%s expected %d, got %d" % [label, expected_value, actual_value])
func _expect_string_equal(
actual_value: String,
expected_value: String,
label: String,
failures: Array[String]
) -> void:
if actual_value != expected_value:
failures.append("%s expected %s, got %s" % [label, expected_value, actual_value])
func _expect_string_array(
actual_values: Array,
expected_values: Array,
label: String,
failures: Array[String]
) -> void:
if actual_values != expected_values:
failures.append("%s expected %s, got %s" % [label, expected_values, actual_values])
func _expect_true(actual_value: bool, label: String, failures: Array[String]) -> void:
if not actual_value:
failures.append("%s expected true" % label)
@@ -0,0 +1 @@
uid://inw4fft20be0
+12 -2
View File
@@ -27,11 +27,12 @@ Runtime и Editor используют facade; planner/scheduler тестиру
## Evidence ## Evidence
- Date: 2026-07-16 - Date: 2026-07-16
- Revision/worktree: master merges `7ece2ab`, `7e35de7`, `80cb084`, `d6e5b53`, `c69abd6`, `606770c`, `630a0c1`, `f36fabb`, `24aef13`; - Revision/worktree: master merges `7ece2ab`, `7e35de7`, `80cb084`, `d6e5b53`, `c69abd6`, `606770c`, `630a0c1`, `f36fabb`, `24aef13`, `2342430`;
packages `M03-RND-SCHEDULER-001`, `M03-RND-INTERNAL-ACCESS-GATE-001`, packages `M03-RND-SCHEDULER-001`, `M03-RND-INTERNAL-ACCESS-GATE-001`,
`M03-RND-FACADE-GROUND-QUERY-001`, `M03-RND-FACADE-ENVIRONMENT-001`, `M03-RND-FACADE-GROUND-QUERY-001`, `M03-RND-FACADE-ENVIRONMENT-001`,
`M03-RND-FACADE-ENTITY-001`, `M03-RND-TERRAIN-CACHE-SERVICE-001`, `M03-RND-FACADE-ENTITY-001`, `M03-RND-TERRAIN-CACHE-SERVICE-001`,
`M03-RND-TERRAIN-LOD-PLANNER-001`, `M03-RND-TERRAIN-CHUNK-QUEUE-001` `M03-RND-TERRAIN-LOD-PLANNER-001`, `M03-RND-TERRAIN-CHUNK-QUEUE-001`,
`M03-RND-M2-UNIQUE-REGISTRY-001`
- Commands: dedicated scheduler/planner/facade/focus/coordinate/manifest/shutdown - Commands: dedicated scheduler/planner/facade/focus/coordinate/manifest/shutdown
headless verifiers; Godot cold editor parse; coordination, documentation and diff gates. headless verifiers; Godot cold editor parse; coordination, documentation and diff gates.
- Results: scheduler `cases=6 iterations=20000 elapsed_ms=10.216`; planner - Results: scheduler `cases=6 iterations=20000 elapsed_ms=10.216`; planner
@@ -53,6 +54,9 @@ Runtime и Editor используют facade; planner/scheduler тестиру
Terrain chunk geometry queue planner passed `cases=8 iterations=100 Terrain chunk geometry queue planner passed `cases=8 iterations=100
elapsed_ms=229.324`; internal-access remained `42` and post-merge facade and elapsed_ms=229.324`; internal-access remained `42` and post-merge facade and
`7/7` manifest gates stayed green. `7/7` manifest gates stayed green.
M2 unique placement registry passed `cases=10 iterations=100 elapsed_ms=35.513`;
the historical `uid:11785` cross-tile smoke, internal-access `42`, facade and
`7/7` manifest gates remained green after merge.
M02 terrain-query regression M02 terrain-query regression
remained green (13 pre-existing expired M00 claim warnings). remained green (13 pre-existing expired M00 claim warnings).
- Fidelity comparison: all 16 historical operation limits and drain sites were - Fidelity comparison: all 16 historical operation limits and drain sites were
@@ -71,6 +75,8 @@ Runtime и Editor используют facade; planner/scheduler тестиру
Chunk operation planning preserves stable removals, nearest-first create sorting, Chunk operation planning preserves stable removals, nearest-first create sorting,
tile-LOD remove-all takeover, sparse tile-origin fallback and XZ-only distance; tile-LOD remove-all takeover, sparse tile-origin fallback and XZ-only distance;
loader still drains removals first through the shared `chunk_geometry` budget. loader still drains removals first through the shared `chunk_geometry` budget.
M2 positive `MDDF.uniqueId` retains `uid:<decimal>` first-owner semantics,
within-call duplicate suppression, cross-tile skip/retry and unkeyed pass-through.
No new build-12340 parity claim is made; asset-backed visual/p95/p99 comparison remains pending. No new build-12340 parity claim is made; asset-backed visual/p95/p99 comparison remains pending.
- Changed files: scene-free scheduler and verifier; streamer budget composition/ - Changed files: scene-free scheduler and verifier; streamer budget composition/
permit delegation; scheduler module spec; world renderer/module registry/RENDER notes; permit delegation; scheduler module spec; world renderer/module registry/RENDER notes;
@@ -80,6 +86,8 @@ Runtime и Editor используют facade; planner/scheduler тестиру
scene-free terrain quality Mesh cache and loader store/restore/clear adapters, scene-free terrain quality Mesh cache and loader store/restore/clear adapters,
immutable terrain chunk LOD policy, pure planner and both loader plan adapters, immutable terrain chunk LOD policy, pure planner and both loader plan adapters,
pure terrain chunk geometry queue planner and loader queue-adoption adapter, pure terrain chunk geometry queue planner and loader queue-adoption adapter,
stateful M2 unique placement registry, loader reserve/release/clear adapters and
migrated historical dedupe smoke,
expanded facade/internal-access/coordinate verifiers; work-package claims and this evidence. expanded facade/internal-access/coordinate verifiers; work-package claims and this evidence.
- Remaining risks: queue storage length and worker concurrency/stale-result logic - Remaining risks: queue storage length and worker concurrency/stale-result logic
remain streamer-owned; cancellation stops new permits but does not interrupt remain streamer-owned; cancellation stops new permits but does not interrupt
@@ -90,5 +98,7 @@ Runtime и Editor используют facade; planner/scheduler тестиру
chunk LOD asset-backed p95/p99 and visual acceptance remain pending; chunk LOD asset-backed p95/p99 and visual acceptance remain pending;
chunk queue planning adds a second tile-state pass whose asset-backed p95/p99 chunk queue planning adds a second tile-state pass whose asset-backed p95/p99
still needs acceptance, while queue drains and geometry finalization remain monolithic; still needs acceptance, while queue drains and geometry finalization remain monolithic;
private Azeroth data was absent for the asset-backed ADT placement probe; M2
grouping/build/tasks/caches/finalization and boundary traversal p95/p99 remain pending;
M03 still needs further terrain/M2/WMO/liquid service extraction M03 still needs further terrain/M2/WMO/liquid service extraction
and dependency/performance acceptance. and dependency/performance acceptance.