fix(ui): T-1156 live rounds — zoom-compensated marker sizes; toggle-redraw regression pin

Live round 2 (the real bug): every nature-overlay marker size was a raw
screen-space constant drawn inside _canvas, whose scale IS view_zoom —
at Lendel's orbital fit zoom (0.0063) a 2.2px trunk dot rendered at
~0.014px, invisible; the same code at District's 3.75 zoom produced the
correctly-visible mouth ring, which is why one capture worked and the
headline rung didn't. Fixed via AtlasWindowGeometry.zoom_compensated_
size() (pure, floor-guarded) wired through every radius/line-width;
basin FILL points are positions and correctly stay unscaled. Suspect
tile-mode-rung-detection was ruled out live (granularity_v2=Region
confirmed in tile mode) but pinned with a named regression test anyway.
+8 pure-function tests incl. a numeric pin of the pre-fix magnitude
(<0.02px at orbital zoom); revert-verified by name. Draw-smoke suite
documented as supplementary (the shared SubViewport background harness
can pass vacuously under X11 BadMatch — the pure suite is the gate).

Live round 3 (drive-script bug, no product change): the lead's scratch
drive passed the button LABEL to set_overlay_visible() and the unknown-
id guard silently no-op'd — but the chase banked a real pin:
test_set_overlay_visible_gen_basins_flips_gate_and_redraws_nature_
overlay (draw-counting spy per the cold-start precedent; is_queued_for_
redraw does not exist in this build). Revert-verified.

Basins verified live: 7 Lendel watershed boundaries render at the
ruling's alphas. Suites: viewer 76/76, geometry-nature 42/42, nature-
overlay 22/22, zoom-ladder 50/50, no regressions across the cluster.

