test(ui): spy tests for the B3 arrival-redraw fix (PR #197 Hoshe #3)

Two _CountingNatureOverlay draw-spy tests (the PR #196 pattern):
window arrival and rung-swap arrival both advance the nature
overlay's draw count past baseline. Two isolation bugs caught before
reporting: (1) _on_window_ready has a second pre-existing redraw path
via _fit_and_center -> _apply_transform that fires on first arrivals
and masks a sabotaged line 507 — isolated by setting _user_adjusted
(the real pan/zoom guard, a reachable state); (2) simulating the swap
via _enter_at_rung resets _awaiting_first_window and takes the same
masked branch — the real production trigger is _maybe_reselect_rung's
request path, so the test drives request_now directly. Revert-
verified: dropping the queue_redraw line fails exactly both tests by
name; 82/82 restored. Viewer file itself zero net diff.

Tickets: T-1170

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-23 14:54:10 +02:00
co-authored by Claude Fable 5
parent b9afa19d65
commit 581be31549
+140
View File
@@ -233,6 +233,146 @@ func test_set_overlay_visible_gen_basins_flips_gate_and_redraws_nature_overlay()
).is_greater(baseline)
## T-1170 B3 (PR #197 review, Hoshe #3): _on_window_ready() now also calls
## _nature_overlay.queue_redraw() (atlas_window_viewer.gd:507) — courses ride
## `DistrictWindowLayer.courses`, the SAME `_window` this handler adopts, so
## a window arrival that never redraws the nature overlay would leave freshly
## arrived courses invisible until an UNRELATED pan/zoom gesture happened to
## redraw it. Same spy-and-baseline shape as
## test_set_overlay_visible_gen_basins_flips_gate_and_redraws_nature_overlay()
## above — this is the exact pattern PR #196 established for "prove a
## specific queue_redraw() call site actually fires", applied to the
## window-arrival call site instead of the toggle call site.
##
## **Isolation note (live finding while writing this test):** `_on_window_ready()`
## ALSO calls `_fit_and_center()` on the first-ever arrival
## (`_awaiting_first_window and not _user_adjusted`), and `_fit_and_center()`
## itself already ends in `_apply_transform()`, which redraws the nature
## overlay through a SEPARATE, pre-existing call site. That path would mask
## a broken/removed line 507 (both call sites fire on a fresh entry's first
## arrival, so removing just one wouldn't drop draw_count below baseline).
## Setting `_user_adjusted = true` before the response arrives — the SAME
## guard a real pan/zoom gesture sets (`_maybe_refloat_window()`/`_zoom_at()`)
## — skips the fit-and-center branch, so ONLY line 507 can be the source of
## any redraw the assertion below observes. This is a real, reachable state
## (any window arrival after the player's first manual pan/zoom), not a
## test-only fiction.
func test_window_arrival_redraws_nature_overlay() -> void:
var v: AtlasWindowViewer = auto_free(AtlasWindowViewer.new())
add_child(v)
v.enter({"body_id": "GJ380c"}, {}, Vector2i(10, 20), 2)
v._user_adjusted = true # isolate line 507 from the first-arrival fit-and-center redraw
# Swap in the counting spy AFTER enter() (matching the gen_basins test's
# own "swap after construction, then let it settle" shape) so enter()'s
# own queue_redraw() calls don't pollute the baseline.
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 window"
+ " arrives, or this test can't distinguish 'redrawn BY the arrival'"
+ " from 'never drawn at all'"
).is_greater(0)
var window: Dictionary = _mock_window(Vector2i(10, 20), 2)
SimBridge.atlas_layers_received.emit(_mock_response("GJ380c", window))
await get_tree().process_frame
assert_that(v.get_district_window()).override_failure_message(
"sanity: the response must actually have been adopted (matching echo)"
+ " or this test proves nothing about the arrival path specifically"
).is_equal(window)
assert_int(spy.draw_count).override_failure_message(
"_on_window_ready() must queue_redraw() the NATURE overlay — courses"
+ " ride the SAME _window this handler adopts, so a window arrival"
+ " that doesn't redraw the nature overlay leaves freshly arrived"
+ " courses invisible until an unrelated pan/zoom happens to redraw"
+ " it — draw_count must have advanced past the baseline (%d)" % baseline
).is_greater(baseline)
## The RUNG-SWAP arrival case (a later _on_window_ready() call for a
## DIFFERENT granularity_v2 than the one the viewer entered at — e.g. a
## wheel-zoom crossing from District into Quarter) — cheap to cover in the
## SAME test file per the review's own "if cheap" allowance. Confirms the
## redraw fires on EVERY window adoption, not just the first-ever one
## (T-1153's progressive-refinement doc is explicit that _window only ever
## gets REPLACED, never renulled, on a rung swap).
##
## **Uses `_window_request.request_now()` directly, NOT `_enter_at_rung()`**
## — a live finding while writing this test: `_enter_at_rung()` sets
## `_awaiting_first_window = true` again (it's the SAME reset path a fresh
## descent uses), which would route the swap response back through
## `_fit_and_center()`'s OWN redraw call site, masking line 507 exactly like
## the note on the test above. The REAL production rung-swap path,
## `_maybe_reselect_rung()`, never touches `_awaiting_first_window` at all —
## it only calls `_window_request.request_debounced(...)`. `request_now()`
## (the non-debounced sibling, same effect minus the timer) is called
## directly here to update `_window_request`'s own `_granularity_v2` — the
## exact field `_on_window_ready()`'s echo-matching guard reads — mirroring
## the real path's state change without needing a live debounce timer in a
## unit test.
func test_rung_swap_window_arrival_redraws_nature_overlay() -> void:
var v: AtlasWindowViewer = auto_free(AtlasWindowViewer.new())
add_child(v)
v.enter({"body_id": "GJ380c"}, {}, Vector2i(10, 20), 2)
# First arrival (District, matches enter()'s own default rung) — settles
# the viewer into a held window, exactly as a real progressive-refinement
# sequence would before a rung swap. Uses the REAL (non-spy) nature
# overlay for this leg — only the swap leg itself needs the spy.
var district_window: Dictionary = _mock_window(Vector2i(10, 20), 2)
SimBridge.atlas_layers_received.emit(_mock_response("GJ380c", district_window))
await get_tree().process_frame
assert_that(v.get_district_window()).override_failure_message(
"sanity: the first (District) arrival must have been adopted before"
+ " simulating the swap"
).is_equal(district_window)
# Now swap in the spy and simulate the RUNG SWAP itself — update the
# request's echoed granularity_v2 to "Quarter" (what
# _maybe_reselect_rung() -> request_debounced() would do on a real
# wheel-zoom crossing) WITHOUT touching _awaiting_first_window, so the
# response below takes the "not first window" branch — the genuinely
# different code path from the test above.
var spy := _CountingNatureOverlay.new(v)
v._nature_overlay.queue_free()
v._nature_overlay = spy
v._canvas.add_child(spy)
v._window_request.request_now(
"GJ380c", Vector2i(10, 20), 2, AtlasWindowRequest.GRANULARITY_V2_QUARTER
)
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 rung-swap"
+ " response arrives"
).is_greater(0)
var quarter_window: Dictionary = _mock_window(Vector2i(10, 20), 2)
quarter_window["granularity_v2"] = "Quarter"
SimBridge.atlas_layers_received.emit(_mock_response("GJ380c", quarter_window))
await get_tree().process_frame
assert_that(v.get_district_window()).override_failure_message(
"sanity: the rung-swap response must actually have been adopted"
).is_equal(quarter_window)
assert_int(spy.draw_count).override_failure_message(
"a RUNG-SWAP window arrival (a later _on_window_ready() call at a"
+ " DIFFERENT granularity_v2 than entry) must ALSO redraw the nature"
+ " overlay — draw_count must have advanced past the post-first-"
+ " arrival baseline (%d)" % baseline
).is_greater(baseline)
# =============================================================================
# T-1120 capture-API parity (the ticket's explicit note: must survive here too)
# =============================================================================