## T-1145 item 3 (interim presentation, pending the T-1143 design pass): ## tests for AtlasWindowOverlay's smoothed-composite texture rebuild cache — ## the "rebuild ONLY when window/overlay/tint inputs change, not per frame" ## requirement. Does not test the actual PIXEL CONTENT of the built texture ## (that content is exactly _cell_color()/_apply_glaciation(), already ## covered by test_atlas_window_colors.gd's colorizer tests — this file is ## about WHEN a rebuild happens, not what color a given cell produces). class_name TestAtlasWindowOverlay extends GdUnitTestSuite static func _mock_window(n: int = 2) -> Dictionary: return { "center": [0, 0], "n": n, "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]), } ## Minimal viewer stub — AtlasWindowOverlay only reaches the viewer through ## get_district_window()/is_overlay_visible()/get_cell_pixel_size()/ ## is_tile_mode(), so a bare stub with just those methods is a legitimate ## "viewer" for these tests, matching the duck-typed-viewer precedent this ## whole overlay cluster already relies on (atlas_overlay_bar.gd/ ## atlas_legend_panel.gd). is_tile_mode() always returns false — this suite ## covers the single-window composite-cache path only; the tile mosaic path ## is covered separately by test_atlas_window_tile_set.gd + the viewer's own ## is_tile_mode()-branching tests. class _ViewerStub: var window: Variant = null var active_overlay: String = "" func get_district_window() -> Variant: return window func is_overlay_visible(overlay_id: String) -> bool: return overlay_id == active_overlay func get_cell_pixel_size() -> float: return 16.0 func is_tile_mode() -> bool: return false func test_composite_smooth_defaults_true() -> void: assert_bool(AtlasWindowOverlay.COMPOSITE_SMOOTH).override_failure_message( "T-1145 item 3 ships the smoothed composite as the DEFAULT presentation" ).is_true() ## A fresh overlay with no draw yet has never built a texture — the cache ## starts empty. func test_no_texture_before_first_draw() -> void: var o: AtlasWindowOverlay = auto_free(AtlasWindowOverlay.new()) assert_that(o._cached_texture).is_null() ## First _rebuild_texture_if_needed() call for a real window builds a texture. func test_rebuild_builds_a_texture_on_first_call() -> void: var o: AtlasWindowOverlay = auto_free(AtlasWindowOverlay.new()) var window: Dictionary = _mock_window() o._rebuild_texture_if_needed(window, 2, "") assert_that(o._cached_texture).override_failure_message( "the first rebuild call for a real window must produce a texture" ).is_not_null() ## Calling _rebuild_texture_if_needed() AGAIN with the SAME window object ## (same reference) and the same active toggle must NOT rebuild — the exact ## same ImageTexture instance survives (reference equality, not just ## "another texture that happens to look the same"). func test_rebuild_is_a_noop_when_window_and_toggle_are_unchanged() -> void: var o: AtlasWindowOverlay = auto_free(AtlasWindowOverlay.new()) var window: Dictionary = _mock_window() o._rebuild_texture_if_needed(window, 2, "") var first_texture: ImageTexture = o._cached_texture o._rebuild_texture_if_needed(window, 2, "") assert_bool(is_same(o._cached_texture, first_texture)).override_failure_message( "an unchanged (window, active_toggle) pair must reuse the SAME texture" + " object, not rebuild an equivalent-but-new one" ).is_true() ## A DIFFERENT window object (even with identical field VALUES) — matching ## what a fresh server response always is, a new Dictionary — MUST trigger a ## rebuild. This is the reference-vs-value distinction the class doc calls ## out explicitly (is_same(), not a deep compare). func test_rebuild_fires_for_a_different_window_object_with_same_values() -> void: var o: AtlasWindowOverlay = auto_free(AtlasWindowOverlay.new()) var window_a: Dictionary = _mock_window() o._rebuild_texture_if_needed(window_a, 2, "") var first_texture: ImageTexture = o._cached_texture # A structurally-IDENTICAL but DISTINCT Dictionary object — the exact # shape a second server response for the same window content would be. var window_b: Dictionary = _mock_window() o._rebuild_texture_if_needed(window_b, 2, "") assert_bool(is_same(o._cached_texture, first_texture)).override_failure_message( "a new window object (even with identical field values) must trigger a" + " fresh rebuild — the cache key is REFERENCE identity, not value equality" ).is_false() ## Changing the active toggle overlay (same window object) must ALSO trigger ## a rebuild — temp/moisture/veg/base each read different colors per cell. func test_rebuild_fires_when_active_toggle_changes() -> void: var o: AtlasWindowOverlay = auto_free(AtlasWindowOverlay.new()) var window: Dictionary = _mock_window() o._rebuild_texture_if_needed(window, 2, "") var first_texture: ImageTexture = o._cached_texture o._rebuild_texture_if_needed(window, 2, "gen_dw_temp") assert_bool(is_same(o._cached_texture, first_texture)).override_failure_message( "switching the active toggle overlay must trigger a rebuild — the SAME" + " window's cells read different colors under a different toggle" ).is_false() ## Panning/zooming (which redraw this node constantly via _apply_transform()) ## never touches window/overlay state — repeated rebuild CALLS with identical ## inputs (simulating many redraws while nothing about the DATA changed) must ## all be no-ops after the first, confirming the "not per frame" requirement ## end to end, not just for a single repeat. func test_repeated_rebuild_calls_with_unchanged_inputs_all_reuse_the_same_texture() -> void: var o: AtlasWindowOverlay = auto_free(AtlasWindowOverlay.new()) var window: Dictionary = _mock_window() o._rebuild_texture_if_needed(window, 2, "") var first_texture: ImageTexture = o._cached_texture for _i in range(20): # 20 simulated redraws (pan/zoom frames) o._rebuild_texture_if_needed(window, 2, "") assert_bool(is_same(o._cached_texture, first_texture)).override_failure_message( "20 repeated rebuild calls with unchanged inputs must never touch the cache" ).is_true() ## The overlay's real _draw() entry point (via the smoothed path) produces a ## texture through the SAME _ViewerStub duck-typed interface every other ## caller in this cluster uses — an end-to-end sanity check that _draw() ## actually reaches _rebuild_texture_if_needed() for a real window, not just ## that the helper works in isolation. func test_draw_builds_a_texture_through_the_viewer_stub() -> void: var o: AtlasWindowOverlay = auto_free(AtlasWindowOverlay.new()) var stub := _ViewerStub.new() stub.window = _mock_window() o.viewer = stub o._draw() assert_that(o._cached_texture).is_not_null() # ============================================================================= # T-1152/T-1153: cell_grid_side_for_window() — the district-extent-vs- # derived-cell-grid split every rung's response now carries. # ============================================================================= ## District (the default/omitted tag): cell_grid_side == n, unchanged from ## the pre-T-1152 identity mapping. func test_cell_grid_side_for_window_district_matches_n() -> void: var window: Dictionary = {"n": 32, "granularity_v2": "District"} assert_int(AtlasWindowOverlay.cell_grid_side_for_window(window)).is_equal(32) ## Quarter: 4x MORE cells than districts (WINDOW_GRANULARITY_QUARTER). func test_cell_grid_side_for_window_quarter_multiplies_by_four() -> void: var window: Dictionary = {"n": 32, "granularity_v2": "Quarter"} assert_int(AtlasWindowOverlay.cell_grid_side_for_window(window)).is_equal(128) ## Region: FAR FEWER cells than districts — round(n/100), matching ## WindowGranularity::cell_grid_side's own Region branch exactly (the ## "inversion" the server doc calls out: finer rungs multiply, Region divides). func test_cell_grid_side_for_window_region_divides_by_districts_per_region() -> void: var window: Dictionary = {"n": 6400, "granularity_v2": "Region"} assert_int(AtlasWindowOverlay.cell_grid_side_for_window(window)).is_equal(64) ## A Region window smaller than one region (n < 100) must still derive a ## minimum 1x1 cell grid, never 0 — matching the server's `.max(1)`. func test_cell_grid_side_for_window_region_minimum_is_one() -> void: var window: Dictionary = {"n": 50, "granularity_v2": "Region"} assert_int(AtlasWindowOverlay.cell_grid_side_for_window(window)).is_equal(1) ## Missing granularity_v2 (an old-shape response) falls back to District — ## matching the server's own "unknown -> District" posture at every ## resolution boundary. func test_cell_grid_side_for_window_missing_tag_falls_back_to_district() -> void: var window: Dictionary = {"n": 32} assert_int(AtlasWindowOverlay.cell_grid_side_for_window(window)).is_equal(32) ## T-1152/T-1153, design doc §6 encoding continuity: a Region-rung window (a ## FAR SPARSER cell grid — one region cell spans 100 districts) renders ## through the EXACT SAME _draw()/_rebuild_texture_if_needed() path as ## District — no separate branch, no crash reading past the (much smaller) ## per-cell arrays. This is the direct "same colorizer family at every rung" ## behavioral test the ticket asks for. func test_draw_builds_a_texture_for_a_region_rung_window() -> void: var o: AtlasWindowOverlay = auto_free(AtlasWindowOverlay.new()) var stub := _ViewerStub.new() # n=200 districts -> cell_grid_side = round(200/100) = 2 -> 4 cells, # matching the 4-entry per-cell arrays below (same shape _mock_window() # uses, just at Region's district-to-cell ratio). stub.window = { "center": [0, 0], "n": 200, "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]), } o.viewer = stub o._draw() assert_that(o._cached_texture).override_failure_message( "a Region-rung window must render through the same composite path as District" ).is_not_null() assert_int(o._cached_texture.get_width()).override_failure_message( "the built texture's resolution must be the DERIVED cell-grid side (2)," + " not the window's district extent (200)" ).is_equal(2) ## §6 "no mode flip" acceptance criterion, restated at the texture-cache ## level: swapping from a District-rung window to a Region-rung window at a ## NEW window object (the progressive-refinement swap) must still go through ## a single rebuild call producing a fresh texture — not a crash, not a ## silently-stale texture sized for the wrong rung. func test_rebuild_handles_a_rung_swap_from_district_to_region() -> void: var o: AtlasWindowOverlay = auto_free(AtlasWindowOverlay.new()) var district_window: Dictionary = _mock_window() # n=2, District, 4 cells o._rebuild_texture_if_needed( district_window, AtlasWindowOverlay.cell_grid_side_for_window(district_window), "" ) assert_int(o._cached_texture.get_width()).is_equal(2) var region_window: Dictionary = { "center": [0, 0], "n": 200, "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]), } o._rebuild_texture_if_needed( region_window, AtlasWindowOverlay.cell_grid_side_for_window(region_window), "" ) assert_int(o._cached_texture.get_width()).override_failure_message( "a rung swap must rebuild at the NEW rung's derived cell-grid resolution" ).is_equal(2) # region_window's cell_grid_side is also 2 here (200/100) — same size, different data