Tickets: T-1156

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-23 08:55:29 +02:00
co-authored by Claude Fable 5
parent 0d22e50e66
commit 60faf667a5
7 changed files with 314 additions and 15 deletions
@@ -218,3 +218,66 @@ func test_attractors_visible_at_rung_region_only() -> void:
assert_bool(AtlasWindowGeometry.attractors_visible_at_rung("Region")).is_true()
assert_bool(AtlasWindowGeometry.attractors_visible_at_rung("District")).is_false()
assert_bool(AtlasWindowGeometry.attractors_visible_at_rung("Quarter")).is_false()
# =============================================================================
# Coordinator live-eyeball finding (2026-07-23): zoom_compensated_size() —
# marker sizes must stay CONSTANT on screen regardless of _view_zoom
# (Araminta's ruling), but draw calls execute inside a Node2D whose .scale IS
# _view_zoom — a raw constant gets multiplied by that transform at render
# time. This function pre-divides so the transform's multiply cancels back
# out to the literal screen-space value.
# =============================================================================
## At zoom=1.0 (the canvas transform's identity scale) the compensated size
## must equal the input unchanged — no over/under-correction at the one zoom
## level where compensation is a no-op by construction.
func test_zoom_compensated_size_at_zoom_one_is_unchanged() -> void:
assert_float(AtlasWindowGeometry.zoom_compensated_size(2.2, 1.0)).is_equal_approx(2.2, 0.0001)
## The exact regression shape: at Lendel's real orbital fit zoom (~0.0063,
## live drive script), the compensated size must be much LARGER than the
## raw screen-space constant — inversely proportional to zoom — so that once
## the canvas transform re-multiplies it by view_zoom at render time, the
## EFFECTIVE on-screen size lands back at the literal ruling value, not a
## sub-pixel sliver.
func test_zoom_compensated_size_at_orbital_zoom_scales_up_inversely() -> void:
var view_zoom := 0.0063
var screen_space_size := 2.2
var compensated: float = AtlasWindowGeometry.zoom_compensated_size(screen_space_size, view_zoom)
# Round-trip: compensated * view_zoom must reconstruct the original
# screen-space size — this IS the property that makes the on-screen
# result zoom-invariant (the canvas transform performs exactly this
# multiply at render time).
assert_float(compensated * view_zoom).is_equal_approx(screen_space_size, 0.001)
assert_float(compensated).override_failure_message(
"at a tiny orbital zoom, the compensated size must be dramatically LARGER"
+ " than the raw screen-space constant — that's the whole point of the fix"
).is_greater(screen_space_size * 10.0)
## The exact BUG this fix closes, pinned as a regression: an UNCOMPENSATED
## radius (screen_space_size used directly, the pre-fix behavior) multiplied
## by Lendel's real orbital zoom produces a sub-pixel effective size — this
## is the "the ruling's px value, at orbital fit zoom, is invisible" claim
## from the coordinator's diagnosis, verified numerically rather than just
## asserted.
func test_uncompensated_radius_at_orbital_zoom_would_be_sub_pixel() -> void:
var view_zoom := 0.0063
var raw_screen_space_radius := 2.2 # RIVER_DOT_RADIUS_BY_CLASS_REGION[TRUNK]
var effective_size_if_uncompensated: float = raw_screen_space_radius * view_zoom
assert_float(effective_size_if_uncompensated).override_failure_message(
"an uncompensated radius at orbital zoom must be sub-pixel — pinning the"
+ " numeric magnitude of the bug this fix closes, not just its existence"
).is_less(0.02)
## A degenerate zero (or negative) view_zoom must not divide-by-zero/produce
## infinity/NaN — the floor guard keeps this function total.
func test_zoom_compensated_size_zero_zoom_does_not_blow_up() -> void:
var result: float = AtlasWindowGeometry.zoom_compensated_size(2.2, 0.0)
assert_bool(is_finite(result)).override_failure_message(
"a degenerate zero view_zoom must not produce inf/NaN"
).is_true()
@@ -14,14 +14,17 @@ const AtlasWindowNatureOverlay := preload("res://ui/implant/apps/atlas/atlas_win
## Minimal viewer stub — AtlasWindowNatureOverlay only reaches the viewer
## through get_held_granularity_v2()/get_body_radius_km()/get_held_center()/
## get_held_n()/get_cell_pixel_size()/is_overlay_visible(), the same
## duck-typed-viewer precedent test_atlas_window_overlay.gd's _ViewerStub
## already establishes for AtlasWindowOverlay.
## get_held_n()/get_cell_pixel_size()/get_view_zoom()/is_overlay_visible(),
## the same duck-typed-viewer precedent test_atlas_window_overlay.gd's
## _ViewerStub already establishes for AtlasWindowOverlay. get_view_zoom()
## added post-live-eyeball (coordinator finding, 2026-07-23): _draw() now
## reads it for the zoom-compensated marker-size fix.
class _ViewerStub:
var held_granularity_v2: String = "Region"
var body_radius_km: float = 6371.0
var held_center: Vector2i = Vector2i.ZERO
var held_n: int = 64
var view_zoom: float = 1.0
var overlay_visibility: Dictionary = {"gen_rivers": true, "gen_basins": false, "gen_attractors": false}
func get_held_granularity_v2() -> String:
@@ -39,6 +42,9 @@ class _ViewerStub:
func get_cell_pixel_size() -> float:
return 16.0
func get_view_zoom() -> float:
return view_zoom
func is_overlay_visible(overlay_id: String) -> bool:
return bool(overlay_visibility.get(overlay_id, false))
@@ -19,6 +19,25 @@
## godot4 --display-driver x11 --rendering-driver opengl3 \
## -s addons/gdUnit4/bin/GdUnitCmdTool.gd --ignoreHeadlessMode -c \
## -a res://tests/test_atlas_window_nature_overlay_draw_smoke.gd
##
## **Known limitation, verified directly (2026-07-23 zoom-compensation fix
## verification):** the shared _BackgroundRect/_render_to_image() harness
## (copied from test_atlas_window_overlay_draw_smoke.gd) occasionally fails
## to composite the COLOR_BG fill at all under a real X11/opengl3 run in this
## environment (sampled pixels read (0,0,0,0), not COLOR_BG — an unrelated,
## intermittent X11/SubViewport timing issue, confirmed via an XServer
## "BadMatch" warning in that same run's log). When that happens EVERY pixel
## reads as "non-background" regardless of what this overlay actually draws,
## making a low MIN_NON_BACKGROUND_FRACTION threshold pass VACUOUSLY (it
## would pass even with zoom-compensation deliberately broken — confirmed by
## direct revert-test). The terrain sibling suite is accidentally immune to
## this (its checkerboard composite covers most of the frame regardless of
## background correctness); this suite's SPARSE markers are not. **The
## reliable, environment-independent regression gate for the zoom-
## compensation fix is therefore the pure-function suite in
## test_atlas_window_geometry_nature.gd** (zoom_compensated_size()'s own
## tests) — this smoke suite is a supplementary "does it actually paint"
## check when the harness cooperates, not the primary gate.
class_name TestAtlasWindowNatureOverlayDrawSmoke
extends GdUnitTestSuite
@@ -44,11 +63,17 @@ static func _dummy_renderer_active() -> bool:
## Same duck-typed viewer contract as test_atlas_window_nature_overlay.gd's
## _ViewerStub, minus the SimBridge-signal machinery this smoke test doesn't
## need (layer1 is injected directly via _layer1, not through a response).
## view_zoom MUST be kept in sync with whatever zoom _render_to_image() is
## called with — the whole point of this smoke suite (post-live-eyeball,
## coordinator finding 2026-07-23) is proving markers stay visible at the
## REAL orbital fit zoom, not an artificially large test zoom that would
## mask the zoom-compensation bug the fix addresses.
class _ViewerStub:
var held_granularity_v2: String = "Region"
var body_radius_km: float = 6371.0
var held_center: Vector2i = Vector2i.ZERO
var held_n: int = 64
var view_zoom: float = 1.0
var overlay_visibility: Dictionary = {"gen_rivers": true, "gen_basins": true, "gen_attractors": true}
func get_held_granularity_v2() -> String:
@@ -66,6 +91,9 @@ class _ViewerStub:
func get_cell_pixel_size() -> float:
return 16.0
func get_view_zoom() -> float:
return view_zoom
func is_overlay_visible(overlay_id: String) -> bool:
return bool(overlay_visibility.get(overlay_id, false))
@@ -171,6 +199,7 @@ func test_region_rung_draws_visible_pixels() -> void:
# Zoom chosen so the held window's canvas footprint (held_n * cell_px =
# 64 * 16 = 1024 units) comfortably fills the 512px viewport.
var zoom: float = float(VIEWPORT_SIZE.x) / (float(stub.held_n) * stub.get_cell_pixel_size())
stub.view_zoom = zoom
var image: Image = await _render_to_image(overlay, zoom)
var fraction: float = _non_background_fraction(image)
@@ -200,6 +229,7 @@ func test_district_rung_still_draws_visible_pixels() -> void:
overlay._requested_body_id = "SmokeBody"
var zoom: float = float(VIEWPORT_SIZE.x) / (float(stub.held_n) * stub.get_cell_pixel_size())
stub.view_zoom = zoom
var image: Image = await _render_to_image(overlay, zoom)
var fraction: float = _non_background_fraction(image)
@@ -211,3 +241,41 @@ func test_district_rung_still_draws_visible_pixels() -> void:
)
% (fraction * 100.0)
).is_greater(MIN_NON_BACKGROUND_FRACTION)
## Coordinator live-eyeball regression (2026-07-23): the ORBITAL TILE MOSAIC
## rest state's REAL fit zoom on a Lendel-scale body (~0.0063, confirmed
## directly via a live drive script — held_n=19139 districts,
## cell_px=16, a ~1600px viewport) — NOT the comfortable ~0.5 zoom the
## sibling test above uses. Before the zoom-compensation fix, this exact
## zoom magnitude produced ZERO visible river/mouth pixels (a 2.2px trunk
## dot rasterized at ~0.014 screen px) despite RVR being on and the policy
## table correctly returning full Region visibility — the captures showed
## terrain-only with literally nothing drawn. Pins the regression at
## production scale, not a toy zoom that could accidentally still pass.
func test_orbital_scale_zoom_still_draws_visible_pixels() -> void:
if _dummy_renderer_active():
print(SKIP_REASON)
return
var overlay := AtlasWindowNatureOverlay.new()
var stub := _ViewerStub.new()
stub.held_n = 19139 # Lendel's own raw circumference, live drive script
stub.view_zoom = 0.0063 # Lendel's own live orbital fit zoom
overlay.viewer = stub
overlay._layer1 = _mock_layer1_dense()
overlay._requested_body_id = "SmokeBody"
var image: Image = await _render_to_image(overlay, stub.view_zoom)
var fraction: float = _non_background_fraction(image)
assert_float(fraction).override_failure_message(
(
"at Lendel's REAL orbital fit zoom (~0.0063), the Region-rung nature"
+ " composite must still render VISIBLE non-background pixels — got only"
+ " %.4f%% of the frame differing from COLOR_BG. This is exactly the"
+ " coordinator's live-eyeball finding: uncompensated screen-space marker"
+ " sizes get multiplied by the canvas's own zoom transform, vanishing"
+ " sub-pixel at the orbital rest state's tiny fit zoom."
)
% (fraction * 100.0)
).is_greater(MIN_NON_BACKGROUND_FRACTION)
+79
View File
@@ -16,6 +16,11 @@ const AtlasWindowRequest := preload("res://ui/implant/apps/atlas/atlas_window_re
# T-1142: district_extent()/canonicalize_district_center() — used to derive
# real (cols/rows_half) bounds for the wrap/pole-wall tests below.
const AtlasDescendGeometry := preload("res://ui/implant/apps/atlas/atlas_descend_geometry.gd")
# T-1156 wave 1 round 3: AtlasWindowNatureOverlay has no class_name (matching
# atlas_overlay_bar.gd/atlas_window_request.gd's own no-class_name precedent,
# review #8) — subclassing it (the _CountingNatureOverlay spy below) needs
# the preloaded script's PATH via `extends`, not a global class name.
const AtlasWindowNatureOverlay := preload("res://ui/implant/apps/atlas/atlas_window_nature_overlay.gd")
## Build a hand-authored DistrictWindowLayer dict (n=2, matching the shape
@@ -131,6 +136,80 @@ func test_set_overlay_visible_unknown_id_is_a_noop() -> void:
assert_bool(v.is_overlay_visible("not_a_real_overlay")).is_false()
## Counts real _draw() invocations on AtlasWindowNatureOverlay — CanvasItem
## exposes no public "is a redraw pending" query in this Godot version
## (confirmed directly: is_queued_for_redraw() does not exist on Node2D here
## — an earlier version of this test assumed it did and failed with
## "Invalid call. Nonexistent function"), so the only reliable signal that
## queue_redraw() actually had an effect is the engine calling _draw() again
## on a subsequent frame. Matches test_atlas_cold_start.gd's own
## _CountingOverlay precedent exactly (same file's own doc: "the only
## reliable signal... is the engine calling _draw() again") — subclasses the
## REAL AtlasWindowNatureOverlay so drawing still runs through genuine
## production code, this spy only adds counting.
class _CountingNatureOverlay extends AtlasWindowNatureOverlay:
var draw_count := 0
func _draw() -> void:
draw_count += 1
super._draw()
## Coordinator live-eyeball round 3 (2026-07-23): R1/R2 captures were
## byte-identical because a scratch drive script called
## set_overlay_visible("BAS", true) — the button LABEL, not the overlay id
## ("gen_basins") — which set_overlay_visible()'s own `not
## _overlay_visibility.has(overlay_id): push_warning(...); return` guard
## silently no-ops on. Real callers (atlas_overlay_bar.gd's
## _on_toggle_changed()) always pass def["id"], never the label, so product
## code was never actually broken — but this pins the EXACT gate the
## coordinator asked to verify: toggling gen_basins via the real viewer API
## must (a) flip is_overlay_visible("gen_basins") — the nature overlay's OWN
## draw gate, read live via viewer.is_overlay_visible() at _draw() time, not
## a stale copy — AND (b) actually cause the NATURE overlay (not just the
## terrain overlay/viewer) to redraw on the next frame, proven by swapping in
## a _draw()-counting spy (matching test_atlas_cold_start.gd's
## _CountingOverlay pattern) and confirming draw_count advances past a
## settled baseline after the toggle, with no other gesture.
func test_set_overlay_visible_gen_basins_flips_gate_and_redraws_nature_overlay() -> void:
var v: AtlasWindowViewer = auto_free(AtlasWindowViewer.new())
add_child(v)
assert_bool(v.is_overlay_visible("gen_basins")).override_failure_message(
"gen_basins must default to OFF (Araminta's ruling — BAS defaults off)"
).is_false()
# Swap in the counting spy (matching _CountingOverlay's own swap-after-
# construction shape) so entry's own queue_redraw() calls don't pollute
# the baseline, then let it settle before touching the toggle.
var spy := _CountingNatureOverlay.new(v)
v._nature_overlay.queue_free()
v._nature_overlay = spy
v._canvas.add_child(spy)
await get_tree().process_frame
await get_tree().process_frame
var baseline: int = spy.draw_count
assert_int(baseline).override_failure_message(
"sanity: the spy must have drawn at least once before the toggle, or"
+ " this test can't distinguish 'redrawn BY the toggle' from 'never"
+ " drawn at all'"
).is_greater(0)
v.set_overlay_visible("gen_basins", true)
await get_tree().process_frame
assert_bool(v.is_overlay_visible("gen_basins")).override_failure_message(
"the toggle must flip the draw gate is_overlay_visible() reads live"
).is_true()
assert_int(spy.draw_count).override_failure_message(
"the toggle must queue_redraw() the NATURE overlay specifically —"
+ " queue_redraw() on the viewer/terrain overlay alone leaves the"
+ " nature node's last frame cached (a Node2D child does not redraw"
+ " because its sibling did) — draw_count must have advanced past the"
+ " baseline (%d)" % baseline
).is_greater(baseline)
# =============================================================================
# T-1120 capture-API parity (the ticket's explicit note: must survive here too)
# =============================================================================
+32
View File
@@ -109,6 +109,38 @@ func test_enter_orbital_tile_mode_held_n_is_the_whole_body_extent() -> void:
assert_int(v._held_n).is_equal(raw_cols)
## Coordinator live-eyeball dossier (2026-07-23, suspect 2 — DIAGNOSED FALSE
## but pinned as a regression guard anyway per the coordinator's own
## instruction): AtlasWindowNatureOverlay's _draw() reads
## viewer.get_held_granularity_v2() to key its per-rung policy tables
## (RIVER_CLASS_VISIBLE_BY_RUNG etc.) — if that accessor returned anything
## other than the EXACT string "Region" while is_tile_mode() is true (an
## empty string, a stale District default, a different-cased tag...), the
## visibility tables would silently return their empty/default disposition
## and NOTHING would draw, indistinguishable from the live captures'
## "literally zero river dots" symptom. Live drive-script evidence
## (NATURE_DEBUG print, since removed) confirmed this was NOT the actual bug
## — get_held_granularity_v2() already correctly returns "Region" in tile
## mode — but this test makes that fact load-bearing instead of merely
## observed once, so a future refactor of _enter_tile_mode()'s
## _held_granularity_v2 assignment trips a named failure here.
func test_get_held_granularity_v2_is_exactly_region_string_in_tile_mode() -> void:
var v: AtlasWindowViewer = auto_free(AtlasWindowViewer.new())
add_child(v)
var radius_km := 6238.4 # GJ380c (Lendel) — needs tiling
v.enter_orbital({"body_id": "GJ380c", "body_radius_km": radius_km}, {})
assert_bool(v.is_tile_mode()).override_failure_message(
"this test's premise requires tile mode — Lendel must still need tiling"
).is_true()
assert_str(v.get_held_granularity_v2()).override_failure_message(
"AtlasWindowNatureOverlay's _draw() keys its ENTIRE per-rung policy off"
+ " this exact string — anything other than the literal 'Region' silently"
+ " empties every visibility table and draws nothing, indistinguishable"
+ " from the live-capture symptom (zero river dots at the orbital rest state)"
).is_equal("Region")
## **The live-round-3 regression, end to end for TILE mode:** enter_orbital()
## on GJ380c/Lendel followed by delivering ONE tile's wire-accurate response
## (clamped n=6,400, "Region" granularity_v2, the legacy sentinel in the old
@@ -895,3 +895,25 @@ static func basins_visible_at_rung(granularity_v2: String) -> bool:
static func attractors_visible_at_rung(granularity_v2: String) -> bool:
return bool(ATTRACTORS_VISIBLE_BY_RUNG.get(granularity_v2, true))
## Coordinator live-eyeball finding (2026-07-23): Araminta's ruling specifies
## nature-overlay marker sizes as SCREEN-SPACE px, constant regardless of
## zoom — but every draw call in this cluster (river dots, mouth rings, basin
## line widths) executes inside `_canvas`, a Node2D whose `.scale` IS
## `_view_zoom` (AtlasWindowViewer._apply_transform()). A raw radius/width
## constant handed to draw_circle()/draw_arc()/draw_polyline() therefore gets
## multiplied by `_view_zoom` at render time — invisible at the Region
## orbital tile mosaic's fit zoom (~0.0063 for Lendel: a 2.2px trunk-river
## dot rasterizes at ~0.014 screen px, sub-pixel), even though the SAME
## drawing code produces a correctly-sized (visible) mouth ring at District's
## much larger fit zoom (~3.75, live capture confirmed this). The fix: every
## marker's draw-time radius/width must be pre-divided by `view_zoom` so the
## canvas transform's multiply cancels back out to the ruling's literal
## screen-space value. `view_zoom` is clamped to a small positive floor
## (MIN_ZOOM's own order of magnitude) to avoid a divide-by-zero/near-zero
## blowup on a degenerate zero-zoom caller — this floor is far below any
## legal `_view_zoom` (AtlasWindowViewer.MIN_ZOOM = 0.0005), so it is inert
## 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)
@@ -165,6 +165,15 @@ func _draw() -> void:
"cell_px": cell_px,
"cols": cols,
"granularity_v2": granularity_v2,
# Coordinator live-eyeball finding (2026-07-23): every marker size
# below is drawn as a SCREEN-SPACE constant (Araminta's ruling), but
# draw calls execute inside _canvas, whose .scale IS view_zoom — a
# raw constant gets multiplied by that transform at render time,
# invisible at the Region orbital tile mosaic's tiny fit zoom
# (~0.006). zs() below pre-divides by view_zoom so the transform's
# multiply cancels back to the literal screen-space value. See
# AtlasWindowGeometry.zoom_compensated_size()'s own doc.
"view_zoom": viewer.get_view_zoom(),
}
if AtlasWindowGeometry.basins_visible_at_rung(granularity_v2) and viewer.is_overlay_visible(
@@ -190,6 +199,15 @@ func _cols_for_wrap(radius_km: float) -> int:
return int(AtlasDescendGeometryRef.district_extent(radius_km).get("cols", 0))
## Zoom-compensated screen-space size — thin per-ctx wrapper over
## AtlasWindowGeometry.zoom_compensated_size() (see that function's own doc
## for the "why divide" rationale). Every draw_circle()/draw_arc()/
## draw_polyline() radius or line-width in this file routes through this so
## Araminta's "constant on-screen size" ruling holds at every rung/zoom.
func _zs(screen_space_size: float, ctx: Dictionary) -> float:
return AtlasWindowGeometry.zoom_compensated_size(screen_space_size, ctx["view_zoom"])
## Pixel (row, col) -> canvas-local, wrap-resolved to whichever longitude
## image is nearest the currently-held view — the SAME two-step
## (map-then-nearest-wrap) the tile mosaic draw path uses, just for a single
@@ -234,7 +252,7 @@ func _draw_rivers(rn: Dictionary, ctx: Dictionary) -> void:
var p: Vector2 = _pos(float(c[0]), float(c[1]), ctx)
if is_region:
var radius: float = AtlasWindowGeometry.RIVER_DOT_RADIUS_BY_CLASS_REGION.get(cls, 2.2)
draw_circle(p, radius, COLOR_GEN_RIVER)
draw_circle(p, _zs(radius, ctx), COLOR_GEN_RIVER)
else:
# District: trunk-only (already filtered above), reduced size +
# opacity — the ruling's "fade down" treatment.
@@ -244,37 +262,43 @@ func _draw_rivers(rn: Dictionary, ctx: Dictionary) -> void:
COLOR_GEN_RIVER.b,
COLOR_GEN_RIVER.a * AtlasWindowGeometry.RIVER_DOT_OPACITY_DISTRICT_TRUNK
)
draw_circle(p, AtlasWindowGeometry.RIVER_DOT_RADIUS_DISTRICT_TRUNK, faded)
draw_circle(p, _zs(AtlasWindowGeometry.RIVER_DOT_RADIUS_DISTRICT_TRUNK, ctx), faded)
if AtlasWindowGeometry.confluences_visible_at_rung(granularity_v2):
for cf: Variant in rn.get("confluences", []):
if cf is Array and cf.size() >= 2:
var p: Vector2 = _pos(float(cf[0]), float(cf[1]), ctx)
draw_circle(p, AtlasWindowGeometry.RIVER_CONFLUENCE_RADIUS_REGION, COLOR_GEN_RIVER)
var radius: float = _zs(AtlasWindowGeometry.RIVER_CONFLUENCE_RADIUS_REGION, ctx)
draw_circle(p, radius, COLOR_GEN_RIVER)
if AtlasWindowGeometry.mouths_visible_at_rung(granularity_v2):
for m: Variant in rn.get("mouths", []):
if m is Array and m.size() >= 2:
_draw_mouth(_pos(float(m[0]), float(m[1]), ctx))
_draw_mouth(_pos(float(m[0]), float(m[1]), ctx), ctx)
## Double-ring sea-terminus marker — verbatim geometry from the retired
## atlas_marker_overlay.gd _draw_gen_rivers() (:536-539). Mouths never fade
## (Araminta's ruling: "a mouth is always a landmark") — same styling at
## every rung it's visible at (Region, District; never Quarter).
func _draw_mouth(p: Vector2) -> void:
draw_arc(p, AtlasWindowGeometry.MOUTH_RING_RADIUS, 0.0, TAU, 18, COLOR_GEN_MOUTH, 1.5)
func _draw_mouth(p: Vector2, ctx: Dictionary) -> void:
draw_arc(
p, _zs(AtlasWindowGeometry.MOUTH_RING_RADIUS, ctx), 0.0, TAU, 18, COLOR_GEN_MOUTH, _zs(1.5, ctx)
)
var halo := Color(
COLOR_GEN_MOUTH.r, COLOR_GEN_MOUTH.g, COLOR_GEN_MOUTH.b, AtlasWindowGeometry.MOUTH_HALO_ALPHA
)
draw_arc(p, AtlasWindowGeometry.MOUTH_HALO_RADIUS, 0.0, TAU, 22, halo, 1.0)
draw_arc(p, _zs(AtlasWindowGeometry.MOUTH_HALO_RADIUS, ctx), 0.0, TAU, 22, halo, _zs(1.0, ctx))
## Basins — Region only, binary (no fade), per the ruling. Polygon fill +
## boundary polyline, verbatim geometry from the retired
## atlas_marker_overlay.gd _draw_gen_basins() (:542-557), coordinate mapping
## replaced with _pos() (this file's wrap-aware canvas-local mapping) in place
## of the retired _gen_pos() texture-fraction mapping.
## of the retired _gen_pos() texture-fraction mapping. The FILL polygon's
## points are positions (never zoom-compensated — the fill must track the
## real district-space shape); only the boundary LINE's width is a
## screen-space marker size and goes through _zs().
func _draw_basins(ctx: Dictionary) -> void:
for b: Variant in _layer1.get("drainage_basins", []):
if not b is Dictionary:
@@ -290,7 +314,7 @@ func _draw_basins(ctx: Dictionary) -> void:
draw_colored_polygon(pts, COLOR_GEN_BASIN_FILL)
var loop: PackedVector2Array = pts.duplicate()
loop.append(pts[0])
draw_polyline(loop, COLOR_GEN_BASIN_LINE, 0.8, true)
draw_polyline(loop, COLOR_GEN_BASIN_LINE, _zs(0.8, ctx), true)
## Attractors — Region only, wave 1 (per the ruling; District/Quarter never
@@ -298,7 +322,9 @@ func _draw_basins(ctx: Dictionary) -> void:
## attractors_visible_at_rung()). Ported from the retired
## atlas_marker_overlay.gd _draw_gen_attractors()/_draw_attractor_shape()
## (:560-...) — the shape vocabulary (7 attractor-type glyphs) is Araminta's
## existing design, unchanged; only the coordinate mapping moves to _pos().
## existing design, unchanged; only the coordinate mapping moves to _pos()
## and the size is zoom-compensated before reaching the shape drawer (that
## function stays a pure "draw this size at this position", zoom-agnostic).
func _draw_attractors(ctx: Dictionary) -> void:
for a: Variant in _layer1.get("attractors", []):
if not a is Dictionary:
@@ -310,13 +336,16 @@ func _draw_attractors(ctx: Dictionary) -> void:
if not pos_rc is Array or pos_rc.size() < 2:
continue
var p: Vector2 = _pos(float(pos_rc[0]), float(pos_rc[1]), ctx)
var size: float = 5.0 + strength * 4.0
var size: float = _zs(5.0 + strength * 4.0, ctx)
var color: Color = AtlasOverlayColors.sub_biome_color(str(a.get("sub_biome", "")))
_draw_attractor_shape(str(a.get("attractor_type", "")), p, size, color)
## Attractor type -> marker shape — verbatim from the retired
## atlas_marker_overlay.gd _draw_attractor_shape(), ported unchanged.
## atlas_marker_overlay.gd _draw_attractor_shape(), ported unchanged. `size`
## arrives ALREADY zoom-compensated from _draw_attractors() — this function
## stays a pure "draw at this literal size" primitive with no ctx/zoom
## knowledge of its own, matching the retired code's own signature exactly.
func _draw_attractor_shape(atype: String, pos: Vector2, size: float, color: Color) -> void:
match atype:
"RiverMouth":