# ADT Water Load Pipeline State

## Metadata

| Field | Value |
|---|---|
| Status | Implemented |
| Target/work package | M03 / `M03-RND-ADT-WATER-LOAD-PIPELINE-001` |
| Owners | ADT water pending FIFO/dedupe, active task IDs and worker-safe result mailbox |
| Last verified | Worktree `work/sindo-main-codex/m03-adt-water-load-pipeline`, 2026-07-17 |
| Profiles/capabilities | ADT MH2O/MCLQ water loading; profile-independent state |

## Purpose

Own the cross-frame and cross-thread bookkeeping for asynchronous ADT water
loads outside `StreamingWorldLoader`: FIFO requests, queued-key deduplication,
opaque active task IDs and parsed-result delivery. The loader retains task
execution, parsing, budgets, stale-result decisions and scene finalization.

## Non-goals

- Start, cancel or wait WorkerThreadPool tasks.
- Instantiate `ADTLoader` or interpret MH2O/MCLQ data.
- Select concurrency limits or consume render permits.
- Create, attach or free Nodes, Meshes, materials or RIDs.
- Change liquid visuals, coordinates, tile acceptance or loaded-state timing.

## Context and boundaries

```mermaid
flowchart LR
    Tile[Loaded tile needing water] --> Loader[StreamingWorldLoader]
    Loader --> State[AdtWaterLoadPipelineState]
    State --> Request[Oldest tile/path request]
    Request --> Loader
    Loader --> Worker[WorkerThreadPool ADTLoader task]
    Worker --> State
    State --> Result[Parsed ADT result]
    Result --> Loader
    Loader --> Budget[WATER_FINALIZE permit]
    Loader --> Finalizer[AdtWaterSceneFinalizer]
    Finalizer --> Builder[ADTBuilder water scene]
    Finalizer --> Scene[Main-thread tile Node attach]
```

Allowed dependencies are RefCounted, Dictionary/Array/String and one Godot
`Mutex` around results. WorkerThreadPool, ADTLoader, scheduler, tile state,
scene-tree/render resources and gameplay/editor/network are forbidden.

## Public API

| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|---|---|---|---|---|
| `enqueue_request(tile_key, adt_path)` | Command/query | Append deduplicated FIFO request | Renderer main thread; until dequeue/cancel/clear | Empty/queued/active returns false |
| `take_next_request()` | Command/query | Pop oldest request and release queued marker | Renderer main thread | Empty returns `{}` |
| `cancel_pending_requests(tile_key)` | Command/query | Remove all matching pending entries | Tile release/main thread | Empty/unknown removes zero; active unaffected |
| `remember_active_task(tile_key, task_id)` | Command/query | Retain loader-created opaque task ID | Main thread; until completion/clear | Empty/duplicate returns false |
| `has_active_task` / `active_task_id_for` | Query | Test/borrow active task identity | Main thread | Unknown ID is `-1` |
| `complete_active_task(tile_key)` | Command/query | Remove and return completed task ID | Main thread after wait | Unknown returns `-1` |
| `active_task_ids_snapshot()` | Query | Copy IDs for orderly shutdown waiting | Main thread | Detached Array |
| `publish_result(tile_key, path, data)` | Command/query | Append parsed result under mutex | Worker or main thread | Empty key/path returns false |
| `has_result()` / `pop_result()` | Query/command | Observe/pop oldest result under mutex | Main-thread drain | Empty pop returns `{}` |
| `total_load_count()` | Query | Pending plus active metric | Main thread | Results intentionally excluded |
| `pending_request_count()` / `active_task_count()` | Query | Support loader concurrency loop | Main thread | None |
| `clear()` | Command | Release all bookkeeping and results | Main thread after wait or map reset | Does not interrupt workers |
| `diagnostic_snapshot()` | Query | Return detached scalar requests/tasks/results | Main thread; result mailbox locked | Water data omitted |

## Inputs and outputs

| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|---|---|---|---|---|---|
| Input | Tile key and ADT path | Loader tile state | Pending FIFO | Copied Strings | Until dequeue/cancel/clear |
| Input | Opaque task ID | Loader WorkerThreadPool adapter | Active task map | Integer value | Until wait/completion/clear |
| Input | Tile/path/parsed ADT Dictionary | Worker parser adapter | Result mailbox | Dictionary reference retained | Until pop/clear |
| Output | Oldest request Dictionary | Pipeline state | Loader task-start adapter | Fresh record from FIFO | One start attempt |
| Output | Active task ID snapshot | Pipeline state | Loader shutdown wait | Detached Array | Shutdown loop |
| Output | Oldest result Dictionary | Pipeline state | Loader finalize adapter | Mailbox record transferred | One finalize attempt |
| Output | Scalar diagnostics | Pipeline state | Verifier/future metrics | Detached records | Snapshot lifetime |

Side effects are limited to collection mutation, reference retention and result
mutex locking. Parsing, permits and scene mutation remain outside the module.

## Data flow

```mermaid
flowchart TD
    Enqueue[enqueue tile/path] --> Duplicate{Queued or active?}
    Duplicate -->|yes| Reject[false; unchanged]
    Duplicate -->|no| FIFO[Append FIFO and queued key]
    FIFO --> Take[take next; release queued marker]
    Take --> Validate[Loader checks tile/path]
    Validate --> Start[Loader starts worker and remembers task ID]
    Start --> Parse[Worker parses ADT]
    Parse --> Publish[Mutex-protected publish result]
    Publish --> Permit[Loader obtains WATER_FINALIZE permit]
    Permit --> Pop[Pop oldest result]
    Pop --> Wait[Loader waits task and completes active ID]
    Wait --> Stale{Tile/path/root current?}
    Stale -->|no| Discard[Discard result]
    Stale -->|yes| Attach[Build and attach water scene]
```

## Lifecycle/state

A tile request progresses through `Absent`, `Queued`, `Active` and
`ResultReady`; the loader wait/completion returns bookkeeping to `Absent` before
validating/attaching the result. Queued tile release cancels only `Queued`.
Active work is not interrupted; a later stale result is drained and discarded.

```mermaid
stateDiagram-v2
    [*] --> Absent
    Absent --> Queued: accepted enqueue
    Queued --> Absent: pending cancellation or invalid dequeue
    Queued --> Active: loader starts task
    Active --> ResultReady: worker publishes result
    ResultReady --> Absent: loader waits and completes task
    Queued --> Absent: clear
    Active --> Absent: clear bookkeeping only
    ResultReady --> Absent: clear
```

## Main sequence

```mermaid
sequenceDiagram
    participant Loader as StreamingWorldLoader main thread
    participant State as AdtWaterLoadPipelineState
    participant Worker as WorkerThreadPool task
    participant Budget as RenderBudgetScheduler
    participant Finalizer as AdtWaterSceneFinalizer
    Loader->>State: enqueue_request(tile key, ADT path)
    Loader->>State: take_next_request()
    Loader->>Worker: add_task(parse binding)
    Loader->>State: remember_active_task(key, task ID)
    Worker->>Worker: ADTLoader.load_adt(path)
    Worker->>State: publish_result(key, path, data)
    Note over State: result mutex protects publication/pop
    Loader->>Budget: try_consume_permit(WATER_FINALIZE)
    Loader->>State: pop_result()
    Loader->>Worker: wait_for_task_completion(task ID)
    Loader->>State: complete_active_task(key)
    Loader->>Loader: reject stale tile/path/root
    Loader->>Finalizer: attach_water_scene(data, origin, tile root, editor owner)
    Loader->>Loader: mark water_loaded
```

## Dependency diagram

```mermaid
flowchart TB
    Loader[StreamingWorldLoader] --> State[AdtWaterLoadPipelineState]
    Loader --> Scheduler[RenderBudgetScheduler]
    Loader --> Worker[WorkerThreadPool / ADTLoader]
    Loader --> Finalizer[AdtWaterSceneFinalizer]
    Finalizer --> Builder[ADTBuilder / SceneTree]
    Worker -->|publish only| State
    State -. no dependency .-> Scheduler
    State -. no dependency .-> Finalizer
```

