feat(ui): T-1161 — per-rung composite filter policy (Region NEAREST, District/Quarter LINEAR)

Araminta's ruling (PR #192 follow-up): filter keyed on rung IDENTITY via
the window's own echoed granularity_v2 — Region (incl. the orbital tile
mosaic, whose tiles are all Region-rung requests) samples NEAREST because
GPU bilinear at 204.8 km/cell reads as smoothing-over-absence; District/
Quarter keep LINEAR where cell density earns the blend. One shared helper
(_filter_for_granularity_v2) at both draw call sites; unknown/missing
wire tags fall back to LINEAR (never trusted into NEAREST). COMPOSITE_
SMOOTH survives as the independent compile-time pipeline axis — the
two-axes split is documented in the file header. Washes/border fades
untouched per the ruling. Focused suite 44/44; revert-verified (helper
hardcoded LINEAR -> exactly the three Region-NEAREST tests fail).

Tickets: T-1161

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-22 22:36:05 +02:00
co-authored by Claude Fable 5
parent bbe9099e22
commit c9028af77e
2 changed files with 206 additions and 20 deletions
+132
View File
@@ -34,6 +34,13 @@ static func _mock_window(n: int = 2) -> Dictionary:
class _ViewerStub:
var window: Variant = null
var active_overlay: String = ""
# T-1161: the viewer's currently-HELD rung tag — added alongside the
# per-rung filter tests below. Not read by AtlasWindowOverlay today (the
# overlay trusts each window dict's OWN echoed granularity_v2, per
# cell_grid_side_for_window()'s precedent) but a real AtlasWindowViewer
# exposes get_held_granularity_v2() (T-1153), so the stub carries it too
# for parity with the real duck-typed interface.
var held_granularity_v2: String = "District"
func get_district_window() -> Variant:
return window
@@ -47,7 +54,17 @@ class _ViewerStub:
func is_tile_mode() -> bool:
return false
func get_held_granularity_v2() -> String:
return held_granularity_v2
## T-1161 reframe: COMPOSITE_SMOOTH is now Axis 1 of TWO independent axes
## (see the file header doc) — the texture-vs-flat-rects PIPELINE choice.
## This assertion survives unchanged: the composite is still a TEXTURE at
## every rung. Axis 2 (which FILTER that texture samples with) is now a
## per-rung runtime decision covered separately below by the
## _filter_for_granularity_v2() tests — it is no longer bundled into this
## compile-time const.
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"
@@ -265,3 +282,118 @@ func test_rebuild_handles_a_rung_swap_from_district_to_region() -> void:
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
# =============================================================================
# T-1161: _filter_for_granularity_v2() — the per-rung sampling filter policy
# (Araminta's ruling: Region incl. the orbital tile mosaic -> NEAREST;
# District/Quarter -> LINEAR; no hysteresis, no px-per-cell threshold, keyed
# purely on rung IDENTITY).
# =============================================================================
## Region is the sparse rung the ruling targets — GPU bilinear blending at
## 204.8 km/cell reads as smoothing-over-absence, so it samples NEAREST.
func test_filter_for_granularity_v2_region_is_nearest() -> void:
assert_int(AtlasWindowOverlay._filter_for_granularity_v2("Region")).override_failure_message(
"Region must sample TEXTURE_FILTER_NEAREST — dense-enough rungs get LINEAR,"
+ " Region is the sparse one the ruling targets"
).is_equal(CanvasItem.TEXTURE_FILTER_NEAREST)
## District is dense enough that the bilinear blend reads as texture, not as
## papering over sparse data — LINEAR is earned.
func test_filter_for_granularity_v2_district_is_linear() -> void:
assert_int(AtlasWindowOverlay._filter_for_granularity_v2("District")).override_failure_message(
"District must sample TEXTURE_FILTER_LINEAR"
).is_equal(CanvasItem.TEXTURE_FILTER_LINEAR)
## Quarter (4x MORE cells than District) is denser still — also LINEAR.
func test_filter_for_granularity_v2_quarter_is_linear() -> void:
assert_int(AtlasWindowOverlay._filter_for_granularity_v2("Quarter")).override_failure_message(
"Quarter must sample TEXTURE_FILTER_LINEAR"
).is_equal(CanvasItem.TEXTURE_FILTER_LINEAR)
## An unrecognized/empty tag must NEVER be trusted into the crisp NEAREST
## treatment — mirrors cell_grid_side_for_window()'s own "unknown -> District"
## fallback posture, failing toward the already-shipped LINEAR look rather
## than an unintended NEAREST for a wire shape this code doesn't recognize.
func test_filter_for_granularity_v2_unknown_falls_back_to_linear() -> void:
assert_int(AtlasWindowOverlay._filter_for_granularity_v2("")).override_failure_message(
"an empty/unrecognized granularity_v2 tag must fall back to LINEAR, never NEAREST"
).is_equal(CanvasItem.TEXTURE_FILTER_LINEAR)
assert_int(AtlasWindowOverlay._filter_for_granularity_v2("SomeFutureRung")).override_failure_message(
"an unrecognized granularity_v2 tag must fall back to LINEAR, never NEAREST"
).is_equal(CanvasItem.TEXTURE_FILTER_LINEAR)
## Integration-shaped: a Region-rung window dict, drawn through the real
## _draw() entry point via the _ViewerStub duck-typed interface (same
## end-to-end shape as test_draw_builds_a_texture_for_a_region_rung_window()
## above), must drive the NODE's own texture_filter property to NEAREST —
## not just the helper function in isolation.
func test_draw_sets_node_texture_filter_to_nearest_for_region_window() -> void:
var o: AtlasWindowOverlay = auto_free(AtlasWindowOverlay.new())
var stub := _ViewerStub.new()
stub.held_granularity_v2 = "Region"
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_int(o.texture_filter).override_failure_message(
"a Region-rung window must drive the node's texture_filter to NEAREST after a draw"
).is_equal(CanvasItem.TEXTURE_FILTER_NEAREST)
## The District-rung counterpart of the above — confirms the node's
## texture_filter lands on LINEAR (not left over from a previous NEAREST
## draw, and not defaulting to NEAREST) for the dense rung.
func test_draw_sets_node_texture_filter_to_linear_for_district_window() -> void:
var o: AtlasWindowOverlay = auto_free(AtlasWindowOverlay.new())
var stub := _ViewerStub.new()
stub.window = _mock_window() # District (the default/omitted tag)
o.viewer = stub
o._draw()
assert_int(o.texture_filter).override_failure_message(
"a District-rung window must drive the node's texture_filter to LINEAR after a draw"
).is_equal(CanvasItem.TEXTURE_FILTER_LINEAR)
## A rung SWAP (Region -> District, on the SAME node) must flip texture_filter
## along with it — confirms the property is recomputed every draw, not
## sticky from the first rung the node ever rendered.
func test_draw_flips_node_texture_filter_on_a_rung_swap() -> void:
var o: AtlasWindowOverlay = auto_free(AtlasWindowOverlay.new())
var stub := _ViewerStub.new()
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_int(o.texture_filter).is_equal(CanvasItem.TEXTURE_FILTER_NEAREST)
stub.window = _mock_window() # swap to District
o._draw()
assert_int(o.texture_filter).override_failure_message(
"swapping to a District-rung window must flip texture_filter to LINEAR,"
+ " not leave it stuck at the previous rung's NEAREST"
).is_equal(CanvasItem.TEXTURE_FILTER_LINEAR)