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>
953 lines
48 KiB
GDScript
953 lines
48 KiB
GDScript
## T-1153 (D-226 T-1143-rulings amendment): tests for the continuous
|
|
## cursor-anchored zoom ladder — enter_orbital() (the canonical planetary
|
|
## frame), progressive refinement (held composite survives a rung-crossing
|
|
## request), the full-zoom-out reset (Jeroen's HARD condition), rung
|
|
## reselection on zoom, and E/W wrap + pole-wall clamps at Region
|
|
## granularity. Split out of test_atlas_window_viewer.gd (which owns the
|
|
## pre-T-1153 window-viewer behavior — entry, cache reuse, WASD/edge-scroll,
|
|
## fit-and-center) purely for file-length reasons (gdlint max-file-lines);
|
|
## same instantiation/mock-response conventions as that file, not a
|
|
## different testing philosophy.
|
|
class_name TestAtlasZoomLadder
|
|
extends GdUnitTestSuite
|
|
|
|
const AtlasWindowRequest := preload("res://ui/implant/apps/atlas/atlas_window_request.gd")
|
|
const AtlasDescendGeometry := preload("res://ui/implant/apps/atlas/atlas_descend_geometry.gd")
|
|
const AtlasWindowGeometry := preload("res://ui/implant/apps/atlas/atlas_window_geometry.gd")
|
|
|
|
## Dudley's WINDOW_GRANULARITY_REGION_KEY (server/src/atlas/layer_proxy.rs) —
|
|
## `u32::MAX`, a RESERVED KEY-SPACE TAG the real server ALWAYS puts in the
|
|
## legacy `granularity` slot for every Region response (never a real
|
|
## multiplier — District=1/Quarter=4 are the only legal wire multipliers).
|
|
## Do NOT "fix" this to 1 — that would silently un-repro the live-round bug
|
|
## this constant exists to guard against (a real server's actual wire byte,
|
|
## not a convenient test value). See _echoed_granularity_matches()'s own doc
|
|
## (atlas_window_request.gd) for why this value can NEVER equal a client's
|
|
## stored `_granularity` (which stays pinned at DISTRICT_GRANULARITY=1 for
|
|
## every rung a T-1152-aware client requests) — that mismatch is exactly
|
|
## what silently dropped every Region response before the v2-authoritative
|
|
## fix.
|
|
const SERVER_LEGACY_GRANULARITY_REGION_SENTINEL: int = 4294967295
|
|
|
|
|
|
## Build a hand-authored DistrictWindowLayer dict (n=2 by default) — mirrors
|
|
## test_atlas_window_viewer.gd's own _mock_window().
|
|
static func _mock_window(center: Vector2i, n: int = 2) -> Dictionary:
|
|
return {
|
|
"center": [center.x, center.y],
|
|
"n": n,
|
|
"morphology": PackedByteArray([8, 14, 0, 1]),
|
|
"elev_q": PackedByteArray([40, 90, 5, 60]),
|
|
"temp_dc": [120, 95, -32768, 60],
|
|
"moisture_q": PackedByteArray([50, 30, 90, 20]),
|
|
"vegetation": PackedByteArray([2, 1, 6, 3]),
|
|
"glaciation": PackedByteArray([0, 0, 1, 2]),
|
|
}
|
|
|
|
|
|
static func _mock_response(body_id: String, window: Variant) -> Dictionary:
|
|
return {"body_id": body_id, "status": "Ready", "district_window": window}
|
|
|
|
|
|
# =============================================================================
|
|
# T-1153: enter_orbital() — the canonical planetary frame, the ladder's TOP
|
|
# REST STATE (Jeroen's HARD condition, D-226 T-1143-rulings amendment).
|
|
# =============================================================================
|
|
|
|
|
|
## enter_orbital() must center on district (0,0) — "district (0,0) sits at
|
|
## lon 0 / the equator" (AtlasDescendGeometry's own doc).
|
|
func test_enter_orbital_centers_on_the_canonical_origin() -> void:
|
|
var v: AtlasWindowViewer = auto_free(AtlasWindowViewer.new())
|
|
add_child(v)
|
|
v.enter_orbital({"body_id": "GJ380c", "body_radius_km": 6238.4}, {})
|
|
assert_that(v._held_center).is_equal(Vector2i.ZERO)
|
|
|
|
|
|
## enter_orbital() must request at Region granularity — the orbital view IS
|
|
## the Region rung at high n, not a separate screen/mode (the ticket's own
|
|
## framing).
|
|
func test_enter_orbital_requests_region_granularity() -> void:
|
|
var v: AtlasWindowViewer = auto_free(AtlasWindowViewer.new())
|
|
add_child(v)
|
|
v.enter_orbital({"body_id": "GJ380c", "body_radius_km": 6238.4}, {})
|
|
assert_str(v._held_granularity_v2).is_equal("Region")
|
|
|
|
|
|
## **Superseded by live round 3's tiling fix — retargeted, not deleted.**
|
|
## GJ380c/Lendel (radius 6238.4 km) was the ORIGINAL single-window C1 repro
|
|
## (raw cols ~19,139 vs. the 6,400 clamp ceiling) — but that SAME threshold
|
|
## (`DISTRICT_WINDOW_MAX_N_REGION * DISTRICT_M` = the coverage ceiling
|
|
## `compute_tile_grid()` tiles past) means any body needing the n-clamp ALSO
|
|
## needs tiling: there is no real body where enter_orbital() takes the
|
|
## single-window path with a raw `n` big enough to require clamping.
|
|
## GJ380c now correctly enters TILE mode (test_enter_orbital_n_is_the_clamped_value_not_raw_circumference's
|
|
## old assertion on a single clamped `_held_n` no longer applies — see
|
|
## test_enter_orbital_tile_mode_held_n_is_the_whole_body_extent below for
|
|
## what `_held_n` means in tile mode instead). The single-window clamp-mirror
|
|
## fix itself remains covered: `_enter_at_rung()`'s own doc/the clamp
|
|
## mirror's unit tests (test_atlas_window_request.gd) pin the formula
|
|
## directly, and test_zoom_crossing_fires_request_and_accepts_wire_accurate_refinement
|
|
## exercises the SAME clamp-mirror lesson at the reselect (not entry)
|
|
## boundary, which single-window mode still reaches on the way DOWN from a
|
|
## tile-mode zoom-in.
|
|
func test_enter_orbital_tile_mode_held_n_is_the_whole_body_extent() -> void:
|
|
var v: AtlasWindowViewer = auto_free(AtlasWindowViewer.new())
|
|
add_child(v)
|
|
var radius_km := 6238.4 # GJ380c (Lendel)
|
|
var extent: Dictionary = AtlasDescendGeometry.district_extent(radius_km)
|
|
var raw_cols: int = int(extent["cols"])
|
|
v.enter_orbital({"body_id": "GJ380c", "body_radius_km": radius_km}, {})
|
|
|
|
assert_bool(v.is_tile_mode()).override_failure_message(
|
|
"GJ380c/Lendel needs tiling — enter_orbital() must have entered tile mode"
|
|
).is_true()
|
|
# In TILE mode, _held_n is the WHOLE body's extent (unclamped) — each
|
|
# TILE clamps its own request independently inside AtlasWindowTileSet
|
|
# (see that file's own tests), so _held_n here is NOT expected to equal
|
|
# any single clamped value the way single-window mode's is.
|
|
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
|
|
## granularity slot — exactly what a real server sends) must be ACCEPTED
|
|
## into that tile's own slot — not silently dropped. This exercises BOTH
|
|
## live-round fixes (the v2-authoritative precedence AND per-tile clamping)
|
|
## through the tile-set path specifically, complementing
|
|
## test_atlas_window_tile_set.gd's own more granular orchestration tests.
|
|
func test_enter_orbital_tile_mode_accepts_a_wire_accurate_tile_response() -> void:
|
|
var v: AtlasWindowViewer = auto_free(AtlasWindowViewer.new())
|
|
add_child(v)
|
|
var radius_km := 6238.4 # GJ380c (Lendel) — the live-repro body
|
|
v.enter_orbital({"body_id": "GJ380c", "body_radius_km": radius_km}, {})
|
|
assert_bool(v.is_tile_mode()).is_true()
|
|
|
|
var tile_set = v.get_tile_set()
|
|
var tiles: Array = tile_set.get_tiles()
|
|
assert_int(tiles.size()).is_greater(1)
|
|
var first_tile_center: Vector2i = tiles[0]["center"]
|
|
|
|
var tile_window: Dictionary = {
|
|
"center": [first_tile_center.x, first_tile_center.y],
|
|
"n": AtlasWindowRequest.SERVER_DISTRICT_WINDOW_MAX_N_REGION,
|
|
"granularity": SERVER_LEGACY_GRANULARITY_REGION_SENTINEL,
|
|
"granularity_v2": "Region",
|
|
"morphology": PackedByteArray([8, 14, 0, 1]),
|
|
"elev_q": PackedByteArray([40, 90, 5, 60]),
|
|
"temp_dc": [120, 95, -32768, 60],
|
|
"moisture_q": PackedByteArray([50, 30, 90, 20]),
|
|
"vegetation": PackedByteArray([2, 1, 6, 3]),
|
|
"glaciation": PackedByteArray([0, 0, 1, 2]),
|
|
}
|
|
SimBridge.atlas_layers_received.emit(_mock_response("GJ380c", tile_window))
|
|
|
|
assert_that(tile_set.get_tiles()[0]["window"]).override_failure_message(
|
|
"a wire-accurate response (clamped n, Region granularity_v2, the legacy"
|
|
+ " sentinel) for the first tile must be ACCEPTED into that tile's slot"
|
|
).is_equal(tile_window)
|
|
|
|
|
|
## A no-radius body (tiny test body) has no circumference concept —
|
|
## enter_orbital() falls back to the District-rung default window rather
|
|
## than crashing or deriving a degenerate n.
|
|
func test_enter_orbital_no_radius_body_falls_back_to_district() -> void:
|
|
var v: AtlasWindowViewer = auto_free(AtlasWindowViewer.new())
|
|
add_child(v)
|
|
v.enter_orbital({"body_id": "GJ380c"}, {})
|
|
assert_str(v._held_granularity_v2).is_equal("District")
|
|
assert_int(v._held_n).is_equal(AtlasWindowRequest.DISTRICT_WINDOW_DEFAULT_N)
|
|
|
|
|
|
# =============================================================================
|
|
# T-1153: progressive refinement — the held composite survives until the
|
|
# replacement arrives (§6 "no mode flip": never a blank frame, never a
|
|
# clear-then-redraw).
|
|
# =============================================================================
|
|
|
|
|
|
## The core acceptance test: once a window is held, a request for a
|
|
## DIFFERENT rung being in-flight must NOT clear `_window` — the old
|
|
## composite stays exactly what get_district_window() returns until the new
|
|
## rung's response actually arrives and is adopted.
|
|
func test_held_window_survives_while_a_different_rung_request_is_in_flight() -> void:
|
|
var v: AtlasWindowViewer = auto_free(AtlasWindowViewer.new())
|
|
add_child(v)
|
|
v.enter({"body_id": "GJ380c"}, {}, Vector2i(10, 20), 2)
|
|
var district_window: Dictionary = _mock_window(Vector2i(10, 20), 2)
|
|
SimBridge.atlas_layers_received.emit(_mock_response("GJ380c", district_window))
|
|
assert_that(v.get_district_window()).is_equal(district_window)
|
|
|
|
# Simulate a rung-reselect firing a NEW (Region) request without the
|
|
# response having arrived yet — direct call, mirroring what
|
|
# _maybe_reselect_rung() does internally.
|
|
v._window_request.request_debounced("GJ380c", Vector2i(10, 20), 2, "Region")
|
|
|
|
assert_that(v.get_district_window()).override_failure_message(
|
|
"the OLD composite must survive while a different-rung request is in"
|
|
+ " flight — no blank frame, no premature clear"
|
|
).is_equal(district_window)
|
|
|
|
|
|
## Once the new rung's response actually arrives (matching the CURRENTLY
|
|
## in-flight request's granularity_v2), it swaps in — the composite reference
|
|
## changes from the old rung's window to the new one.
|
|
func test_new_rung_window_swaps_in_once_it_arrives() -> void:
|
|
var v: AtlasWindowViewer = auto_free(AtlasWindowViewer.new())
|
|
add_child(v)
|
|
v.enter({"body_id": "GJ380c"}, {}, Vector2i(10, 20), 2)
|
|
var district_window: Dictionary = _mock_window(Vector2i(10, 20), 2)
|
|
SimBridge.atlas_layers_received.emit(_mock_response("GJ380c", district_window))
|
|
|
|
v._window_request.request_debounced("GJ380c", Vector2i(10, 20), 2, "Region")
|
|
var region_window: Dictionary = {
|
|
"center": [10, 20], "n": 2, "granularity_v2": "Region",
|
|
"morphology": PackedByteArray([1, 2, 3, 4]),
|
|
"elev_q": PackedByteArray([10, 20, 30, 40]),
|
|
"temp_dc": [0, 0, 0, 0],
|
|
"moisture_q": PackedByteArray([0, 0, 0, 0]),
|
|
"vegetation": PackedByteArray([0, 0, 0, 0]),
|
|
"glaciation": PackedByteArray([0, 0, 0, 0]),
|
|
}
|
|
SimBridge.atlas_layers_received.emit(_mock_response("GJ380c", region_window))
|
|
|
|
assert_that(v.get_district_window()).override_failure_message(
|
|
"once the new rung's matching response arrives, it must swap in"
|
|
).is_equal(region_window)
|
|
assert_str(v._held_granularity_v2).is_equal("Region")
|
|
|
|
|
|
## refresh() clear()s via queue_free() (deferred, not synchronous) — a legend
|
|
## that has refreshed more than once in the same frame (build-time refresh at
|
|
## _ready(), then an entry-time refresh) can have STALE not-yet-freed
|
|
## children still parented alongside the new ones. add_component() always
|
|
## APPENDS, so the current ImplantHeader is the LAST one in the list, never
|
|
## assumed to be [0].
|
|
static func _current_legend_header(legend_panel) -> ImplantHeader:
|
|
var children: Array = legend_panel.get_implant_children()
|
|
for i in range(children.size() - 1, -1, -1):
|
|
if children[i] is ImplantHeader:
|
|
return children[i]
|
|
return null
|
|
|
|
|
|
## PR #192 review (Araminta, BLOCKING): the legend subtitle used to hardcode
|
|
## District's own "2.048 km/cell" — a 100x lie whenever the viewer actually
|
|
## holds Region (204.8 km/cell). While in the orbital tile-mode rest state
|
|
## (Region granularity), the legend must read Region's real spacing, not the
|
|
## stale District literal.
|
|
func test_legend_subtitle_reflects_region_spacing_in_tile_mode() -> void:
|
|
var v: AtlasWindowViewer = auto_free(AtlasWindowViewer.new())
|
|
add_child(v)
|
|
var radius_km := 6238.4 # GJ380c (Lendel) — needs tiling, so enters at Region
|
|
v.enter_orbital({"body_id": "GJ380c", "body_radius_km": radius_km}, {})
|
|
assert_bool(v.is_tile_mode()).is_true()
|
|
assert_str(v._held_granularity_v2).is_equal("Region")
|
|
|
|
var header: ImplantHeader = _current_legend_header(v._legend_panel)
|
|
assert_str(header._subtitle_label.text).override_failure_message(
|
|
"legend subtitle must reflect Region's real 204.800 km/cell spacing while"
|
|
+ " the viewer holds Region granularity, not a hardcoded District figure"
|
|
).contains("204.800 km/cell")
|
|
|
|
|
|
## Same bug, the other direction: after crossing INTO a single-window District
|
|
## rung, the legend must re-render with District's own spacing — proving the
|
|
## legend actually refreshes on a rung change rather than being stuck at
|
|
## whatever it showed on the FIRST refresh() call (T-1153's _build_legend_panel()
|
|
## fires one at _ready() time, before any real rung is held).
|
|
func test_legend_subtitle_reflects_district_spacing_after_crossing_in() -> void:
|
|
var v: AtlasWindowViewer = auto_free(AtlasWindowViewer.new())
|
|
add_child(v)
|
|
v.enter({"body_id": "GJ380c"}, {}, Vector2i(10, 20), 2)
|
|
assert_str(v._held_granularity_v2).is_equal("District")
|
|
|
|
var header: ImplantHeader = _current_legend_header(v._legend_panel)
|
|
assert_str(header._subtitle_label.text).override_failure_message(
|
|
"legend subtitle must re-render at District's own 2.048 km/cell spacing"
|
|
+ " once the viewer holds a District-rung window — proving refresh() is"
|
|
+ " actually wired to the rung change, not just called once at build time"
|
|
).contains("2.048 km/cell")
|
|
|
|
|
|
## A response for a rung OTHER than what's currently requested (e.g. a
|
|
## District response arriving after the viewer has already moved on to a
|
|
## Region request — a rapid wheel-zoom race) must be discarded as stale, the
|
|
## held composite untouched.
|
|
func test_stale_rung_response_after_moving_on_is_discarded() -> void:
|
|
var v: AtlasWindowViewer = auto_free(AtlasWindowViewer.new())
|
|
add_child(v)
|
|
v.enter({"body_id": "GJ380c"}, {}, Vector2i(10, 20), 2)
|
|
var district_window: Dictionary = _mock_window(Vector2i(10, 20), 2)
|
|
SimBridge.atlas_layers_received.emit(_mock_response("GJ380c", district_window))
|
|
|
|
v._window_request.request_debounced("GJ380c", Vector2i(10, 20), 2, "Region")
|
|
# A LATE district-rung response for the same (center, n) arrives after the
|
|
# viewer has already moved on to requesting Region — must be dropped.
|
|
var late_district_window: Dictionary = _mock_window(Vector2i(10, 20), 2)
|
|
late_district_window["morphology"] = PackedByteArray([9, 9, 9, 9]) # distinguishable payload
|
|
SimBridge.atlas_layers_received.emit(_mock_response("GJ380c", late_district_window))
|
|
|
|
assert_that(v.get_district_window()).override_failure_message(
|
|
"a stale response for a rung the viewer has since moved on from must be discarded"
|
|
).is_equal(district_window)
|
|
|
|
|
|
# =============================================================================
|
|
# T-1153: full-zoom-out reset (Jeroen's HARD condition).
|
|
# =============================================================================
|
|
|
|
|
|
## Directly at the canonical frame already (center (0,0), Region granularity)
|
|
## must be a no-op — never re-fights a player zooming back IN from the top.
|
|
func test_reset_to_canonical_frame_is_noop_when_already_there() -> void:
|
|
var v: AtlasWindowViewer = auto_free(AtlasWindowViewer.new())
|
|
add_child(v)
|
|
v.enter_orbital({"body_id": "GJ380c", "body_radius_km": 6238.4}, {})
|
|
v._held_center = Vector2i.ZERO
|
|
v._held_granularity_v2 = "Region"
|
|
var fired: bool = v._maybe_reset_to_canonical_frame()
|
|
assert_bool(fired).override_failure_message(
|
|
"already at the canonical frame — the reset must not re-fire"
|
|
).is_false()
|
|
|
|
|
|
## A no-radius body must never trigger the reset (no circumference concept —
|
|
## matches enter_orbital()'s own guard).
|
|
func test_reset_to_canonical_frame_never_fires_for_no_radius_body() -> void:
|
|
var v: AtlasWindowViewer = auto_free(AtlasWindowViewer.new())
|
|
add_child(v)
|
|
v.enter({"body_id": "GJ380c"}, {}, Vector2i(5, 5), 2)
|
|
var fired: bool = v._maybe_reset_to_canonical_frame()
|
|
assert_bool(fired).is_false()
|
|
|
|
|
|
## Away from the canonical frame (a drifted District-rung pan/zoom state)
|
|
## with a fully-zoomed-out world extent must reset — the direct wiring test
|
|
## for Jeroen's HARD condition: enter() at a far-off center, then force the
|
|
## view zoom low enough that the displayed extent covers the whole body.
|
|
func test_reset_to_canonical_frame_fires_and_re_centers_when_fully_zoomed_out() -> void:
|
|
var v: AtlasWindowViewer = auto_free(AtlasWindowViewer.new())
|
|
add_child(v)
|
|
var radius_km := 50.0 # tiny synthetic body — small circumference, reachable by a modest zoom-out
|
|
v.enter({"body_id": "GJ380c", "body_radius_km": radius_km}, {}, Vector2i(500, 10), 32)
|
|
# Force a very low zoom — a huge displayed world extent, comfortably over
|
|
# this tiny body's whole circumference.
|
|
v._view_zoom = AtlasWindowViewer.MIN_ZOOM
|
|
|
|
var fired: bool = v._maybe_reset_to_canonical_frame()
|
|
|
|
assert_bool(fired).override_failure_message(
|
|
"a fully-zoomed-out view on a real-radius body must trigger the reset"
|
|
).is_true()
|
|
assert_that(v._held_center).override_failure_message(
|
|
"the reset must re-center on the canonical origin (0,0)"
|
|
).is_equal(Vector2i.ZERO)
|
|
assert_str(v._held_granularity_v2).override_failure_message(
|
|
"the reset must land on the Region rung — the ladder's top rest state"
|
|
).is_equal("Region")
|
|
|
|
|
|
## Live round 5's OWN repro, end to end: enter a TILING body's canonical
|
|
## frame, wheel-zoom IN far enough to cross out of tile mode (leaving
|
|
## `_held_granularity_v2` STALE at "Region" — a real, expected lag per
|
|
## `_maybe_reselect_rung()`'s own "does NOT touch _held_granularity_v2"
|
|
## doc, not a bug in that function), then wheel-zoom back OUT past the
|
|
## fully-zoomed-out threshold. The reset must fire and land EXACTLY on
|
|
## enter_orbital()'s own fit zoom for this body/viewport — not merely
|
|
## re-center while leaving `_view_zoom` wherever continued `_zoom_at()`
|
|
## scaling left it. Before the fix, the stale "Region" granularity
|
|
## satisfied the guard's OLD (center + granularity only) check forever,
|
|
## so the reset never fired again and `_view_zoom` kept shrinking via
|
|
## plain multiplication all the way to MIN_ZOOM.
|
|
func test_reset_after_crossing_out_and_back_snaps_to_the_canonical_fit_zoom() -> void:
|
|
var v: AtlasWindowViewer = auto_free(AtlasWindowViewer.new())
|
|
add_child(v)
|
|
v.size = Vector2(1600.0, 900.0)
|
|
var radius_km := 6238.4 # GJ380c (Lendel) — a tiling body, the live-repro shape
|
|
v.enter_orbital({"body_id": "GJ380c", "body_radius_km": radius_km}, {})
|
|
assert_bool(v.is_tile_mode()).override_failure_message(
|
|
"sanity: Lendel must enter tile mode — this repro needs a TILING body,"
|
|
+ " since that's where _held_granularity_v2 can lag is_tile_mode()"
|
|
).is_true()
|
|
|
|
# Zoom IN far enough to cross out of tile mode (matching
|
|
# test_zoom_crossing_recomputes_view_offset_so_the_new_window_is_on_screen's
|
|
# own gesture shape).
|
|
var cursor_pos := Vector2(800.0, 450.0)
|
|
for _i in range(60):
|
|
v._zoom_at(cursor_pos, 1.15)
|
|
if not v.is_tile_mode():
|
|
break
|
|
assert_bool(v.is_tile_mode()).override_failure_message(
|
|
"sanity: this test needs to actually leave tile mode before zooming back out"
|
|
).is_false()
|
|
assert_str(v._held_granularity_v2).override_failure_message(
|
|
"sanity: _held_granularity_v2 must be STALE at Region here (no mock response"
|
|
+ " ever adopted a new value) — this is the exact lagging-field condition"
|
|
+ " the guard fix targets, not an artificial setup"
|
|
).is_equal("Region")
|
|
|
|
# Zoom back OUT past the fully-zoomed-out threshold — the reset must fire
|
|
# (possibly after a few more _zoom_at() ticks, matching a real wheel
|
|
# gesture rather than asserting it fires on the very first step back).
|
|
for _i in range(200):
|
|
v._zoom_at(cursor_pos, 1.0 / 1.05)
|
|
if v.is_tile_mode():
|
|
break
|
|
|
|
assert_bool(v.is_tile_mode()).override_failure_message(
|
|
"zooming back out past the threshold must re-fire the reset and land back"
|
|
+ " in tile mode — the stale-granularity guard bug left this permanently false"
|
|
).is_true()
|
|
assert_that(v._held_center).is_equal(Vector2i.ZERO)
|
|
|
|
var extent: Dictionary = AtlasDescendGeometry.district_extent(radius_km)
|
|
var n: int = int(extent["cols"])
|
|
var expected_fit: Dictionary = AtlasWindowGeometry.fit_window_view(
|
|
v.size, n, AtlasWindowViewer.CELL_PIXEL_SIZE, AtlasWindowViewer.MIN_ZOOM, AtlasWindowViewer.MAX_ZOOM
|
|
)
|
|
assert_float(v._view_zoom).override_failure_message(
|
|
(
|
|
"post-reset _view_zoom (%.6f) must equal enter_orbital()'s own fit zoom"
|
|
+ " (%.6f) for this body/viewport — Jeroen's condition is the ORIGINAL"
|
|
+ " frame (center AND offset AND fit zoom), not merely re-centered at"
|
|
+ " whatever zoom continued _zoom_at() scaling left behind"
|
|
)
|
|
% [v._view_zoom, expected_fit["zoom"]]
|
|
).is_equal_approx(float(expected_fit["zoom"]), 0.000001)
|
|
|
|
|
|
## Live round 5's OWN live-drive repro, exactly: a REAL wheel gesture does
|
|
## NOT stop the instant the reset first fires — the coordinator's own
|
|
## tmp_drive_ladder.gd keeps sending wheel-down ticks toward a fixed target
|
|
## zoom (0.004, chosen below the fit zoom) regardless of the reset. This
|
|
## test reproduces that shape directly: continue zooming out PAST the point
|
|
## where the reset first re-enters tile mode, all the way to a target zoom
|
|
## BELOW the fit value. Before the round-5 fix, `_view_zoom` drifted back
|
|
## down from the fit value on every subsequent `_zoom_at()` tick while the
|
|
## mode/center/granularity guard read "already canonical" and silently let
|
|
## it drift, landing on whatever the LOOP's target zoom happened to be
|
|
## instead of the fit value. **Live round 6 update:** the MECHANISM that
|
|
## now holds this assertion changed — `_zoom_at()`'s own zoom FLOOR (not a
|
|
## re-firing reset) is what keeps `_view_zoom` pinned at fit through
|
|
## continued zoom-out ticks; see `_maybe_reset_to_canonical_frame()`'s own
|
|
## doc for why re-firing on every tick caused a request storm. This test's
|
|
## own assertions are unchanged — only the doc below was updated to match.
|
|
func test_reset_resnaps_even_after_continued_zoom_out_past_the_first_reset() -> void:
|
|
var v: AtlasWindowViewer = auto_free(AtlasWindowViewer.new())
|
|
add_child(v)
|
|
v.size = Vector2(1600.0, 900.0)
|
|
var radius_km := 6238.4 # GJ380c (Lendel) — the live-repro body
|
|
v.enter_orbital({"body_id": "GJ380c", "body_radius_km": radius_km}, {})
|
|
|
|
var extent: Dictionary = AtlasDescendGeometry.district_extent(radius_km)
|
|
var n: int = int(extent["cols"])
|
|
var expected_fit: Dictionary = AtlasWindowGeometry.fit_window_view(
|
|
v.size, n, AtlasWindowViewer.CELL_PIXEL_SIZE, AtlasWindowViewer.MIN_ZOOM, AtlasWindowViewer.MAX_ZOOM
|
|
)
|
|
var fit_zoom: float = float(expected_fit["zoom"])
|
|
|
|
# Zoom IN far enough to leave tile mode (same shape as the test above).
|
|
var cursor_pos := Vector2(1100.0, 300.0) # matches tmp_drive_ladder.gd's own aim point
|
|
for _i in range(60):
|
|
v._zoom_at(cursor_pos, 1.15)
|
|
if not v.is_tile_mode():
|
|
break
|
|
assert_bool(v.is_tile_mode()).is_false()
|
|
|
|
# Zoom back OUT toward a target BELOW the fit zoom — matching
|
|
# tmp_drive_ladder.gd's own `_zoom_until(wv, 0.004, false)` exactly
|
|
# (Lendel's own fit zoom is ~0.00627, comfortably above this target),
|
|
# WITHOUT stopping early the moment tile mode is first regained. A real
|
|
# wheel gesture has no way to know when the reset internally fires.
|
|
var target_zoom := 0.004
|
|
for _i in range(200):
|
|
if v._view_zoom <= target_zoom:
|
|
break
|
|
v._zoom_at(cursor_pos, 1.0 / 1.05)
|
|
|
|
assert_bool(v.is_tile_mode()).override_failure_message(
|
|
"after continued zoom-out past the reset point, the view must settle back"
|
|
+ " into tile mode — a genuinely re-snapped canonical frame can't have zoomed"
|
|
+ " OUT further than the fit value in the first place"
|
|
).is_true()
|
|
assert_float(v._view_zoom).override_failure_message(
|
|
(
|
|
"post-reset _view_zoom (%.6f) must equal the canonical fit zoom (%.6f) even"
|
|
+ " though the wheel gesture continued past the point where the reset first"
|
|
+ " fired (target was %.6f, BELOW the fit zoom) — _zoom_at()'s own zoom floor"
|
|
+ " must keep pinning it at fit through every subsequent tick, not just once"
|
|
)
|
|
% [v._view_zoom, fit_zoom, target_zoom]
|
|
).is_equal_approx(fit_zoom, 0.000001)
|
|
|
|
|
|
## Live round 6's ANTI-STORM test — the exact repro the coordinator's live
|
|
## drive caught: drive a REAL continued zoom-out gesture (via `_zoom_at()`,
|
|
## the same call path the live drive uses — NOT calling
|
|
## `_maybe_reset_to_canonical_frame()` directly with unchanged state, which
|
|
## trivially can't reproduce the drift the storm depends on) many ticks past
|
|
## the point where the reset first fires — asserts ZERO additional tile-set
|
|
## entries occur across the WHOLE gesture. Spies on `AtlasWindowTileSet`'s
|
|
## own child `AtlasWindowRequest` node INSTANCES (captured right after the
|
|
## FIRST reset) — a fresh `enter_orbital()` call tears down (`queue_free()`s)
|
|
## every one of them and creates BRAND NEW ones, so "the same node
|
|
## instances are still alive and still the tile set's children after 100
|
|
## more ticks" is a direct, non-invasive proxy for "the reset never fired
|
|
## again" — no new production instrumentation needed. Before the round-6
|
|
## fix, `_zoom_at()`'s continued multiplicative zoom-out drifted `_view_zoom`
|
|
## below fit on every subsequent tick, the level-triggered guard read "not
|
|
## already there" every time, and `enter_orbital()` fired repeatedly:
|
|
## tearing down and recreating the tile set (and its 6 request nodes) every
|
|
## tick — exactly the "889 of 897 wire responses arrived during one
|
|
## zoom-out phase" storm.
|
|
func test_reset_evaluated_repeatedly_at_canonical_frame_issues_zero_additional_requests() -> void:
|
|
var v: AtlasWindowViewer = auto_free(AtlasWindowViewer.new())
|
|
add_child(v)
|
|
v.size = Vector2(1600.0, 900.0)
|
|
var radius_km := 6238.4 # GJ380c (Lendel) — a tiling body, the live-repro shape
|
|
v.enter_orbital({"body_id": "GJ380c", "body_radius_km": radius_km}, {})
|
|
assert_bool(v.is_tile_mode()).is_true()
|
|
|
|
# Zoom IN far enough to leave tile mode, then zoom back OUT past the
|
|
# first reset — same shape as the round-5 continued-zoom-out test, but
|
|
# this time spying on the tile set across the WHOLE remaining gesture
|
|
# instead of only checking the final zoom value.
|
|
var cursor_pos := Vector2(1100.0, 300.0) # matches tmp_drive_ladder.gd's own aim point
|
|
for _i in range(60):
|
|
v._zoom_at(cursor_pos, 1.15)
|
|
if not v.is_tile_mode():
|
|
break
|
|
assert_bool(v.is_tile_mode()).is_false()
|
|
|
|
for _i in range(200):
|
|
v._zoom_at(cursor_pos, 1.0 / 1.05)
|
|
if v.is_tile_mode():
|
|
break
|
|
assert_bool(v.is_tile_mode()).override_failure_message(
|
|
"sanity: the first reset must have fired before spying on the tile set"
|
|
).is_true()
|
|
|
|
var tile_set = v.get_tile_set()
|
|
var original_requests: Array = tile_set.get_children()
|
|
assert_int(original_requests.size()).override_failure_message(
|
|
"sanity: the first reset must have created real tile-request child nodes to spy on"
|
|
).is_greater(0)
|
|
|
|
# Continue the SAME zoom-out gesture 100 MORE ticks past the first
|
|
# reset — a real wheel gesture has no way to stop exactly at the reset
|
|
# point, and holding the wheel down (or residual scroll momentum) keeps
|
|
# sending ticks. None of these must tear down/recreate the tile set.
|
|
for _i in range(100):
|
|
v._zoom_at(cursor_pos, 1.0 / 1.05)
|
|
|
|
var current_requests: Array = tile_set.get_children()
|
|
assert_int(current_requests.size()).override_failure_message(
|
|
"the tile set's child count must be unchanged after 100 more continued"
|
|
+ " zoom-out ticks — a changed count means teardown/recreate happened"
|
|
).is_equal(original_requests.size())
|
|
for i in range(original_requests.size()):
|
|
assert_bool(is_instance_valid(original_requests[i])).override_failure_message(
|
|
"original tile-request node #%d must still be alive — a storm would have"
|
|
+ " queue_free()'d it and created a fresh one" % i
|
|
).is_true()
|
|
assert_bool(is_same(original_requests[i], current_requests[i])).override_failure_message(
|
|
(
|
|
"tile-request node #%d must be the SAME instance as right after the"
|
|
+ " first reset — a different object at the same index means the tile"
|
|
+ " set was torn down and recreated (a storm), even if the count"
|
|
+ " coincidentally matches"
|
|
)
|
|
% i
|
|
).is_true()
|
|
|
|
|
|
## Live round 6's BLACK-ENTRY repro: enter_orbital(), then deliver the six
|
|
## wire-accurate tile responses WHILE a REAL continued zoom-out gesture (via
|
|
## `_zoom_at()`, matching the live drive's actual input shape — a held
|
|
## wheel-down keeps sending ticks concurrently with responses streaming in
|
|
## from the server) is in flight — asserts all six are accepted and HELD
|
|
## (tile set stable throughout, no teardown between delivery and the final
|
|
## assertion). Before the round-6 fix, the level-triggered guard fired on
|
|
## every zoom-out tick once `_view_zoom` drifted below fit, tearing down the
|
|
## tile set mid-delivery and orphaning responses addressed to now-freed
|
|
## request nodes — nothing ever accumulated, and the mosaic stayed black
|
|
## even though the server dutifully answered every request.
|
|
func test_six_tile_responses_survive_concurrent_reset_evaluation_and_are_held() -> void:
|
|
var v: AtlasWindowViewer = auto_free(AtlasWindowViewer.new())
|
|
add_child(v)
|
|
v.size = Vector2(1600.0, 900.0)
|
|
var radius_km := 6238.4 # GJ380c (Lendel) — 6 tiles, the live-repro shape
|
|
v.enter_orbital({"body_id": "GJ380c", "body_radius_km": radius_km}, {})
|
|
assert_bool(v.is_tile_mode()).is_true()
|
|
|
|
var tile_set = v.get_tile_set()
|
|
var tiles: Array = tile_set.get_tiles()
|
|
assert_int(tiles.size()).override_failure_message(
|
|
"sanity: Lendel must produce Lendel's own real tile count (6) for this"
|
|
+ " repro to be faithful, not a smaller synthetic count"
|
|
).is_equal(6)
|
|
|
|
# Same continued zoom-out gesture as the anti-storm test above — leave
|
|
# tile mode, cross back into it (the first reset), then KEEP sending
|
|
# zoom-out ticks (a real held wheel has no way to stop exactly at the
|
|
# reset point). Responses are delivered interleaved with these ticks,
|
|
# exactly matching the live drive's concurrent shape.
|
|
var cursor_pos := Vector2(1100.0, 300.0)
|
|
for _i in range(60):
|
|
v._zoom_at(cursor_pos, 1.15)
|
|
if not v.is_tile_mode():
|
|
break
|
|
assert_bool(v.is_tile_mode()).is_false()
|
|
for _i in range(200):
|
|
v._zoom_at(cursor_pos, 1.0 / 1.05)
|
|
if v.is_tile_mode():
|
|
break
|
|
assert_bool(v.is_tile_mode()).override_failure_message(
|
|
"sanity: the first reset must have fired before delivering responses"
|
|
).is_true()
|
|
|
|
for i in range(tiles.size()):
|
|
var center: Vector2i = tiles[i]["center"]
|
|
var tile_window: Dictionary = {
|
|
"center": [center.x, center.y],
|
|
"n": AtlasWindowRequest.SERVER_DISTRICT_WINDOW_MAX_N_REGION,
|
|
"granularity": SERVER_LEGACY_GRANULARITY_REGION_SENTINEL,
|
|
"granularity_v2": "Region",
|
|
"morphology": PackedByteArray([8, 14, 0, 1]),
|
|
"elev_q": PackedByteArray([40, 90, 5, 60]),
|
|
"temp_dc": [120, 95, -32768, 60],
|
|
"moisture_q": PackedByteArray([50, 30, 90, 20]),
|
|
"vegetation": PackedByteArray([2, 1, 6, 3]),
|
|
"glaciation": PackedByteArray([0, 0, 1, 2]),
|
|
}
|
|
SimBridge.atlas_layers_received.emit(_mock_response("GJ380c", tile_window))
|
|
# Interleave several MORE continued zoom-out ticks, matching the live
|
|
# drive's per-frame cadence — none of these must tear anything down.
|
|
for _tick in range(5):
|
|
v._zoom_at(cursor_pos, 1.0 / 1.05)
|
|
|
|
var final_tiles: Array = tile_set.get_tiles()
|
|
assert_int(final_tiles.size()).override_failure_message(
|
|
"the tile set must still have all 6 tile slots — a storm mid-delivery"
|
|
+ " would have torn it down and rebuilt it with fresh (unfulfilled) slots"
|
|
).is_equal(6)
|
|
for i in range(final_tiles.size()):
|
|
assert_that(final_tiles[i]["window"]).override_failure_message(
|
|
(
|
|
"tile #%d's window must be HELD (non-null) — all six wire-accurate"
|
|
+ " responses delivered during a concurrent continued zoom-out gesture"
|
|
+ " must survive to be accepted, not be silently dropped by an"
|
|
+ " orphaning teardown"
|
|
)
|
|
% i
|
|
).is_not_null()
|
|
|
|
|
|
## Not fully zoomed out (a normal District-rung view) must NOT trigger the
|
|
## reset — only reaching the top of the ladder resets, not every zoom step.
|
|
func test_reset_to_canonical_frame_does_not_fire_when_not_fully_zoomed_out() -> void:
|
|
var v: AtlasWindowViewer = auto_free(AtlasWindowViewer.new())
|
|
add_child(v)
|
|
v.enter({"body_id": "GJ380c", "body_radius_km": 6238.4}, {}, Vector2i(500, 10), 32)
|
|
v._view_zoom = 1.0 # a normal, non-extreme zoom — nowhere near full planetary coverage
|
|
|
|
var fired: bool = v._maybe_reset_to_canonical_frame()
|
|
|
|
assert_bool(fired).override_failure_message(
|
|
"an ordinary District-rung view must not trigger the top-rest-state reset"
|
|
).is_false()
|
|
|
|
|
|
# =============================================================================
|
|
# T-1153: rung reselection — _zoom_at() crossing a rung threshold fires a
|
|
# new request without touching the held composite.
|
|
# =============================================================================
|
|
|
|
|
|
## Zooming OUT far enough from a District-rung window (small n, so a modest
|
|
## zoom-out already covers a huge world extent) must fire a coarser-rung
|
|
## request — the wheel-zoom-driven wiring test for _maybe_reselect_rung().
|
|
func test_zoom_out_past_district_threshold_requests_a_coarser_rung() -> void:
|
|
var v: AtlasWindowViewer = auto_free(AtlasWindowViewer.new())
|
|
add_child(v)
|
|
v.size = Vector2(800.0, 600.0)
|
|
v.enter({"body_id": "GJ380c"}, {}, Vector2i(0, 0), 2) # n=2 — a tiny window, easy to overshoot
|
|
var district_window: Dictionary = _mock_window(Vector2i(0, 0), 2)
|
|
SimBridge.atlas_layers_received.emit(_mock_response("GJ380c", district_window))
|
|
assert_str(v._window_request.get_granularity_v2()).is_equal("District")
|
|
|
|
# A big zoom-OUT factor (well under 1.0) from a tiny n=2 window blows the
|
|
# displayed world extent WAY past District's threshold.
|
|
v._zoom_at(Vector2(400.0, 300.0), 0.01)
|
|
|
|
assert_str(v._window_request.get_granularity_v2()).override_failure_message(
|
|
"zooming out far enough from a small District window must re-request a coarser rung"
|
|
).is_not_equal("District")
|
|
# The OLD composite must still be what's held — progressive refinement,
|
|
# not a block-on-derive clear.
|
|
assert_that(v.get_district_window()).is_equal(district_window)
|
|
|
|
|
|
## Zooming IN on a District-rung window (well within its own legal coverage
|
|
## band, `(32,768 m, 131,072 m]` per select_rung()'s redesigned per-rung
|
|
## ceiling model — viewport-independent since `canvas_px` no longer affects
|
|
## selection) must NOT trigger a rung change — this is the "zoom is
|
|
## client-side on the already-held composite" case, unchanged for in-rung
|
|
## zoom. Sets _view_zoom DIRECTLY to a value inside District's band (rather
|
|
## than relying on enter()'s COVER auto-fit, which for a small n can already
|
|
## sit right at Quarter's own threshold — a fit's zoom level is a
|
|
## display-density choice independent of what rung selection would pick from
|
|
## scratch, and this test is specifically about a SINGLE zoom-in STEP not
|
|
## crossing a boundary, not about where the auto-fit itself lands). The
|
|
## small 100x80 viewport here is incidental (any size works under the new
|
|
## viewport-independent model) — kept small only because that's what the
|
|
## original version of this test used.
|
|
func test_zoom_in_within_district_threshold_does_not_change_rung() -> void:
|
|
var v: AtlasWindowViewer = auto_free(AtlasWindowViewer.new())
|
|
add_child(v)
|
|
v.size = Vector2(100.0, 80.0)
|
|
v.enter({"body_id": "GJ380c"}, {}, Vector2i(0, 0), 32)
|
|
var district_window: Dictionary = _mock_window(Vector2i(0, 0), 32)
|
|
SimBridge.atlas_layers_received.emit(_mock_response("GJ380c", district_window))
|
|
v._view_zoom = 0.10666666666666667 # E=120,000m at C=100px — inside District's legal band
|
|
v._apply_transform()
|
|
|
|
v._zoom_at(Vector2(50.0, 40.0), 1.15) # a single ordinary zoom-in step
|
|
|
|
assert_str(v._window_request.get_granularity_v2()).override_failure_message(
|
|
"a single ordinary zoom-in step must not cross a rung threshold"
|
|
).is_equal("District")
|
|
|
|
|
|
## **Live round 3 regression, the direct end-to-end fix target:** a real
|
|
## wheel-zoom gesture (many `_zoom_at()` ticks, matching the shape a
|
|
## continuous mouse-wheel scroll actually produces) crossing from the
|
|
## Region rest state down through District into Quarter territory must (i)
|
|
## fire a request at the NEW granularity — `_window_request.get_granularity_v2()`
|
|
## must have changed by the end of the gesture — and (ii) accept a
|
|
## WIRE-ACCURATE response for that request: echoing the REQUEST's own
|
|
## (already re-centered, already re-clamped) center/n, which the live round
|
|
## found DIFFERS from the ORIGINAL held center (screen-center-anchored
|
|
## refinement re-centers on wherever the cursor currently maps to, not
|
|
## wherever the player started) — this is the "second latent drop" the
|
|
## coordinator specifically flagged: comparing the echo against a STALE
|
|
## `_held_center` (frozen at the pre-crossing value) rather than the
|
|
## request's own center would silently drop this response too.
|
|
## **Live round 3 update:** GJ380c/Lendel now enters TILE mode via
|
|
## enter_orbital() (bug B's fix), so this test starts from THERE — zooming
|
|
## in far enough crosses Region's coverage ceiling and must LEAVE tile mode
|
|
## for the single-window path at the new (finer) rung, exactly the
|
|
## `_maybe_reselect_rung()` "leaving_tile_mode" branch this test exercises.
|
|
func test_zoom_crossing_fires_request_and_accepts_wire_accurate_refinement() -> void:
|
|
var v: AtlasWindowViewer = auto_free(AtlasWindowViewer.new())
|
|
add_child(v)
|
|
v.size = Vector2(1600.0, 900.0)
|
|
var radius_km := 6238.4 # GJ380c (Lendel) — the live-repro body
|
|
v.enter_orbital({"body_id": "GJ380c", "body_radius_km": radius_km}, {})
|
|
assert_bool(v.is_tile_mode()).override_failure_message(
|
|
"GJ380c/Lendel must enter tile mode at the orbital rest state (live round 3)"
|
|
).is_true()
|
|
|
|
# A real wheel-zoom gesture: many ticks, cursor OFF-CENTER (so cursor-
|
|
# anchored zoom genuinely drifts the screen-to-district mapping away from
|
|
# the canonical origin, not just scaling in place) — matching the live
|
|
# drive's actual input shape, not a single synthetic jump. Zooming in far
|
|
# enough must cross OUT of Region's coverage ceiling, leaving tile mode.
|
|
var cursor_pos := Vector2(1100.0, 300.0) # off-center, biased toward one quadrant
|
|
for _i in range(60):
|
|
v._zoom_at(cursor_pos, 1.15)
|
|
if not v.is_tile_mode():
|
|
break
|
|
|
|
# (i) Tile mode must have been LEFT, and a request must have gone out at
|
|
# a NEW (finer) granularity via the single-window path.
|
|
assert_bool(v.is_tile_mode()).override_failure_message(
|
|
"zooming in far enough must leave tile mode for the single-window path"
|
|
).is_false()
|
|
var request_granularity: String = v._window_request.get_granularity_v2()
|
|
assert_str(request_granularity).override_failure_message(
|
|
"leaving tile mode must fire a request at a new (finer) granularity"
|
|
).is_not_equal("Region")
|
|
|
|
# (ii) The request's own center/n — read AFTER leaving tile mode, so this
|
|
# is whatever _maybe_reselect_rung() actually computed — is what a
|
|
# wire-accurate response must echo to be accepted.
|
|
var request_center: Vector2i = v._window_request._center
|
|
var request_n: int = v._window_request._n
|
|
|
|
var refinement_window: Dictionary = {
|
|
"center": [request_center.x, request_center.y],
|
|
"n": request_n,
|
|
"granularity_v2": request_granularity,
|
|
"morphology": PackedByteArray([1, 2, 3, 4]),
|
|
"elev_q": PackedByteArray([10, 20, 30, 40]),
|
|
"temp_dc": [0, 0, 0, 0],
|
|
"moisture_q": PackedByteArray([0, 0, 0, 0]),
|
|
"vegetation": PackedByteArray([0, 0, 0, 0]),
|
|
"glaciation": PackedByteArray([0, 0, 0, 0]),
|
|
}
|
|
SimBridge.atlas_layers_received.emit(_mock_response("GJ380c", refinement_window))
|
|
|
|
assert_that(v.get_district_window()).override_failure_message(
|
|
"a wire-accurate refinement response (echoing the REQUEST's own center/n/"
|
|
+ " granularity after leaving tile mode) must be ACCEPTED — comparing"
|
|
+ " against a stale/wrong reference instead of the request's own would"
|
|
+ " silently drop this response forever"
|
|
).is_equal(refinement_window)
|
|
assert_str(v._held_granularity_v2).is_equal(request_granularity)
|
|
|
|
|
|
## Live round 4's SECOND bug, pinned directly: `_maybe_reselect_rung()` must
|
|
## recompute `_view_offset` (via AtlasWindowGeometry.
|
|
## recompute_offset_for_held_n_change()) the instant `_held_n` changes across
|
|
## a rung crossing — leaving it untouched (the round-4 bug) means the single-
|
|
## window `Rect2(0,0,extent)` draw call renders at whatever screen position
|
|
## the OLD (Region-scale) offset happened to put canvas-local (0,0), which
|
|
## for a whole-body `held_n` vs. a 64-district District `held_n` is tens or
|
|
## hundreds of thousands of px away from the viewport — the exact "pitch
|
|
## black" repro. Asserts the NEW held window's own extent actually overlaps
|
|
## the viewport after the crossing, the concrete on-screen consequence a
|
|
## stale offset breaks.
|
|
func test_zoom_crossing_recomputes_view_offset_so_the_new_window_is_on_screen() -> void:
|
|
var v: AtlasWindowViewer = auto_free(AtlasWindowViewer.new())
|
|
add_child(v)
|
|
v.size = Vector2(1600.0, 900.0)
|
|
var radius_km := 6238.4 # GJ380c (Lendel) — the live-repro body
|
|
v.enter_orbital({"body_id": "GJ380c", "body_radius_km": radius_km}, {})
|
|
assert_bool(v.is_tile_mode()).is_true()
|
|
|
|
var cursor_pos := Vector2(1100.0, 300.0)
|
|
for _i in range(60):
|
|
v._zoom_at(cursor_pos, 1.15)
|
|
if not v.is_tile_mode():
|
|
break
|
|
assert_bool(v.is_tile_mode()).override_failure_message(
|
|
"sanity: this test needs to actually cross out of tile mode to exercise"
|
|
+ " the held_n change _maybe_reselect_rung() must react to"
|
|
).is_false()
|
|
|
|
# The new (post-crossing) window's screen-space rect, using the SAME
|
|
# formula the overlay's single-window _draw() itself uses
|
|
# (Rect2(0,0,extent,extent) in canvas-local space, then _canvas's own
|
|
# position/scale transform — _view_offset/_view_zoom here mirror that
|
|
# exactly, since _apply_transform() is what sets _canvas.position/scale).
|
|
var extent_screen: float = float(v._held_n) * v.CELL_PIXEL_SIZE * v._view_zoom
|
|
var screen_top_left: Vector2 = v._view_offset
|
|
var screen_bottom_right: Vector2 = screen_top_left + Vector2(extent_screen, extent_screen)
|
|
var viewport_rect := Rect2(Vector2.ZERO, v.size)
|
|
var window_rect := Rect2(screen_top_left, Vector2(extent_screen, extent_screen))
|
|
|
|
assert_bool(viewport_rect.intersects(window_rect)).override_failure_message(
|
|
(
|
|
"the new (post-crossing) held window's screen rect %s must overlap the"
|
|
+ " viewport %s — a stale _view_offset (never recomputed for the new"
|
|
+ " held_n=%d) is exactly live round 4's 'pitch black' bug: the composite"
|
|
+ " renders somewhere entirely off-canvas despite request/response/data"
|
|
+ " all being individually correct"
|
|
)
|
|
% [window_rect, viewport_rect, v._held_n]
|
|
).is_true()
|
|
|
|
|
|
# =============================================================================
|
|
# T-1153: E/W wrap and pole-wall clamps at EVERY rung — both are extent-
|
|
# relative (CELL_PIXEL_SIZE-based district-space math, unchanged regardless
|
|
# of which rung's data is actually held), so they must keep working
|
|
# unmodified at Region granularity, not just District/Quarter.
|
|
# =============================================================================
|
|
|
|
|
|
## The pole wall, wired through the real _apply_pan_delta() path, must still
|
|
## clamp at Region granularity — same mechanism as the existing District-rung
|
|
## test (test_wasd_pan_is_clamped_by_the_pole_wall_when_wired), just entered
|
|
## via enter_orbital() instead of enter().
|
|
func test_pole_wall_clamps_at_region_granularity_too() -> void:
|
|
var v: AtlasWindowViewer = auto_free(AtlasWindowViewer.new())
|
|
add_child(v)
|
|
v.size = Vector2(800.0, 800.0)
|
|
var radius_km := 50.0 # tiny synthetic body — pole wall reachable by an ordinary tick
|
|
v.enter_orbital({"body_id": "GJ380c", "body_radius_km": radius_km}, {})
|
|
assert_str(v._held_granularity_v2).is_equal("Region")
|
|
|
|
var unclamped_magnitude: float = 500.0 * AtlasWindowViewer.PAN_SPEED_CANVAS_PX_S * v.get_view_zoom()
|
|
v._apply_pan_delta(Vector2(0.0, -1.0), 500.0) # "W"/north held, an absurdly long tick
|
|
|
|
assert_float(absf(v.get_view_offset().y)).override_failure_message(
|
|
"the pole wall must still clamp an extreme pan at Region granularity"
|
|
).is_less(unclamped_magnitude * 0.5)
|
|
|
|
|
|
## East-west wrap (canonicalize_district_center()) must still apply to the
|
|
## pan-edge refloat's resulting center at Region granularity — a pan that
|
|
## carries the screen-center column past the body's circumference must wrap
|
|
## into [0, cols), never run away to an out-of-range column, exactly as the
|
|
## District-rung wrap tests already pin (T-1142 item 6a). At Region's own
|
|
## enormous held_n (a whole circumference), an ORDINARY pan tick's
|
|
## canvas-space delta is negligible relative to the window's half-extent
|
|
## (confirmed: ~0.08 districts per 5-second tick vs. a ~9,772-district
|
|
## half-window) — so this drives _maybe_refloat_window() DIRECTLY off a
|
|
## manually-set _view_offset large enough to genuinely cross the held
|
|
## window's edge, the same "exercise the actual edge-crossing branch, not
|
|
## just its no-op early-return" discipline _maybe_refloat_window()'s own
|
|
## inside-check comment describes.
|
|
func test_pan_edge_refloat_wraps_columns_at_region_granularity_too() -> void:
|
|
var v: AtlasWindowViewer = auto_free(AtlasWindowViewer.new())
|
|
add_child(v)
|
|
v.size = Vector2(800.0, 800.0)
|
|
var radius_km := 6371.0
|
|
var extent: Dictionary = AtlasDescendGeometry.district_extent(radius_km)
|
|
var cols: int = int(extent["cols"])
|
|
v.enter_orbital({"body_id": "GJ380c", "body_radius_km": radius_km}, {})
|
|
assert_str(v._held_granularity_v2).is_equal("Region")
|
|
|
|
# Force the held center to sit one column short of the wrap seam, then
|
|
# shift the CANVAS offset by more than half the window's own on-screen
|
|
# extent — enough to move the screen-center's mapped column past the
|
|
# window's far edge (i.e. past `cols`, crossing the seam) regardless of
|
|
# Region's huge held_n.
|
|
v._held_center = Vector2i(cols - 1, 0)
|
|
var half_window_screen_px: float = float(v._held_n) * v.get_cell_pixel_size() * v.get_view_zoom() * 0.5
|
|
v._view_offset = v.get_view_offset() - Vector2(half_window_screen_px * 1.5, 0.0)
|
|
v._maybe_refloat_window()
|
|
|
|
assert_int(v._held_center.x).override_failure_message(
|
|
"a pan crossing the antimeridian at Region granularity must wrap the"
|
|
+ " resulting center into [0, cols), never run past cols"
|
|
).is_less(cols)
|
|
assert_int(v._held_center.x).is_greater_equal(0)
|