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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user