From 42fdf40282deb67c21b7a5d35825ae440590a233 Mon Sep 17 00:00:00 2001 From: sindoring Date: Sat, 11 Jul 2026 22:06:22 +0400 Subject: [PATCH] test(M00): diagnose waterfall tile ownership Work-Package: M00-QAR-TILE-OWNERSHIP-001 Agent: sindo-main-codex Tests: strict five-point terrain probe, coordination and documentation gates Fidelity: tile 30_49 ownership is healthy; exact ray miss classified as triangle seam --- .../claims/M00-QAR-TILE-OWNERSHIP-001.md | 78 +++++++++++++++++++ docs/RENDER_BASELINE.md | 4 +- docs/modules/world-renderer.md | 2 +- src/tools/probe_render_terrain_height.gd | 74 +++++++++++++++++- 4 files changed, 155 insertions(+), 3 deletions(-) create mode 100644 coordination/claims/M00-QAR-TILE-OWNERSHIP-001.md diff --git a/coordination/claims/M00-QAR-TILE-OWNERSHIP-001.md b/coordination/claims/M00-QAR-TILE-OWNERSHIP-001.md new file mode 100644 index 0000000..0ab9c3b --- /dev/null +++ b/coordination/claims/M00-QAR-TILE-OWNERSHIP-001.md @@ -0,0 +1,78 @@ +# M00-QAR-TILE-OWNERSHIP-001 — Waterfall terrain tile ownership + + + +## Ownership + +- Target: M00 +- Program: QAR +- Owner/Agent ID: sindo-main-codex +- Branch: `work/sindo-main-codex/m00-waterfall-tile-ownership` +- Lease expires UTC: 2026-07-13 +- Integrator: milestone integrator + +## Outcome + +Identify the runtime transition that prevents waterfall tile `30_49` from exposing a terrain mesh to the height probe. + +## Non-goals + +- Changing streaming budgets or terrain behavior. +- Rebuilding caches or modifying extracted assets. +- Adding a production terrain query service. + +## Paths + +- Exclusive: terrain probe runtime ownership diagnostics +- Shared/hotspots: renderer baseline documentation +- Generated/ignored: local probe reports and caches + +## Contracts and data + +- Public API/events: additive diagnostic JSON fields +- Schema/format version: terrain report remains schema 1 +- Migration/compatibility: additive fields only +- Consumers: M00 fidelity diagnosis + +## Dependencies + +- Requires: merged terrain-height probe and local cache inventory +- Blocks: waterfall terrain ownership classification +- External state: local extracted/cache data + +## Verification + +- Commands: isolated waterfall probe, coordination and documentation gates +- Fixtures: checkpoint tile `30_49` +- Fidelity evidence: runtime state correlated with build 12340 waterfall viewpoint +- Performance budget: offline diagnostic + +## Documentation deliverables + +- Inline public API docs: diagnostic output fields +- Module specification: verification/source map if behavior changes +- Data-flow diagram: update ownership transition if needed +- Sequence/state/dependency diagrams: document observed tile transition +- Source map/status updates: baseline findings + +## Simplicity and naming + +- Important names introduced: `available`, `queued_index`, `loading`, `state_present`, `mesh_source` +- Simplest considered solution: inspect existing loader registries read-only +- Rejected complexity/abstractions: new tracing framework +- Unavoidable complexity and justification: none +- Measured optimization evidence: not applicable + +## Status + +- State: ready +- Done: raw/cache inventory, isolated runtime state, mesh AABB/local probe and nearby sampling diagnosis +- Next: integrator review; placement/composition remains the actual paired-camera gap +- Blocked by: + +## Handoff + +- Commit: branch HEAD +- Results: tile 30_49 ownership and meshes are healthy; exact ray misses a triangle seam/edge, while a 2-unit offset samples terrain at 113.872 +- Remaining risks: nearby estimate is diagnostic and must not become a gameplay terrain-query contract +- Documentation updated: `docs/RENDER_BASELINE.md`, `docs/modules/world-renderer.md` diff --git a/docs/RENDER_BASELINE.md b/docs/RENDER_BASELINE.md index 4ef6a09..394618b 100644 --- a/docs/RENDER_BASELINE.md +++ b/docs/RENDER_BASELINE.md @@ -155,4 +155,6 @@ flowchart LR T --> H[Terrain height and camera clearance] ``` -Измеренный clearance: terrain overview `89.044`, ADT boundary `44.788`, dense M2 `90.178`, large WMO `12.034` Godot units. Следовательно, эти четыре камеры находятся над rendered terrain; visual obstruction принадлежит placements/WMO/composition, а не terrain height. Waterfall tile `30_49` не публикует доступный terrain mesh даже после isolated 10-second settle; это отдельный streaming/terrain ownership gap. Default probe сохраняет partial report, а `--require-all` включает строгий ненулевой exit при любой неснятой точке. +Измеренный clearance: terrain overview `89.044`, ADT boundary `44.788`, dense M2 `90.178`, large WMO `12.034`, waterfall примерно `76.128` Godot units. Следовательно, все пять камер находятся над rendered terrain; visual obstruction принадлежит placements/WMO/composition, а не terrain height. + +Waterfall XZ сначала давал `no_intersection`, хотя tile `30_49` был available, полностью загружен, имел `control_splat_cache` quality mesh и LOD0 mesh, а probe находился внутри mesh AABB. Ray со смещением `2.0` units пересёк тот же mesh на высоте `113.872`; точная XZ попала на triangle seam/edge numerical miss. Probe теперь сообщает `sampled_nearby`, distance и source tile вместо ложного streaming ownership gap. `--require-all` остаётся строгим режимом для действительно неснятых точек. diff --git a/docs/modules/world-renderer.md b/docs/modules/world-renderer.md index d038545..0e0b1d4 100644 --- a/docs/modules/world-renderer.md +++ b/docs/modules/world-renderer.md @@ -223,7 +223,7 @@ Exact exported settings and cache versions remain documented in [`../../RENDER.m - Direct camera path remains until M01/M03. - Original-client paired fidelity evidence incomplete. - Первый paired run выявил coordinate/placement mismatch: несколько совпадающих server-derived camera positions оказываются под terrain или внутри WMO/rocks OpenWC. -- Terrain-height probe исключил under-terrain состояние для четырёх точек; waterfall tile `30_49` не предоставил terrain mesh после isolated settle. +- Terrain-height probe исключил under-terrain состояние для всех пяти точек; waterfall exact-XZ miss классифицирован как TriangleMesh seam/edge и подтверждён nearby sample в 2 units. - D3D12 descriptor and shutdown RID/resource issues remain. - M2/WMO/material/particle/ribbon/portal parity incomplete. - Public API is mostly exported configuration rather than stable contracts. diff --git a/src/tools/probe_render_terrain_height.gd b/src/tools/probe_render_terrain_height.gd index 12955a0..6a0502c 100644 --- a/src/tools/probe_render_terrain_height.gd +++ b/src/tools/probe_render_terrain_height.gd @@ -112,10 +112,18 @@ func _sample_terrain(world: Node3D, world_position: Vector3) -> Dictionary: highest_terrain_height = terrain_height intersected_tile_key = tile_key if not is_finite(highest_terrain_height): - return { + var missing_result := { "status": "no_intersection" if ready_mesh_count > 0 else "mesh_not_ready", "tile": "%d_%d" % [tile_coordinate.x, tile_coordinate.y], } + missing_result.merge(_tile_runtime_diagnostic(world, String(missing_result.tile), world_position), true) + if bool(missing_result.get("quality_mesh_present", false)) or bool(missing_result.get("tile_lod_mesh_present", false)): + missing_result["status"] = "no_intersection" + missing_result.merge(_nearest_terrain_sample(world, world_position), true) + if missing_result.get("nearest_sample_distance", null) != null: + missing_result["status"] = "sampled_nearby" + missing_result["terrain_height"] = float(missing_result.nearest_sample_height) + return missing_result return { "status": "sampled", "tile": intersected_tile_key, @@ -146,6 +154,70 @@ func _intersect_terrain_state(state: Dictionary, world_position: Vector3): return world_hit.y +func _tile_runtime_diagnostic(world: Node3D, tile_key: String, world_position: Vector3) -> Dictionary: + var available_tiles: Dictionary = world.get("_available_tiles") + var loading_tasks: Dictionary = world.get("_tile_loading_tasks") + var tile_states: Dictionary = world.get("_tile_states") + var load_queue: Array = world.get("_tile_load_queue") + var queued_index := -1 + for index in load_queue.size(): + var request: Dictionary = load_queue[index] + if String(request.get("key", "")) == tile_key: + queued_index = index + break + var diagnostic := { + "available": available_tiles.has(tile_key), + "queued_index": queued_index, + "loading": loading_tasks.has(tile_key), + "state_present": tile_states.has(tile_key), + "load_queue_size": load_queue.size(), + } + if tile_states.has(tile_key): + var state: Dictionary = tile_states[tile_key] + var quality_mesh: Mesh = state.get("quality_terrain_mesh", null) + var tile_lod_mesh: Mesh = state.get("tile_lod_mesh", null) + diagnostic["quality_mesh_present"] = quality_mesh != null + diagnostic["tile_lod_mesh_present"] = tile_lod_mesh != null + diagnostic["quality_source"] = String(state.get("quality_terrain_source", "")) + diagnostic["tile_lod"] = int(state.get("tile_lod", -1)) + var tile_root := state.get("root", null) as Node3D + if tile_root != null: + var local_position := tile_root.global_transform.affine_inverse() * world_position + diagnostic["tile_root_position"] = _vector3_array(tile_root.global_position) + diagnostic["probe_local_position"] = _vector3_array(local_position) + var diagnostic_mesh := quality_mesh if quality_mesh != null else tile_lod_mesh + if diagnostic_mesh != null: + var mesh_aabb := diagnostic_mesh.get_aabb() + diagnostic["mesh_aabb_position"] = _vector3_array(mesh_aabb.position) + diagnostic["mesh_aabb_size"] = _vector3_array(mesh_aabb.size) + return diagnostic + + +func _nearest_terrain_sample(world: Node3D, world_position: Vector3) -> Dictionary: + var tile_states: Dictionary = world.get("_tile_states") + for radius in [2.0, 5.0, 10.0, 20.0, 40.0]: + for offset in [Vector2(radius, 0.0), Vector2(-radius, 0.0), Vector2(0.0, radius), Vector2(0.0, -radius)]: + var sample_position := world_position + Vector3(offset.x, 0.0, offset.y) + var sample_tile := Vector2i( + int(floor(sample_position.x / TILE_SIZE)), + int(floor(sample_position.z / TILE_SIZE))) + var sample_key := "%d_%d" % [sample_tile.x, sample_tile.y] + if not tile_states.has(sample_key): + continue + var height_variant = _intersect_terrain_state(tile_states[sample_key], sample_position) + if height_variant != null: + return { + "nearest_sample_distance": radius, + "nearest_sample_height": float(height_variant), + "nearest_sample_tile": sample_key, + } + return {"nearest_sample_distance": null} + + +func _vector3_array(value: Vector3) -> Array[float]: + return [value.x, value.y, value.z] + + func _vector3(value_variant) -> Vector3: if not (value_variant is Array) or value_variant.size() != 3: return Vector3.ZERO -- 2.52.0