Files
open-wc/docs/modules/terrain-query.md
T
sindoring 0ce3cae208 docs(M02): publish terrain query handoff
Work-Package: M02-GMP-TERRAIN-QUERY-001
Agent: sindo-main-codex
Tests: coordination and documentation gates; diff check
Fidelity: documents inherited height/snap regression and remaining collision evidence gap
2026-07-14 23:11:23 +04:00

13 KiB

Terrain Query

Metadata

Field Value
Status Implemented
Target/work package M02 / M02-GMP-TERRAIN-QUERY-001
Owners Gameplay terrain-query contract; ADT adapter owns parsed tile cache
Last verified a45d521, 2026-07-14
Profiles/capabilities Current sandbox ADT ground height; renderer/physics backend planned

Purpose

Provide a replaceable, scene-free ground-height query for player movement. Isolate native ADT loading, per-tile caching, chunk selection and legacy outer height-grid interpolation from the player scene behind a typed GodotWorldPosition → TerrainGroundSample contract.

Non-goals

  • Own player transforms, movement velocity or ground-snap policy.
  • Provide collision normals, slopes, holes, liquids, WMO/M2 collision or ray queries.
  • Change ADT topology, coordinate conversion, renderer caches or native parser format.
  • Claim original-client terrain-contact parity from height regression alone.
  • Couple gameplay to the monolithic streaming renderer before a public backend exists.

Context and boundaries

flowchart LR
    Scene[ThirdPersonWowController adapter] --> Position[GodotWorldPosition]
    Position --> Contract[TerrainQuery]
    Contract --> Adapter[AdtTerrainQuery]
    Adapter --> Mapper[CoordinateMapper]
    Adapter --> Native[ADTLoader]
    Native --> Tile[ADT tile Dictionary]
    Tile --> Cache[Owned tile cache]
    Cache --> Sample[TerrainGroundSample]
    Sample --> Scene
    Scene --> Snap[Existing ground-snap policy]

Allowed dependencies:

  • Typed GodotWorldPosition and CoordinateMapper contracts.
  • Existing native ADTLoader at the ADT adapter boundary.
  • Dynamic dictionaries only inside the native-format adapter.
  • Player scene composition may inject any TerrainQuery implementation.

Forbidden dependencies:

  • TerrainQuery contract depending on Nodes, cameras, input or renderer state.
  • Player scene loading/parsing ADT data or owning a parsed tile cache.
  • Manual WoW/Godot/ADT world-coordinate conversion outside CoordinateMapper.
  • Terrain query mutating transforms, gameplay movement state or visual meshes.

Public API

Symbol Kind Purpose Thread/lifetime Errors
TerrainGroundSample.available(height_units) Immutable value factory Creates a finite Godot-world ground height Any thread; caller-held value Non-finite height becomes unavailable
TerrainGroundSample.unavailable(failure_code) Immutable value factory Creates explicit unavailable result Any thread; caller-held value Empty code normalizes to terrain_unavailable
TerrainQuery.sample_ground_height(position) Replaceable boundary Samples ground at typed Godot world position Implementation-defined; current adapter main-thread owned Base reports terrain_query_not_implemented
AdtTerrainQuery.new(extracted_directory, map_directory_name, loader_override) Adapter constructor Configures one map and optional test/data-source loader Query lifetime Production validates native loader and file presence lazily
AdtTerrainQuery.sample_ground_height(position) Adapter method Loads/caches tile and interpolates ADT outer heights Main thread; cache lives with query Returns stable failure code
ThirdPersonWowController.set_terrain_query(query) Composition method Replaces terrain backend independently of movement/presentation Call before scene ready or on main thread Null is rejected and current query retained

Inputs and outputs

Direction Contract/data Producer Consumer Ownership Thread/lifetime
Input GodotWorldPosition Player scene adapter TerrainQuery Immutable caller-held value One query
Input Extracted directory/map name Scene composition AdtTerrainQuery Copied strings Query lifetime
Input ADT file/dictionary Filesystem/native loader or injected loader AdtTerrainQuery Adapter-owned cached dictionary Query lifetime
Output TerrainGroundSample Terrain query Player ground-snap adapter Immutable caller-held value One query
Output Failure code Terrain query Diagnostics/tests Immutable StringName Sample lifetime

