Work-Package: M01-FND-COORDS-001 Agent: sindo-main-codex Tests: coordinate mapper, M00 coordinate calibration, baseline manifest, coordination and documentation gates passed Fidelity: five build 12340 camera points map within 0.002 yard; no visual parity claim
5.7 KiB
ADR 0001: Canonical world coordinate contract
- Status: Accepted for M01 contract implementation
- Date: 2026-07-13
- Work package:
M01-FND-COORDS-001 - Decision owners: M01 foundation/integration owners
Context
OpenWC currently performs coordinate conversion in native ADT/WDT loaders,
StreamingWorldLoader, sky, player and renderer diagnostic scripts. The same
numbers are frequently carried in Vector3 or dictionaries, so the coordinate
space is implicit. Existing names also mix ADT filename indices, renderer X/Z
indices and MDDF/MODF placement fields.
M00 recorded five positions observed in WoW 3.3.5a build 12340. They verify the
current client-position to Godot-position arithmetic within 0.002 yard, but
they do not define a cross-layer contract and do not prove camera composition
or visual parity.
Independent references agree on a 64 by 64 ADT grid and an approximately
533.3333 yard tile size. They do not all use the word “canonical” for the same
axis order. OpenWC therefore needs names tied to observable formats instead of
adopting an internal convention from one reference project.
Decision
Canonical position
CanonicalWowWorldPosition(X, Y, Z) preserves the numeric X/Y/Z order used by
the WoW client, movement protocol and TrinityCore/AzerothCore world positions.
X and Y are horizontal; Z is height. Units are WoW yards.
ServerWorldPosition is a distinct type but converts component-for-component
to canonical. The type boundary prevents a server record from being passed
directly to renderer code and leaves room for adapter validation without a
silent axis swap.
World extent and ADT indices
The contract uses:
ADT_TILE_SIZE_YARDS = 1600 / 3
ADT_TILES_PER_MAP_AXIS = 64
WOW_WORLD_HALF_EXTENT_YARDS = 32 * ADT_TILE_SIZE_YARDS
tileX = floor(32 - canonical.X / ADT_TILE_SIZE_YARDS)
tileY = floor(32 - canonical.Y / ADT_TILE_SIZE_YARDS)
AdtTileCoordinate(tile_x, tile_y) always means the indices in
Map_tileX_tileY.adt. Results are intentionally not clamped: the exact
south-east extent produces index 64 and remains diagnosable as out of bounds.
Normal valid indices are 0 through 63.
An AdtTileLocalPosition is measured east and south from the tile's
north-west corner. An AdtChunkCoordinate uses chunk_x south and chunk_y
east, with valid indices 0 through 15. These direction names avoid pretending
that ADT file axes are Godot X/Z axes.
ADT placement fields
MDDF/MODF position fields are represented in on-disk X/height/Z order:
adt.X = halfExtent - canonical.X
adt.height = canonical.Z
adt.Z = halfExtent - canonical.Y
Model-local MDDF/MODF Euler corrections are not world orientation. They remain outside this contract until a placement-orientation package has format- and model-specific fixtures.
Godot renderer basis
The compatibility renderer keeps one Godot unit equal to one WoW yard and uses Y-up coordinates from the north-west map extent:
godot.X = halfExtent - canonical.Y # east
godot.Y = canonical.Z # up
godot.Z = halfExtent - canonical.X # south
GodotWorldPosition contains scalars rather than Vector3. A scene/render
adapter may construct Vector3 only at its boundary.
World yaw
Server/canonical yaw zero faces increasing WoW X; positive yaw turns toward
increasing WoW Y. In the mapped Godot basis, Godot forward -Z and positive
Y-axis yaw produce the same numeric world angle. Mapper conversions therefore
normalize the value to [-PI, PI) without adding an offset.
This identity applies to world-facing yaw only. It does not apply to M2/WMO asset axes or placement Euler triples.
Alternatives considered
Make renderer coordinates canonical
Rejected because protocol, database and ADT adapters would silently inherit a presentation-specific offset and Y-up basis.
Adopt WoWee's internal canonical axis names unchanged
Rejected because that reference swaps server X/Y into its chosen north/west ordering. OpenWC preserves observable client/server field names and makes the render/ADT transformations explicit instead.
Use Vector3 plus comments
Rejected because comments cannot prevent server, ADT and Godot values from being passed across the wrong boundary.
Generic vector-space or matrix framework
Rejected as unnecessary complexity for fixed affine transforms and integer grid ownership rules.
Consequences
- Network and server-data adapters can expose typed positions without knowing Godot.
- Renderer consumers must explicitly map and then create Godot vectors at their own boundary.
- ADT filename indices can no longer be confused with renderer X/Z tile names.
- Existing native/parser and renderer conversions remain unchanged until separate consumer migration packages merge after this contract.
- Manual axis conversions become review/test violations once migration is complete; this ADR alone does not delete compatibility paths.
- Persisted schemas are unchanged. Future serialized positions must include an explicit coordinate-space/version discriminator.
Verification
- Five M00 build-12340 client camera positions map to the recorded Godot
checkpoints within
0.002yard. - Server, ADT placement, Godot and tile-local conversions round-trip.
- Exact center, tile, chunk and world-extent boundaries have explicit tests.
- World-yaw normalization and round-trip cases cover cardinal angles and wrap.
References
- TrinityCore GridDefines
reference/open-realm/games/world-of-warcraft/renderer/wow/r_wowmap_adt.creference/open-realm/games/world-of-warcraft/renderer/wow/r_wowmap_objects.creference/WoWee/include/core/coordinates.hpp../M00_FINAL_PAIRED_EVIDENCE.md