Files
open-wc/docs/modules/terrain-chunk-lod-planner.md
T

7.8 KiB

Terrain Chunk LOD Planner

Metadata

Field Value
Status Implemented extraction
Target/work package M03 / M03-RND-TERRAIN-LOD-PLANNER-001
Owners Pure desired chunk-LOD calculation only
Last verified Worktree work/sindo-main-codex/m03-terrain-chunk-lod-planner, 2026-07-16
Profiles/capabilities Existing Performance/Balanced/High/Custom radii

Purpose

Calculate the desired terrain LOD for each populated parsed ADT chunk from one typed Godot-space focus and one immutable renderer-policy snapshot.

Non-goals

  • Own tile/chunk state, queues, budgets, tasks, Mesh, Node, material or RID.
  • Select tile LOD, create/remove geometry or apply a completed plan.
  • Normalize custom radii, change profile defaults or claim visual parity.
  • Parse ADT data or convert between WoW and Godot coordinate spaces.

Context and boundaries

flowchart LR
    Focus[GodotWorldPosition] --> Planner[TerrainChunkLodPlanner]
    Chunks[Parsed ADT chunk records] --> Planner
    Config[Streamer LOD configuration] --> Policy[TerrainChunkLodPolicy]
    Policy --> Planner
    Planner --> Desired[chunk index to LOD 0/1/2]
    Desired --> Loader[StreamingWorldLoader state adapter]
    Loader --> Queue[Existing create/remove queues and budgets]

Allowed dependencies are the typed Godot position, immutable policy and call-local Array/Dictionary/Vector3 values. SceneTree, RenderingServer, gameplay, network, files, tasks and persistent caches are forbidden.

Public API

Symbol Kind Purpose Thread/lifetime Errors
TerrainChunkLodPolicy.new(...) Immutable value Snapshot enabled flag, three raw radii and chunk size Caller-owned; one planning refresh Values are preserved without normalization
TerrainChunkLodPlanner.plan(chunks, focus_position, policy) Pure query Map populated source indices to LOD 0/1/2 Synchronous; result caller-owned Invalid/null typed inputs or disabled policy return empty

The planner validates scripts by preloaded identity so it works from a cold Godot global-class cache.

Inputs and outputs

Direction Contract/data Producer Consumer Ownership Thread/lifetime
Input Parsed chunk array with origin: Vector3 ADT load result Planner Loader-owned; read only One call
Input GodotWorldPosition Streamer focus adapter Planner Immutable caller reference One call
Input TerrainChunkLodPolicy Streamer exported configuration Planner Immutable caller reference One call
Output {source_chunk_index: 0/1/2} Planner Loader desired state Fresh caller dictionary One call

The Y coordinate is intentionally ignored. Empty chunk records and chunks beyond the third radius are absent from the result.

Data flow

flowchart TD
    Start[plan] --> Valid{Typed inputs and enabled?}
    Valid -->|no| Empty[Return empty dictionary]
    Valid -->|yes| Radii[Square raw radius times chunk size]
    Radii --> Iterate[Visit source chunk indices in order]
    Iterate --> Populated{Record populated?}
    Populated -->|no| Iterate
    Populated -->|yes| Center[origin XZ plus half chunk]
    Center --> Distance[Squared horizontal distance]
    Distance --> Threshold{First inclusive threshold}
    Threshold -->|LOD0/1/2| Record[Record source index]
    Threshold -->|outside| Iterate
    Record --> Iterate
    Iterate --> Done[Return desired mapping]

Lifecycle/state

The planner is stateless. A policy is constructed for one refresh, read during synchronous planning and released. The returned dictionary is then owned by the loader state. There is no cancellation, recovery state or retained resource.

Main sequence

sequenceDiagram
    participant Stream as StreamingWorldLoader
    participant Policy as TerrainChunkLodPolicy
    participant Planner as TerrainChunkLodPlanner
    Stream->>Policy: snapshot enabled/radii/chunk size
    Stream->>Planner: plan(chunks, typed focus, policy)
    Planner-->>Stream: fresh desired LOD dictionary
    Stream->>Stream: existing tile state, queue and budget application

Ownership, threading and resources

  • Planner and policy own no engine resources, nodes, RIDs, tasks or caches.
  • The loader retains parsed chunks, desired state and every application side effect.
  • Current calls run synchronously on the renderer main thread; the pure inputs also make isolated contract testing possible without a world scene.

Errors, cancellation and recovery

Failure Detection Behavior Diagnostic Recovery
Null/wrong focus or policy script Identity guard Empty plan Contract verifier Supply typed values next refresh
Disabled chunk LOD Policy flag Empty plan; existing tile-LOD fallback applies Existing renderer state Enable profile option
Empty chunk record Record guard Preserve sparse source index; omit record None Not an error
Custom negative radius No normalization Existing squared-radius behavior is retained Regression fixture Change caller configuration if undesired

There is no asynchronous work or cancellation token.

Configuration and capabilities

The policy snapshots use_chunk_terrain_lods, lod0_radius_chunks, lod1_radius_chunks, lod2_radius_chunks and the existing CHUNK_SIZE. Inclusive squared thresholds and raw custom values deliberately preserve prior behavior.

Persistence, cache and migration

No serialization or cache format changes. Existing baked terrain, control-splat and splat cache versions remain unchanged and require no rebuild.

Diagnostics and observability

The service emits no logs. The contract verifier reports semantic cases and a bounded synthetic timing. Asset-backed p95/p99 remains milestone acceptance work.

Verification

  • verify_terrain_chunk_lod_planner.gd: center and vertical invariance, inclusive thresholds, sparse/outside chunks, disabled/custom-negative policy, loader delegation and 250-by-256 bounded planning.
  • Renderer cache, facade, internal-access, baseline, scheduler, target-planner, focus, ground-query and coordinate-boundary regressions remain required.
  • Fidelity evidence is formula preservation only; no new build-12340 visual equivalence claim is made.

Extension points

  • Add measured direction/velocity only through an explicit policy/contract change.
  • Keep queue prioritization and LOD application as separately bounded services.

Capability status

Capability Status Evidence Gap/next step
Pure chunk desired-LOD calculation Implemented extraction Asset-free semantic/source-boundary contract Asset-backed p95/p99 pending
Queue/state/finalization ownership Remains in loader Source-boundary regression Further terrain decomposition required

Known gaps and risks

  • Raw negative and non-monotonic custom radii retain historical squared/ordered semantics, even though such settings may be surprising.
  • The planner is linear in parsed chunk count and allocates one result dictionary.
  • Terrain build, quality upgrades and geometry lifecycle remain monolithic.

Source map

Path Responsibility
src/render/terrain/terrain_chunk_lod_policy.gd Immutable configuration snapshot
src/render/terrain/terrain_chunk_lod_planner.gd Pure horizontal-distance plan
src/scenes/streaming/streaming_world_loader.gd Composition and plan application adapter
src/tools/verify_terrain_chunk_lod_planner.gd Contract, dependency and timing regression