# 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_` 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)