Side effects:

  • Production adapter performs lazy file existence check and native ADT parse on first tile query.
  • Adapter mutates only its per-query tile/failure caches.
  • No scene tree, transform, renderer RID, network, database or persisted setting mutation occurs.

Data flow

flowchart LR
    Position[GodotWorldPosition] --> TileCoord[CoordinateMapper tile/local/chunk]
    TileCoord --> Lookup{Tile cached?}
    Lookup -->|no| Load[ADTLoader or injected loader]
    Load --> Cache[Cache Dictionary or unavailable result]
    Lookup -->|yes| Cache
    Cache --> Chunk[Select MCNK by origin]
    Chunk --> Heights[Validate 145 heights]
    Heights --> Bilinear[Interpolate 9x9 outer grid]
    Bilinear --> Available[TerrainGroundSample available]
    Cache -->|missing/invalid| Unavailable[TerrainGroundSample unavailable]

Lifecycle/state

stateDiagram-v2
    [*] --> TileUnknown
    TileUnknown --> TileAvailable: first query / successful load
    TileUnknown --> TileUnavailable: first query / missing or failed
    TileAvailable --> TileAvailable: cached queries
    TileUnavailable --> TileUnavailable: cached queries
    TileAvailable --> [*]: query released
    TileUnavailable --> [*]: query released

Unavailable tiles are cached intentionally to avoid repeated filesystem/native work each physics tick. Recovery after files appear requires replacing the query instance; hot-reload invalidation is outside this M02 package.

Main sequence

sequenceDiagram
    participant Player as ThirdPersonWowController
    participant Query as TerrainQuery
    participant ADT as AdtTerrainQuery
    participant Mapper as CoordinateMapper
    participant Native as ADTLoader
    Player->>Query: sample_ground_height(GodotWorldPosition)
    Query->>ADT: implementation dispatch
    ADT->>Mapper: tile/local/chunk coordinates
    alt tile not cached
        ADT->>Native: load_adt(absolute path)
        Native-->>ADT: ADT Dictionary
    end
    ADT->>ADT: select chunk and interpolate heights
    ADT-->>Player: TerrainGroundSample
    Player->>Player: apply existing offset/snap if available

Ownership, threading and resources

  • Player scene owns the active TerrainQuery reference.
  • AdtTerrainQuery exclusively owns tile/failure dictionaries until released.
  • Current production calls and cache mutations occur on the main physics thread.
  • Native loader result dictionaries do not cross the adapter boundary.
  • Samples and typed positions are immutable caller-held values.
  • The module creates no Nodes, Resources, RIDs or background jobs.
  • Future worker-backed queries require an explicit async/result contract rather than concurrent mutation of the current dictionaries.

Errors, cancellation and recovery

Failure Detection Behavior Diagnostic Recovery
Base contract used directly Base method Unavailable sample terrain_query_not_implemented Inject implementation
Native loader absent ClassDB check Cache unavailable tile adt_loader_unavailable Load extension and replace query
ADT file absent File check Cache unavailable tile adt_source_missing Restore corpus and replace query
Native parse empty/fails Result validation Cache unavailable tile adt_load_failed Inspect source/parser; replace query
Injected loader returns wrong type Type check Cache unavailable tile adt_loader_result_invalid Fix adapter
Chunk absent Chunk lookup Unavailable sample adt_chunk_missing Validate ADT dictionary
Height array shorter than 145 Size check Unavailable sample adt_chunk_heights_invalid Fix parser/source
Non-finite interpolated height Sample invariant Unavailable sample terrain_height_not_finite Inspect source values
Null query injection Scene guard Existing query retained Player composition error Pass valid query

There are no asynchronous operations to cancel. Replacing/releasing the query is the deterministic cache-reset and recovery path.

