Compare commits

..

5 Commits

13 changed files with 673 additions and 36 deletions
+20 -1
View File
@@ -34,6 +34,7 @@ Paired run 2026-07-11 подтвердил крупный coordinate/placement g
- `src/render/wmo/wmo_render_resource_cache_state.gd` - validated lightweight WMO render Resources, negative cache and pending cache paths without ResourceLoader I/O. - `src/render/wmo/wmo_render_resource_cache_state.gd` - validated lightweight WMO render Resources, negative cache and pending cache paths without ResourceLoader I/O.
- `src/render/wmo/wmo_scene_resource_cache_state.gd` - validated cached-WMO PackedScenes, negative cache and pending `.tscn` paths without file/I/O/Node ownership. - `src/render/wmo/wmo_scene_resource_cache_state.gd` - validated cached-WMO PackedScenes, negative cache and pending `.tscn` paths without file/I/O/Node ownership.
- `src/render/liquid/adt_water_load_pipeline_state.gd` - ADT water pending FIFO/dedupe, active task IDs and worker-safe parsed-result mailbox without parser or Node ownership. - `src/render/liquid/adt_water_load_pipeline_state.gd` - ADT water pending FIFO/dedupe, active task IDs and worker-safe parsed-result mailbox without parser or Node ownership.
- `src/render/liquid/adt_water_scene_finalizer.gd` - stateless main-thread ADT water build/attach and optional persisted Editor ownership through the existing ADTBuilder.
- `src/scenes/streaming/eastern_kingdoms_streaming.tscn` - текущая сцена для проверки Azeroth/Eastern Kingdoms. - `src/scenes/streaming/eastern_kingdoms_streaming.tscn` - текущая сцена для проверки Azeroth/Eastern Kingdoms.
- `addons/mpq_extractor/loaders/adt_builder.gd` - сборка ADT terrain, control splat материалов и MH2O liquids. - `addons/mpq_extractor/loaders/adt_builder.gd` - сборка ADT terrain, control splat материалов и MH2O liquids.
- `addons/mpq_extractor/loaders/m2_builder.gd` - сборка статического M2 mesh/material. - `addons/mpq_extractor/loaders/m2_builder.gd` - сборка статического M2 mesh/material.
@@ -1084,7 +1085,8 @@ $exe = Join-Path $env:TEMP 'godot-4.6.1-openwc\Godot_v4.6.1-stable_win64.exe'
active WorkerThreadPool task IDs and the mutex-protected parsed-result mailbox. active WorkerThreadPool task IDs and the mutex-protected parsed-result mailbox.
- `StreamingWorldLoader` still selects concurrency, starts/waits tasks, invokes - `StreamingWorldLoader` still selects concurrency, starts/waits tasks, invokes
`ADTLoader`, consumes `WATER_FINALIZE` permits, rejects stale tile/path/root `ADTLoader`, consumes `WATER_FINALIZE` permits, rejects stale tile/path/root
results and builds/attaches water Nodes through `ADTBuilder` on the main thread. results and owns `water_loaded` timing. Main-thread Water subtree construction
and attachment are delegated separately as described below.
- Tile release still cancels only pending water requests; active work completes - Tile release still cancels only pending water requests; active work completes
and its result is discarded when tile ownership is stale. Shutdown waits task and its result is discarded when tile ownership is stale. Shutdown waits task
IDs before clearing state; reset does not interrupt workers. IDs before clearing state; reset does not interrupt workers.
@@ -1093,6 +1095,23 @@ $exe = Join-Path $env:TEMP 'godot-4.6.1-openwc\Godot_v4.6.1-stable_win64.exe'
- Asset-backed MH2O/MCLQ fidelity, long-traversal leak/p95/p99 and paired capture - Asset-backed MH2O/MCLQ fidelity, long-traversal leak/p95/p99 and paired capture
evidence remain pending. evidence remain pending.
## 2026-07-17 ADT Water Scene Finalizer Extraction
- `AdtWaterSceneFinalizer` now owns the two main-thread operations that turn
validated parsed ADT data into a Water subtree: the existing
`ADTBuilder.build_tile_water_scene` call and attachment to the tile root.
- Both raw tile finalization and delayed cached-tile water results use the same
service. Optional persisted Editor ownership is assigned recursively only
when the loader supplies its existing edited-scene owner.
- The service is stateless and retains no Node, Mesh, material or RID. The tile
SceneTree subtree remains the lifetime owner; dry data still causes ADTBuilder
to free its temporary empty Water root.
- Parsing, task/result state, concurrency, stale validation, `WATER_FINALIZE`
permits, `enable_water` and `water_loaded` timing remain loader-owned.
- Geometry, materials, coordinates, cache formats, profiles and visible output
are unchanged. Synthetic geometry/ownership evidence is not an MH2O/MCLQ or
original-client parity claim; asset-backed traversal/p95/p99 remains pending.
## 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,116 @@
# M03-RND-ADT-WATER-SCENE-FINALIZER-001 — ADT water scene finalizer
<!-- OPENWC_CLAIM:M03-RND-ADT-WATER-SCENE-FINALIZER-001:sindo-main-codex:2026-07-20 -->
## Ownership
- Target: M03
- Program: RND
- Owner/Agent ID: sindo-main-codex
- Branch: `work/sindo-main-codex/m03-adt-water-scene-finalizer`
- Lease expires UTC: 2026-07-20
- Integrator: M03 milestone integrator
## Outcome
Extract main-thread ADT water subtree construction, tile-root attachment and
optional recursive editor ownership from `StreamingWorldLoader` while retaining
the existing `ADTBuilder` geometry/material implementation.
## Non-goals
- Parse or reinterpret MH2O/MCLQ data.
- Own pending requests, WorkerThreadPool tasks or parsed-result delivery.
- Consume `WATER_FINALIZE` permits or validate tile/path/result staleness.
- Mark tile state `water_loaded` or choose whether water is enabled.
- Change liquid geometry, materials, coordinates, cache formats or profiles.
## Paths
- Exclusive: `src/render/liquid/adt_water_scene_finalizer.gd`,
`src/tools/verify_adt_water_scene_finalizer.gd`,
`docs/modules/adt-water-scene-finalizer.md`, this claim
- Shared/hotspots: `src/scenes/streaming/streaming_world_loader.gd`,
`src/tools/verify_adt_water_load_pipeline_state.gd`,
`docs/modules/adt-water-load-pipeline-state.md`,
`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
- Empty water data or invalid tile root produces no Node and no scene mutation.
- Existing `ADTBuilder.build_tile_water_scene` remains the only geometry/material source.
- A non-empty built Water root is attached exactly once to the supplied tile root.
- Optional editor ownership is assigned recursively only when explicitly enabled
and an edited scene root is available.
- The returned Node is borrowed and remains owned by the tile SceneTree subtree.
## Dependencies
- Requires: accepted water pipeline package on current master `e1b2886`
- Blocks: further liquid finalization/material responsibility extraction
- External state: none; verification uses synthetic liquid dictionaries
## Verification
- Commands: dedicated empty/invalid/build/attach/editor-owner/source/timing
verifier; water pipeline, materials, shutdown, facade, internal-access,
manifest, documentation and coordination gates
- Fixtures: synthetic one-cell liquid mask/heights and temporary Node roots
- Fidelity evidence: exact existing ADTBuilder output and two attachment paths preserved
- Performance budget: 100 empty finalization attempts under 1 second
## Documentation deliverables
- Inline public API docs
- Module specification with inputs/outputs, ownership and source map
- Data-flow, lifecycle/state, main-thread sequence and dependency diagrams
- Water-pipeline, world-renderer, module registry and RENDER status updates
## Simplicity and naming
- Important names introduced: `AdtWaterSceneFinalizer`
- Simplest considered solution: one RefCounted with one explicit attach operation
- Rejected complexity/abstractions: generic scene finalizer, signals, callbacks,
builder interface, queue ownership or alternate liquid renderer
- Unavoidable complexity and justification: editor-persisted generated subtrees
require recursive owner assignment after SceneTree attachment
- Measured optimization evidence: bounded synthetic no-water loop
## Status
- State: accepted
- Done: stateless finalizer implemented; both loader water paths migrated;
synthetic scene/owner regression and required documentation added
- Next: continue liquid parsing/task execution extraction or another remaining M03 boundary
- Blocked by:
<!-- OPENWC_INTEGRATION:ACCEPTED:M03-RND-ADT-WATER-SCENE-FINALIZER-001:874fd0f -->
## Handoff
- Commit: `235be3a`
- Results: scene finalizer PASS (`cases=8`, `iterations=100`,
`elapsed_ms=0.042`; initial `0.024ms`); synthetic liquid produced one
four-vertex/six-index surface with tile attachment and recursive owner; water
pipeline (`119.648ms`), WMO/M2/terrain/facade/scheduler/streaming/coordinate,
materials, shutdown, internal-access `36` and 32 headless contract regressions
PASS; documentation and coordination gates PASS; checkpoint dry-run PASS `7/7`.
- Remaining risks: main-thread mesh/material construction remains synchronous
behind one existing permit; the Dictionary builder boundary remains untyped;
proprietary MH2O/MCLQ traversal/leak/p95/p99/original-client evidence is absent.
A verifier launched concurrently with the initial cold editor import observed
the pre-existing global-class cache race; serialized editor parse and shutdown
rerun passed, with only expected missing private asset diagnostics.
- Documentation updated: inline public API; new
`docs/modules/adt-water-scene-finalizer.md`; water-pipeline spec, module registry,
world renderer and `RENDER.md`; data-flow/state/sequence/dependency diagrams,
ownership, main-thread effects, failure/recovery, migration and source map included.
- Integration: merged as `874fd0f`; post-merge finalizer (`cases=8`,
`iterations=100`, `elapsed_ms=0.025`), water pipeline (`98.398ms`), shutdown,
renderer materials, facade, internal-access (`private_symbols=36`), `7/7`
manifest, documentation and coordination gates passed.
<!-- OPENWC_HANDOFF:READY:M03-RND-ADT-WATER-SCENE-FINALIZER-001:235be3a -->
+1
View File
@@ -16,6 +16,7 @@
| 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) |
| ADT water load pipeline state | Implemented extraction | [`adt-water-load-pipeline-state.md`](adt-water-load-pipeline-state.md) | | ADT water load pipeline state | Implemented extraction | [`adt-water-load-pipeline-state.md`](adt-water-load-pipeline-state.md) |
| ADT water scene finalizer | Implemented extraction | [`adt-water-scene-finalizer.md`](adt-water-scene-finalizer.md) |
| M2 unique placement registry | Implemented extraction | [`m2-unique-placement-registry.md`](m2-unique-placement-registry.md) | | M2 unique placement registry | Implemented extraction | [`m2-unique-placement-registry.md`](m2-unique-placement-registry.md) |
| M2 placement transform resolver | Implemented | [`m2-placement-transform-resolver.md`](m2-placement-transform-resolver.md) | | M2 placement transform resolver | Implemented | [`m2-placement-transform-resolver.md`](m2-placement-transform-resolver.md) |
| M2 placement grouper | Implemented extraction | [`m2-placement-grouper.md`](m2-placement-grouper.md) | | M2 placement grouper | Implemented extraction | [`m2-placement-grouper.md`](m2-placement-grouper.md) |
+17 -12
View File
@@ -38,8 +38,9 @@ flowchart LR
State --> Result[Parsed ADT result] State --> Result[Parsed ADT result]
Result --> Loader Result --> Loader
Loader --> Budget[WATER_FINALIZE permit] Loader --> Budget[WATER_FINALIZE permit]
Loader --> Builder[ADTBuilder water scene] Loader --> Finalizer[AdtWaterSceneFinalizer]
Builder --> Scene[Main-thread tile Node attach] Finalizer --> Builder[ADTBuilder water scene]
Finalizer --> Scene[Main-thread tile Node attach]
``` ```
Allowed dependencies are RefCounted, Dictionary/Array/String and one Godot Allowed dependencies are RefCounted, Dictionary/Array/String and one Godot
@@ -127,7 +128,7 @@ sequenceDiagram
participant State as AdtWaterLoadPipelineState participant State as AdtWaterLoadPipelineState
participant Worker as WorkerThreadPool task participant Worker as WorkerThreadPool task
participant Budget as RenderBudgetScheduler participant Budget as RenderBudgetScheduler
participant Builder as ADTBuilder participant Finalizer as AdtWaterSceneFinalizer
Loader->>State: enqueue_request(tile key, ADT path) Loader->>State: enqueue_request(tile key, ADT path)
Loader->>State: take_next_request() Loader->>State: take_next_request()
Loader->>Worker: add_task(parse binding) Loader->>Worker: add_task(parse binding)
@@ -140,8 +141,8 @@ sequenceDiagram
Loader->>Worker: wait_for_task_completion(task ID) Loader->>Worker: wait_for_task_completion(task ID)
Loader->>State: complete_active_task(key) Loader->>State: complete_active_task(key)
Loader->>Loader: reject stale tile/path/root Loader->>Loader: reject stale tile/path/root
Loader->>Builder: build_tile_water_scene(data, origin) Loader->>Finalizer: attach_water_scene(data, origin, tile root, editor owner)
Loader->>Loader: attach Node and mark water_loaded Loader->>Loader: mark water_loaded
``` ```
## Dependency diagram ## Dependency diagram
@@ -151,10 +152,11 @@ flowchart TB
Loader[StreamingWorldLoader] --> State[AdtWaterLoadPipelineState] Loader[StreamingWorldLoader] --> State[AdtWaterLoadPipelineState]
Loader --> Scheduler[RenderBudgetScheduler] Loader --> Scheduler[RenderBudgetScheduler]
Loader --> Worker[WorkerThreadPool / ADTLoader] Loader --> Worker[WorkerThreadPool / ADTLoader]
Loader --> Builder[ADTBuilder / SceneTree] Loader --> Finalizer[AdtWaterSceneFinalizer]
Finalizer --> Builder[ADTBuilder / SceneTree]
Worker -->|publish only| State Worker -->|publish only| State
State -. no dependency .-> Scheduler State -. no dependency .-> Scheduler
State -. no dependency .-> Builder State -. no dependency .-> Finalizer
``` ```
## Ownership, threading and resources ## Ownership, threading and resources
@@ -163,7 +165,8 @@ flowchart TB
- Worker threads may call only `publish_result`; result observe/pop/clear also lock. - Worker threads may call only `publish_result`; result observe/pop/clear also lock.
- Parsed water Dictionary identity is retained until main-thread pop. - Parsed water Dictionary identity is retained until main-thread pop.
- Loader owns WorkerThreadPool task lifetime and must wait before orderly shutdown clear. - Loader owns WorkerThreadPool task lifetime and must wait before orderly shutdown clear.
- Loader owns tile states, ADTBuilder, water Nodes and every render resource. - Loader owns tile states and the tile subtree; `AdtWaterSceneFinalizer` performs
the main-thread ADTBuilder call and attachment without retaining Nodes.
- `clear` cannot cancel a worker; post-reset publication becomes a stale result - `clear` cannot cancel a worker; post-reset publication becomes a stale result
and is discarded because no active task/tile ownership remains. and is discarded because no active task/tile ownership remains.
@@ -211,8 +214,8 @@ versions are unchanged; no migration or rebuild is required.
## Extension points ## Extension points
- ADT parse execution can later move behind a parser/job adapter without changing state. - ADT parse execution can later move behind a parser/job adapter without changing state.
- Water scene materialization can be extracted separately because it owns Nodes - Water scene materialization is now delegated to `AdtWaterSceneFinalizer`,
and main-thread effects rather than async bookkeeping. independently of this async bookkeeping service.
## Capability status ## Capability status
@@ -220,7 +223,7 @@ versions are unchanged; no migration or rebuild is required.
|---|---|---|---| |---|---|---|---|
| ADT water load pipeline state | Implemented extraction | FIFO/task/thread/source/timing verifier | Asset-backed traversal/leak evidence pending | | ADT water load pipeline state | Implemented extraction | FIFO/task/thread/source/timing verifier | Asset-backed traversal/leak evidence pending |
| ADT liquid parsing | Existing/loader-owned adapter | Native ADTLoader and dry-run paths | MH2O/MCLQ fidelity fixtures pending | | ADT liquid parsing | Existing/loader-owned adapter | Native ADTLoader and dry-run paths | MH2O/MCLQ fidelity fixtures pending |
| Water scene finalization | Existing/loader-owned | Scheduler/material/shutdown regressions | Separate materialization extraction possible | | Water scene finalization | Implemented extraction | Dedicated synthetic scene/ownership verifier | Asset-backed traversal/hitch evidence pending |
## Known gaps and risks ## Known gaps and risks
@@ -235,13 +238,15 @@ versions are unchanged; no migration or rebuild is required.
| Path | Responsibility | | Path | Responsibility |
|---|---| |---|---|
| `src/render/liquid/adt_water_load_pipeline_state.gd` | Request/task/result bookkeeping and result mutex | | `src/render/liquid/adt_water_load_pipeline_state.gd` | Request/task/result bookkeeping and result mutex |
| `src/scenes/streaming/streaming_world_loader.gd` | Parse task execution, permits, stale checks and Node finalization | | `src/render/liquid/adt_water_scene_finalizer.gd` | Main-thread ADTBuilder call, attach and optional editor ownership |
| `src/scenes/streaming/streaming_world_loader.gd` | Parse task execution, permits, stale checks and tile loaded timing |
| `addons/mpq_extractor/loaders/adt_builder.gd` | ADT liquid geometry/material construction | | `addons/mpq_extractor/loaders/adt_builder.gd` | ADT liquid geometry/material construction |
| `src/tools/verify_adt_water_load_pipeline_state.gd` | Lifecycle/thread/boundary/timing regression | | `src/tools/verify_adt_water_load_pipeline_state.gd` | Lifecycle/thread/boundary/timing regression |
## Related decisions and references ## Related decisions and references
- [`world-renderer.md`](world-renderer.md) - [`world-renderer.md`](world-renderer.md)
- [`adt-water-scene-finalizer.md`](adt-water-scene-finalizer.md)
- [`render-budget-scheduler.md`](render-budget-scheduler.md) - [`render-budget-scheduler.md`](render-budget-scheduler.md)
- [`../../RENDER.md`](../../RENDER.md) - [`../../RENDER.md`](../../RENDER.md)
- [`../../targets/roadmap/02-rendering-and-graphics.md`](../../targets/roadmap/02-rendering-and-graphics.md) - [`../../targets/roadmap/02-rendering-and-graphics.md`](../../targets/roadmap/02-rendering-and-graphics.md)
+232
View File
@@ -0,0 +1,232 @@
# ADT Water Scene Finalizer
## Metadata
| Field | Value |
|---|---|
| Status | Implemented extraction |
| Target/work package | M03 / `M03-RND-ADT-WATER-SCENE-FINALIZER-001` |
| Owners | Main-thread ADT water subtree build, attach and optional editor ownership |
| Last verified | Worktree `work/sindo-main-codex/m03-adt-water-scene-finalizer`, 2026-07-17 |
| Profiles/capabilities | Existing ADT MH2O/MCLQ scene output; profile-independent adapter |
## Purpose
Provide one liquid-specific main-thread boundary for converting parsed ADT
water data through the existing `ADTBuilder`, attaching the resulting `Water`
subtree to a caller-owned tile root and assigning optional persisted Editor
ownership. The service is stateless and retains no Node or Resource.
## Non-goals
- Parse or validate MH2O/MCLQ binary structures.
- Replace ADTBuilder geometry, material or coordinate calculations.
- Own water request/task/result state, permits or concurrency.
- Validate tile/path/root freshness beyond receiving a valid root.
- Mark `water_loaded`, enable water or remove water subtrees.
## Context and boundaries
```mermaid
flowchart LR
Parse[Parsed ADT Dictionary] --> Loader[StreamingWorldLoader]
Loader -->|validated data, origin, tile root| Finalizer[AdtWaterSceneFinalizer]
Finalizer --> Builder[ADTBuilder]
Builder --> Water[Water Node3D or null]
Water --> Finalizer
Finalizer -->|main-thread add_child| Tile[Caller-owned tile root]
Loader -->|after return| Loaded[water_loaded timing]
```
Allowed dependencies are `ADTBuilder` and Godot Node/mesh types needed to adopt
its output. WorkerThreadPool, scheduler, tile-state dictionaries, facade,
gameplay, network and editor plugin packages are forbidden.
## Public API
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|---|---|---|---|---|
| `attach_water_scene(water_data, tile_origin_godot_units, tile_root, editor_owner)` | Command/query | Build and attach one existing-format water subtree | Renderer main thread; returned Node is borrowed for tile lifetime | Empty data, invalid root or dry build returns null without attachment |
`editor_owner` is optional. When supplied it must be an ancestor suitable for
Godot `Node.owner`; ownership is recursively assigned to the generated subtree.
## Inputs and outputs
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|---|---|---|---|---|---|
| Input | Parsed ADT water-containing Dictionary | ADTLoader/raw tile path | Finalizer/ADTBuilder | Caller retains Dictionary; not mutated | One main-thread call |
| Input | Tile origin in Godot world units | Loader tile state/ADTBuilder | Finalizer | Value copy | One call |
| Input | Valid tile root | StreamingWorldLoader | Finalizer | Caller/SceneTree-owned | Must outlive call and attached subtree |
| Input | Optional edited scene root | Loader Editor policy | Finalizer | SceneTree-owned borrowed Node | One call |
| Output | Attached `Water` Node3D or null | Finalizer | Loader/SceneTree | Tile root owns attached Node | Tile subtree lifetime |
Side effects are limited to ADTBuilder mesh/material creation, `add_child` and
optional recursive `Node.owner` assignment. No cache, filesystem, task or tile
state is mutated.
## Data flow
```mermaid
flowchart TD
Input[water data + Godot origin + tile root] --> Guard{Data and root valid?}
Guard -->|no| None[Return null; unchanged]
Guard -->|yes| Build[ADTBuilder.build_tile_water_scene]
Build --> Dry{Water root exists?}
Dry -->|no| None
Dry -->|yes| Attach[tile root add_child]
Attach --> Owner{Editor owner supplied?}
Owner -->|yes| Assign[Assign owner recursively]
Owner -->|no| Return[Return borrowed root]
Assign --> Return
```
## Lifecycle/state
The service has no retained lifecycle. Each call moves one temporary builder
result through the following call-local states.
```mermaid
stateDiagram-v2
[*] --> Validating
Validating --> Rejected: empty data or invalid root
Validating --> Building: valid input
Building --> Dry: no geometry
Building --> Attached: Water root built
Attached --> EditorOwned: owner supplied
Attached --> Complete: no owner
EditorOwned --> Complete
Rejected --> [*]
Dry --> [*]
Complete --> [*]
```
## Main sequence
```mermaid
sequenceDiagram
participant Loader as StreamingWorldLoader main thread
participant Finalizer as AdtWaterSceneFinalizer
participant Builder as ADTBuilder
participant Tile as Tile root
Loader->>Finalizer: attach_water_scene(data, origin, tile, editor owner)
Finalizer->>Builder: build_tile_water_scene(data, origin)
Builder-->>Finalizer: Water root or null
alt Water root exists
Finalizer->>Tile: add_child(Water)
opt editor owner supplied
Finalizer->>Finalizer: assign owner recursively
end
Finalizer-->>Loader: borrowed Water root
else dry/invalid
Finalizer-->>Loader: null
end
Loader->>Loader: preserve existing water_loaded timing
```
## Dependency diagram
```mermaid
flowchart TB
Loader[StreamingWorldLoader] --> Finalizer[AdtWaterSceneFinalizer]
Finalizer --> Builder[ADTBuilder]
Builder --> Material[WowLiquidMaterial]
Finalizer --> Scene[Godot SceneTree]
Pipeline[AdtWaterLoadPipelineState] --> Loader
Scheduler[RenderBudgetScheduler] --> Loader
Finalizer -. no dependency .-> Pipeline
Finalizer -. no dependency .-> Scheduler
```
## Ownership, threading and resources
- The method is main-thread only because it creates Mesh/Material/Node objects
and mutates the SceneTree.
- `ADTBuilder` owns temporary construction. It frees an empty `Water` root and
returns null for dry/invalid liquid geometry.
- After `add_child`, the caller's tile subtree owns the returned Water root and
all MeshInstance children; the finalizer retains no references.
- Optional editor ownership affects serialization ownership only and does not
replace SceneTree lifetime ownership.
- Parsed Dictionaries are borrowed and not mutated.
## Errors, cancellation and recovery
| Failure | Detection | Behavior | Diagnostic | Recovery |
|---|---|---|---|---|
| Empty parsed data | Input guard | Return null, no builder call | Contract verifier | Loader preserves existing loaded timing |
| Missing/freed tile root | Input guard | Return null, no scene mutation | Contract verifier | Stale-result validation normally rejects earlier |
| Dry or malformed liquid arrays | ADTBuilder returns null | No attachment | Builder/shutdown regression | Reload after corrected source |
| Tile/path stale | Loader before call | Finalizer is not invoked | Existing loader behavior | Current tile may enqueue again |
| Shutdown/cancellation | Loader drains/cancels before call | No retained finalizer work | Shutdown verifier | New loader starts stateless |
## Configuration and capabilities
| Setting/capability | Default | Profile | Runtime mutable | Effect |
|---|---|---|---|---|
| `enable_water` | Loader setting | Quality/custom | Yes | Gates both caller paths; not owned here |
| `water_finalize_ops_per_tick` | `1` | Quality/custom | Yes | Budgets async calls before finalizer |
| `editor_persist_generated_nodes` | `false` | Editor only | Yes | Supplies or omits editor owner |
## Persistence, cache and migration
The service writes no files and changes no cache/resource schema. Existing ADT,
terrain cache and liquid material versions remain valid; no rebuild or migration
is required. Persisted Editor ownership follows the loader's existing opt-in.
## Diagnostics and observability
- Logs: none; existing loader/builder diagnostics remain authoritative.
- Metrics: no independent counters; existing water queue/finalize metrics remain.
- Debug views: generated `Water/Liquid_<id>` names remain unchanged.
- Correlation IDs: tile/path correlation stays in loader pipeline state.
## Verification
- `verify_adt_water_scene_finalizer.gd`: empty/invalid/dry guards, one-cell
geometry, attachment, recursive owner, loader delegation and bounded timing.
- `verify_adt_water_load_pipeline_state.gd`: adjacent async ownership and loaded timing.
- Renderer material and shutdown verifiers cover material construction and dry-root cleanup.
- Fidelity evidence is the unchanged ADTBuilder implementation and unchanged two
loader call positions; no new original-client liquid parity claim is made.
- Performance budget: 100 empty calls under one second.
## Extension points
- A future liquid renderer may replace the internal builder only after paired
MH2O/MCLQ fixtures establish equivalent geometry/material behavior.
- Removal/eviction remains ordinary tile subtree ownership and needs no parallel API.
## Capability status
| Capability | Status | Evidence | Gap/next step |
|---|---|---|---|
| ADT water scene finalization seam | Implemented extraction | Synthetic geometry/attach/owner/source verifier | Asset-backed traversal and hitch metrics pending |
| ADT liquid geometry/materials | Existing implementation | ADTBuilder/material/checkpoint evidence | MH2O/MCLQ and original-client parity incomplete |
| Async water scheduling | Existing extracted state | Water pipeline verifier | Active worker interruption remains loader-owned |
## Known gaps and risks
- The service intentionally wraps the existing Dictionary-based ADTBuilder boundary.
- It does not verify that `editor_owner` is an ancestor; the loader supplies the
edited scene root under the existing policy.
- Mesh/material creation remains synchronous main-thread work behind the existing permit.
- No proprietary liquid corpus, long traversal leak run, p95/p99 or paired capture is included.
## Source map
| Path | Responsibility |
|---|---|
| `src/render/liquid/adt_water_scene_finalizer.gd` | Main-thread build, attach and optional recursive editor ownership |
| `addons/mpq_extractor/loaders/adt_builder.gd` | Existing ADT liquid geometry/material creation and dry-root cleanup |
| `src/render/liquid/adt_water_load_pipeline_state.gd` | Pending/task/result state before finalization |
| `src/scenes/streaming/streaming_world_loader.gd` | Permits, stale checks, tile state and finalizer calls |
| `src/tools/verify_adt_water_scene_finalizer.gd` | Synthetic scene/ownership/boundary/timing regression |
## Related decisions and references
- [`adt-water-load-pipeline-state.md`](adt-water-load-pipeline-state.md)
- [`world-renderer.md`](world-renderer.md)
- [`render-budget-scheduler.md`](render-budget-scheduler.md)
- [`../../RENDER.md`](../../RENDER.md)
- [`../../targets/roadmap/02-rendering-and-graphics.md`](../../targets/roadmap/02-rendering-and-graphics.md)
+7 -2
View File
@@ -7,7 +7,7 @@
| 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 packages; M03 M2 packages; M03 WMO placement package | | 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 M2 packages; M03 WMO placement package |
| Owners | Renderer workstream / milestone integrator | | Owners | Renderer workstream / milestone integrator |
| Last verified | Worktree `work/sindo-main-codex/m03-adt-water-load-pipeline`, 2026-07-17 | | Last verified | Worktree `work/sindo-main-codex/m03-adt-water-scene-finalizer`, 2026-07-17 |
| Profiles/capabilities | `Performance`, `Balanced`, `High`, `Custom`; Blizzlike fidelity incomplete | | Profiles/capabilities | `Performance`, `Balanced`, `High`, `Custom`; Blizzlike fidelity incomplete |
## Purpose ## Purpose
@@ -139,6 +139,7 @@ from externally reading/writing loader-private queue, task, cache and tile-state
| `WmoRenderResourceCacheState` | Internal WMO cache-state service | Owns validated Resources, negative entries and pending cache paths | Renderer main thread; map/cache session | Invalid/occupied request and unknown completion are rejected | | `WmoRenderResourceCacheState` | Internal WMO cache-state service | Owns validated Resources, negative entries and pending cache paths | Renderer main thread; map/cache session | Invalid/occupied request and unknown completion are rejected |
| `WmoSceneResourceCacheState` | Internal WMO cache-state service | Owns validated PackedScenes, negative entries and pending `.tscn` paths | Renderer main thread; map/cache session | Direct missing and terminal request transitions remain distinct | | `WmoSceneResourceCacheState` | Internal WMO cache-state service | Owns validated PackedScenes, negative entries and pending `.tscn` paths | Renderer main thread; map/cache session | Direct missing and terminal request transitions remain distinct |
| `AdtWaterLoadPipelineState` | Internal liquid async-state service | Owns ADT water FIFO/dedupe, active task IDs and mutex result mailbox | Main-thread state; worker result publication | Invalid/duplicate requests rejected; clear does not interrupt workers | | `AdtWaterLoadPipelineState` | Internal liquid async-state service | Owns ADT water FIFO/dedupe, active task IDs and mutex result mailbox | Main-thread state; worker result publication | Invalid/duplicate requests rejected; clear does not interrupt workers |
| `AdtWaterSceneFinalizer.attach_water_scene` | Internal liquid main-thread service | Builds and attaches one existing-format ADT Water subtree | Main thread; stateless, returned Node tile-owned | Empty/invalid/dry input returns null without attachment |
Публичным 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
@@ -361,7 +362,9 @@ sequenceDiagram
- `AdtWaterLoadPipelineState` owns pending request order/deduplication, opaque - `AdtWaterLoadPipelineState` owns pending request order/deduplication, opaque
active task IDs and the worker-safe parsed-result mailbox. The loader retains active task IDs and the worker-safe parsed-result mailbox. The loader retains
WorkerThreadPool start/wait, ADTLoader parsing, concurrency/finalize permits, WorkerThreadPool start/wait, ADTLoader parsing, concurrency/finalize permits,
stale tile/path/root checks and ADTBuilder/Node finalization. stale tile/path/root checks and `water_loaded` timing. `AdtWaterSceneFinalizer`
performs the existing ADTBuilder call, tile attach and optional recursive
Editor ownership on the main thread without retaining the generated subtree.
- 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
@@ -561,6 +564,7 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
| `src/render/terrain/rendered_ground_sample.gd` | Immutable renderer-owned available/unavailable ground query result | | `src/render/terrain/rendered_ground_sample.gd` | Immutable renderer-owned available/unavailable ground query result |
| `src/render/terrain/terrain_quality_mesh_cache.gd` | Full-quality terrain revisit Mesh retention and LRU ownership | | `src/render/terrain/terrain_quality_mesh_cache.gd` | Full-quality terrain revisit Mesh retention and LRU ownership |
| `src/render/liquid/adt_water_load_pipeline_state.gd` | ADT water request/task/result bookkeeping and worker-safe mailbox | | `src/render/liquid/adt_water_load_pipeline_state.gd` | ADT water request/task/result bookkeeping and worker-safe mailbox |
| `src/render/liquid/adt_water_scene_finalizer.gd` | Main-thread ADT water build, tile attachment and optional editor ownership |
| `src/render/streaming/streaming_target_planner.gd` | Scene-free wanted/retained ADT target calculation | | `src/render/streaming/streaming_target_planner.gd` | Scene-free wanted/retained ADT target calculation |
| `src/render/streaming/streaming_target_policy.gd` | Immutable renderer radius/prefetch policy | | `src/render/streaming/streaming_target_policy.gd` | Immutable renderer radius/prefetch policy |
| `src/render/streaming/streaming_target_plan.gd` | Immutable planner result with read-only tile-key sets | | `src/render/streaming/streaming_target_plan.gd` | Immutable planner result with read-only tile-key sets |
@@ -574,6 +578,7 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
| `src/tools/verify_rendered_ground_sample.gd` | Cold-start renderer ground result invariant regression | | `src/tools/verify_rendered_ground_sample.gd` | Cold-start renderer ground result invariant regression |
| `src/tools/verify_terrain_quality_mesh_cache.gd` | Terrain cache admission, LRU, ownership and loader-boundary regression | | `src/tools/verify_terrain_quality_mesh_cache.gd` | Terrain cache admission, LRU, ownership and loader-boundary regression |
| `src/tools/verify_adt_water_load_pipeline_state.gd` | ADT water FIFO/task/thread/boundary/timing regression | | `src/tools/verify_adt_water_load_pipeline_state.gd` | ADT water FIFO/task/thread/boundary/timing regression |
| `src/tools/verify_adt_water_scene_finalizer.gd` | Synthetic ADT water scene/ownership/boundary/timing regression |
| `src/tools/verify_streaming_target_planner.gd` | Planner behavior, dependency and bounded timing regression | | `src/tools/verify_streaming_target_planner.gd` | Planner behavior, dependency and bounded timing regression |
| `src/tools/verify_render_budget_scheduler.gd` | Scheduler bounds, shared-lane priority, cancellation and timing regression | | `src/tools/verify_render_budget_scheduler.gd` | Scheduler bounds, shared-lane priority, cancellation and timing regression |
| `src/tools/verify_renderer_internal_access.gd` | Gameplay/EditorPlugin/registered renderer-tool boundary gate derived from private streamer fields | | `src/tools/verify_renderer_internal_access.gd` | Gameplay/EditorPlugin/registered renderer-tool boundary gate derived from private streamer fields |
@@ -0,0 +1,40 @@
class_name AdtWaterSceneFinalizer
extends RefCounted
## Builds and attaches one ADT water subtree on the renderer main thread. The
## supplied tile root owns the returned subtree; this service retains no state.
const ADT_BUILDER_SCRIPT := preload("res://addons/mpq_extractor/loaders/adt_builder.gd")
## Builds water from parsed ADT data and attaches it once to [param tile_root].
## [param tile_origin_godot_units] is the existing Godot-world tile origin used
## by [ADTBuilder]. When [param editor_owner] is non-null and is an ancestor,
## ownership is assigned recursively for persisted Editor previews. Returns the
## attached borrowed root, or null for empty/invalid input or dry ADT data.
## This method mutates the SceneTree and must run on the main thread.
func attach_water_scene(
water_data: Dictionary,
tile_origin_godot_units: Vector3,
tile_root: Node,
editor_owner: Node = null) -> Node3D:
if water_data.is_empty() or tile_root == null or not is_instance_valid(tile_root):
return null
var water_root: Node3D = ADT_BUILDER_SCRIPT.build_tile_water_scene(
water_data,
tile_origin_godot_units
)
if water_root == null:
return null
tile_root.add_child(water_root)
if editor_owner != null and is_instance_valid(editor_owner):
_assign_owner_recursive(water_root, editor_owner)
return water_root
func _assign_owner_recursive(generated_node: Node, editor_owner: Node) -> void:
generated_node.owner = editor_owner
for child in generated_node.get_children():
_assign_owner_recursive(child, editor_owner)
@@ -0,0 +1 @@
uid://ck4sdc8a3kg00
+23 -14
View File
@@ -45,6 +45,9 @@ const TERRAIN_CHUNK_GEOMETRY_QUEUE_PLANNER_SCRIPT := preload(
const ADT_WATER_LOAD_PIPELINE_STATE_SCRIPT := preload( const ADT_WATER_LOAD_PIPELINE_STATE_SCRIPT := preload(
"res://src/render/liquid/adt_water_load_pipeline_state.gd" "res://src/render/liquid/adt_water_load_pipeline_state.gd"
) )
const ADT_WATER_SCENE_FINALIZER_SCRIPT := preload(
"res://src/render/liquid/adt_water_scene_finalizer.gd"
)
const M2_UNIQUE_PLACEMENT_REGISTRY_SCRIPT := preload( const M2_UNIQUE_PLACEMENT_REGISTRY_SCRIPT := preload(
"res://src/render/m2/m2_unique_placement_registry.gd" "res://src/render/m2/m2_unique_placement_registry.gd"
) )
@@ -260,6 +263,7 @@ var _terrain_splat_tasks: Dictionary = {}
var _terrain_splat_result_mutex := Mutex.new() var _terrain_splat_result_mutex := Mutex.new()
var _terrain_splat_result_queue: Array = [] var _terrain_splat_result_queue: Array = []
var _adt_water_load_pipeline_state := ADT_WATER_LOAD_PIPELINE_STATE_SCRIPT.new() var _adt_water_load_pipeline_state := ADT_WATER_LOAD_PIPELINE_STATE_SCRIPT.new()
var _adt_water_scene_finalizer := ADT_WATER_SCENE_FINALIZER_SCRIPT.new()
var _tile_result_mutex := Mutex.new() var _tile_result_mutex := Mutex.new()
var _tile_result_queue: Array = [] var _tile_result_queue: Array = []
var _shared_tex_cache: Dictionary = {} var _shared_tex_cache: Dictionary = {}
@@ -2294,10 +2298,12 @@ func _drain_water_load_results() -> void:
var data: Dictionary = result.get("data", {}) var data: Dictionary = result.get("data", {})
if not data.is_empty(): if not data.is_empty():
var origin: Vector3 = state.get("origin", Vector3.ZERO) var origin: Vector3 = state.get("origin", Vector3.ZERO)
var water_root: Node3D = _builder.build_tile_water_scene(data, origin) _adt_water_scene_finalizer.attach_water_scene(
if water_root: data,
tile_root.add_child(water_root) origin,
_set_editor_owner_recursive(water_root) tile_root,
_editor_owner_for_generated_nodes()
)
state["water_loaded"] = true state["water_loaded"] = true
_tile_states[key] = state _tile_states[key] = state
@@ -2448,10 +2454,12 @@ func _finalize_loaded_tile(request: Dictionary, data: Dictionary) -> void:
tile_root.position = tile_origin tile_root.position = tile_origin
if enable_water: if enable_water:
var water_root: Node3D = _builder.build_tile_water_scene(data, tile_origin) _adt_water_scene_finalizer.attach_water_scene(
if water_root: data,
tile_root.add_child(water_root) tile_origin,
_set_editor_owner_recursive(water_root) tile_root,
_editor_owner_for_generated_nodes()
)
if enable_m2_placeholders: if enable_m2_placeholders:
_build_tile_m2_placeholders( _build_tile_m2_placeholders(
tile_root, tile_root,
@@ -5421,12 +5429,7 @@ func _tile_key(tx: int, ty: int) -> String:
func _set_editor_owner_recursive(node: Node) -> void: func _set_editor_owner_recursive(node: Node) -> void:
if not Engine.is_editor_hint(): var edited_root := _editor_owner_for_generated_nodes()
return
if not editor_persist_generated_nodes:
return
var edited_root := get_tree().edited_scene_root
if edited_root == null: if edited_root == null:
return return
@@ -5435,6 +5438,12 @@ func _set_editor_owner_recursive(node: Node) -> void:
_set_editor_owner_recursive(child) _set_editor_owner_recursive(child)
func _editor_owner_for_generated_nodes() -> Node:
if not Engine.is_editor_hint() or not editor_persist_generated_nodes:
return null
return get_tree().edited_scene_root
func _make_editor_signature() -> String: func _make_editor_signature() -> String:
var values := [ var values := [
extracted_dir, extracted_dir,
@@ -241,8 +241,7 @@ func _verify_ownership_boundaries(failures: Array[String]) -> void:
"ClassDB.instantiate(\"ADTLoader\")", "ClassDB.instantiate(\"ADTLoader\")",
"WorkerThreadPool.add_task(_load_tile_water_task.bind(key, path))", "WorkerThreadPool.add_task(_load_tile_water_task.bind(key, path))",
"RENDER_BUDGET_SCHEDULER_SCRIPT.WATER_FINALIZE", "RENDER_BUDGET_SCHEDULER_SCRIPT.WATER_FINALIZE",
"_builder.build_tile_water_scene(data, origin)", "_adt_water_scene_finalizer.attach_water_scene(",
"tile_root.add_child(water_root)",
"state[\"water_loaded\"] = true", "state[\"water_loaded\"] = true",
]: ]:
_expect_true(loader_source.contains(retained_loader_rule), "loader retains %s" % retained_loader_rule, failures) _expect_true(loader_source.contains(retained_loader_rule), "loader retains %s" % retained_loader_rule, failures)
@@ -0,0 +1,194 @@
extends SceneTree
## Synthetic main-thread scene, ownership, boundary and timing regression for
## ADT water finalization.
const FINALIZER_SCRIPT := preload("res://src/render/liquid/adt_water_scene_finalizer.gd")
const FINALIZER_PATH := "res://src/render/liquid/adt_water_scene_finalizer.gd"
const LOADER_PATH := "res://src/scenes/streaming/streaming_world_loader.gd"
func _initialize() -> void:
var failures: Array[String] = []
_verify_invalid_and_dry_input(failures)
_verify_build_attach_geometry_and_owner(failures)
_verify_ownership_boundaries(failures)
var elapsed_milliseconds := _verify_bounded_timing(failures)
if not failures.is_empty():
for failure in failures:
push_error("ADT_WATER_SCENE_FINALIZER: %s" % failure)
quit(1)
return
print(
"ADT_WATER_SCENE_FINALIZER PASS cases=8 iterations=100 elapsed_ms=%.3f"
% elapsed_milliseconds
)
quit(0)
func _verify_invalid_and_dry_input(failures: Array[String]) -> void:
var finalizer: RefCounted = FINALIZER_SCRIPT.new()
var tile_root := Node3D.new()
_expect_null(
finalizer.call("attach_water_scene", {}, Vector3.ZERO, tile_root, null),
"empty water data returns null",
failures
)
_expect_equal(tile_root.get_child_count(), 0, "empty data does not mutate tile", failures)
_expect_null(
finalizer.call("attach_water_scene", _synthetic_water_data(), Vector3.ZERO, null, null),
"missing tile root returns null",
failures
)
_expect_null(
finalizer.call(
"attach_water_scene",
{"chunks": [{"origin": Vector3.ZERO, "liquids": []}]},
Vector3.ZERO,
tile_root,
null
),
"dry parsed ADT returns null",
failures
)
_expect_equal(tile_root.get_child_count(), 0, "dry data does not mutate tile", failures)
tile_root.free()
func _verify_build_attach_geometry_and_owner(failures: Array[String]) -> void:
var finalizer: RefCounted = FINALIZER_SCRIPT.new()
var editor_scene_root := Node3D.new()
var tile_root := Node3D.new()
get_root().add_child(editor_scene_root)
editor_scene_root.add_child(tile_root)
var tile_origin := Vector3(10.0, 20.0, 30.0)
var water_root: Node3D = finalizer.call(
"attach_water_scene",
_synthetic_water_data(tile_origin),
tile_origin,
tile_root,
editor_scene_root
)
_expect_not_null(water_root, "synthetic liquid builds a water root", failures)
if water_root != null:
_expect_true(water_root.get_parent() == tile_root, "water root attached once", failures)
_expect_equal(tile_root.get_child_count(), 1, "tile owns one water root", failures)
_expect_true(water_root.owner == editor_scene_root, "water root editor owner", failures)
_expect_equal(water_root.get_child_count(), 1, "one liquid surface grouped", failures)
if water_root.get_child_count() == 1:
var liquid_mesh_instance := water_root.get_child(0) as MeshInstance3D
_expect_not_null(liquid_mesh_instance, "liquid child is MeshInstance3D", failures)
if liquid_mesh_instance != null:
_expect_true(
liquid_mesh_instance.owner == editor_scene_root,
"liquid child editor owner",
failures
)
var liquid_mesh := liquid_mesh_instance.mesh as ArrayMesh
_expect_not_null(liquid_mesh, "liquid surface has ArrayMesh", failures)
if liquid_mesh != null:
_expect_equal(
liquid_mesh.surface_get_array_len(0),
4,
"one enabled liquid cell has four vertices",
failures
)
_expect_equal(
liquid_mesh.surface_get_array_index_len(0),
6,
"one enabled liquid cell has six indices",
failures
)
editor_scene_root.free()
func _verify_ownership_boundaries(failures: Array[String]) -> void:
var finalizer_source := FileAccess.get_file_as_string(FINALIZER_PATH)
var loader_source := FileAccess.get_file_as_string(LOADER_PATH)
_expect_true(
finalizer_source.contains("ADT_BUILDER_SCRIPT.build_tile_water_scene"),
"finalizer retains existing ADTBuilder implementation",
failures
)
_expect_true(
finalizer_source.contains("tile_root.add_child(water_root)"),
"finalizer owns water subtree attachment",
failures
)
_expect_equal(
loader_source.count("_adt_water_scene_finalizer.attach_water_scene("),
2,
"both loader water paths delegate finalization",
failures
)
_expect_false(
loader_source.contains("_builder.build_tile_water_scene"),
"loader no longer builds water scenes directly",
failures
)
_expect_true(
loader_source.contains("state[\"water_loaded\"] = true"),
"loader retains water-loaded timing",
failures
)
_expect_true(
loader_source.contains("WATER_FINALIZE"),
"loader retains finalization permit ownership",
failures
)
func _verify_bounded_timing(failures: Array[String]) -> float:
var finalizer: RefCounted = FINALIZER_SCRIPT.new()
var tile_root := Node3D.new()
var started_microseconds := Time.get_ticks_usec()
for _iteration in range(100):
finalizer.call("attach_water_scene", {}, Vector3.ZERO, tile_root, null)
var elapsed_milliseconds := float(Time.get_ticks_usec() - started_microseconds) / 1000.0
_expect_true(elapsed_milliseconds < 1000.0, "100 empty attempts under 1 second", failures)
_expect_equal(tile_root.get_child_count(), 0, "timing loop creates no nodes", failures)
tile_root.free()
return elapsed_milliseconds
func _synthetic_water_data(tile_origin: Vector3 = Vector3.ZERO) -> Dictionary:
var liquid_mask := PackedByteArray()
liquid_mask.resize(64)
liquid_mask.fill(0)
liquid_mask[0] = 1
var liquid_heights := PackedFloat32Array()
liquid_heights.resize(81)
liquid_heights.fill(tile_origin.y + 5.0)
return {
"chunks": [{
"origin": tile_origin,
"liquids": [{
"liquid_id": 7,
"mask": liquid_mask,
"heights": liquid_heights,
}],
}],
}
func _expect_true(condition: bool, label: String, failures: Array[String]) -> void:
if not condition:
failures.append(label)
func _expect_false(condition: bool, label: String, failures: Array[String]) -> void:
_expect_true(not condition, label, failures)
func _expect_null(value: Variant, label: String, failures: Array[String]) -> void:
_expect_true(value == null, label, failures)
func _expect_not_null(value: Variant, label: String, failures: Array[String]) -> void:
_expect_true(value != null, label, failures)
func _expect_equal(actual: int, expected: int, label: String, failures: Array[String]) -> void:
if actual != expected:
failures.append("%s expected=%d actual=%d" % [label, expected, actual])
@@ -0,0 +1 @@
uid://c1gqfpxg7ajsh
+20 -5
View File
@@ -27,7 +27,7 @@ Runtime и Editor используют facade; planner/scheduler тестиру
## Evidence ## Evidence
- Date: 2026-07-17 - Date: 2026-07-17
- Revision/worktree: master merges `7ece2ab`, `7e35de7`, `80cb084`, `d6e5b53`, `c69abd6`, `606770c`, `630a0c1`, `f36fabb`, `24aef13`, `2342430`, `396be5e`, `f88bf97`, `3f84717`, `d2cb52f`, `f9e5a4f`, `c06aed5`, `a878e7c`, `60603e1`, `b4955d6`, `ada0fd9`, `f470775`; - Revision/worktree: master merges `7ece2ab`, `7e35de7`, `80cb084`, `d6e5b53`, `c69abd6`, `606770c`, `630a0c1`, `f36fabb`, `24aef13`, `2342430`, `396be5e`, `f88bf97`, `3f84717`, `d2cb52f`, `f9e5a4f`, `c06aed5`, `a878e7c`, `60603e1`, `b4955d6`, `ada0fd9`, `f470775`, `874fd0f`;
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`,
@@ -42,7 +42,8 @@ Runtime и Editor используют facade; planner/scheduler тестиру
`M03-RND-WMO-RENDER-BUILD-QUEUE-001`, `M03-RND-WMO-RENDER-BUILD-QUEUE-001`,
`M03-RND-WMO-RENDER-RESOURCE-CACHE-001`, `M03-RND-WMO-RENDER-RESOURCE-CACHE-001`,
`M03-RND-WMO-SCENE-RESOURCE-CACHE-001`, `M03-RND-WMO-SCENE-RESOURCE-CACHE-001`,
`M03-RND-ADT-WATER-LOAD-PIPELINE-001` `M03-RND-ADT-WATER-LOAD-PIPELINE-001`,
`M03-RND-ADT-WATER-SCENE-FINALIZER-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
@@ -117,6 +118,14 @@ Runtime и Editor используют facade; planner/scheduler тестиру
checkpoint dry-run retained `7/7` plans. Post-merge water state (`86.935ms`), checkpoint dry-run retained `7/7` plans. Post-merge water state (`86.935ms`),
shutdown, renderer materials, facade, internal-access `36`, `7/7` manifest, shutdown, renderer materials, facade, internal-access `36`, `7/7` manifest,
documentation and coordination gates remained green. documentation and coordination gates remained green.
ADT water scene finalizer passed `cases=8 iterations=100 elapsed_ms=0.042`
after an initial `0.024ms` run; synthetic data produced one four-vertex/
six-index liquid surface with exact tile attachment and recursive editor owner.
Water pipeline, WMO/M2/terrain/facade/scheduler/streaming/coordinate, material,
shutdown and 32 headless contract regressions passed; checkpoint dry-run
retained `7/7` plans. Post-merge finalizer (`0.025ms`), water pipeline
(`98.398ms`), shutdown, materials, facade, internal-access `36`, `7/7`
manifest, documentation and coordination gates remained green.
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
@@ -172,7 +181,11 @@ Runtime и Editor используют facade; planner/scheduler тестиру
result publication order are unchanged, and the mutex remains limited to the result publication order are unchanged, and the mutex remains limited to the
worker/main-thread mailbox. The loader still owns ADT parsing, concurrency and worker/main-thread mailbox. The loader still owns ADT parsing, concurrency and
profile limits, stale tile/path/root rejection, `WATER_FINALIZE` permits, profile limits, stale tile/path/root rejection, `WATER_FINALIZE` permits,
water Node construction/attachment and `water_loaded` timing. tile subtree lifetime and `water_loaded` timing.
Both raw-tile and delayed cached-tile water paths now call the same stateless
finalizer. It invokes the unchanged `ADTBuilder.build_tile_water_scene`,
attaches the returned root once and applies the existing optional recursive
Editor ownership; tile subtree lifetime and dry-root cleanup are unchanged.
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;
@@ -196,6 +209,7 @@ Runtime и Editor используют facade; planner/scheduler тестиру
validation/reset adapters, validation/reset adapters,
ADT water pending/task/result pipeline state and loader enqueue/start/publish/ ADT water pending/task/result pipeline state and loader enqueue/start/publish/
drain/release/reset/shutdown/metrics adapters, drain/release/reset/shutdown/metrics adapters,
stateless ADT water scene finalizer and both loader build/attach adapters,
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: worker concurrency and stale-result validation remain - Remaining risks: worker concurrency and stale-result validation remain
streamer-owned; cancellation stops new permits but does not interrupt streamer-owned; cancellation stops new permits but does not interrupt
@@ -213,7 +227,8 @@ Runtime и Editor используют facade; planner/scheduler тестиру
WMO ResourceLoader/FileAccess I/O, live fallback and group materialization WMO ResourceLoader/FileAccess I/O, live fallback and group materialization
remain in the loader; remain in the loader;
asset-backed WMO placement/portal/material/leak/p95/p99 evidence remains pending; asset-backed WMO placement/portal/material/leak/p95/p99 evidence remains pending;
ADT water parsing, main-thread materialization and active worker interruption ADT water parsing and active worker interruption remain loader-owned;
remain loader-owned, and asset-backed MH2O/MCLQ traversal/leak/p95/p99/ materialization is separated but remains synchronous main-thread work behind
the existing permit, and asset-backed MH2O/MCLQ traversal/leak/p95/p99/
original-client evidence is pending; M03 still needs further terrain/M2/WMO/ original-client evidence is pending; M03 still needs further terrain/M2/WMO/
liquid service extraction and dependency/performance acceptance. liquid service extraction and dependency/performance acceptance.