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.
91 lines
4.4 KiB
GDScript
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")
|