# Terrain Chunk Geometry Queue Planner

## Metadata

| Field | Value |
|---|---|
| Status | Implemented extraction |
| Target/work package | M03 / `M03-RND-TERRAIN-CHUNK-QUEUE-001` |
| Owners | Pure chunk geometry create/remove request calculation |
| Last verified | Worktree `work/sindo-main-codex/m03-terrain-chunk-queue`, 2026-07-16 |
| Profiles/capabilities | Existing chunk/tile LOD desired state; all profiles |

## Purpose

Compare loader-owned current chunk geometry with desired chunk/tile LOD state
and produce fresh removal and nearest-first creation request arrays.

## Non-goals

- Retain, drain or bound queues, or own per-frame operation permits.
- Create/remove Mesh, Node, material or RID resources.
- Calculate desired chunk/tile LOD, parse ADT data or schedule worker tasks.
- Change cache versions, renderer profiles or visible fidelity rules.
- Provide a generic queue/operation framework.

## Context and boundaries

```mermaid
flowchart LR
    Current[Loader-owned tile/chunk state] --> Planner[TerrainChunkGeometryQueuePlanner]
    Desired[Desired chunk and tile LOD state] --> Planner
    Focus[GodotWorldPosition] --> Planner
    Planner --> Remove[Stable-order remove requests]
    Planner --> Create[Nearest-first create requests]
    Remove --> Loader[StreamingWorldLoader queues]
    Create --> Loader
    Loader --> Budget[RenderBudgetScheduler permit]
    Budget --> GPU[Existing Mesh/Node/RID side effects]
```

Allowed dependencies are typed Godot position and call-local scalar/container
values. SceneTree, RenderingServer, ResourceLoader, worker APIs, gameplay,
network, editor UI, files and persistent caches are forbidden.

## Public API

| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|---|---|---|---|---|
| `TerrainChunkGeometryQueuePlanner.plan(tile_states, focus_position)` | Pure query | Return fresh `create_requests` and `remove_requests` arrays | Synchronous; caller owns result | Null/wrong focus returns two empty arrays |

Script-identity validation uses a preload and therefore works from a cold Godot
global-class cache.

## Inputs and outputs

| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|---|---|---|---|---|---|
| Input | Tile-state read model with current chunks and desired LODs | Streamer | Planner | Loader-owned; read only | One rebuild |
| Input | Desired tile LOD | Tile LOD calculation | Planner | Scalar in loader state | One rebuild |
| Input | Parsed chunk origins and tile fallback origin | ADT result/loader | Planner | Loader-owned; read only | Map session |
| Input | `GodotWorldPosition` | Streamer focus adapter | Planner | Immutable caller reference | One rebuild |
| Output | Remove `{tile, chunk}` records | Planner | Loader removal queue | Fresh caller array/dictionaries | Until drained/rebuilt |
| Output | Create `{tile, chunk, lod, dist_sq}` records | Planner | Loader creation queue | Fresh nearest-first array/dictionaries | Until drained/rebuilt |

Distance is horizontal XZ squared distance from the chunk origin, not its center.
Y is intentionally ignored. A missing/sparse parsed origin falls back to the
tile state's existing origin exactly as before extraction.

## Data flow

```mermaid
flowchart TD
    Start[plan] --> FocusValid{Typed focus?}
    FocusValid -->|no| Empty[Return two empty arrays]
    FocusValid -->|yes| Tile[Iterate tile state insertion order]
    Tile --> TileLod{Desired tile LOD active?}
    TileLod -->|yes| RemoveAll[Append every current chunk removal]
    TileLod -->|no| Compare[Compare current and desired chunks]
    Compare --> RemoveMissing[Append current chunks absent from desired]
    Compare --> CreateMismatch[Append LOD-mismatched creates]
    Compare --> CreateMissing[Append absent desired creates]
    CreateMismatch --> Origin[Parsed chunk origin or tile fallback]
    CreateMissing --> Origin
    Origin --> Distance[Attach squared XZ distance]
    RemoveAll --> Next[Next tile]
    RemoveMissing --> Next
    Distance --> Next
    Next --> Sort[Sort creates nearest-first]
    Sort --> Result[Return fresh arrays]
```

## Lifecycle/state

The planner is stateless. Each loader queue rebuild supplies current read models
and receives new arrays. The loader adopts and drains those arrays until another
rebuild replaces them. Planner destruction requires no cleanup or cancellation.

## Main sequence

