_draw() splits into two independent gates: the Layer-1-gated skeleton path (Region chords, clip retained) and the NEW DistrictWindowLayer- gated course path — build_course_render_plan() (pure, render-free- testable) consumed by draw_polyline with _zs-compensated widths and opacities from the Araminta revisit tables; mouth double-rings at Mouth termini only (EdgeDrain/ContinuesBeyondWindow/None: three meanings, one presentation — draw to last point, stop, documented); zero water clip on the course path by construction (courses carry rung-consistent termini). CourseTerminus wire vocabulary kept re- pointable pending A2's real serde names; synthetic Ruling-3h fixtures mean the suites need zero changes when the server payload lands. Real gap found and fixed: window arrival never redrew the nature overlay after the first fit (one line in _on_window_ready — courses would miss every window swap post-pan). Water-clip header rewritten to RESTRUCTURED status (retired on course rungs; permanent at Region until Region goes windowed, T-1143 ruling 2). Revert-verified (visibility-gate bypass -> 4 named failures). geometry-nature 112/112, nature-overlay 58/58, viewer 78/78, zero collateral; full sweep 3928/3928 after the full-import bootstrap; gdlint clean (viewer 1016->1017, pre-existing overage rides T-1158). Tickets: T-1170 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
172 lines
9.2 KiB
GDScript
172 lines
9.2 KiB
GDScript
extends RefCounted
|
|
|
|
## T-1172 — the river-skeleton waterline-clip fix. Pure geometry, split into
|
|
## its own file (not folded into atlas_window_geometry.gd, which is already
|
|
## close to the gdlint max-file-lines cap): the river skeleton's own SOURCE
|
|
## (server/src/atlas/drainage.rs) is filtered against the RAW heightmap sea
|
|
## level, but the DRAWN ocean this client actually paints is the derived
|
|
## MorphologyZone verdict (server/src/atlas/district_profile.rs) — which
|
|
## post-T-1162 includes the coast-warp invention (the drawn coastline is
|
|
## deterministically displaced from the heightmap coast) and, at Region
|
|
## rung, aggregates to 204.8 km cells. These are two independently-computed
|
|
## waterlines that can legitimately disagree; Tyre's ruling (T-1172): no
|
|
## single server waterline is well-defined, so the fix is a CLIENT draw-time
|
|
## clip against whichever composite cell is currently ON SCREEN at a given
|
|
## river dot's position — strict drop, no snap (a dot that lands on drawn
|
|
## water is simply not drawn; Region's 205 km cells may amputate a river's
|
|
## final coastal dots, an accepted cost per the ruling).
|
|
##
|
|
## T-1170 Ruling 3g update (RESTRUCTURED, not blanket-retired): the clip is
|
|
## RETIRED for the District/Quarter COURSE-drawing rungs
|
|
## (AtlasWindowNatureOverlay._draw_course_path()/_draw_one_course()) —
|
|
## courses carry real rung-consistent termini invented server-side against
|
|
## the SAME rung's drawn coast, so the clip's job is already done there. This
|
|
## file's clip machinery is STILL LIVE and used at the Region SKELETON path
|
|
## (_draw_skeleton_chords()/_segment_touches_drawn_water()) — Region still
|
|
## draws the whole-body skeleton against a rung-dependent drawn coast, which
|
|
## is precisely the presentation-frame reconciliation this file exists for.
|
|
## The Region clip is PERMANENT-UNTIL-REGION-GOES-WINDOWED (T-1143 ruling 2's
|
|
## progressive tiling) — when Region itself becomes a windowed rung, it
|
|
## inherits windowed courses too, and this file retires entirely at that
|
|
## point, not before.
|
|
##
|
|
## const AtlasWindowWaterClip := preload("res://ui/implant/apps/atlas/atlas_window_water_clip.gd")
|
|
|
|
const AtlasWindowGeometryRef := preload("res://ui/implant/apps/atlas/atlas_window_geometry.gd")
|
|
|
|
## Sentinel returned by the lookups below when no arrived composite data
|
|
## covers the queried position — either the position is outside every
|
|
## held/tiled window's own extent, or the window/tile at that position
|
|
## hasn't arrived yet. The caller (AtlasWindowNatureOverlay) must FAIL OPEN
|
|
## on this sentinel (draw the dot) — Tyre's rule 5: the clip is a
|
|
## presentation refinement, never a data gate. Chosen as -1 (not a legal
|
|
## MorphologyZone discriminant, which is always >= 0) so it can never be
|
|
## mistaken for a real "not water" zone.
|
|
const MORPHOLOGY_ZONE_NO_DATA: int = -1
|
|
|
|
|
|
## The derived per-cell grid side length (CELLS) for a window dict `w` — a
|
|
## DELIBERATE duplicate of AtlasWindowOverlay.cell_grid_side_for_window(),
|
|
## not a shared call, matching this codebase's own "each file owns its own
|
|
## reading of a small pure lookup rather than force a dependency" precedent
|
|
## (atlas_overlay_colors.gd's header doc states this explicitly for the
|
|
## color-palette case; the SAME rationale applies here: atlas_window_overlay.gd
|
|
## already depends on atlas_window_geometry.gd, so a dependency back from
|
|
## there — or from this file, if it lived there — would risk a circular or
|
|
## at least confusing import graph). Mirrors
|
|
## server/src/atlas/layer_proxy.rs's `WindowGranularity::cell_grid_side`
|
|
## exactly, matching the canonical function's own doc byte-for-byte in intent.
|
|
static func cell_grid_side_for_window(w: Dictionary) -> int:
|
|
var n: int = int(w.get("n", 0))
|
|
var granularity_v2 := str(w.get("granularity_v2", "District"))
|
|
match granularity_v2:
|
|
"Quarter":
|
|
return n * 4
|
|
"Region":
|
|
return maxi(roundi(float(n) / 100.0), 1)
|
|
_:
|
|
return n
|
|
|
|
|
|
## Resolve a FRACTIONAL district position to the MorphologyZone discriminant
|
|
## of the composite cell covering it, for a SINGLE window dict `w` (the
|
|
## single-window rung path: District/Quarter, and each individual Region
|
|
## tile in tile mode share this same per-window shape). Returns
|
|
## MORPHOLOGY_ZONE_NO_DATA if `w` is null/malformed, has no morphology array,
|
|
## or `district` falls outside `w`'s own `[center - n/2, center + n/2)`
|
|
## extent (the SAME containment convention
|
|
## AtlasWindowGeometry.district_to_canvas_local() uses, so a position judged
|
|
## "inside" here is exactly the position that would draw as part of THIS
|
|
## window's composite on screen — no separate containment rule to drift out
|
|
## of sync with the actual paint).
|
|
static func morphology_zone_in_window(district: Vector2, w: Variant) -> int:
|
|
if not w is Dictionary:
|
|
return MORPHOLOGY_ZONE_NO_DATA
|
|
var window: Dictionary = w
|
|
var center_raw: Variant = window.get("center", [0, 0])
|
|
var center: Vector2i = (
|
|
Vector2i(int(center_raw[0]), int(center_raw[1])) if center_raw is Array else Vector2i.ZERO
|
|
)
|
|
var n: int = int(window.get("n", 0))
|
|
if n <= 0:
|
|
return MORPHOLOGY_ZONE_NO_DATA
|
|
var half: float = float(n) * 0.5
|
|
var local_x: float = district.x - (float(center.x) - half)
|
|
var local_y: float = district.y - (float(center.y) - half)
|
|
if local_x < 0.0 or local_x >= float(n) or local_y < 0.0 or local_y >= float(n):
|
|
return MORPHOLOGY_ZONE_NO_DATA
|
|
var morphology: Variant = window.get("morphology")
|
|
if not (morphology is PackedByteArray or morphology is Array):
|
|
return MORPHOLOGY_ZONE_NO_DATA
|
|
var grid_side: int = cell_grid_side_for_window(window)
|
|
if grid_side <= 0:
|
|
return MORPHOLOGY_ZONE_NO_DATA
|
|
# 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])
|
|
|
|
|
|
## Resolve a fractional district position to a MorphologyZone discriminant
|
|
## across BOTH viewer modes — the single dispatch point
|
|
## AtlasWindowNatureOverlay's clip predicate calls, so it never needs its own
|
|
## 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 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,
|
|
held_center_x: int = 0
|
|
) -> int:
|
|
if not is_tile_mode:
|
|
return morphology_zone_in_window(district, single_window)
|
|
for tile: Dictionary in tiles:
|
|
var window: Variant = tile.get("window")
|
|
if not window is Dictionary:
|
|
continue
|
|
var tile_center: Vector2i = tile.get("center", Vector2i.ZERO)
|
|
var draw_col: int = tile_center.x
|
|
if cols > 0:
|
|
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
|