fix(client): PR #192 review round — per-rung legend, Region coalescing + clamp boundary tests, halving-loop honesty
All Hoshe/Araminta findings addressed (Tyre approved outright), none retracted — plus a Dudley stop-and-flag discovery that improved on the asked-for fix: - Legend 100x lie (Araminta, blocking): subtitle computed via spacing_for_rung() and refreshed at all three _held_granularity_v2 write sites. Region test asserts 204.800 km/cell; the District direction needed a stale-header-aware helper — a District-only test spuriously passes against the old literal by coincidence. - Region coalescing coverage (Hoshe 1): both directions tested (Region-vs-District separate slots; Region-vs-Region coalesces). - Clamp boundary tests + dangling citations (Hoshe 2): writing the requested halving-loop-fires test surfaced that the loop is PROVABLY UNREACHABLE at current constants (per-axis clamp forecloses it — brute-forced independently on both server and client sides). Ruling: the loop stays as defensive code; the test became a property sweep pinning both the wire-cap invariant and the loop's no-op status (a future constant change breaks it loudly); doc comments on both sides drop the load-bearing framing and state the truth; the old client mirror test that claimed the loop fires (passing on the per-axis clamp alone) is replaced the same way. Client citations now name the real server tests verbatim. - Governance (Tyre): D-226 amendment note — progressive cross-rung refinement EXTENDS T-1124 §4 (not supersedes); legacy u32 field scheduled for retirement (T-1159). Server: 1818 lib tests green, clippy/fmt clean. Client: zoom_ladder 48/48, window_request 26/26, viewer 74/74; gdlint clean. Every fix revert-verified.
This commit is contained in:
@@ -16,6 +16,7 @@ const PANEL_MARGIN: float = 16.0
|
||||
const LEGEND_PANEL_WIDTH: float = 260.0
|
||||
|
||||
const AtlasOverlayColors := preload("res://ui/implant/apps/atlas/atlas_overlay_colors.gd")
|
||||
const AtlasWindowGeometry := preload("res://ui/implant/apps/atlas/atlas_window_geometry.gd")
|
||||
|
||||
## The morphology base layer folds its 17 zones into ~5 family rows (§5:
|
||||
## "mirroring T-1112's 'not everything earns permanent screen space'
|
||||
@@ -55,13 +56,28 @@ func reposition() -> void:
|
||||
## Always shows the base-layer key (morphology + elevation reading, always
|
||||
## on) plus glaciation (always-on modifier), then whichever toggle overlay is
|
||||
## currently active, if any.
|
||||
##
|
||||
## The subtitle's km/cell reading is NOT a fixed "district window" label —
|
||||
## it was until this fix hardcoded District's own 2.048 km/cell, which was a
|
||||
## 100x lie whenever the viewer actually holds Region (204.8 km/cell) or
|
||||
## Quarter (0.512 km/cell). Read through AtlasWindowGeometry.
|
||||
## spacing_for_rung() — the SAME pure lookup _refresh_screen_header() uses
|
||||
## for the main screen header's subtitle (screen_header_content()) — keyed
|
||||
## off the viewer's current get_held_granularity_v2(), so the legend and
|
||||
## header can never disagree. AtlasWindowViewer calls refresh() at every
|
||||
## point _held_granularity_v2 changes (_enter_tile_mode(), _enter_at_rung(),
|
||||
## _on_window_ready()'s rung-swap adoption) — see those call sites.
|
||||
func refresh() -> void:
|
||||
if _viewer == null:
|
||||
return
|
||||
clear()
|
||||
visible = true
|
||||
|
||||
add_component(ImplantHeader.new("REGIONAL LEGEND", "district window · 2.048 km/cell"))
|
||||
var spacing_km: float = AtlasWindowGeometry.spacing_for_rung(_viewer.get_held_granularity_v2()) / 1000.0
|
||||
var subtitle: String = "%s window · %.3f km/cell" % [
|
||||
_viewer.get_held_granularity_v2().to_lower(), spacing_km
|
||||
]
|
||||
add_component(ImplantHeader.new("REGIONAL LEGEND", subtitle))
|
||||
add_component(ImplantSeparator.new())
|
||||
|
||||
_add_morphology_section()
|
||||
|
||||
@@ -25,8 +25,8 @@ extends Control
|
||||
## keeps drawing — progressive refinement, no blank frame (§6). A pan
|
||||
## past the held window's edge re-requests the SAME rung at a new center.
|
||||
## - Zooming fully out snaps to the CANONICAL planetary frame (Jeroen's HARD
|
||||
## condition) — see _maybe_reset_to_canonical_frame() — which, on a body
|
||||
## needing tiling, re-enters `_tile_mode` (live round 3, design doc §4).
|
||||
## condition, see _maybe_reset_to_canonical_frame()) — on a body needing
|
||||
## tiling, re-enters `_tile_mode` (live round 3, design doc §4).
|
||||
## - _window_request (atlas_window_request.gd) owns the single-window
|
||||
## cache/debounce/retry; _tile_set (atlas_window_tile_set.gd) owns N of
|
||||
## those for the tiled rest state — this Control decides WHICH is active.
|
||||
@@ -34,8 +34,7 @@ extends Control
|
||||
## Navigation (Jeroen's input-model ruling: LMB-drag panning BREAKS click
|
||||
## semantics with future map objects, so it's removed entirely):
|
||||
## WASD / arrow keys continuous pan, held (frame-rate independent, _process)
|
||||
## Edge scrolling cursor within EDGE_SCROLL_MARGIN_PX of a viewport
|
||||
## edge pans toward it (suppressed over UI / unfocused)
|
||||
## Edge scrolling cursor within EDGE_SCROLL_MARGIN_PX pans toward it (suppressed over UI / unfocused)
|
||||
## Mouse wheel cursor-anchored zoom; crosses rungs continuously (T-1153)
|
||||
## Esc back (nav.pop())
|
||||
|
||||
@@ -47,14 +46,13 @@ const OVERLAY_BAR_HEADER_RESERVE: float = 360.0
|
||||
## T-1153: MIN_ZOOM/MAX_ZOOM are a wide safety clamp on the raw display
|
||||
## multiplier, NOT a rung boundary — wheel zoom is CONTINUOUS and UNCLAMPED
|
||||
## ACROSS RUNGS (D-013): crossing a rung's coverage ceiling
|
||||
## (AtlasWindowGeometry.select_rung()) re-requests a DIFFERENT granularity
|
||||
## at the SAME apparent screen extent, never clamping _view_zoom itself.
|
||||
## (AtlasWindowGeometry.select_rung()) re-requests a DIFFERENT granularity at
|
||||
## the SAME apparent screen extent, never clamping _view_zoom itself.
|
||||
## set_view() (T-1120 capture API) clamps to this same range independently.
|
||||
##
|
||||
## MIN_ZOOM must stay low enough that fit_window_view()'s COVER fit for
|
||||
## enter_orbital()'s largest legal `n` is never itself clamped — that would
|
||||
## silently show LESS than the whole body, breaking Jeroen's HARD condition.
|
||||
## 0.0005 covers a ~120,000 km-radius body at a 3840px 4K viewport.
|
||||
## enter_orbital()'s largest legal `n` is never itself clamped (would
|
||||
## silently show LESS than the whole body). 0.0005 covers a ~120,000 km-
|
||||
## radius body at a 3840px 4K viewport.
|
||||
const MIN_ZOOM: float = 0.0005
|
||||
const MAX_ZOOM: float = 64.0
|
||||
const ZOOM_STEP: float = 1.15
|
||||
@@ -66,10 +64,9 @@ const ZOOM_STEP: float = 1.15
|
||||
const PAN_SPEED_CANVAS_PX_S: float = 96.0
|
||||
|
||||
## T-1145 item 2: cursor-to-edge distance (px) that triggers edge-scroll —
|
||||
## Jeroen's own number ("~24px").
|
||||
## Jeroen's own number ("~24px"). Uses the SAME speed as WASD (one pan feel,
|
||||
## two triggers) — no separate constant, _process() reads PAN_SPEED_CANVAS_PX_S for both.
|
||||
const EDGE_SCROLL_MARGIN_PX: float = 24.0
|
||||
## Edge-scroll uses the SAME speed as WASD (one pan feel, two triggers) —
|
||||
## no separate constant, _process() reads PAN_SPEED_CANVAS_PX_S for both.
|
||||
|
||||
## Pixel size of one district cell at zoom=1.0 — a fixed on-screen scale
|
||||
## (unlike AtlasViewer's heightmap, there is no source texture dictating a
|
||||
@@ -82,17 +79,14 @@ const COLOR_BG: Color = Color("#0d1117")
|
||||
## Border-fade target (§5 "what renders during the wait"): the underlying
|
||||
## whole-body heightmap's own background tint, so the newly-exposed edge
|
||||
## reads as "real data seen through", not a placeholder block. Reuses
|
||||
## AtlasViewer's own COLOR_HEIGHTMAP_TINT-adjacent dim value rather than
|
||||
## inventing a new one — this IS a dimmer/less-certain read of the same
|
||||
## planetary data, not a different visual language.
|
||||
## AtlasViewer's own COLOR_HEIGHTMAP_TINT-adjacent dim value — a dimmer/
|
||||
## less-certain read of the same planetary data, not a different visual language.
|
||||
const COLOR_BORDER_FADE: Color = Color(0.20, 0.24, 0.30, 0.55)
|
||||
|
||||
## T-1153/R6: pending-refinement wash — the border-fade's referent repointed
|
||||
## to "the previous derived composite at this position" for a rung-crossing
|
||||
## zoom (progressive refinement leaves real data on screen, unlike the
|
||||
## no-composite-yet case COLOR_BORDER_FADE covers). Same hue family, much
|
||||
## lighter alpha — a hint that something sharper is arriving, not a claim
|
||||
## that the current view is empty or wrong.
|
||||
## T-1153/R6: pending-refinement wash — border-fade's referent repointed to
|
||||
## "the previous derived composite at this position" for a rung-crossing zoom
|
||||
## (real data stays on screen, unlike the no-composite-yet case above). Same
|
||||
## hue, lighter alpha — hints something sharper is arriving, not that the view is wrong.
|
||||
const COLOR_PENDING_REFINEMENT_WASH: Color = Color(0.20, 0.24, 0.30, 0.12)
|
||||
|
||||
## D-243: 2,048 m per district side.
|
||||
@@ -311,6 +305,7 @@ func _enter_tile_mode(body: Dictionary, system: Dictionary, radius_km: float) ->
|
||||
_window_request.reset()
|
||||
_tile_set.enter(_dict_str(_body, "body_id", ""), radius_km)
|
||||
_refresh_screen_header()
|
||||
_legend_panel.refresh()
|
||||
grab_focus()
|
||||
queue_redraw()
|
||||
_overlay_node.queue_redraw()
|
||||
@@ -346,6 +341,7 @@ func _enter_at_rung(
|
||||
_dict_str(_body, "body_id", ""), _held_center, clamped_n, granularity_v2
|
||||
)
|
||||
_refresh_screen_header()
|
||||
_legend_panel.refresh()
|
||||
grab_focus()
|
||||
queue_redraw()
|
||||
_overlay_node.queue_redraw()
|
||||
@@ -413,6 +409,12 @@ func get_held_n() -> int:
|
||||
return _held_n
|
||||
|
||||
|
||||
## Currently-HELD rung tag — legend reads this via spacing_for_rung(),
|
||||
## mirroring _refresh_screen_header()'s own use of the field.
|
||||
func get_held_granularity_v2() -> String:
|
||||
return _held_granularity_v2
|
||||
|
||||
|
||||
## Live round 5: current body's radius — mosaic draw needs `cols` for
|
||||
## nearest_wrap_image()'s wrap resolution. Mirrors the
|
||||
## `_body.get("body_radius_km", 0.0)` pattern used throughout this file.
|
||||
@@ -495,6 +497,7 @@ func _on_window_ready(window: Dictionary) -> void:
|
||||
_fit_and_center()
|
||||
_awaiting_first_window = false
|
||||
_refresh_screen_header()
|
||||
_legend_panel.refresh()
|
||||
queue_redraw()
|
||||
_overlay_node.queue_redraw()
|
||||
|
||||
@@ -968,12 +971,9 @@ func _notification(what: int) -> void:
|
||||
_position_overlay_bar()
|
||||
if _legend_panel:
|
||||
_legend_panel.reposition()
|
||||
# T-1142: re-fit on resize too, same _user_adjusted guard as the other
|
||||
# two auto-fit events (enter, first window arrival) — never fights a
|
||||
# manually-adjusted view. _canvas guard matches _overlay_bar/
|
||||
# _legend_panel above: NOTIFICATION_RESIZED can fire mid-_ready()
|
||||
# (anchor_right/anchor_bottom assignment triggers it) BEFORE _canvas
|
||||
# is constructed — confirmed the hard way (gdUnit add_child() crash).
|
||||
# T-1142: re-fit on resize (_user_adjusted guard, as other auto-fit
|
||||
# events). _canvas guard: NOTIFICATION_RESIZED can fire mid-_ready()
|
||||
# before _canvas exists (gdUnit add_child() crash, confirmed).
|
||||
if _canvas and not _user_adjusted:
|
||||
_fit_and_center()
|
||||
elif what == NOTIFICATION_APPLICATION_FOCUS_OUT:
|
||||
|
||||
Reference in New Issue
Block a user