diff --git a/client/tests/test_atlas_window_geometry_nature.gd b/client/tests/test_atlas_window_geometry_nature.gd index 3c7d63315..73486d8d9 100644 --- a/client/tests/test_atlas_window_geometry_nature.gd +++ b/client/tests/test_atlas_window_geometry_nature.gd @@ -281,3 +281,46 @@ func test_zoom_compensated_size_zero_zoom_does_not_blow_up() -> void: assert_bool(is_finite(result)).override_failure_message( "a degenerate zero view_zoom must not produce inf/NaN" ).is_true() + + +# ============================================================================= +# T-1172 round 2: cell_index_for_local_offset() — the shared painter/clip +# index formula (see its own doc for the "why shared, not duplicated" case). +# ============================================================================= + + +func test_cell_index_for_local_offset_top_left_is_zero_zero() -> void: + var cell: Vector2i = AtlasWindowGeometry.cell_index_for_local_offset(0.0, 0.0, 6400, 64) + assert_that(cell).is_equal(Vector2i(0, 0)) + + +## The exact live-repro numbers from T-1172 round 2's trace: a query whose +## district-space local offset is (2594.09, 5593.15) inside a 6400-wide, +## 64-cell-side window must resolve to (col=25, row=55) — pinned directly +## against the LIVE captured values that closed the investigation (both the +## painter's _build_tile_texture() and the clip independently produced this +## exact pair for the same query in the live trace). +func test_cell_index_for_local_offset_matches_the_live_trace_repro() -> void: + var cell: Vector2i = AtlasWindowGeometry.cell_index_for_local_offset( + 2594.0849609375, 5593.15258789062, 6400, 64 + ) + assert_that(cell).override_failure_message( + "must match the live-captured painter/clip agreement point from the" + + " T-1172 round 2 investigation — (col=25, row=55)" + ).is_equal(Vector2i(25, 55)) + + +func test_cell_index_for_local_offset_bottom_right_boundary_clamps_inside() -> void: + # local offset == n (the exclusive upper boundary) must clamp to the LAST + # cell, not overflow to a nonexistent grid_side'th cell. + var cell: Vector2i = AtlasWindowGeometry.cell_index_for_local_offset(6400.0, 6400.0, 6400, 64) + assert_that(cell).is_equal(Vector2i(63, 63)) + + +func test_cell_index_for_local_offset_zero_n_or_grid_side_returns_sentinel() -> void: + assert_that(AtlasWindowGeometry.cell_index_for_local_offset(10.0, 10.0, 0, 64)).is_equal( + Vector2i(-1, -1) + ) + assert_that(AtlasWindowGeometry.cell_index_for_local_offset(10.0, 10.0, 6400, 0)).is_equal( + Vector2i(-1, -1) + ) diff --git a/client/tests/test_atlas_window_water_clip.gd b/client/tests/test_atlas_window_water_clip.gd index 5c927a5b6..ef16c4294 100644 --- a/client/tests/test_atlas_window_water_clip.gd +++ b/client/tests/test_atlas_window_water_clip.gd @@ -209,28 +209,160 @@ func test_resolve_tile_mode_empty_tile_list_is_no_data() -> void: assert_int(zone).is_equal(AtlasWindowWaterClip.MORPHOLOGY_ZONE_NO_DATA) -## Wrap case: a tile whose CANONICAL center is near the antimeridian, queried -## with a district position expressed in a DIFFERENT (unwrapped) longitude — -## resolve_morphology_zone() must re-express the query against the tile's -## own wrap-image before testing containment, the same discipline -## AtlasWindowOverlay._draw_tile_mosaic() already uses for drawing. -func test_resolve_tile_mode_wraps_the_query_to_the_tiles_own_image() -> void: +## Live round 2 fix (coordinator's trace, T-1172): a tile whose CANONICAL +## center is far from `held_center` (the seam-tile case — exactly Lendel's +## own live repro, tile canonical center 12739 drawn at draw_col=-6400) must +## have its CENTER wrapped toward `held_center_x` — mirroring +## AtlasWindowOverlay._draw_tile_mosaic()'s own `draw_col = +## nearest_wrap_image(center.x, held_center.x, cols)` EXACTLY — before +## testing containment. The query `district` is assumed ALREADY expressed in +## the held-center-wrapped frame (AtlasWindowNatureOverlay._district()'s own +## contract) and is NOT separately re-wrapped. +## +## Original (pre-fix) test asserted the INVERSE — wrapping the query toward +## the tile's raw canonical center — which was the actual bug: it happened +## to land inside the tile's CANONICAL (unwrapped) span by coincidental mod +## arithmetic, silently testing the WRONG real-world location whenever a +## tile needed wrapping to appear on screen at all. Live capture evidence: +## a river dot at district.x=-9569 sitting on the painter's WEST wrap-image +## of a seam tile (canonical center 12739, draw_col=-6400) read a real but +## wrong-location land cell under the old code, and only stopped doing so +## once resolve_morphology_zone() wrapped the TILE's center instead. +func test_resolve_tile_mode_wraps_the_tiles_own_center_toward_held_center() -> void: var cols := 100 var morph := PackedByteArray() morph.resize(16) for i in range(16): morph[i] = MORPHOLOGY_OPEN_OCEAN - # Tile's canonical center is column 2 (near the origin side of the wrap). - var tile := {"center": Vector2i(2, 0), "window": _mock_district_window(Vector2i(2, 0), 4, morph)} - # Query at column -98 — NOT canonical (canonicalizes to 2 under mod 100), - # but expressed as the "west of origin" wrap-image the real district - # position lives at (matching Lendel's own antimeridian repro shape from - # the T-1156 nearest_wrap_image() tests). + # Tile's canonical center is column 98 (far east) — but its nearest + # wrap-image to held_center=0 is column -2 (98 - 100), matching the + # Lendel seam tile's own shape (canonical 12739 -> draw_col -6400). + var tile := {"center": Vector2i(98, 0), "window": _mock_district_window(Vector2i(98, 0), 4, morph)} + # Query at column -2.5 — inside the tile's WRAP-IMAGE span [-4, 0), the + # real on-screen location, held_center-relative (the caller's own + # _district() contract) — NOT inside the canonical span [96, 100). var zone: int = AtlasWindowWaterClip.resolve_morphology_zone( - Vector2(-98.0, 0.0), true, null, [tile], cols + Vector2(-2.5, 0.0), true, null, [tile], cols, 0 ) assert_int(zone).override_failure_message( - "a query expressed in a different (non-canonical) wrap-image of the same" - + " real position must still resolve against the tile whose canonical" - + " center it's periodic-equivalent to" + "the tile's CENTER must be wrapped toward held_center_x (mirroring the" + + " painter's draw_col computation) so a query already expressed in the" + + " held-center frame resolves against the tile's REAL on-screen wrap-image" ).is_equal(MORPHOLOGY_OPEN_OCEAN) + + +## The INVERSE position — a query at the tile's CANONICAL (unwrapped) span — +## must NOT resolve against this tile once wrapping is applied, since that +## span is no longer where the tile actually draws relative to held_center. +## Pins that the fix doesn't just "also succeed at the old span" by accident. +func test_resolve_tile_mode_does_not_match_the_tiles_stale_canonical_span() -> void: + var cols := 100 + var morph := PackedByteArray() + morph.resize(16) + for i in range(16): + morph[i] = MORPHOLOGY_OPEN_OCEAN + var tile := {"center": Vector2i(98, 0), "window": _mock_district_window(Vector2i(98, 0), 4, morph)} + # Query at column 97 — inside the tile's CANONICAL span [96,100) — but + # that is NOT where this tile is drawn relative to held_center=0 (it's + # drawn at the wrap-image [-4,0) instead), so this must NOT resolve. + var zone: int = AtlasWindowWaterClip.resolve_morphology_zone( + Vector2(97.0, 0.0), true, null, [tile], cols, 0 + ) + assert_int(zone).override_failure_message( + "a query at the tile's stale CANONICAL span must not resolve against it" + + " once the tile is wrapped toward held_center — that span is not where" + + " the tile actually draws on screen" + ).is_equal(AtlasWindowWaterClip.MORPHOLOGY_ZONE_NO_DATA) + + +# ============================================================================= +# T-1172 round 2 (coordinator's "wire-accurate fixture" hardening ask — +# cold-start batch discipline): a fixture derived from an ACTUAL live tile +# response captured during the round-2 investigation, at the REAL wire scale +# (n=6400, grid_side=64, Region granularity) — not a hand-shrunk 4x4 mock. +# The round-2 bug (wrapping the query toward the tile instead of the tile +# toward held_center) passed EVERY test against the small mocks above, +## because those mocks never modeled a tile whose canonical center is FAR +# from held_center — the exact condition the bug needed to manifest. This +# fixture reproduces that condition at production scale, so a future +# regression of the same SHAPE (stub-and-code silently agreeing on a wrong +# convention) can't hide behind "the small tests still pass." +# ============================================================================= + + +## Lendel's own seam tile from the live drive capture that closed T-1172 +## round 2 (client/tmp_drive_clip.gd, SR_LIVE=1 against the worktree release +## server): canonical center (12739, -3200), TILE_N=6400 districts, +## Region granularity (grid_side = round(6400/100) = 64). `cols=19139` +## matches Lendel's real district_extent() circumference. The morphology +## array is NOT the real 4096-byte payload (too large to hand-author) — only +## the ONE cell index the live trace actually resolved for the +## district=-9569.414 repro query (idx=1024, col=0/row=16 — the exact +## INDEX_TRACE line from the live investigation) is given a real value; +## every other cell is left at 0 (OpenOcean), which is irrelevant here since +## this fixture exists to pin the WRAP resolution reaching the CORRECT +## tile/cell pair, not to re-verify the index math itself (already covered +## above and in test_atlas_window_geometry_nature.gd). +static func _lendel_seam_tile_fixture() -> Dictionary: + var morph := PackedByteArray() + morph.resize(4096) + morph[1024] = MORPHOLOGY_LAND # col=0, row=16 — the live-traced cell + return { + "center": Vector2i(12739, -3200), + "window": { + "center": [12739, -3200], + "n": 6400, + "granularity_v2": "Region", + "morphology": morph, + } + } + + +## The exact district position from the live capture that ORIGINALLY exposed +## the round-2 bug (district.x=-9569.414 — a dot visibly sitting on the +## painter's WEST wrap-image of the seam tile). held_center_x=0 (the +## viewer's canonical orbital-frame origin, cols=19139 (Lendel's real +## circumference in districts). Must resolve to the SAME land zone the live +## painter trace independently confirmed for this exact query. +## +## Honest note (found DURING revert-verification, worth recording): for a +## SINGLE tile in isolation, the old (query-wrapped-toward-tile) and new +## (tile-wrapped-toward-held_center) formulas are mathematically GUARANTEED +## to agree whenever local_x lands in-range for both — both reduce to +## `query - tile_center (mod cols)`, and a valid `local_x` is unique in +## `[0, n)`. This single-tile fixture therefore does NOT independently +## distinguish old from new (confirmed: it still passes with the pre-fix +## code) — it locks in the real wire-scale numbers as a realistic regression +## fixture (shared index formula, tile shape, wrap arithmetic all exercised +## together), not as the old-vs-new discriminator. The tests that DO reliably +## catch the round-2 regression are +## test_resolve_tile_mode_wraps_the_tiles_own_center_toward_held_center and +## test_resolve_tile_mode_does_not_match_the_tiles_stale_canonical_span above +## (confirmed: the latter fails by name against the reverted code) — the +## real-world bug's actual mechanism was the MULTI-TILE SCAN ORDER matching +## the WRONG tile's data before reaching the right one, not a single-tile +## formula divergence; a true multi-tile live reproduction would need the +## full 6-tile fixture, impractical to hand-author at full 4096-cell scale. +func test_wire_accurate_lendel_seam_tile_resolves_correctly() -> void: + var tile: Dictionary = _lendel_seam_tile_fixture() + var zone: int = AtlasWindowWaterClip.resolve_morphology_zone( + Vector2(-9569.414, -4784.793), true, null, [tile], 19139, 0 + ) + assert_int(zone).override_failure_message( + "the live T-1172 round 2 repro position must resolve against the seam" + + " tile's WRAPPED (on-screen) image and read the real traced land zone" + + " — a regression here reproduces the ORIGINAL over-ocean-dots bug" + ).is_equal(MORPHOLOGY_LAND) + + +## The SAME fixture, queried at a position that legitimately falls OUTSIDE +## even the wrapped tile's span (nowhere near either wrap-image) — must fail +## open (NO_DATA), not silently match by coincidental mod arithmetic (the +## general shape of the original bug, pinned generically here in case a +## future change reintroduces a different mod-arithmetic coincidence). +func test_wire_accurate_lendel_seam_tile_out_of_range_query_is_no_data() -> void: + var tile: Dictionary = _lendel_seam_tile_fixture() + var zone: int = AtlasWindowWaterClip.resolve_morphology_zone( + Vector2(500.0, 500.0), true, null, [tile], 19139, 0 + ) + assert_int(zone).is_equal(AtlasWindowWaterClip.MORPHOLOGY_ZONE_NO_DATA) diff --git a/client/ui/implant/apps/atlas/atlas_window_geometry.gd b/client/ui/implant/apps/atlas/atlas_window_geometry.gd index 649458607..e6f1657fe 100644 --- a/client/ui/implant/apps/atlas/atlas_window_geometry.gd +++ b/client/ui/implant/apps/atlas/atlas_window_geometry.gd @@ -922,3 +922,33 @@ static func attractors_visible_at_rung(granularity_v2: String) -> bool: ## for every real caller and only guards a malformed test input. static func zoom_compensated_size(screen_space_size: float, view_zoom: float) -> float: return screen_space_size / maxf(view_zoom, 0.0001) + + +## T-1172 round 2 (coordinator's "reconsider the split" ask): the SHARED +## cell-index formula both AtlasWindowOverlay's terrain painter (which builds +## the drawn `grid_side x grid_side` per-cell texture — `i = row * grid_side +## + col`, `img.set_pixel(col, row, ...)`) and AtlasWindowWaterClip's clip +## predicate (which must read the EXACT SAME cell for a given position, or +## the clip silently disagrees with what's actually painted) both need. The +## live trace that closed T-1172 round 2's investigation PROVED this formula +## itself was never the bug (painter and clip independently computed the +## identical col/row/idx for the same query throughout) — the actual bug was +## in the WRAP resolution one layer up (resolve_morphology_zone()'s own doc) +## — but factoring the index math into ONE shared function here, rather than +## two independently-maintained copies (atlas_window_overlay.gd's inline +## `row * grid_side + col` vs. the old duplicate in +## atlas_window_water_clip.gd), removes the STRUCTURAL risk of a future +## divergence in exactly the way the coordinator flagged as the general +## danger class ("stub-and-code agreeing on the wrong convention" — here, +## PAINTER-and-clip could drift the same way without a shared source). +## `local_x`/`local_y` are DISTRICT-SPACE offsets from the window's own +## top-left corner (`center - n/2`), in `[0, n)` — the SAME quantity both +## call sites already compute before this function is reached. +static func cell_index_for_local_offset( + local_x: float, local_y: float, n: int, grid_side: int +) -> Vector2i: + if n <= 0 or grid_side <= 0: + return Vector2i(-1, -1) + var col: int = clampi(int(floor(local_x / float(n) * float(grid_side))), 0, grid_side - 1) + var row: int = clampi(int(floor(local_y / float(n) * float(grid_side))), 0, grid_side - 1) + return Vector2i(col, row) diff --git a/client/ui/implant/apps/atlas/atlas_window_nature_overlay.gd b/client/ui/implant/apps/atlas/atlas_window_nature_overlay.gd index b6d34ba3c..aba4ee391 100644 --- a/client/ui/implant/apps/atlas/atlas_window_nature_overlay.gd +++ b/client/ui/implant/apps/atlas/atlas_window_nature_overlay.gd @@ -262,7 +262,12 @@ func _pos(row: float, col: float, ctx: Dictionary) -> Vector2: ## through AtlasWindowWaterClip.resolve_morphology_zone(), which handles ## BOTH the single-window rung path and Region tile mode internally — this ## function never branches on viewer.is_tile_mode() itself, matching that -## function's own "single dispatch point" doc. +## function's own "single dispatch point" doc. `ctx["held_center"].x` is +## threaded through (live round 2, coordinator's trace) — tile-mode +## resolution must wrap each tile's CENTER toward held_center EXACTLY like +## AtlasWindowOverlay._draw_tile_mosaic()'s own `draw_col` computation, or +## the clip silently tests the wrong wrap-image of a seam tile (see +## resolve_morphology_zone()'s own doc for the live repro). func _is_drawn_water(district: Vector2, ctx: Dictionary) -> bool: var is_tile_mode: bool = viewer.is_tile_mode() var single_window: Variant = null if is_tile_mode else viewer.get_district_window() @@ -271,8 +276,9 @@ func _is_drawn_water(district: Vector2, ctx: Dictionary) -> bool: var tile_set = viewer.get_tile_set() if tile_set != null: tiles = tile_set.get_tiles() + var held_center: Vector2i = ctx["held_center"] var zone: int = AtlasWindowWaterClip.resolve_morphology_zone( - district, is_tile_mode, single_window, tiles, ctx["cols"] + district, is_tile_mode, single_window, tiles, ctx["cols"], held_center.x ) if zone == AtlasWindowWaterClip.MORPHOLOGY_ZONE_NO_DATA: return false diff --git a/client/ui/implant/apps/atlas/atlas_window_water_clip.gd b/client/ui/implant/apps/atlas/atlas_window_water_clip.gd index 73b2cb3cd..acb5e699f 100644 --- a/client/ui/implant/apps/atlas/atlas_window_water_clip.gd +++ b/client/ui/implant/apps/atlas/atlas_window_water_clip.gd @@ -89,9 +89,13 @@ static func morphology_zone_in_window(district: Vector2, w: Variant) -> int: var grid_side: int = cell_grid_side_for_window(window) if grid_side <= 0: return MORPHOLOGY_ZONE_NO_DATA - var col: int = clampi(int(floor(local_x / float(n) * float(grid_side))), 0, grid_side - 1) - var row: int = clampi(int(floor(local_y / float(n) * float(grid_side))), 0, grid_side - 1) - var idx: int = row * grid_side + col + # T-1172 round 2: SHARED index formula with the terrain painter + # (AtlasWindowGeometry.cell_index_for_local_offset() — see its own doc + # for why this is now factored out instead of duplicated). + var cell: Vector2i = AtlasWindowGeometryRef.cell_index_for_local_offset( + local_x, local_y, n, grid_side + ) + var idx: int = cell.y * grid_side + cell.x if idx < 0 or idx >= morphology.size(): return MORPHOLOGY_ZONE_NO_DATA return int(morphology[idx]) @@ -103,23 +107,37 @@ static func morphology_zone_in_window(district: Vector2, w: Variant) -> int: ## is_tile_mode() branch. Single-window mode: one direct ## morphology_zone_in_window() call against `single_window`. Tile mode: ## linear scan of `tiles` (Array of {"center": Vector2i, "window": Variant}, -## AtlasWindowTileSet.get_tiles()'s own shape) for whichever tile's -## `[center - TILE_N/2, center + TILE_N/2)` extent contains the position — -## each tile's OWN echoed `window["n"]` is used for the actual containment -## test (not TILE_N assumed), matching this cluster's "the response is the -## source of truth for what it actually contains" precedent, since a -## clamped/still-arriving tile's real extent can differ from the nominal -## per-tile request size. `district.x` is WRAP-RESOLVED against each tile's -## own canonical center via AtlasWindowGeometry.nearest_wrap_image() before -## the containment test — a tile's `center` is always canonical (wrapped -## into `[0, cols)`), but the queried district position may be expressed in -## a DIFFERENT wrap-image (e.g. a river dot near the antimeridian) — the -## same "re-express before comparing" discipline -## AtlasWindowOverlay._draw_tile_mosaic() and AtlasWindowNatureOverlay's own -## _pos() already use. `cols <= 0` (no-radius body) is a safe no-op -## passthrough (nearest_wrap_image()'s own contract). +## AtlasWindowTileSet.get_tiles()'s own shape) for whichever tile's ON-SCREEN +## extent contains the position — each tile's OWN echoed `window["n"]` is +## used for the actual containment test (not TILE_N assumed), matching this +## cluster's "the response is the source of truth for what it actually +## contains" precedent, since a clamped/still-arriving tile's real extent +## can differ from the nominal per-tile request size. +## +## **Live round 2 fix (coordinator's trace, T-1172):** the wrap resolution +## MUST mirror AtlasWindowOverlay._draw_tile_mosaic()'s own +## `draw_col = nearest_wrap_image(center.x, held_center.x, cols)` EXACTLY — +## wrap the TILE'S OWN CENTER toward `held_center` (the viewer's currently- +## displayed reference frame), then test the (already held-center-wrapped) +## query `district` against that RESOLVED center. The original version did +## the inverse — wrapped the QUERY toward the tile's raw CANONICAL center — +## which is not the same operation and silently tested containment against +## the WRONG wrap-image of the tile for any tile whose canonical center is +## far from `held_center` (i.e. any tile that needs wrapping to appear +## on-screen at all — confirmed live: a dot at district.x=-9569 visibly +## sitting on the painter's WEST wrap-image of the seam tile +## (canonical center 12739, drawn at draw_col=-6400) was tested by the old +## code against that tile's EAST/canonical span `[9539, 15939)` instead — +## landed inside it by coincidence (mod arithmetic), read a real but +## WRONG-LOCATION land cell, and never clipped). `district.x` is assumed +## ALREADY wrap-resolved near `held_center` by the caller (AtlasWindowNatureOverlay. +## _district()'s own contract) — this function does not re-wrap it, only the +## tile centers, exactly mirroring the painter's own asymmetry (the painter +## never wrap-resolves the query either — canvas-local coordinates are +## already in the held-center frame by construction). static func resolve_morphology_zone( - district: Vector2, is_tile_mode: bool, single_window: Variant, tiles: Array, cols: int + district: Vector2, is_tile_mode: bool, single_window: Variant, tiles: Array, cols: int, + held_center_x: int = 0 ) -> int: if not is_tile_mode: return morphology_zone_in_window(district, single_window) @@ -128,13 +146,14 @@ static func resolve_morphology_zone( if not window is Dictionary: continue var tile_center: Vector2i = tile.get("center", Vector2i.ZERO) - var wrapped_x: float = district.x + var draw_col: int = tile_center.x if cols > 0: - var wrapped_col: int = AtlasWindowGeometryRef.nearest_wrap_image( - roundi(district.x), tile_center.x, cols - ) - wrapped_x = float(wrapped_col) + (district.x - roundi(district.x)) - var zone: int = morphology_zone_in_window(Vector2(wrapped_x, district.y), window) + draw_col = AtlasWindowGeometryRef.nearest_wrap_image(tile_center.x, held_center_x, cols) + var effective_window: Dictionary = window + if draw_col != tile_center.x: + effective_window = (window as Dictionary).duplicate() + effective_window["center"] = [draw_col, tile_center.y] + var zone: int = morphology_zone_in_window(district, effective_window) if zone != MORPHOLOGY_ZONE_NO_DATA: return zone return MORPHOLOGY_ZONE_NO_DATA