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