test(M01): add coordinate golden fixtures
Work-Package: M01-QAR-COORD-FIXTURES-001 Agent: sindo-main-codex
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
Reference in New Issue
Block a user