gmp(M02): extract player terrain query
Work-Package: M02-GMP-TERRAIN-QUERY-001 Agent: sindo-main-codex Tests: terrain query, player input and movement regressions; asset-free scene; renderer dry-run; coordinate, streaming, documentation and coordination gates Fidelity: preserves existing ADT outer-grid interpolation and player ground snap; build-12340 collision parity remains unverified
This commit is contained in:
@@ -0,0 +1,251 @@
|
||||
# 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 | `working tree`, 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
|
||||
|
||||
```mermaid
|
||||
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
|
||||
|
||||
```mermaid
|
||||
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
|
||||
|
||||
```mermaid
|
||||
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
|
||||
|
||||
```mermaid
|
||||
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 |
|
||||
|
||||
## Related decisions and references
|
||||
|
||||
- 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`.
|
||||
Reference in New Issue
Block a user