fix(ui): T-1172 round 2 — tile wrap resolution mirrored to the painter's own direction

Live round: the first clip removed only 133 pixels. Root cause (dossier-
traced): resolve_morphology_zone wrapped the QUERY toward a tile's raw
canonical center — the inverse of the painter's draw_col =
nearest_wrap_image(tile_center, held_center) — so seam tiles tested
containment against the wrong wrap-image and read real-but-wrong-
location land cells. Fix mirrors the painter exactly: wrap the tile's
own center toward held_center, test the (already held-wrapped) query
against that. Index math itself was confirmed correct end-to-end and
is now factored into shared AtlasWindowGeometry.cell_index_for_local_
offset() (full painter unification not applicable — the painter only
iterates forward, never reverse-looks-up; documented). Post-fix: 1082
traced positions cross-checked against actual painted pixels, 0 real
mismatches; regenerated captures show every dot/mouth on land. The one
pre-existing test encoding the buggy direction as correct was replaced
by positive+negative wrap-semantics tests (the negative one is the
reliable revert discriminator) plus a wire-accurate seam-tile fixture
with an honestly-documented proof limit (a single-tile fixture cannot
distinguish the wrap directions; the multi-tile scan tests can).
Also: two harness traps found and fixed in the lead's drive scratch —
canvas_items stretch factor (root.size now matches the 1920x1080 base
viewport) and mid-arrival snapshots (explicit is_fully_arrived wait;
the round-1 faint result was partly a 4-of-6-tiles capture). 46/46 +
50/50 + five sibling suites regression-free; gdlint clean.

Tickets: T-1172

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-23 11:33:31 +02:00
co-authored by Claude Fable 5
parent 9914dc0d91
commit 0b14cdb21d
5 changed files with 273 additions and 43 deletions
@@ -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)
@@ -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
@@ -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