## Live round 4: a REAL draw smoke test — the "does anything draw at all" ## gap has now bitten twice (round 4's tile-mosaic coordinate bug AND its ## per-tile-texture-lifetime bug, both invisible to test_atlas_window_overlay.gd's ## existing suite, which only asserts on the CACHE FIELDS being populated — ## never on an actual composited pixel). This file closes that gap ## structurally: render AtlasWindowOverlay into a REAL SubViewport, force a ## GPU sync, grab the rendered Image, and assert a meaningful fraction of ## pixels differ from the background color — for BOTH the single-window path ## (a) and the tile-mosaic path (b), matching the coordinator's explicit ask. ## ## **REQUIRES A REAL RENDERING DRIVER — SKIPS (not fails) under ## `tests/run-godot`'s hardcoded `--headless`** (dummy driver, no GPU texture ## output; confirmed directly: SubViewport.get_texture().get_image() returns ## an all-zero/unusable image under it). This matters beyond "the assertions ## are meaningless there": the push gate runs the FULL suite through ## `tests/run-godot --headless` for every push, for everyone — a loud FAILURE ## here would bounce every future push project-wide, not just report a local ## false negative. Every test below carries the gdUnit4 fuzzer-arg skip ## convention (`_do_skip`/`_skip_reason`, matching test_input_gate_live.gd's ## own server-binary-not-built skip) keyed on `_dummy_renderer_active()`, so ## `tests/run-godot --filter test_atlas_window_overlay_draw_smoke` reports ## green-with-skips under headless, not red. ## ## To actually exercise this file's assertions, run it with a real driver: ## godot4 --display-driver x11 --rendering-driver opengl3 \ ## -s addons/gdUnit4/bin/GdUnitCmdTool.gd --ignoreHeadlessMode -c \ ## -a res://tests/test_atlas_window_overlay_draw_smoke.gd ## (matching tests/visual_capture.gd's own documented "requires a real ## rendering driver" precedent — see docs/DEVOPS.md's own note on this file.) class_name TestAtlasWindowOverlayDrawSmoke extends GdUnitTestSuite const AtlasWindowOverlay := preload("res://ui/implant/apps/atlas/atlas_window_overlay.gd") const AtlasWindowGeometry := preload("res://ui/implant/apps/atlas/atlas_window_geometry.gd") const COLOR_BG: Color = Color("#0d1117") # AtlasWindowViewer.COLOR_BG, mirrored (private const) const VIEWPORT_SIZE: Vector2i = Vector2i(512, 512) ## Minimum fraction of the captured image that must differ from COLOR_BG for ## a draw to count as "genuinely rendered something" — low enough to tolerate ## a mostly-water/mostly-one-color composite (round 4's own repro shots were ## legitimately near-uniform ocean at some zooms), high enough that a ## fully-blank/fully-background/fully-white frame (both round 4 bugs) fails it. const MIN_NON_BACKGROUND_FRACTION: float = 0.05 const SKIP_REASON: String = ( "no real rendering driver (dummy/headless) — run with e.g." + " `godot4 --display-driver x11 --rendering-driver opengl3" + " -s addons/gdUnit4/bin/GdUnitCmdTool.gd --ignoreHeadlessMode -c" + " -a res://tests/test_atlas_window_overlay_draw_smoke.gd` to exercise this file" ) ## True under Godot's `--display-driver headless` (the dummy renderer ## `tests/run-godot`'s hardcoded `--headless` flag selects) — `DisplayServer. ## get_name()` reports `"headless"` there and the real driver name (`"X11"`, ## `"Wayland"`, etc.) otherwise, confirmed directly against both this ## worktree's `tests/run-godot` invocation and a real `--display-driver x11 ## --rendering-driver opengl3` run. Named as a function, not a const, since ## `DisplayServer` singleton state isn't available at script-parse time. static func _dummy_renderer_active() -> bool: return DisplayServer.get_name() == "headless" ## Single-window viewer stub — mirrors test_atlas_window_overlay.gd's ## _ViewerStub exactly (is_tile_mode() -> false), so this exercises the ## SAME single-window draw path that suite's cache tests cover, just ## through a REAL render instead of inspecting `_cached_texture` directly. class _SingleWindowViewerStub: var window: Variant = null func get_district_window() -> Variant: return window func is_overlay_visible(_overlay_id: String) -> bool: return false func get_cell_pixel_size() -> float: return 16.0 func is_tile_mode() -> bool: return false ## Tile-mode viewer stub — is_tile_mode() -> true, get_tile_set() returns a ## bare object exposing get_tiles() (AtlasWindowOverlay's own duck-typed ## contract, matching AtlasWindowTileSet.get_tiles()'s public shape exactly: ## Array of {"center": Vector2i, "window": Variant}). class _TileModeViewerStub: # PR #192 cold-start dossier: AtlasWindowOverlay reads viewer.COLOR_BORDER_FADE # directly for a pending tile's wash (avoids a cyclic preload of the # viewer's own script — see that read site's own doc) — mirrored here # byte-for-byte (AtlasWindowViewer.COLOR_BORDER_FADE, private const). const COLOR_BORDER_FADE: Color = Color(0.20, 0.24, 0.30, 0.55) var tiles: Array = [] var held_center: Vector2i = Vector2i.ZERO var held_n: int = 0 # Live round 5: nearest_wrap_image()'s cols input — 0 here (a no-radius # passthrough) is fine for these tests, which don't exercise the wrap # seam itself (that's test_atlas_window_geometry.gd's own coverage); # this stub only needs to satisfy _draw_tile_mosaic()'s duck-typed call. var body_radius_km: float = 0.0 func get_district_window() -> Variant: return null func is_overlay_visible(_overlay_id: String) -> bool: return false func get_cell_pixel_size() -> float: return 16.0 func is_tile_mode() -> bool: return true func get_tile_set() -> Variant: return _TileSetStub.new(tiles) func get_held_center() -> Vector2i: return held_center func get_held_n() -> int: return held_n func get_body_radius_km() -> float: return body_radius_km class _TileSetStub: var _tiles: Array = [] func _init(tiles: Array) -> void: _tiles = tiles func get_tiles() -> Array: return _tiles ## A `Node2D._draw()`-based flat-fill background — deliberately NOT a ## `ColorRect` (a `Control`). A `ColorRect` parented directly under a bare ## `SubViewport` (no intervening `Control` container establishing its own ## layout rect) did not reliably render in this harness: sampled pixels came ## back fully transparent `(0,0,0,0)` regardless of the ColorRect's `color`/ ## `size`, even after entering the tree before sizing. `AtlasWindowOverlay` ## itself is a bare `Node2D` using `draw_rect()` for its own background wash ## (`AtlasWindowViewer._draw()`'s own `COLOR_BG` fill) — matching that same, ## already-proven-working `Node2D.draw_rect()` pattern here sidesteps ## whatever `Control`-specific layout/compositing gap caused the ColorRect ## failure, rather than debugging that gap for its own sake. class _BackgroundRect extends Node2D: var fill_color: Color = Color.BLACK var fill_size: Vector2 = Vector2.ZERO func _draw() -> void: draw_rect(Rect2(Vector2.ZERO, fill_size), fill_color) static func _mock_window(center: Vector2i = Vector2i.ZERO, n: int = 64) -> Dictionary: # A checkerboard-ish morphology spread (not all-one-zone) so the built # composite has genuine color VARIATION, not just "one flat non-background # color" — closer to what a real terrain response looks like. var cells: int = n * n var morphology := PackedByteArray() var elev_q := PackedByteArray() morphology.resize(cells) elev_q.resize(cells) for i in range(cells): morphology[i] = (i % 4) as int # cycles through the 4 morphology zones elev_q[i] = (i * 7) % 100 as int return { "center": [center.x, center.y], "n": n, "granularity_v2": "District", "morphology": morphology, "elev_q": elev_q, "temp_dc": [], "moisture_q": PackedByteArray(), "vegetation": PackedByteArray(), "glaciation": PackedByteArray(), } ## Renders `overlay` (any Node2D with its own `_draw()` — an ## AtlasWindowOverlay for (a)/(b) below, or a bare `_BackgroundRect` probe for ## the harness sanity check) parented under a Node2D positioned/scaled the ## way AtlasWindowViewer._canvas would be, into a fresh SubViewport, and ## returns the captured Image. `zoom` mirrors AtlasWindowViewer._canvas.scale ## (real `fit_window_view()` output is a small fraction, e.g. ~0.006 for a ## whole-body tile mosaic per live round 4's own repro) — WITHOUT it, a ## tile's real-world extent (TILE_N * cell_px = 102,400 local units) is so ## much larger than any realistic test viewport that a mis-POSITIONED tile ## still overlaps the frame purely by being gigantic, making the position ## math this test exists to catch silently unfalsifiable (confirmed ## directly: an earlier version of this test without a zoom scale kept ## passing even with live round 4's tile-coordinate bug deliberately ## reintroduced). Also confirmed directly: a hand-rolled duplicate of this ## same SubViewport/settle-loop setup (the harness sanity check's ORIGINAL ## standalone version) was measurably less reliable under a real driver than ## going through this shared path — reuse over duplication here isn't just ## tidiness, it's the more reliable rendering path. func _render_to_image(overlay: Node2D, zoom: float = 1.0) -> Image: var sub_viewport := SubViewport.new() sub_viewport.size = VIEWPORT_SIZE sub_viewport.render_target_update_mode = SubViewport.UPDATE_ALWAYS sub_viewport.transparent_bg = false add_child(sub_viewport) auto_free(sub_viewport) var bg := _BackgroundRect.new() bg.fill_color = COLOR_BG bg.fill_size = Vector2(VIEWPORT_SIZE) sub_viewport.add_child(bg) bg.queue_redraw() var canvas := Node2D.new() canvas.position = Vector2(VIEWPORT_SIZE) * 0.5 # center the composite's local (0,0) canvas.scale = Vector2(zoom, zoom) sub_viewport.add_child(canvas) canvas.add_child(overlay) overlay.queue_redraw() # Bounded settle wait, NOT `await RenderingServer.frame_post_draw` — that # signal never fires under the dummy/headless driver (confirmed directly: # a first version of this file using it hung for the full 300s # tests/run-godot wall-clock cap and was force-killed, producing a FALSE # "0 tests, passed" result — exactly the silent-hang failure mode the # `_do_skip`/`_dummy_renderer_active()` gate (this file's header doc) now # avoids structurally instead). A fixed small number of `process_frame` # awaits settles real rendering — confirmed sufficient against a real # driver during this fix's own live verification. for _i in range(6): await get_tree().process_frame return sub_viewport.get_texture().get_image() ## Fraction of `image`'s pixels whose RGB differs from COLOR_BG (alpha ## ignored — the ColorRect background is opaque, everything drawn on top of ## it is what's under test). static func _non_background_fraction(image: Image) -> float: var w: int = image.get_width() var h: int = image.get_height() if w <= 0 or h <= 0: return 0.0 var total: int = w * h var differing: int = 0 var bg_rgb: Color = Color(COLOR_BG.r, COLOR_BG.g, COLOR_BG.b, 1.0) for y in range(h): for x in range(w): var px: Color = image.get_pixel(x, y) var px_rgb: Color = Color(px.r, px.g, px.b, 1.0) if not px_rgb.is_equal_approx(bg_rgb): differing += 1 return float(differing) / float(total) ## (a) Single-window path: a District-rung response must render as visibly ## non-background pixels through AtlasWindowOverlay._draw()'s own ## Rect2(0,0,extent,extent) draw call — the "does the OVERLAY actually paint ## something for a real window" half of the gap. Positions it at the ## SubViewport's center via the surrounding Node2D, mirroring _canvas's role ## in the real viewer. ## ## Honest scope note: live round 4's SECOND bug (leaving `_view_offset` ## stale across a rung crossing in `_maybe_reselect_rung()`) lived entirely ## in AtlasWindowViewer's transform bookkeeping, ONE LAYER ABOVE this ## overlay-only test's boundary — it never touched `_draw()` itself, so a ## pure-overlay smoke test structurally cannot reproduce it (there is no ## "stale vs. fresh offset" state to compare inside the overlay alone). That ## regression's coverage is `_maybe_reselect_rung()`'s own unit tests in ## test_atlas_zoom_ladder.gd. This test's job is narrower and still real: ## proving the overlay's draw call itself produces visible output for ## legitimate window data, closing the "the composite Rect2 call is ## silently a no-op" class of bug regardless of which layer caused it. func test_single_window_draw_produces_visible_pixels() -> void: # Guarded early-return instead of the _do_skip fuzzer-arg convention: # gdUnit4 leaks one internal per fuzzer-skipped test, tripping the # orphan detector (exit 101) and bouncing the push gate even at 0 failures # (PR gate run 2026-07-22: "2 skipped | 2 orphans | Exit code: 101"). if _dummy_renderer_active(): print(SKIP_REASON) return var overlay: AtlasWindowOverlay = AtlasWindowOverlay.new() var stub := _SingleWindowViewerStub.new() stub.window = _mock_window(Vector2i.ZERO, 32) overlay.viewer = stub var image: Image = await _render_to_image(overlay) var fraction: float = _non_background_fraction(image) assert_float(fraction).override_failure_message( ( "single-window composite must render VISIBLE non-background pixels — got" + " only %.2f%% of the frame differing from COLOR_BG. This is exactly the" + " shape of live round 4's second bug: _view_offset left stale across a" + " rung crossing pushed the composite off-canvas, so nothing but" + " background/chrome ever appeared, despite the underlying window data" + " and draw calls being individually 'correct' in isolation." ) % (fraction * 100.0) ).is_greater(MIN_NON_BACKGROUND_FRACTION) ## (b) Tile-mosaic path: a tile at a district center AWAY from the body's ## own origin must still render as visibly non-background pixels once ## correctly placed via `district_to_canvas_local()`'s shared held_center/ ## held_n convention — this is the path round 4's FIRST and THIRD bugs ## (tile-local-origin math ignoring that convention, and per-tile ## ImageTexture objects with no persistent reference being garbage- ## collected/GPU-desynced before their draw command flushed) would both ## have failed. Uses production-realistic scale (live round 4's own Lendel ## repro: raw circumference ~19,139 districts) — see the tile-center ## comment below for why scale matters here specifically. func test_tile_mosaic_draw_produces_visible_pixels() -> void: # Guarded early-return, not _do_skip — see the sibling test's comment. if _dummy_renderer_active(): print(SKIP_REASON) return var overlay: AtlasWindowOverlay = AtlasWindowOverlay.new() var stub := _TileModeViewerStub.new() var tile_n: int = AtlasWindowGeometry.TILE_N var cell_px: float = stub.get_cell_pixel_size() stub.held_center = Vector2i.ZERO stub.held_n = 19139 # A SINGLE tile chosen so the CORRECT canvas-local formula # (`district_to_canvas_local()`, anchored at `held_center - held_n/2`) # lands it centered in the viewport, while the round-4 BUGGY formula # (anchored at absolute district (0,0) directly) lands it almost # `held_n/2 * cell_px` local units away — tens of thousands of units at # this scale, i.e. genuinely fully off a 512x512 viewport, not just # "shifted but still overlapping" (confirmed by hand-computation: a # smaller/toy-scale version of this test stayed green with the bug # reintroduced, because the shift stayed within the viewport bounds # either way — this scale/center combination is chosen specifically to # avoid that false-negative). var half_tile: float = float(tile_n) * 0.5 var half_body: float = float(stub.held_n) * 0.5 var lone_tile_center := Vector2i(roundi(half_tile - half_body), roundi(half_tile - half_body)) stub.tiles = [ {"center": lone_tile_center, "window": _mock_window(lone_tile_center, 64)}, ] overlay.viewer = stub # A zoom small enough that the buggy-vs-correct shift (~half_body * cell_px # local units) is comfortably larger than the viewport — see the center # choice's own doc above for why this specific magnitude matters. var zoom: float = float(VIEWPORT_SIZE.x) * 1.5 / (half_body * cell_px) var image: Image = await _render_to_image(overlay, zoom) var fraction: float = _non_background_fraction(image) assert_float(fraction).override_failure_message( ( "tile mosaic must render VISIBLE non-background pixels across its tiles —" + " got only %.2f%% of the frame differing from COLOR_BG. This is exactly" + " the shape of live round 4's bugs: (1) tile local-origin computed" + " relative to absolute district (0,0) instead of the shared" + " held_center/held_n canvas-local convention pushed the whole mosaic" + " off-canvas, and (2) even once correctly positioned, an unstored" + " per-draw-call ImageTexture rendered as a blank/white gap despite" + " provably-correct CPU-side pixel data — both invisible to any test that" + " only inspects Dictionary/cache state, never an actual composited pixel." ) % (fraction * 100.0) ).is_greater(MIN_NON_BACKGROUND_FRACTION) ## (c) PR #192 cold-start dossier, BUG 3: a mosaic tile with NO window yet ## (the cold-server "still working" state, every tile in the mosaic at once ## right after enter_orbital() on a real cold server) must render the SAME ## border-fade wash the single-window path already gives its own ## no-composite-yet wait — not a bare COLOR_BG gap that reads as broken. ## Same real-render infrastructure as (a)/(b): a pending tile (`window: null`) ## must still produce visible non-background pixels, proving the wash ## genuinely draws rather than the loop just `continue`-ing past it silently. func test_pending_tile_gets_a_visible_border_fade_wash() -> void: if _dummy_renderer_active(): print(SKIP_REASON) return var overlay: AtlasWindowOverlay = AtlasWindowOverlay.new() var stub := _TileModeViewerStub.new() var tile_n: int = AtlasWindowGeometry.TILE_N var cell_px: float = stub.get_cell_pixel_size() stub.held_center = Vector2i.ZERO stub.held_n = 19139 # GJ380c/Lendel's own raw circumference (live round 4) # Same centered-tile placement as (b) above, but with `window: null` — # the pending state this fix targets, instead of a real response. var half_tile: float = float(tile_n) * 0.5 var half_body: float = float(stub.held_n) * 0.5 var lone_tile_center := Vector2i(roundi(half_tile - half_body), roundi(half_tile - half_body)) stub.tiles = [{"center": lone_tile_center, "window": null}] overlay.viewer = stub var zoom: float = float(VIEWPORT_SIZE.x) * 1.5 / (half_body * cell_px) var image: Image = await _render_to_image(overlay, zoom) var fraction: float = _non_background_fraction(image) assert_float(fraction).override_failure_message( ( "a pending tile (window == null) must still render a visible border-fade" + " wash — got only %.2f%% of the frame differing from COLOR_BG, meaning" + " the tile is a bare background gap during the cold-server wait, which" + " reads as broken rather than 'still working' (coordinator's closing" + " question, PR #192 cold-start dossier)." ) % (fraction * 100.0) ).is_greater(MIN_NON_BACKGROUND_FRACTION)