diff --git a/client/tests/test_atlas_window_viewer.gd b/client/tests/test_atlas_window_viewer.gd index 94fc603f5..916ac1a49 100644 --- a/client/tests/test_atlas_window_viewer.gd +++ b/client/tests/test_atlas_window_viewer.gd @@ -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) # =============================================================================