Files
open-wc/docs/modules/adt-water-scene-finalizer.md
T

10 KiB

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

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

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.

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

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

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