Configuration and capabilities

Setting/capability Default Profile Runtime mutable Effect
Extracted directory Player extracted_dir Current sandbox Captured at creation ADT source root
Map directory name Player map_name Current sandbox Captured at creation ADT filename/path
Loader override Invalid callable Test/custom data source Constructor only Bypasses production ClassDB/file loading
Tile cache Enabled All current uses Cleared by replacing query Avoids repeated parse/failure work
Height policy 9x9 outer-grid bilinear Current sandbox Fixed Preserves pre-extraction behavior

Persistence, cache and migration

The cache is in-memory and unversioned because it holds native loader output for one query lifetime. No disk cache, schema or migration changes are introduced. Existing ADT/native/cache format versions remain unchanged.

Diagnostics and observability

  • Logs: dedicated verifier emits TERRAIN_QUERY PASS or named failures.
  • Structured status: every unavailable sample has a stable failure_code.
  • Metrics: synthetic verifier counts loader calls to prove one load per tile.
  • Debug views: none.
  • Correlation IDs: not applicable to synchronous local query.

Verification

  • Contract: available/unavailable/base/non-finite result invariants.
  • Pure synthetic adapter: typed coordinate mapping, exact chunk selection, 145-height layout and bilinear interpolation within 0.002 units (Vector3 source precision), plus cached second query.
  • Failures: empty tile and invalid height array produce stable codes.
  • Integration: asset-free player scene accepts injected flat query and preserves spawn offset 0.05 plus physics ground snap.
  • Boundary: source regression rejects ADT loader/cache/chunk helpers in player.
  • Fidelity evidence: existing interpolation and snap behavior only; no original-client or collision parity claim.
  • Performance budget: one dictionary cache lookup and scalar interpolation per cached query; native parse only on first tile access.

Extension points

  • Streaming renderer or physics backend may implement TerrainQuery without changing player/movement.
  • Future result may grow separate slope, surface, liquid or collision contracts; incompatible additions require a versioned contract rather than Dictionary fields.
  • Test/authoring data sources may use constructor loader override.
  • Async streaming requires a separate availability/update model.

Capability status

Capability Status Evidence Gap/next step
Typed replaceable ground-height query Implemented Contract and injected player regression Integrate target checklist after review
Existing ADT height interpolation Implemented Synthetic fractional-grid fixture Compare terrain contact with build 12340
Per-tile success/failure cache Implemented Loader call-count regression Add explicit invalidation only with authoring/hot reload
Renderer/streaming cache backend Planned Boundary permits replacement M03 renderer facade/public terrain backend
Holes/slopes/collision Planned Outside height-only contract Later movement/physics package
Liquid/swim query Planned Outside contract M09/M12 world gameplay

Known gaps and risks

  • Bilinear outer-grid height is the inherited sandbox approximation; exact ADT triangle choice, holes and collision are not represented.
  • Query is synchronous; first access may parse an ADT on the main thread exactly as before extraction. A streaming/backend replacement is future work.
  • Unavailable results remain cached until query replacement.
  • Player still owns ground offset/snap policy; only data access is extracted.
  • Existing native dictionaries remain dynamic inside the adapter.

Source map

Path Responsibility
src/gameplay/terrain/terrain_ground_sample.gd Immutable available/unavailable result
src/gameplay/terrain/terrain_query.gd Replaceable typed query boundary
src/gameplay/terrain/adt_terrain_query.gd Native ADT adapter, cache, chunk selection and interpolation
src/scenes/player/third_person_wow_controller.gd Query composition and existing ground-snap consumer
src/tools/verify_terrain_query.gd Synthetic contract/cache/failure/injection regression
src/tests/scenes/player_input_regression.tscn Asset-free player composition fixture
  • ADR: none; no coordinate, format or authority decision changed.
  • Upstream/reference: targets/02-player-decomposition.md, targets/roadmap/02-rendering-and-graphics.md, targets/roadmap/04-gameplay-systems.md, docs/ARCHITECTURE.md.