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
16 KiB
Coordinate Mapping
Metadata
| Field | Value |
|---|---|
| Status | Implemented |
| Target/work package | M01-FND-COORDS-001, M01-FND-COORD-TILE-AXIS-002, M01-QAR-COORD-FIXTURES-001, M01-FND-COORD-BOUNDARY-GATE-001, M01-QAR-SERVER-SPAWN-RENDERER-001, M02-GMP-TERRAIN-QUERY-001 consumer migration |
| Owners | Foundation/domain |
| Last verified | a45d521, 2026-07-14 |
| Profiles/capabilities | All profiles; contract version 2 |
Purpose
Provide scene-free value objects and one pure CoordinateMapper for positions,
ADT grid ownership and world-facing yaw. The module makes every source space
explicit before renderer, network, gameplay and authoring consumers migrate.
Non-goals
- Reading ADT, packet, SQL or scene data.
- Constructing
Vector3,Transform3D, Node or Resource values. - Correcting M2/WMO local axes or MDDF/MODF Euler rotations.
- Selecting maps, validating terrain height or owning streaming focus.
- Persisting coordinate values in this work package.
Context and boundaries
flowchart LR
Server[Server / movement X,Y,Z] --> ServerPosition[ServerWorldPosition]
ADT[MDDF/MODF X,height,Z] --> AdtPosition[AdtPlacementPosition]
ServerPosition --> Mapper[CoordinateMapper]
AdtPosition --> Mapper
Mapper --> Canonical[CanonicalWowWorldPosition]
Canonical --> Tile[AdtTileCoordinate + AdtTileLocalPosition]
Canonical --> GodotPosition[GodotWorldPosition]
GodotPosition --> Adapter[Future scene/render adapter]
Adapter --> Vector[Vector3 at boundary]
Scene[Node / Camera3D / scene lookup] -. forbidden .-> Mapper
Allowed dependencies:
- GDScript scalar math and
RefCountedlifetime; - other value objects in
src/domain/coordinates; - callers in network, gameplay, render and server-data adapters.
Forbidden dependencies:
Node,Resource, SceneTree, Camera3D and global scene lookup;- packet buffers, SQL rows, asset parsers and renderer state;
Vector3as a public mapper input or output;- model-specific placement corrections.
Public API
| Symbol | Kind | Purpose | Thread/lifetime | Errors |
|---|---|---|---|---|
CanonicalWowWorldPosition |
Immutable value | Client/server X/Y/Z in yards | Any thread; caller-owned reference | No bounds validation |
ServerWorldPosition |
Immutable value | Server/wire X/Y/Z in yards | Any thread; caller-owned reference | No adapter validation |
AdtPlacementPosition |
Immutable value | MDDF/MODF X/height/Z fields | Any thread; caller-owned reference | No file validation |
GodotWorldPosition |
Immutable value | Godot east/up/south scalars | Any thread; caller-owned reference | Does not create Vector3 |
AdtTileCoordinate |
Immutable value | Map_tileX_tileY.adt indices |
Any thread; caller-owned reference | is_within_map_grid() reports invalid indices |
AdtTileLocalPosition |
Immutable value | East/height/south offsets from tile north-west corner | Any thread; caller-owned reference | Values are intentionally unclamped |
AdtChunkCoordinate |
Immutable value | MCNK X-east/Y-south indices inside one tile | Any thread; caller-owned reference | is_within_map_grid() reports invalid indices |
CanonicalWowWorldYaw |
Immutable value | Canonical world yaw in radians | Any thread; caller-owned reference | Constructor does not normalize |
ServerWorldYaw |
Immutable value | Movement orientation in radians | Any thread; caller-owned reference | Constructor does not normalize |
GodotWorldYaw |
Immutable value | Godot Y-axis world yaw in radians | Any thread; caller-owned reference | Constructor does not normalize |
CoordinateMapper.server_to_canonical / inverse |
Pure static methods | Preserve server X/Y/Z as canonical | Synchronous; new result value | Null input is a caller contract violation |
CoordinateMapper.canonical_to_godot / inverse |
Pure static methods | Apply Y-up renderer basis and map offset | Synchronous; new result value | Non-finite scalars propagate |
CoordinateMapper.godot_to_adt_tile |
Pure static method | Resolve filename indices from typed renderer position | Synchronous; new result value | Result remains unclamped |
CoordinateMapper.godot_to_adt_tile_local |
Pure static method | Resolve east/height/south offsets from typed renderer position | Synchronous; new result value | Caller validates ownership/range |
CoordinateMapper.adt_tile_local_to_godot |
Pure static method | Compose typed renderer position from tile/local values | Synchronous; new result value | Non-finite scalars propagate |
CoordinateMapper.canonical_to_adt_placement / inverse |
Pure static methods | Convert ADT placement field basis | Synchronous; new result value | Non-finite scalars propagate |
CoordinateMapper.canonical_to_adt_tile |
Pure static method | Compute unclamped filename indices | Synchronous; new result value | Out-of-map result remains invalid |
CoordinateMapper.canonical_to_adt_tile_local / inverse |
Pure static methods | Split/reconstruct tile-local position | Synchronous; new result value | Caller validates tile ownership |
CoordinateMapper.adt_tile_local_to_chunk |
Pure static method | Compute unclamped MCNK indices | Synchronous; new result value | Boundary/out-of-range remains diagnosable |
| World-yaw mapper methods | Pure static methods | Normalize and cross the world-basis boundary | Synchronous; new result value | Non-finite input is unsupported |
Inputs and outputs
| Direction | Contract/data | Producer | Consumer | Ownership | Thread/lifetime |
|---|---|---|---|---|---|
| Input | ServerWorldPosition, ServerWorldYaw |
Future protocol/server adapter | CoordinateMapper |
Immutable caller value | Any thread, call duration |
| Input | AdtPlacementPosition |
Future ADT adapter | CoordinateMapper |
Immutable caller value | Any thread, call duration |
| Input | CanonicalWowWorldPosition, CanonicalWowWorldYaw |
Gameplay/content/domain | CoordinateMapper |
Immutable caller value | Any thread, call duration |
| Output | CanonicalWowWorldPosition |
CoordinateMapper |
Gameplay/content/adapters | New immutable value | Caller-owned reference |
| Output | AdtTileCoordinate, AdtTileLocalPosition, AdtChunkCoordinate |
CoordinateMapper |
Streaming/content/terrain-query adapters | New immutable values | Caller-owned references |
| Output | GodotWorldPosition, GodotWorldYaw |
CoordinateMapper |
Future render/scene adapter | New immutable values | Caller-owned references |
| Test input | coordinate_golden_points.json schema 1 |
Pinned public server row plus private numeric observations | Headless fixture verifier | Versioned repository fixture | Test-process lifetime |
Side effects:
- none;
- no scene mutation, file/cache write, network send, database access or logging.
Data flow
flowchart LR
ServerFixture[Pinned AzerothCore spawn] --> FixtureVerifier[Golden fixture verifier]
RawADTFixture[Raw ADT numeric metadata] --> FixtureVerifier
ClientFixture[Build 12340 observations] --> FixtureVerifier
FixtureVerifier --> Mapper
ServerFixture --> SpawnManifest[Server spawn render manifest]
SpawnManifest --> SpawnVerifier[Renderer checkpoint verifier]
SpawnVerifier --> Capture[Live renderer capture]
Capture --> Marker[Diagnostic marker at mapped spawn]
Server[ServerWorldPosition X,Y,Z] -->|identity components| Canonical[CanonicalWowWorldPosition]
ADT[AdtPlacementPosition X,height,Z] -->|half extent - horizontal| Canonical
Canonical -->|tileX from Y; tileY from X| Tile[AdtTileCoordinate]
Canonical -->|north-west edge offsets| Local[AdtTileLocalPosition]
Local -->|floor offset / chunk size| Chunk[AdtChunkCoordinate]
Canonical -->|half extent - Y; Z; half extent - X| Godot[GodotWorldPosition]
Godot -->|typed direct adapter| Tile
Godot -->|typed direct adapter| Local
Godot --> TerrainQuery[AdtTerrainQuery consumer]
TerrainQuery --> Tile
TerrainQuery --> Local
TerrainQuery --> Chunk
Tile -->|tile plus local| Godot
ServerYaw[ServerWorldYaw] -->|normalize only| CanonicalYaw[CanonicalWowWorldYaw]
CanonicalYaw -->|same numeric world angle| GodotYaw[GodotWorldYaw]
Lifecycle/state
The module is stateless. Each call reads immutable scalar inputs and returns a new value object. There is no initialization, shutdown, cancellation or retry state machine.
Main sequence
No cross-layer asynchronous sequence occurs inside this module. Producers parse or validate their external format first, call the mapper synchronously, and then pass only typed results across the next boundary.
Ownership, threading and resources
- Value objects own only scalar fields and, for chunks, one immutable tile reference.
- Callers own returned
RefCountedreferences. - Methods do not mutate inputs or shared state and are safe for worker use.
- No Node, RID, file, socket, database or cache resource is acquired.
Errors, cancellation and recovery
| Failure | Detection | Behavior | Diagnostic | Recovery |
|---|---|---|---|---|
| Tile/chunk outside standard grid | is_within_map_grid() |
Unclamped indices are preserved | Caller reports source and map context | Reject or apply explicit map policy |
| Tile-local value outside half-open tile | Consumer boundary validation | Value is preserved | Caller reports source coordinate | Recompute ownership or reject malformed input |
| Null value object | Static typing/call failure | Programming contract violation | GDScript error with call site | Fix producer; no fallback value |
| NaN/infinite scalar | Adapter validation | Arithmetic propagates invalid value | Adapter must report source field | Reject malformed external input |
There is no cancellation because every conversion is constant-time scalar math.
Configuration and capabilities
| Setting/capability | Default | Profile | Runtime mutable | Effect |
|---|---|---|---|---|
| Contract version | 2 | All | No | Corrected filename/chunk axes in ADR 0001 |
| Godot units per WoW yard | 1 | Blizzlike335 and current tools |
No | Preserves current renderer scale |
| ADT grid | 64 tiles/axis, 16 chunks/tile | WotLK 3.3.5a | No | Defines index and boundary math |
Persistence, cache and migration
Production code adds no persisted format, cache or migration. Test schema 1 in
coordinate_golden_points.json persists explicit source provenance, coordinate
contract version 2, tolerances, source-space values and expected outputs. It
contains no proprietary bytes, database dump or original-client image. Existing
dictionaries, native Vector3 payloads and renderer conversion helpers remain compatible.
Consumer migration is ordered after this contract and must remove old manual
conversions only after its producer/consumer tests pass.
Any future serialized coordinate must store a schema version and explicit space; raw three-number arrays are not an accepted new persistence contract.
Diagnostics and observability
- Logs: none in production mapper.
- Metrics: none; conversion cost is constant and outside frame-budget concern.
- Debug views: headless contract probe prints one PASS summary.
- Correlation IDs: supplied by external adapters if an invalid source is reported.
Verification
- Unit/contract tests:
src/tools/verify_coordinate_mapper.gd. - Cross-source golden test:
src/tools/verify_coordinate_golden_fixtures.gd. - Boundary enforcement:
src/tools/verify_coordinate_conversion_boundaries.gd. - Server-spawn renderer contract:
src/tools/verify_server_spawn_renderer.gdvalidates the dedicated manifest against the same pinned fixture before GUI capture. - Integration/E2E: sky, player spawn, streamer, terrain probe and the extracted
AdtTerrainQueryuse mapper boundaries;StreamingFocusprovides typed renderer focus, and the pinned AzerothCore Human Warrior spawn is captured in the Eastern Kingdoms renderer at ADT(32,48), chunk(3,12). - Fidelity evidence: five build-12340 camera positions from the M00 manifest map
within
0.002yard; a pinned AzerothCore human-warrior spawn and one raw MCNK header observation independently confirm tile(32,48)and chunk(3,12); independent reference formulas are recorded in ADR 0001. - Performance budgets: constant-time arithmetic; no scene or file access.
- Manual diagnostics: not required for the pure contract; renderer checkpoints are required when consumers migrate.
Extension points
- Add typed map IDs around positions without changing axis semantics.
- Add a format-specific placement-orientation adapter with its own fixtures.
- Add serialization only with explicit space/version fields and migrations.
- Add consumer adapters that construct engine-native vectors at their boundary.
Capability status
| Capability | Status | Evidence | Gap/next step |
|---|---|---|---|
| Canonical/server/Godot position mapping | Implemented | Headless round-trip, five M00 golden points and migrated GDScript consumers | Network/server adapters remain future consumers |
| ADT placement mapping | Implemented | Synthetic/direct round-trip fixtures | Compare native parsed placements during migration |
| ADT tile/local/chunk ownership | Implemented | Center/extent boundaries, raw Azeroth_31_49.adt evidence and direct Godot adapter round-trip |
Native parser remains explicit boundary |
| World-facing yaw mapping | Implemented | Cardinal/wrap round-trip fixtures | Placement Euler remains separate |
| Scene-free production contract | Implemented | Source boundary, headless tests and repository-wide conversion gate | Maintain gate with new consumers |
| Cross-source coordinate fixture | Implemented | Pinned AzerothCore spawn, raw ADT MCNK metadata and five build-12340 points pass one headless verifier | Add live adapter/renderer integration |
| TrinityCore populated spawn equivalence | Not verified | The base development schema has no pinned populated row used by this fixture | Pin and verify a TDB snapshot before claiming parity |
| Visual/server-spawn integration | Implemented | Dedicated manifest/verifier plus cold/warm GUI capture of the mapped AzerothCore spawn | This is coordinate/renderer integration evidence, not original-client visual parity |
Known gaps and risks
- Native ADT/WDT parsing retains one shared boundary formula because worker parsers cannot call GDScript; the gate prevents additional definitions.
- Contract version 1 briefly transposed ADT filename/chunk axes; version 2 fixes the defect before any consumer migration.
- MDDF/MODF rotation comments and implementations disagree across existing paths; this package deliberately does not generalize them into world yaw.
- Finite-number validation belongs to external format adapters and is not yet implemented.
- Arithmetic agreement does not prove terrain, placement, camera direction or visual parity.
- The golden server row is verified against pinned AzerothCore data only; no TrinityCore equivalence claim is made without a pinned populated TDB snapshot.
Source map
| Path | Responsibility |
|---|---|
src/domain/coordinates/coordinate_mapper.gd |
Constants and all coordinate-axis conversions |
src/domain/coordinates/*_position.gd |
Immutable typed position values |
src/domain/coordinates/adt_tile_coordinate.gd |
ADT filename tile indices and bounds |
src/domain/coordinates/adt_chunk_coordinate.gd |
MCNK indices and bounds |
src/domain/coordinates/*_yaw.gd |
Immutable typed world-facing yaw values |
src/tools/verify_coordinate_mapper.gd |
Headless fixtures and boundary regressions |
src/tests/fixtures/coordinate_golden_points.json |
Versioned cross-source values, provenance and tolerances |
src/tools/verify_coordinate_golden_fixtures.gd |
Cross-source fixture schema and mapper validation |
src/tools/verify_coordinate_conversion_boundaries.gd |
Repository-wide manual conversion and required-consumer gate |
src/gameplay/terrain/adt_terrain_query.gd |
Typed Godot-position consumer for ADT tile/local/chunk lookup |
src/tools/server_spawn_render_manifest.json |
Pinned server-spawn camera, player, marker and expected ADT ownership |
src/tools/verify_server_spawn_renderer.gd |
Fixture-to-renderer manifest contract verifier |
docs/adr/0001-canonical-world-coordinates.md |
Normative axes, units, boundaries and rollout decision |
Related decisions and references
- ADR:
../adr/0001-canonical-world-coordinates.md - M00 evidence:
../M00_FINAL_PAIRED_EVIDENCE.md - Architecture:
../ARCHITECTURE.md - Testing rules:
../TESTING.md