## T-1156 wave 1: tests for AtlasWindowNatureOverlay — the whole-body ## Layer-1 (river/basin/attractor) draw node on the zoom ladder. Covers ## request/response lifecycle (idempotent-per-body, staleness guard, decode ## tolerance for a missing river_class array) and the draw-gate wiring ## (rung/overlay-bar double-gate), NOT pixel-level draw output — the ## coordinate math itself is covered directly in ## test_atlas_window_geometry_nature.gd, matching test_atlas_window_overlay.gd's ## own "cache/lifecycle here, colorizer pixels elsewhere" split. class_name TestAtlasWindowNatureOverlay extends GdUnitTestSuite const AtlasWindowNatureOverlay := preload("res://ui/implant/apps/atlas/atlas_window_nature_overlay.gd") ## Minimal viewer stub — AtlasWindowNatureOverlay only reaches the viewer ## through get_held_granularity_v2()/get_body_radius_km()/get_held_center()/ ## get_held_n()/get_cell_pixel_size()/get_view_zoom()/is_overlay_visible(), ## the same duck-typed-viewer precedent test_atlas_window_overlay.gd's ## _ViewerStub already establishes for AtlasWindowOverlay. get_view_zoom() ## added post-live-eyeball (coordinator finding, 2026-07-23): _draw() now ## reads it for the zoom-compensated marker-size fix. class _ViewerStub: var held_granularity_v2: String = "Region" var body_radius_km: float = 6371.0 var held_center: Vector2i = Vector2i.ZERO var held_n: int = 64 var view_zoom: float = 1.0 var overlay_visibility: Dictionary = {"gen_rivers": true, "gen_basins": false, "gen_attractors": false} func get_held_granularity_v2() -> String: return held_granularity_v2 func get_body_radius_km() -> float: return body_radius_km func get_held_center() -> Vector2i: return held_center func get_held_n() -> int: return held_n func get_cell_pixel_size() -> float: return 16.0 func get_view_zoom() -> float: return view_zoom func is_overlay_visible(overlay_id: String) -> bool: return bool(overlay_visibility.get(overlay_id, false)) static func _mock_layer1(river_class: Variant = null) -> Dictionary: var rn: Dictionary = { "river_cells": [[10, 10], [20, 20], [30, 30]], "confluences": [[15, 15]], "mouths": [[40, 40]], } if river_class != null: rn["river_class"] = river_class return { "river_network": rn, "drainage_basins": [{"basin_id": 1, "boundary": [[0, 0], [0, 10], [10, 10], [10, 0]]}], "attractors": [{"position": [10, 10], "strength": 0.5, "attractor_type": "Oasis", "sub_biome": ""}], "grid_w": 256, "grid_h": 128, } static func _mock_response(body_id: String, layer1: Variant) -> Dictionary: return {"body_id": body_id, "status": "Ready", "layer1": layer1} ## Window-only responses (the OTHER shape SimBridge.atlas_layers_received ## carries, per AtlasWindowRequest's own test conventions) must be ignored — ## `layer1` is null on that envelope, matching atlas_response_from_raw()'s ## "only one of layer1/district_window populated per response" contract. static func _mock_window_response(body_id: String) -> Dictionary: return {"body_id": body_id, "status": "Ready", "district_window": {"n": 32}, "layer1": null} func _make_overlay(viewer: Variant = null) -> Variant: var o = auto_free(AtlasWindowNatureOverlay.new(viewer if viewer != null else _ViewerStub.new())) add_child(o) return o # ============================================================================= # Request lifecycle # ============================================================================= func test_no_layer1_before_any_request() -> void: var o = _make_overlay() assert_that(o.get_layer1()).is_null() func test_response_for_requested_body_is_adopted() -> void: var o = _make_overlay() o.request_layer1("GJ380c") var layer1: Dictionary = _mock_layer1() SimBridge.atlas_layers_received.emit(_mock_response("GJ380c", layer1)) assert_that(o.get_layer1()).is_equal(layer1) ## Staleness guard: a response for a body this node never asked for (or ## navigated away from) must be ignored — same posture ## AtlasGenerationProxy.on_response()'s own body_id guard establishes. func test_response_for_a_different_body_is_ignored() -> void: var o = _make_overlay() o.request_layer1("GJ380c") SimBridge.atlas_layers_received.emit(_mock_response("SomeOtherBody", _mock_layer1())) assert_that(o.get_layer1()).is_null() ## A window-shaped response (the windowed DistrictWindowLayer envelope, ## `layer1` null) must be ignored outright — this node only ever adopts the ## whole-body Layer-1 envelope. func test_window_only_response_is_ignored() -> void: var o = _make_overlay() o.request_layer1("GJ380c") SimBridge.atlas_layers_received.emit(_mock_window_response("GJ380c")) assert_that(o.get_layer1()).is_null() ## request_layer1() for the SAME body id, after data has already arrived, ## must NOT clear the held data — a re-entrant enter_orbital() on the body ## already showing keeps drawing rivers instead of flashing them away. func test_request_layer1_same_body_after_arrival_keeps_held_data() -> void: var o = _make_overlay() o.request_layer1("GJ380c") var layer1: Dictionary = _mock_layer1() SimBridge.atlas_layers_received.emit(_mock_response("GJ380c", layer1)) o.request_layer1("GJ380c") assert_that(o.get_layer1()).is_equal(layer1) ## request_layer1() for a DIFFERENT body id must clear the previous body's ## held data immediately — the old body's rivers must never draw over the ## new body's terrain during the in-flight gap. func test_request_layer1_different_body_clears_stale_data() -> void: var o = _make_overlay() o.request_layer1("GJ380c") SimBridge.atlas_layers_received.emit(_mock_response("GJ380c", _mock_layer1())) o.request_layer1("AnotherBody") assert_that(o.get_layer1()).override_failure_message( "switching bodies must clear the previous body's layer1 data immediately," + " not just leave it drawn until the new response arrives" ).is_null() func test_request_layer1_empty_body_id_is_a_noop() -> void: var o = _make_overlay() o.request_layer1("") SimBridge.atlas_layers_received.emit(_mock_response("", _mock_layer1())) assert_that(o.get_layer1()).override_failure_message( "an empty body_id must never be requested/adopted" ).is_null() # ============================================================================= # Decode tolerance — layer1 without river_class (pre-T-1156 payload / the # graceful-fallback empty-array case, Dudley's #[serde(default)] contract). # ============================================================================= ## A response with NO river_class key at all (river_network dict omits it — ## the msgpack-decode equivalent of Dudley's serde default producing an ## empty Vec) must still be adopted without error; per-cell class then falls ## back to RIVER_CLASS_FALLBACK (TRUNK) at draw time, not a crash/decode failure. func test_layer1_without_river_class_key_is_still_adopted() -> void: var o = _make_overlay() o.request_layer1("GJ380c") var layer1: Dictionary = _mock_layer1() # no river_class arg -> key absent SimBridge.atlas_layers_received.emit(_mock_response("GJ380c", layer1)) assert_that(o.get_layer1()).is_equal(layer1) assert_bool((o.get_layer1()["river_network"] as Dictionary).has("river_class")).is_false() ## An explicitly EMPTY river_class array (the actual wire shape Dudley's ## `#[serde(default)]` produces for a pre-T-1156 payload) must also decode ## without error and be adopted — the per-cell RIVER_CLASS_FALLBACK ## resolution this enables is exercised at draw time by ## test_atlas_window_nature_overlay_draw_smoke.gd (real-render smoke suite; ## draw_circle()/draw_rect() calls require a live render pass under this ## engine version — confirmed directly, matching ## test_atlas_window_overlay_draw_smoke.gd's own header doc on why a plain ## unit test cannot call `_draw()` outside one). func test_layer1_with_empty_river_class_array_is_adopted() -> void: var o = _make_overlay() o.request_layer1("GJ380c") var layer1: Dictionary = _mock_layer1(PackedByteArray([])) SimBridge.atlas_layers_received.emit(_mock_response("GJ380c", layer1)) assert_that(o.get_layer1()).is_equal(layer1) ## No viewer at all (viewer == null, matching AtlasWindowOverlay's own ## "viewer == null -> return" guard convention) is a legal, inert state — ## _draw()'s null-viewer early-return (BEFORE any draw_*() call) is safe to ## call directly since it never reaches the engine's draw-context requirement. func test_draw_with_null_viewer_is_a_noop() -> void: var o = auto_free(AtlasWindowNatureOverlay.new(null)) add_child(o) o._draw() assert_that(o.get_layer1()).is_null() ## grid_w/grid_h missing or zero (a malformed/degenerate layer1) must decode ## and adopt cleanly — _draw()'s own grid_w<=0/grid_h<=0 early-return (also ## BEFORE any draw_*() call) is exercised the same direct way. func test_draw_with_zero_grid_dims_returns_before_any_draw_call() -> void: var o = _make_overlay() o.request_layer1("GJ380c") var layer1: Dictionary = _mock_layer1() layer1["grid_w"] = 0 layer1["grid_h"] = 0 SimBridge.atlas_layers_received.emit(_mock_response("GJ380c", layer1)) o._draw() # grid_w<=0 -> returns before touching the canvas — safe to call directly assert_that(o.get_layer1()).is_not_null() ## PR #195 review (Tyre I1) regression pin: every stroke-WIDTH argument in ## _draw_attractor_shape() must route through the pre-compensated `px_w` ## param, never a raw numeric literal — Godot multiplies stroke widths by the ## canvas scale exactly like radii, so a raw `2.0` rasterizes at ~0.01px at ## the Region orbital fit zoom (the identical sub-pixel failure the dot/ring ## zoom compensation fixed, missed on glyph outlines in the first pass). The ## draw-smoke suite cannot gate this (its own header documents the vacuous- ## pass mode under X11 BadMatch), so this is a SOURCE-SCAN pin: parse the ## overlay script's _draw_attractor_shape body and assert no draw_arc/ ## draw_line call carries a bare numeric width literal. Crude but ## environment-independent, and it pins the exact regression class (someone ## reintroducing a literal width in a new glyph arm). func test_attractor_shape_stroke_widths_are_never_raw_literals() -> void: var src: String = ( FileAccess.get_file_as_string("res://ui/implant/apps/atlas/atlas_window_nature_overlay.gd") ) var fn_start := src.find("func _draw_attractor_shape(") assert_that(fn_start).override_failure_message( "_draw_attractor_shape must exist in atlas_window_nature_overlay.gd" ).is_not_equal(-1) var next_fn := src.find("\nfunc ", fn_start + 1) var body := src.substr(fn_start, (next_fn - fn_start) if next_fn != -1 else -1) var stroke_re := RegEx.new() # A draw_arc/draw_line call whose FINAL (width) argument is a bare numeric # literal: `, )` at call end. px_w-scaled forms # (`px_w`, `2.0 * px_w`) do not match. stroke_re.compile("draw_(arc|line)\\([^\\n]*,\\s*\\d+(\\.\\d+)?\\s*\\)") var hits := stroke_re.search_all(body) var offenders: Array[String] = [] for hit in hits: offenders.append(hit.get_string()) assert_array(offenders).override_failure_message( "raw numeric stroke width(s) in _draw_attractor_shape — route through" + " px_w (PR #195 Tyre I1): %s" % [offenders] ).is_empty()