a45d521567
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
262 lines
16 KiB
Markdown
262 lines
16 KiB
Markdown
# 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 | `working tree`, 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
|
|
|
|
```mermaid
|
|
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 `RefCounted` lifetime;
|
|
- 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;
|
|
- `Vector3` as 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
|
|
|
|
```mermaid
|
|
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 `RefCounted` references.
|
|
- 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.gd`
|
|
validates the dedicated manifest against the same pinned fixture before GUI capture.
|
|
- Integration/E2E: sky, player spawn, streamer, terrain probe and the extracted
|
|
`AdtTerrainQuery` use mapper boundaries;
|
|
`StreamingFocus` provides 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.002` yard; 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`](../adr/0001-canonical-world-coordinates.md)
|
|
- M00 evidence: [`../M00_FINAL_PAIRED_EVIDENCE.md`](../M00_FINAL_PAIRED_EVIDENCE.md)
|
|
- Architecture: [`../ARCHITECTURE.md`](../ARCHITECTURE.md)
|
|
- Testing rules: [`../TESTING.md`](../TESTING.md)
|