Files
settled-reach/client/tests/test_regional_screen.gd
T
jpmschweitzer d4526e51ff fix(client): cold-start black screen — self-healing tile repaint, legend re-entry root cause, pending-tile wash
Jeroen hit a black mosaic zooming into a body from a fresh make-atlas
spawn. Live diagnosis showed all six tiles held with colored textures
and no repaint; the original line-specific diagnosis (unpaired
queue_redraw in _on_tile_ready) turned out WRONG — the pairing already
existed (lead's truncated grep misread the function; Stig verified via
git log -p before acting). Rather than chase the exact dropped signal
edge, _process() now self-heals: both viewer and overlay redraw every
frame while the tile set has pending tiles (has_pending_tiles(), new) —
a strict superset that closes the black regardless of which edge drops,
pinned by a _draw()-counting real-subclass spy test.

Legend stacking root cause found by trace, not guess: ImplantApp.
_on_screen_changed() re-runs enter() unconditionally on repeat
same-screen pushes — each re-entry tore down and rebuilt all six tile
requests (the round-6 orphaning fingerprint via a new trigger) and
stacked another legend (~10 deep, full-height dark panel). Fixed both
ends: ImplantPanel.clear() frees immediately (same-frame re-entrant
refresh can never observe stale children — protects every implant app),
and RegionalScreen.enter() no-ops for the same body (different body
still re-enters fresh). The load-bearing regression asserts an ARRIVED
TILE'S DATA survives a repeat push — node identity would not catch the
teardown (the tile-set Node is a fixed field; only its internals reset).

Cold-start UX: pending tiles now draw the single-window path's
COLOR_BORDER_FADE wash instead of raw background — a deriving mosaic
reads as loading, not broken.

Suites green (zoom_ladder 48, viewer 74, tile_set 20, overlay 30,
overlays 46 + 2 new files), gdlint clean, revert-verified throughout.
2026-07-22 19:05:15 +02:00

91 lines
4.4 KiB
GDScript

## PR #192 cold-start dossier (BUG 2): RegionalScreen.enter() tests. Split
## into its own file rather than folded into test_atlas_zoom_ladder.gd —
## these exercise the NAV-LAYER re-entry guard (RegionalScreen itself), not
## AtlasWindowViewer's own zoom-ladder mechanics that suite already owns.
class_name TestRegionalScreen
extends GdUnitTestSuite
const AtlasWindowRequest := preload("res://ui/implant/apps/atlas/atlas_window_request.gd")
## Dudley's WINDOW_GRANULARITY_REGION_KEY sentinel — mirrors
## test_atlas_zoom_ladder.gd's own constant (see that file's doc for why the
## real wire value matters, not a convenient placeholder).
const SERVER_LEGACY_GRANULARITY_REGION_SENTINEL: int = 4294967295
static func _mock_response(body_id: String, window: Variant) -> Dictionary:
return {"body_id": body_id, "status": "Ready", "district_window": window}
## BUG 2 root cause: ImplantApp._on_screen_changed() calls enter()
## UNCONDITIONALLY on every screen_changed, including a repeat
## nav.push("regional", ...) landing on the SAME screen already showing
## (reachable from more than one input path — body-click, panel+Enter — and
## plausible for a player to trigger twice on a slow cold server before the
## first descent settles). Without RegionalScreen's own guard, a repeat
## entry re-ran the FULL enter_orbital() teardown/rebuild — tearing down
## every in-flight tile request node and rebuilding fresh (null-window) ones
## — orphaning whatever had already arrived, plus refreshing the legend
## from scratch each time (the confirmed ~10x legend stack). Proven here by
## an ARRIVED tile's data: a teardown+rebuild resets it to null; a genuine
## no-op leaves it exactly as it was — `is_same()` on `_tile_set` itself
## can't tell (that Node is a fixed field, never reassigned — only its
## INTERNAL request children get torn down and rebuilt).
func test_repeat_enter_for_the_same_body_does_not_orphan_an_arrived_tile() -> void:
var screen: RegionalScreen = auto_free(RegionalScreen.new())
add_child(screen)
var body: Dictionary = {"body_id": "GJ380c", "body_radius_km": 6238.4}
screen.enter({"body": body, "system": {}})
var tile_set = screen._viewer.get_tile_set()
var first_tile_center: Vector2i = tile_set.get_tiles()[0]["center"]
var arrived_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", arrived_window))
assert_that(tile_set.get_tiles()[0]["window"]).override_failure_message(
"sanity: the first tile's response must have been adopted before the repeat enter()"
).is_equal(arrived_window)
screen.enter({"body": body, "system": {}})
assert_that(screen._viewer.get_tile_set().get_tiles()[0]["window"]).override_failure_message(
"a repeat enter() for the SAME body must not orphan an already-arrived"
+ " tile — a teardown+rebuild resets every tile's window back to null,"
+ " which is the confirmed source of the cold-start legend stacking bug"
).is_equal(arrived_window)
## A GENUINE body change (different body_id) must still enter fresh — the
## guard is scoped to "the same body, re-entered", never a blanket "ignore
## the second enter() call ever".
func test_enter_for_a_different_body_still_re_enters() -> void:
var screen: RegionalScreen = auto_free(RegionalScreen.new())
add_child(screen)
screen.enter({"body": {"body_id": "GJ380c", "body_radius_km": 6238.4}, "system": {}})
screen.enter({"body": {"body_id": "OtherBody", "body_radius_km": 100.0}, "system": {}})
assert_str(screen._viewer.get_body_id()).override_failure_message(
"a genuinely different body must still re-enter — not be swallowed by"
+ " the same-body guard"
).is_equal("OtherBody")
## get_body_id() itself: empty before any entry, the entered body's id after.
func test_get_body_id_reflects_the_currently_held_body() -> void:
var v: AtlasWindowViewer = auto_free(AtlasWindowViewer.new())
add_child(v)
assert_str(v.get_body_id()).is_equal("")
v.enter_orbital({"body_id": "GJ380c", "body_radius_km": 6238.4}, {})
assert_str(v.get_body_id()).is_equal("GJ380c")