Compare commits

...

3 Commits

Author SHA1 Message Date
sindoring 52e1b2ba67 docs(M01): mark coordinate fixtures ready
Work-Package: M01-QAR-COORD-FIXTURES-001

Agent: sindo-main-codex
2026-07-13 14:28:03 +04:00
sindoring ce4f7ec0e2 test(M01): add coordinate golden fixtures
Work-Package: M01-QAR-COORD-FIXTURES-001

Agent: sindo-main-codex
2026-07-13 14:27:38 +04:00
sindoring 758b375e7e docs(M01): claim coordinate golden fixtures
Work-Package: M01-QAR-COORD-FIXTURES-001
Agent: sindo-main-codex
Tests: not applicable (claim only)
Fidelity: pinned AzerothCore spawn, raw ADT metadata and build 12340 observations scoped
2026-07-13 14:23:23 +04:00
5 changed files with 429 additions and 6 deletions
@@ -0,0 +1,109 @@
# M01-QAR-COORD-FIXTURES-001 — Coordinate golden fixtures
<!-- OPENWC_CLAIM:M01-QAR-COORD-FIXTURES-001:sindo-main-codex:2026-07-15 -->
<!-- OPENWC_HANDOFF:READY:M01-QAR-COORD-FIXTURES-001:ce4f7ec -->
## Ownership
- Target: M01
- Program: QAR
- Owner/Agent ID: sindo-main-codex
- Branch: `work/sindo-main-codex/m01-coordinate-fixtures`
- Lease expires UTC: 2026-07-15
- Integrator: M01 milestone integrator
## Outcome
Add a versioned, non-proprietary fixture and headless validator joining one
pinned AzerothCore player spawn, raw ADT/MCNK metadata and the five M00
original-client coordinate observations through coordinate contract version 2.
## Non-goals
- Committing ADT files, SQL dumps or original-client screenshots.
- Claiming TrinityCore spawn equivalence without a pinned populated TDB source.
- Migrating renderer/native consumers or rendering a live server spawn.
- Changing coordinate formulas, IDs or `StreamingFocus`.
## Paths
- Exclusive: `src/tests/fixtures/coordinate_golden_points.json`,
`src/tools/verify_coordinate_golden_fixtures.gd`
- Shared/hotspots: `docs/modules/coordinate-mapping.md`, this claim
- Generated/ignored: extracted ADT data, server database downloads and Godot metadata
## Contracts and data
- Public API/events: no new production API; fixture schema version 1
- Coordinate contract: version 2, unchanged
- Migration/compatibility: additive test data; fixture consumers reject unknown schema versions
- Consumers: M01 coordinate mapper, future native/renderer/server adapter migrations
## Dependencies
- Requires: merged coordinate contract version 2 at `b55bfca`
- Blocks: coordinate consumer migration and golden server-spawn renderer integration
- External state: source evidence reduced to pinned revision, numeric rows and SHA-256 hashes
## Verification
- Commands: fixture validator, coordinate mapper probe, M00 calibration,
manifest, coordination and documentation gates
- Fixtures: AzerothCore Human Warrior start; raw `Azeroth_32_48.adt` MCNK
`(3,12)`; five build 12340 camera points
- Fidelity evidence: exact source provenance and mapper outputs with bounded tolerances
- Performance budget: negligible headless JSON and scalar arithmetic
## Documentation deliverables
- Inline public API docs: fixture-validator header and field validation functions
- Module specification: fixture schema, provenance, verification and source map
- Data-flow diagram: server/raw/client fixtures into mapper assertions
- State/sequence/dependency diagrams: unchanged; synchronous stateless validation
## Simplicity and naming
- Important names introduced: `coordinate_golden_points`,
`verify_coordinate_golden_fixtures`
- Simplest considered solution: one JSON fixture and one existing-style headless script
- Rejected complexity/abstractions: test framework dependency, SQL parser, ADT bytes in Git
- Unavoidable complexity and justification: three independent source categories need explicit provenance
- Measured optimization evidence: not applicable
## Status
- State: ready-for-review
- Done: pinned source research, schema 1 fixture, headless validator, module
documentation and all package gates
- Next: integrator review and merge before coordinate consumer migration
- Blocked by:
## Handoff
- Commit: `ce4f7ec`
- Branch: `work/sindo-main-codex/m01-coordinate-fixtures`
- Outcome: one versioned fixture now joins a pinned AzerothCore Human Warrior
spawn, private raw MCNK metadata and five original-client build 12340 points
through coordinate contract version 2
- Contract/schema: additive test fixture schema 1; production coordinate API and
contract version 2 are unchanged; proprietary bytes remain outside Git
- Verification: golden fixture PASS (`1` server spawn, `1` raw ADT chunk, `5`
original-client points); CoordinateMapper PASS (`5` golden points, `8` tile
boundaries, `2` raw MCNK cases, `5` yaw cases); M00 calibration PASS with
maximum error `0.000015`; renderer manifest PASS (`7` checkpoints); coordination,
documentation, dependency and whitespace checks passed
- Fidelity: AzerothCore source pinned at revision
`68fcf0098b16388093989627f37be530fc91f07d`; private
`Azeroth_32_48.adt` SHA-256
`D66E27FD82B461AE38B78562952AED428E619777A3F3E35DD8FF97444644770A`;
server and raw data independently resolve tile `(32,48)` and chunk `(3,12)`
- Rebuild: no proprietary input is required to rerun the committed fixture;
local ignored renderer resources are still required by the inherited M00
manifest probe
- Remaining risks: TrinityCore equivalence needs a pinned populated TDB snapshot;
live server adapter, renderer integration and MDDF/MODF Euler semantics remain
subsequent M01 work
- Recommended merge order: after coordinate contract version 2 (`b55bfca`) and
before live coordinate consumer migration
- Documentation: coordinate-mapping module inputs/outputs, persistence, data-flow
diagram, verification, capability matrix, risks and source map updated
+23 -6
View File
@@ -5,9 +5,9 @@
| Field | Value | | Field | Value |
|---|---| |---|---|
| Status | Implemented | | Status | Implemented |
| Target/work package | `M01-FND-COORDS-001`, `M01-FND-COORD-TILE-AXIS-002` | | Target/work package | `M01-FND-COORDS-001`, `M01-FND-COORD-TILE-AXIS-002`, `M01-QAR-COORD-FIXTURES-001` |
| Owners | Foundation/domain | | Owners | Foundation/domain |
| Last verified | Worktree `work/sindo-main-codex/m01-coordinate-tile-axis-fix`, 2026-07-13 | | Last verified | Worktree `work/sindo-main-codex/m01-coordinate-fixtures`, 2026-07-13 |
| Profiles/capabilities | All profiles; contract version 2 | | Profiles/capabilities | All profiles; contract version 2 |
## Purpose ## Purpose
@@ -85,6 +85,7 @@ Forbidden dependencies:
| Output | `CanonicalWowWorldPosition` | `CoordinateMapper` | Gameplay/content/adapters | New immutable value | Caller-owned reference | | Output | `CanonicalWowWorldPosition` | `CoordinateMapper` | Gameplay/content/adapters | New immutable value | Caller-owned reference |
| Output | `AdtTileCoordinate`, `AdtTileLocalPosition`, `AdtChunkCoordinate` | `CoordinateMapper` | Streaming/content adapters | New immutable values | Caller-owned references | | Output | `AdtTileCoordinate`, `AdtTileLocalPosition`, `AdtChunkCoordinate` | `CoordinateMapper` | Streaming/content adapters | New immutable values | Caller-owned references |
| Output | `GodotWorldPosition`, `GodotWorldYaw` | `CoordinateMapper` | Future render/scene adapter | 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: Side effects:
@@ -95,6 +96,10 @@ Side effects:
```mermaid ```mermaid
flowchart LR flowchart LR
ServerFixture[Pinned AzerothCore spawn] --> FixtureVerifier[Golden fixture verifier]
RawADTFixture[Raw ADT numeric metadata] --> FixtureVerifier
ClientFixture[Build 12340 observations] --> FixtureVerifier
FixtureVerifier --> Mapper
Server[ServerWorldPosition X,Y,Z] -->|identity components| Canonical[CanonicalWowWorldPosition] Server[ServerWorldPosition X,Y,Z] -->|identity components| Canonical[CanonicalWowWorldPosition]
ADT[AdtPlacementPosition X,height,Z] -->|half extent - horizontal| Canonical ADT[AdtPlacementPosition X,height,Z] -->|half extent - horizontal| Canonical
Canonical -->|tileX from Y; tileY from X| Tile[AdtTileCoordinate] Canonical -->|tileX from Y; tileY from X| Tile[AdtTileCoordinate]
@@ -145,8 +150,11 @@ There is no cancellation because every conversion is constant-time scalar math.
## Persistence, cache and migration ## Persistence, cache and migration
This package adds no persisted format, cache or migration. Existing dictionaries, Production code adds no persisted format, cache or migration. Test schema 1 in
native `Vector3` payloads and renderer conversion helpers remain compatible. `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 Consumer migration is ordered after this contract and must remove old manual
conversions only after its producer/consumer tests pass. conversions only after its producer/consumer tests pass.
@@ -163,9 +171,12 @@ raw three-number arrays are not an accepted new persistence contract.
## Verification ## Verification
- Unit/contract tests: `src/tools/verify_coordinate_mapper.gd`. - Unit/contract tests: `src/tools/verify_coordinate_mapper.gd`.
- Cross-source golden test: `src/tools/verify_coordinate_golden_fixtures.gd`.
- Integration/E2E: consumer migration and `StreamingFocus` packages remain pending. - Integration/E2E: consumer migration and `StreamingFocus` packages remain pending.
- Fidelity evidence: five build-12340 camera positions from the M00 manifest map - Fidelity evidence: five build-12340 camera positions from the M00 manifest map
within `0.002` yard; independent reference formulas are recorded in ADR 0001. 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. - Performance budgets: constant-time arithmetic; no scene or file access.
- Manual diagnostics: not required for the pure contract; renderer checkpoints - Manual diagnostics: not required for the pure contract; renderer checkpoints
are required when consumers migrate. are required when consumers migrate.
@@ -186,7 +197,9 @@ raw three-number arrays are not an accepted new persistence contract.
| ADT tile/local/chunk ownership | Implemented | Center/extent boundaries plus raw `Azeroth_31_49.adt` filename and MCNK direction evidence | Migrate streaming consumers | | ADT tile/local/chunk ownership | Implemented | Center/extent boundaries plus raw `Azeroth_31_49.adt` filename and MCNK direction evidence | Migrate streaming consumers |
| World-facing yaw mapping | Implemented | Cardinal/wrap round-trip fixtures | Placement Euler remains separate | | World-facing yaw mapping | Implemented | Cardinal/wrap round-trip fixtures | Placement Euler remains separate |
| Scene-free production contract | Implemented | Source boundary and headless test | Add dependency search gate later in M01 | | Scene-free production contract | Implemented | Source boundary and headless test | Add dependency search gate later in M01 |
| Visual/server-spawn integration | Planned | M01 target criteria | Add golden server spawn after adapter migration | | 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 | Planned | M01 target criteria | Route the fixture through migrated adapters and renderer |
## Known gaps and risks ## Known gaps and risks
@@ -197,6 +210,8 @@ raw three-number arrays are not an accepted new persistence contract.
this package deliberately does not generalize them into world yaw. this package deliberately does not generalize them into world yaw.
- Finite-number validation belongs to external format adapters and is not yet implemented. - 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. - 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 ## Source map
@@ -208,6 +223,8 @@ raw three-number arrays are not an accepted new persistence contract.
| `src/domain/coordinates/adt_chunk_coordinate.gd` | MCNK 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/domain/coordinates/*_yaw.gd` | Immutable typed world-facing yaw values |
| `src/tools/verify_coordinate_mapper.gd` | Headless fixtures and boundary regressions | | `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 |
| `docs/adr/0001-canonical-world-coordinates.md` | Normative axes, units, boundaries and rollout decision | | `docs/adr/0001-canonical-world-coordinates.md` | Normative axes, units, boundaries and rollout decision |
## Related decisions and references ## Related decisions and references
+99
View File
@@ -0,0 +1,99 @@
{
"schema_version": 1,
"coordinate_contract_version": 2,
"tolerances": {
"position_yards": 0.002,
"yaw_radians": 0.000001
},
"sources": {
"azerothcore_playercreateinfo": {
"status": "verified",
"repository": "azerothcore/database-wotlk",
"revision": "68fcf0098b16388093989627f37be530fc91f07d",
"path": "sql/base/playercreateinfo.sql",
"url": "https://raw.githubusercontent.com/azerothcore/database-wotlk/68fcf0098b16388093989627f37be530fc91f07d/sql/base/playercreateinfo.sql"
},
"trinitycore_playercreateinfo": {
"status": "not_verified",
"repository": "TrinityCore/TrinityCore",
"branch": "3.3.5",
"reason": "The base development schema does not provide a pinned populated playercreateinfo row; verify against a pinned TDB snapshot before claiming equivalence."
},
"azeroth_32_48_adt": {
"status": "verified_private_metadata",
"file_name": "Azeroth_32_48.adt",
"sha256": "D66E27FD82B461AE38B78562952AED428E619777A3F3E35DD8FF97444644770A",
"distribution": "Numeric metadata only; proprietary file remains outside Git."
},
"original_client_build_12340": {
"status": "verified_private_observation",
"build": 12340,
"manifest": "res://src/tools/render_baseline_manifest.json",
"evidence_document": "docs/M00_FINAL_PAIRED_EVIDENCE.md"
}
},
"server_spawns": [
{
"name": "azerothcore_human_warrior_start",
"source": "azerothcore_playercreateinfo",
"backend": "AzerothCore",
"table": "playercreateinfo",
"key": {"race": 1, "class": 1},
"map_id": 0,
"map_directory": "Azeroth",
"zone_id": 12,
"server_position": [-8949.95, -132.493, 83.5312],
"server_yaw_radians": 0.0,
"expected_adt_placement": [26016.616666667, 83.5312, 17199.159666667],
"expected_adt_tile": [32, 48],
"expected_adt_chunk": [3, 12],
"expected_adt_tile_local": [132.493, 83.5312, 416.616666667],
"expected_godot_position": [17199.159666667, 83.5312, 26016.616666667],
"expected_godot_yaw_radians": 0.0
}
],
"raw_adt_chunks": [
{
"name": "azeroth_32_48_mcnk_3_12",
"source": "azeroth_32_48_adt",
"map_directory": "Azeroth",
"adt_tile": [32, 48],
"mcnk_index": [3, 12],
"raw_header_canonical_origin": [-8933.33203125, -100.0, 84.238929749],
"sample_position": "chunk_center",
"expected_godot_chunk_center": [17183.333333333, 84.238929749, 26016.665364583]
}
],
"original_client_points": [
{
"name": "elwynn_terrain_overview",
"source": "original_client_build_12340",
"client_position": [-9153.334, 386.666, 180.0],
"expected_godot_position": [16680.0, 180.0, 26220.0]
},
{
"name": "elwynn_adt_boundary",
"source": "original_client_build_12340",
"client_position": [-9523.334, 46.666, 105.0],
"expected_godot_position": [17020.0, 105.0, 26590.0]
},
{
"name": "goldshire_dense_m2",
"source": "original_client_build_12340",
"client_position": [-9400.0, 110.0, 150.0],
"expected_godot_position": [16956.666, 150.0, 26466.666]
},
{
"name": "goldshire_inn_large_wmo",
"source": "original_client_build_12340",
"client_position": [-9385.0, 53.0, 72.0],
"expected_godot_position": [17013.666, 72.0, 26451.666]
},
{
"name": "elwynn_waterfall_liquid",
"source": "original_client_build_12340",
"client_position": [-9300.0, 585.0, 190.0],
"expected_godot_position": [16481.666, 190.0, 26366.666]
}
]
}
@@ -0,0 +1,197 @@
extends SceneTree
## Validates the non-proprietary M01 coordinate fixture against coordinate
## contract version 2 and the existing M00 build-12340 manifest. The script does
## not read server databases, proprietary ADT bytes or original-client images.
const CoordinateMapperScript = preload("res://src/domain/coordinates/coordinate_mapper.gd")
const CanonicalWowWorldPositionScript = preload("res://src/domain/coordinates/canonical_wow_world_position.gd")
const ServerWorldPositionScript = preload("res://src/domain/coordinates/server_world_position.gd")
const ServerWorldYawScript = preload("res://src/domain/coordinates/server_world_yaw.gd")
const FIXTURE_PATH := "res://src/tests/fixtures/coordinate_golden_points.json"
const RENDER_MANIFEST_PATH := "res://src/tools/render_baseline_manifest.json"
const SUPPORTED_FIXTURE_SCHEMA_VERSION := 1
const REQUIRED_COORDINATE_CONTRACT_VERSION := 2
func _initialize() -> void:
var failures: Array[String] = []
var fixture := _load_json_object(FIXTURE_PATH, failures)
var render_manifest := _load_json_object(RENDER_MANIFEST_PATH, failures)
if fixture.is_empty() or render_manifest.is_empty():
_report_and_quit(failures)
return
_expect_equal(fixture.get("schema_version", -1), SUPPORTED_FIXTURE_SCHEMA_VERSION, "fixture schema version", failures)
_expect_equal(fixture.get("coordinate_contract_version", -1), REQUIRED_COORDINATE_CONTRACT_VERSION, "coordinate contract version", failures)
var position_tolerance_yards := float(fixture.get("tolerances", {}).get("position_yards", 0.0))
var yaw_tolerance_radians := float(fixture.get("tolerances", {}).get("yaw_radians", 0.0))
_expect_true(position_tolerance_yards > 0.0, "positive position tolerance", failures)
_expect_true(yaw_tolerance_radians > 0.0, "positive yaw tolerance", failures)
_verify_source_provenance(fixture.get("sources", {}), failures)
_verify_server_spawns(fixture.get("server_spawns", []), position_tolerance_yards, yaw_tolerance_radians, failures)
_verify_raw_adt_chunks(fixture.get("raw_adt_chunks", []), position_tolerance_yards, failures)
_verify_original_client_points(fixture.get("original_client_points", []), render_manifest, position_tolerance_yards, failures)
if not failures.is_empty():
_report_and_quit(failures)
return
print("COORDINATE_GOLDEN_FIXTURES PASS server_spawns=1 raw_adt_chunks=1 original_client_points=5")
quit(0)
func _verify_source_provenance(sources: Dictionary, failures: Array[String]) -> void:
var azerothcore_source: Dictionary = sources.get("azerothcore_playercreateinfo", {})
_expect_equal(azerothcore_source.get("status", ""), "verified", "AzerothCore source status", failures)
_expect_equal(String(azerothcore_source.get("revision", "")).length(), 40, "AzerothCore pinned revision length", failures)
_expect_true(String(azerothcore_source.get("url", "")).contains(String(azerothcore_source.get("revision", ""))), "AzerothCore URL contains pinned revision", failures)
var trinitycore_source: Dictionary = sources.get("trinitycore_playercreateinfo", {})
_expect_equal(trinitycore_source.get("status", ""), "not_verified", "TrinityCore source status", failures)
_expect_true(not String(trinitycore_source.get("reason", "")).is_empty(), "TrinityCore gap reason", failures)
var adt_source: Dictionary = sources.get("azeroth_32_48_adt", {})
_expect_equal(adt_source.get("status", ""), "verified_private_metadata", "ADT source status", failures)
_expect_equal(String(adt_source.get("sha256", "")).length(), 64, "ADT SHA-256 length", failures)
func _verify_server_spawns(server_spawns: Array, position_tolerance_yards: float, yaw_tolerance_radians: float, failures: Array[String]) -> void:
_expect_equal(server_spawns.size(), 1, "server spawn count", failures)
for spawn_variant in server_spawns:
if not (spawn_variant is Dictionary):
failures.append("server spawn must be an object")
continue
var spawn: Dictionary = spawn_variant
var name := String(spawn.get("name", "server_spawn"))
var server_position_values := _require_three_numbers(spawn.get("server_position", null), "%s server_position" % name, failures)
if server_position_values.is_empty():
continue
var server_position = ServerWorldPositionScript.new(server_position_values[0], server_position_values[1], server_position_values[2])
var canonical_position = CoordinateMapperScript.server_to_canonical(server_position)
var godot_position = CoordinateMapperScript.canonical_to_godot(canonical_position)
var adt_placement = CoordinateMapperScript.canonical_to_adt_placement(canonical_position)
var adt_tile = CoordinateMapperScript.canonical_to_adt_tile(canonical_position)
var adt_local = CoordinateMapperScript.canonical_to_adt_tile_local(canonical_position)
var adt_chunk = CoordinateMapperScript.adt_tile_local_to_chunk(adt_tile, adt_local)
_expect_three_near([godot_position.x_units, godot_position.y_units, godot_position.z_units], spawn.get("expected_godot_position", null), position_tolerance_yards, "%s Godot position" % name, failures)
_expect_three_near([adt_placement.x_offset_yards, adt_placement.height_yards, adt_placement.z_offset_yards], spawn.get("expected_adt_placement", null), position_tolerance_yards, "%s ADT placement" % name, failures)
_expect_three_near([adt_local.east_yards, adt_local.height_yards, adt_local.south_yards], spawn.get("expected_adt_tile_local", null), position_tolerance_yards, "%s ADT local" % name, failures)
_expect_pair([adt_tile.tile_x, adt_tile.tile_y], spawn.get("expected_adt_tile", null), "%s ADT tile" % name, failures)
_expect_pair([adt_chunk.chunk_x, adt_chunk.chunk_y], spawn.get("expected_adt_chunk", null), "%s ADT chunk" % name, failures)
var server_yaw = ServerWorldYawScript.new(float(spawn.get("server_yaw_radians", 0.0)))
var godot_yaw = CoordinateMapperScript.canonical_to_godot_yaw(CoordinateMapperScript.server_to_canonical_yaw(server_yaw))
_expect_near(godot_yaw.radians, float(spawn.get("expected_godot_yaw_radians", 0.0)), yaw_tolerance_radians, "%s Godot yaw" % name, failures)
func _verify_raw_adt_chunks(raw_adt_chunks: Array, position_tolerance_yards: float, failures: Array[String]) -> void:
_expect_equal(raw_adt_chunks.size(), 1, "raw ADT chunk count", failures)
for chunk_variant in raw_adt_chunks:
if not (chunk_variant is Dictionary):
failures.append("raw ADT chunk must be an object")
continue
var chunk: Dictionary = chunk_variant
var name := String(chunk.get("name", "raw_adt_chunk"))
var origin_values := _require_three_numbers(chunk.get("raw_header_canonical_origin", null), "%s raw origin" % name, failures)
if origin_values.is_empty():
continue
var half_chunk_size_yards := CoordinateMapperScript.ADT_CHUNK_SIZE_YARDS * 0.5
var chunk_center = CanonicalWowWorldPositionScript.new(
origin_values[0] - half_chunk_size_yards,
origin_values[1] - half_chunk_size_yards,
origin_values[2]
)
var tile_coordinate = CoordinateMapperScript.canonical_to_adt_tile(chunk_center)
var local_position = CoordinateMapperScript.canonical_to_adt_tile_local(chunk_center)
var chunk_coordinate = CoordinateMapperScript.adt_tile_local_to_chunk(tile_coordinate, local_position)
_expect_pair([tile_coordinate.tile_x, tile_coordinate.tile_y], chunk.get("adt_tile", null), "%s ADT tile" % name, failures)
_expect_pair([chunk_coordinate.chunk_x, chunk_coordinate.chunk_y], chunk.get("mcnk_index", null), "%s MCNK index" % name, failures)
var godot_center = CoordinateMapperScript.canonical_to_godot(chunk_center)
_expect_three_near([godot_center.x_units, godot_center.y_units, godot_center.z_units], chunk.get("expected_godot_chunk_center", null), position_tolerance_yards, "%s Godot center" % name, failures)
func _verify_original_client_points(original_client_points: Array, render_manifest: Dictionary, position_tolerance_yards: float, failures: Array[String]) -> void:
_expect_equal(original_client_points.size(), 5, "original-client point count", failures)
var manifest_checkpoint_by_name: Dictionary = {}
for checkpoint_variant in render_manifest.get("checkpoints", []):
if checkpoint_variant is Dictionary:
manifest_checkpoint_by_name[String(checkpoint_variant.get("name", ""))] = checkpoint_variant
for point_variant in original_client_points:
if not (point_variant is Dictionary):
failures.append("original-client point must be an object")
continue
var point: Dictionary = point_variant
var name := String(point.get("name", "original_client_point"))
var client_position_values := _require_three_numbers(point.get("client_position", null), "%s client position" % name, failures)
if client_position_values.is_empty():
continue
var canonical_position = CanonicalWowWorldPositionScript.new(client_position_values[0], client_position_values[1], client_position_values[2])
var godot_position = CoordinateMapperScript.canonical_to_godot(canonical_position)
_expect_three_near([godot_position.x_units, godot_position.y_units, godot_position.z_units], point.get("expected_godot_position", null), position_tolerance_yards, "%s mapped position" % name, failures)
var manifest_checkpoint: Dictionary = manifest_checkpoint_by_name.get(name, {})
_expect_true(not manifest_checkpoint.is_empty(), "%s exists in render manifest" % name, failures)
if not manifest_checkpoint.is_empty():
_expect_three_near(client_position_values, manifest_checkpoint.get("reference_wow_camera", null), position_tolerance_yards, "%s manifest client position" % name, failures)
_expect_three_near(point.get("expected_godot_position", null), manifest_checkpoint.get("camera", null), position_tolerance_yards, "%s manifest Godot position" % name, failures)
func _require_three_numbers(value_variant, label: String, failures: Array[String]) -> Array[float]:
if not (value_variant is Array) or value_variant.size() != 3:
failures.append("%s must contain three numbers" % label)
return []
return [float(value_variant[0]), float(value_variant[1]), float(value_variant[2])]
func _expect_three_near(actual_values, expected_variant, tolerance: float, label: String, failures: Array[String]) -> void:
var expected_values := _require_three_numbers(expected_variant, label, failures)
if expected_values.is_empty() or not (actual_values is Array) or actual_values.size() != 3:
return
for component_index in range(3):
_expect_near(float(actual_values[component_index]), expected_values[component_index], tolerance, "%s[%d]" % [label, component_index], failures)
func _expect_pair(actual_values: Array, expected_variant, label: String, failures: Array[String]) -> void:
if not (expected_variant is Array) or expected_variant.size() != 2:
failures.append("%s must contain two integers" % label)
return
_expect_equal(actual_values[0], int(expected_variant[0]), "%s[0]" % label, failures)
_expect_equal(actual_values[1], int(expected_variant[1]), "%s[1]" % label, failures)
func _expect_near(actual_value: float, expected_value: float, tolerance: float, label: String, failures: Array[String]) -> void:
if absf(actual_value - expected_value) > tolerance:
failures.append("%s expected %.9f, got %.9f" % [label, expected_value, actual_value])
func _expect_equal(actual_value, expected_value, label: String, failures: Array[String]) -> void:
if actual_value != expected_value:
failures.append("%s expected %s, got %s" % [label, expected_value, actual_value])
func _expect_true(actual_value: bool, label: String, failures: Array[String]) -> void:
if not actual_value:
failures.append("%s expected true" % label)
func _load_json_object(path: String, failures: Array[String]) -> Dictionary:
var file := FileAccess.open(path, FileAccess.READ)
if file == null:
failures.append("cannot open JSON fixture: %s" % path)
return {}
var parsed = JSON.parse_string(file.get_as_text())
if not (parsed is Dictionary):
failures.append("JSON fixture is not an object: %s" % path)
return {}
return parsed
func _report_and_quit(failures: Array[String]) -> void:
if failures.is_empty():
failures.append("fixture validation stopped without a diagnostic")
for failure in failures:
push_error("COORDINATE_GOLDEN_FIXTURES: %s" % failure)
quit(1)
@@ -0,0 +1 @@
uid://cr8f88swvk002