```mermaid
sequenceDiagram
    participant Loader as StreamingWorldLoader
    participant Planner as TerrainChunkGeometryQueuePlanner
    participant Budget as RenderBudgetScheduler
    participant Render as Existing terrain finalization
    Loader->>Planner: plan(tile states, typed focus)
    Planner-->>Loader: fresh create/remove arrays
    Loader->>Budget: request shared chunk_geometry permit
    Budget-->>Loader: permit
    Loader->>Render: pop_back removal first
    Loader->>Budget: request shared chunk_geometry permit
    Budget-->>Loader: permit
    Loader->>Render: pop_front nearest creation
```

## Ownership, threading and resources

- The planner owns only call-local arrays and dictionaries until return.
- The loader owns adopted queue storage, drain ordering, tile/chunk state and all
  Mesh/Node/material/RID side effects.
- Calls and drains currently run on the renderer main thread. The planner has no
  lock, task, retained resource or concurrent mutation.
- Removal and creation continue to share the existing `chunk_geometry` budget lane.

## Errors, cancellation and recovery

| Failure | Detection | Behavior | Diagnostic | Recovery |
|---|---|---|---|---|
| Null/wrong focus script | Identity guard | Return empty request arrays | Contract verifier | Supply typed focus and rebuild |
| Sparse/out-of-range chunk data | Origin resolver | Use tile fallback origin | Synthetic fixture | Normal ADT completion restores precise origin |
| Tile LOD becomes desired | Desired tile LOD check | Remove every current chunk; no chunk creates | Queue metrics | Existing tile-LOD create path takes over |
| Queue rebuild before drain | Loader lifecycle | Replace old desired-operation arrays | Existing queue metrics | Latest state wins |
| Shutdown/map reset | Loader lifecycle | Loader clears queues; planner has nothing to cancel | Shutdown verifier | New session replans |

## Configuration and capabilities

The planner introduces no settings. It consumes already-calculated desired LOD
state. Operation limits, profile defaults and removal-first drain order remain
loader/scheduler configuration.

## Persistence, cache and migration

No serialization, cache entry or format changes. Baked terrain, control-splat
and splat caches require no rebuild or migration.

## Diagnostics and observability

The planner emits no logs. Existing queue metrics observe the adopted arrays.
The contract verifier reports semantic cases and bounded multi-tile timing.
Asset-backed p95/p99 remains milestone acceptance work.

## Verification

- `verify_terrain_chunk_geometry_queue_planner.gd`: missing/mismatched/unchanged
  chunks, tile-LOD removal, sparse origin fallback, XZ-only distance, stable
  removals, nearest creates, invalid focus, loader boundary and bounded timing.
- Existing LOD planner, terrain cache, facade, internal-access, manifest,
  shutdown, scheduler, streaming and coordinate regressions remain required.
- Fidelity evidence is exact branch/request/order preservation only. No new
  build-12340 visual parity claim is made.

## Extension points

- Extract tile-LOD operation planning as its own bounded contract if justified.
- Add explicit typed tile-state adapters only when more than one consumer exists.

## Capability status

| Capability | Status | Evidence | Gap/next step |
|---|---|---|---|
| Pure chunk create/remove planning | Implemented extraction | Asset-free semantic/source/timing verifier | Asset-backed p95/p99 pending |
| Queue execution/resource ownership | Remains in loader | Drain source boundary and scheduler regression | Further terrain decomposition required |

## Known gaps and risks

- Raw tile/chunk dictionaries remain a transitional ADT adapter boundary.
- The planner performs a second linear tile-state pass beside tile-LOD planning;
  bounded synthetic timing is green, but asset-backed frame metrics are pending.
- Equal-distance create ordering retains comparator behavior without a tie-breaker.
- Terrain geometry finalization and state mutation remain monolithic.

## Source map

| Path | Responsibility |
|---|---|
| `src/render/terrain/terrain_chunk_geometry_queue_planner.gd` | Pure request calculation and nearest-first creation order |
| `src/scenes/streaming/streaming_world_loader.gd` | Queue adoption, tile-LOD planning, budgets and render side effects |
| `src/tools/verify_terrain_chunk_geometry_queue_planner.gd` | Semantic, boundary and timing regression |

## Related decisions and references

- [`terrain-chunk-lod-planner.md`](terrain-chunk-lod-planner.md)
- [`world-renderer.md`](world-renderer.md)
- [`../../RENDER.md`](../../RENDER.md)
- [`../../targets/roadmap/02-rendering-and-graphics.md`](../../targets/roadmap/02-rendering-and-graphics.md)
