test(M00): add renderer coordinate calibration

Work-Package: M00-QAR-COORD-CALIBRATION-001
Agent: sindo-main-codex
Tests: coordinate probe, M00 dry-run, coordination and documentation gates
Fidelity: five build 12340 camera points round-trip within 0.000015 units
This commit is contained in:
2026-07-11 19:09:46 +04:00
parent 19df5b7968
commit 8e8ea32ba3
6 changed files with 192 additions and 1 deletions
@@ -0,0 +1,78 @@
# M00-QAR-COORD-CALIBRATION-001 — Renderer coordinate calibration
<!-- OPENWC_CLAIM:M00-QAR-COORD-CALIBRATION-001:sindo-main-codex:2026-07-13 -->
## Ownership
- Target: M00
- Program: QAR
- Owner/Agent ID: sindo-main-codex
- Branch: `work/sindo-main-codex/m00-coordinate-calibration`
- Lease expires UTC: 2026-07-13
- Integrator: milestone integrator
## Outcome
Record the five observed build 12340 camera positions as non-proprietary golden coordinates and determine whether the existing WoW/Godot position formula round-trips them.
## Non-goals
- Introducing the M01 production `CoordinateMapper` contract.
- Changing terrain, placement, WMO, liquid, or streaming behavior.
- Claiming camera composition parity from coordinate round-trip alone.
## Paths
- Exclusive: `src/tools/verify_render_coordinate_calibration.gd`
- Shared/hotspots: `src/tools/render_baseline_manifest.json`, renderer baseline documentation
- Generated/ignored: original-client screenshots and local reports
## Contracts and data
- Public API/events: none; headless diagnostic only
- Schema/format version: additive checkpoint calibration metadata, manifest schema remains 1
- Migration/compatibility: existing capture consumers ignore additive fields
- Consumers: M00 fidelity workflow and future M01 golden fixtures
## Dependencies
- Requires: five accepted original-client build 12340 viewpoints
- Blocks: diagnosis of paired camera/placement mismatch
- External state: screenshots remain outside Git
## Verification
- Commands: coordinate calibration probe, baseline manifest, coordination and documentation gates
- Fixtures: five `reference_wow_camera` values in renderer manifest
- Fidelity evidence: positions were observed in build 12340 during the paired session
- Performance budget: negligible headless arithmetic
## Documentation deliverables
- Inline public API docs: diagnostic script header
- Module specification: renderer verification/source map update
- Data-flow diagram: baseline calibration flow update
- Sequence/state/dependency diagrams: not applicable; stateless synchronous probe
- Source map/status updates: renderer module and baseline document
## Simplicity and naming
- Important names introduced: `reference_wow_camera`, `maximum_round_trip_error`
- Simplest considered solution: direct formula probe over manifest fixtures
- Rejected complexity/abstractions: production domain mapper before M01
- Unavoidable complexity and justification: none
- Measured optimization evidence: not applicable
## Status
- State: ready
- Done: five golden camera points, headless round-trip probe, runner integration and documentation
- Next: integrator review; diagnose terrain height/placement/composition separately
- Blocked by:
## Handoff
- Commit: branch HEAD
- Results: five points passed with maximum mapping/round-trip error 0.000015
- Remaining risks: correct position arithmetic does not prove terrain height, placement or camera direction parity
- Documentation updated: `docs/RENDER_BASELINE.md`, `docs/modules/world-renderer.md`
+18
View File
@@ -120,3 +120,21 @@ Baseline пока не имеет approved парных кадров ориги
Первый paired run после калибровки создал десять сравнений (пять reference × cold/warm), без missing pairs. Все пары ожидаемо превысили строгий tolerance: mean perceptual error `0.0707..0.1746`, changed-pixel ratio `0.5504..0.8187`. Human inspection показал, что это пока не чистая material/lighting ошибка: при тех же WoW-derived координатах OpenWC terrain-overview camera находится под terrain/placements, WMO camera — внутри таверны, liquid camera — внутри скал; ADT и dense-M2 композиции также существенно смещены. До исправления coordinate/placement mismatch эти значения являются gap evidence, а не основанием расширять tolerance. Первый paired run после калибровки создал десять сравнений (пять reference × cold/warm), без missing pairs. Все пары ожидаемо превысили строгий tolerance: mean perceptual error `0.0707..0.1746`, changed-pixel ratio `0.5504..0.8187`. Human inspection показал, что это пока не чистая material/lighting ошибка: при тех же WoW-derived координатах OpenWC terrain-overview camera находится под terrain/placements, WMO camera — внутри таверны, liquid camera — внутри скал; ADT и dense-M2 композиции также существенно смещены. До исправления coordinate/placement mismatch эти значения являются gap evidence, а не основанием расширять tolerance.
Capture tool строит camera basis явно из target и world-up. Это исключает неоднозначный roll `look_at` при автоматической съёмке. `ViewportTexture.get_image()` сохраняется без дополнительного vertical flip для Godot 4.6.1. Capture tool строит camera basis явно из target и world-up. Это исключает неоднозначный roll `look_at` при автоматической съёмке. `ViewportTexture.get_image()` сохраняется без дополнительного vertical flip для Godot 4.6.1.
## Coordinate calibration
Пять принятых build 12340 viewpoints записаны в manifest как `reference_wow_camera`. Они содержат только числовые world coordinates и не включают proprietary данные. Headless probe:
```powershell
godot --headless --path . --script res://src/tools/verify_render_coordinate_calibration.gd
```
```mermaid
flowchart LR
O[Observed build 12340 WoW XYZ] --> W[WoW to Godot formula]
W --> G[Manifest Godot camera XYZ]
G --> R[Godot to WoW round-trip]
R --> E[Maximum numeric error]
```
На пяти точках maximum mapping/round-trip error равен `0.000015`. Это исключает текущую формулу `gx = center - wy`, `gy = wz`, `gz = center - wx` как источник крупного paired mismatch. Результат не доказывает renderer parity: следующая диагностика должна отдельно проверить terrain height, placement transforms и фактическое camera direction/FOV. Production `CoordinateMapper` остаётся задачей M01; M00 probe не создаёт второй публичный coordinate contract.
+2 -1
View File
@@ -192,7 +192,7 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
## Verification ## Verification
- Unit/contract tests: material mapping, unique-ID dedupe, placement probes, baseline manifest, synthetic perceptual checkpoint diff. - Unit/contract tests: material mapping, unique-ID dedupe, placement probes, baseline manifest, five-point coordinate calibration, synthetic perceptual checkpoint diff.
- Integration/E2E: Eastern Kingdoms/Kalimdor streaming scenes and seven cold/warm checkpoints. - Integration/E2E: Eastern Kingdoms/Kalimdor streaming scenes and seven cold/warm checkpoints.
- Fidelity evidence: пять локальных build 12340 reference JPG откалибровали terrain/ADT/M2/WMO/liquid viewpoints; automated paired-image metrics exist, но synthetic animation/dusk и полный human approval ещё не закрыты. - Fidelity evidence: пять локальных build 12340 reference JPG откалибровали terrain/ADT/M2/WMO/liquid viewpoints; automated paired-image metrics exist, но synthetic animation/dusk и полный human approval ещё не закрыты.
- Performance budgets: M00 report records cold/warm p95 and max hitch; no final acceptance threshold yet. - Performance budgets: M00 report records cold/warm p95 and max hitch; no final acceptance threshold yet.
@@ -242,6 +242,7 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m
| `tools/run_render_baseline.ps1` | Unified M00 baseline runner | | `tools/run_render_baseline.ps1` | Unified M00 baseline runner |
| `src/tools/compare_render_checkpoints.gd` | Offline JPG/PNG paired-image perceptual metrics and JSON pass/fail report | | `src/tools/compare_render_checkpoints.gd` | Offline JPG/PNG paired-image perceptual metrics and JSON pass/fail report |
| `src/tools/capture_render_checkpoints.gd` | Deterministic no-roll checkpoint camera, performance and visual capture | | `src/tools/capture_render_checkpoints.gd` | Deterministic no-roll checkpoint camera, performance and visual capture |
| `src/tools/verify_render_coordinate_calibration.gd` | Build 12340 camera-coordinate golden point round-trip diagnostic |
## Related decisions and references ## Related decisions and references
+5
View File
@@ -43,6 +43,7 @@
"name": "elwynn_terrain_overview", "name": "elwynn_terrain_overview",
"coverage": ["terrain"], "coverage": ["terrain"],
"camera": [16680.0, 180.0, 26220.0], "camera": [16680.0, 180.0, 26220.0],
"reference_wow_camera": [-9153.334, 386.666, 180.0],
"target": [16800.0, 62.0, 26400.0], "target": [16800.0, 62.0, 26400.0],
"player": [16800.0, 58.0, 26400.0], "player": [16800.0, 58.0, 26400.0],
"time_hours": 13.0 "time_hours": 13.0
@@ -51,6 +52,7 @@
"name": "elwynn_adt_boundary", "name": "elwynn_adt_boundary",
"coverage": ["adt_boundary"], "coverage": ["adt_boundary"],
"camera": [17020.0, 105.0, 26590.0], "camera": [17020.0, 105.0, 26590.0],
"reference_wow_camera": [-9523.334, 46.666, 105.0],
"target": [17066.666, 62.0, 26666.666], "target": [17066.666, 62.0, 26666.666],
"player": [17060.0, 58.0, 26660.0], "player": [17060.0, 58.0, 26660.0],
"time_hours": 13.0 "time_hours": 13.0
@@ -59,6 +61,7 @@
"name": "goldshire_dense_m2", "name": "goldshire_dense_m2",
"coverage": ["dense_m2"], "coverage": ["dense_m2"],
"camera": [16956.666, 150.0, 26466.666], "camera": [16956.666, 150.0, 26466.666],
"reference_wow_camera": [-9400.0, 110.0, 150.0],
"target": [17015.0, 62.0, 26525.0], "target": [17015.0, 62.0, 26525.0],
"player": [17015.0, 58.0, 26525.0], "player": [17015.0, 58.0, 26525.0],
"time_hours": 13.0 "time_hours": 13.0
@@ -67,6 +70,7 @@
"name": "goldshire_inn_large_wmo", "name": "goldshire_inn_large_wmo",
"coverage": ["large_wmo"], "coverage": ["large_wmo"],
"camera": [17013.666, 72.0, 26451.666], "camera": [17013.666, 72.0, 26451.666],
"reference_wow_camera": [-9385.0, 53.0, 72.0],
"target": [17042.27, 66.0, 26530.91], "target": [17042.27, 66.0, 26530.91],
"player": [17042.27, 58.0, 26530.91], "player": [17042.27, 58.0, 26530.91],
"time_hours": 13.0 "time_hours": 13.0
@@ -75,6 +79,7 @@
"name": "elwynn_waterfall_liquid", "name": "elwynn_waterfall_liquid",
"coverage": ["liquid"], "coverage": ["liquid"],
"camera": [16481.666, 190.0, 26366.666], "camera": [16481.666, 190.0, 26366.666],
"reference_wow_camera": [-9300.0, 585.0, 190.0],
"target": [16518.84, 68.0, 26427.27], "target": [16518.84, 68.0, 26427.27],
"player": [16518.84, 55.0, 26427.27], "player": [16518.84, 55.0, 26427.27],
"time_hours": 13.0 "time_hours": 13.0
@@ -0,0 +1,88 @@
extends SceneTree
## Verifies observed build 12340 camera coordinates against the current renderer
## WoW X/Y/Z to Godot X/Y/Z convention. This diagnostic does not define the
## future M01 production CoordinateMapper contract.
const MANIFEST_PATH := "res://src/tools/render_baseline_manifest.json"
const WOW_WORLD_CENTER := 17066.666
const MAXIMUM_POSITION_ERROR := 0.002
func _initialize() -> void:
var manifest := _load_manifest()
if manifest.is_empty():
quit(1)
return
var failures: Array[String] = []
var calibrated_count := 0
var maximum_round_trip_error := 0.0
for checkpoint_variant in manifest.get("checkpoints", []):
if not (checkpoint_variant is Dictionary):
continue
var checkpoint: Dictionary = checkpoint_variant
var reference_wow_camera_variant = checkpoint.get("reference_wow_camera", null)
if not (reference_wow_camera_variant is Array):
continue
var reference_wow_camera: Array = reference_wow_camera_variant
var checkpoint_name := String(checkpoint.get("name", "checkpoint"))
if reference_wow_camera.size() != 3:
failures.append("%s reference_wow_camera must contain three coordinates" % checkpoint_name)
continue
var expected_godot_camera := _array_to_vector3(checkpoint.get("camera", []))
var reference_wow_position := _array_to_vector3(reference_wow_camera)
var mapped_godot_position := _wow_to_godot(reference_wow_position)
var round_trip_wow_position := _godot_to_wow(mapped_godot_position)
var mapping_error := mapped_godot_position.distance_to(expected_godot_camera)
var round_trip_error := round_trip_wow_position.distance_to(reference_wow_position)
maximum_round_trip_error = maxf(maximum_round_trip_error, maxf(mapping_error, round_trip_error))
if mapping_error > MAXIMUM_POSITION_ERROR:
failures.append("%s maps %.6f units away from manifest camera" % [checkpoint_name, mapping_error])
if round_trip_error > MAXIMUM_POSITION_ERROR:
failures.append("%s round-trip error is %.6f" % [checkpoint_name, round_trip_error])
calibrated_count += 1
if calibrated_count < 5:
failures.append("expected at least five build 12340 camera calibrations, found %d" % calibrated_count)
if not failures.is_empty():
for failure in failures:
push_error("RENDER_COORDINATE_CALIBRATION: %s" % failure)
quit(1)
return
print("RENDER_COORDINATE_CALIBRATION PASS points=%d maximum_error=%.6f" % [calibrated_count, maximum_round_trip_error])
quit(0)
func _wow_to_godot(wow_position: Vector3) -> Vector3:
return Vector3(
WOW_WORLD_CENTER - wow_position.y,
wow_position.z,
WOW_WORLD_CENTER - wow_position.x
)
func _godot_to_wow(godot_position: Vector3) -> Vector3:
return Vector3(
WOW_WORLD_CENTER - godot_position.z,
WOW_WORLD_CENTER - godot_position.x,
godot_position.y
)
func _array_to_vector3(value_variant) -> Vector3:
if not (value_variant is Array) or value_variant.size() != 3:
return Vector3.ZERO
return Vector3(float(value_variant[0]), float(value_variant[1]), float(value_variant[2]))
func _load_manifest() -> Dictionary:
var manifest_file := FileAccess.open(MANIFEST_PATH, FileAccess.READ)
if manifest_file == null:
push_error("Cannot open renderer baseline manifest: %s" % MANIFEST_PATH)
return {}
var parsed = JSON.parse_string(manifest_file.get_as_text())
if not (parsed is Dictionary):
push_error("Renderer baseline manifest is not a JSON object")
return {}
return parsed
+1
View File
@@ -46,6 +46,7 @@ try {
Invoke-GodotStep "render-materials" @("--headless", "--path", ".", "--script", "res://src/tools/verify_render_materials.gd") Invoke-GodotStep "render-materials" @("--headless", "--path", ".", "--script", "res://src/tools/verify_render_materials.gd")
Invoke-GodotStep "m2-unique-dedupe" @("--headless", "--path", ".", "--script", "res://src/tools/verify_m2_unique_dedupe.gd") Invoke-GodotStep "m2-unique-dedupe" @("--headless", "--path", ".", "--script", "res://src/tools/verify_m2_unique_dedupe.gd")
Invoke-GodotStep "baseline-manifest" @("--headless", "--path", ".", "--script", "res://src/tools/verify_render_baseline_manifest.gd") Invoke-GodotStep "baseline-manifest" @("--headless", "--path", ".", "--script", "res://src/tools/verify_render_baseline_manifest.gd")
Invoke-GodotStep "coordinate-calibration" @("--headless", "--path", ".", "--script", "res://src/tools/verify_render_coordinate_calibration.gd")
$captureArgs = @( $captureArgs = @(
"--path", ".", "--path", ".",