## Ownership, threading and resources

- Main thread owns pending and active-task collection mutation.
- Worker threads may call only `publish_result`; result observe/pop/clear also lock.
- Parsed water Dictionary identity is retained until main-thread pop.
- Loader owns WorkerThreadPool task lifetime and must wait before orderly shutdown clear.
- 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
  and is discarded because no active task/tile ownership remains.

## Errors, cancellation and recovery

| Failure | Detection | Behavior | Diagnostic | Recovery |
|---|---|---|---|---|
| Empty/duplicate request | Enqueue guards | Reject unchanged | Contract verifier | Correct caller state |
| Tile removed while queued | Loader release | Remove matching pending requests | Source/lifecycle verifier | Reload can enqueue later |
| Tile removed while active | Loader stale check after result | Wait task, remove active ID, discard result | Existing metrics | Reload can enqueue later |
| Parser unavailable/empty | Worker publishes empty Dictionary | Loader marks `water_loaded` without Node | Existing behavior | Future tile reload |
| Path changed | Loader compares result path | Discard after task completion | Source regression | Current tile can request again |
| Shutdown | Loader snapshots/waits active IDs | Clear all bookkeeping/results | Shutdown/source verifier | New loader starts empty |

## Configuration and capabilities

| Setting/capability | Default | Profile | Runtime mutable | Effect |
|---|---|---|---|---|
| `enable_water` | Loader setting | Quality/custom | Yes | Gates enqueue/process, unchanged |
| `max_concurrent_water_tasks` | Loader setting | Quality/custom | Yes | Bounds active tasks outside state |
| `water_finalize_ops_per_tick` | Loader setting | Quality/custom | Yes | Bounds result pops via scheduler |

## Persistence, cache and migration

No state is serialized or cached. ADT source/cache formats and liquid material
versions are unchanged; no migration or rebuild is required.

## Diagnostics and observability

- `total_load_count` preserves the prior pending-plus-active `water` metric.
- Snapshot exposes FIFO tile/path, sorted active keys and result tile/path only.
- Parsed liquid payloads, Nodes and task internals are not exposed.
- No logs or correlation IDs are introduced.

## Verification

- `verify_adt_water_load_pipeline_state.gd`: invalid/dedupe/FIFO behavior,
  active IDs, payload identity, real Thread publication, pending cancellation,
  clear, detached diagnostics, loader boundaries and bounded timing.
- Existing material/shutdown/facade/streaming regressions cover adjacent behavior.
- Fidelity evidence is preservation of current scheduling/finalize transitions;
  no MH2O/MCLQ visual or original-client parity claim is made.
- Performance budget: 100 cycles of 256 request/task/result transitions under 1 second.

## Extension points

- ADT parse execution can later move behind a parser/job adapter without changing state.
- Water scene materialization is now delegated to `AdtWaterSceneFinalizer`,
  independently of this async bookkeeping service.

## Capability status

| Capability | Status | Evidence | Gap/next step |
|---|---|---|---|
| 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 |
| Water scene finalization | Implemented extraction | Dedicated synthetic scene/ownership verifier | Asset-backed traversal/hitch evidence pending |

## Known gaps and risks

- `clear` intentionally does not interrupt active WorkerThreadPool work.
- Result payload Dictionaries are retained by reference and must not be mutated after publication.
- Result availability and pop use separate mutex acquisitions; one serialized
  main-thread consumer preserves ordering, but the API is not a multi-consumer queue.
- No proprietary liquid corpus, traversal/leak run, p95/p99 or paired capture is included.

## Source map

| Path | Responsibility |
|---|---|
| `src/render/liquid/adt_water_load_pipeline_state.gd` | Request/task/result bookkeeping and result mutex |
| `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 |
| `src/tools/verify_adt_water_load_pipeline_state.gd` | Lifecycle/thread/boundary/timing regression |

## Related decisions and references

- [`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.md`](../../RENDER.md)
- [`../../targets/roadmap/02-rendering-and-graphics.md`](../../targets/roadmap/02-rendering-and-graphics.md)
