Files
settled-reach/client/tests/test_atlas_window_request.gd
T
jpmschweitzer 0159a63cc2 fix(simulation): PR #191 review round — n-clamp mirror, min_wl band quantization, coalescing coverage, fixture consumer
All seven Hoshe/Tyre findings addressed, none retracted:
- n-clamp/echo/staleness triangle (Tyre C1): client _clamp_window_n_mirror
  (bit-for-bit twin of the server clamp, canonicalize_district_center
  precedent) applied before _n is stored/sent; server test pins the
  quarter n=32 -> echo 16 contract.
- min_wl band quantization (Hoshe 1/Tyre C3): quantize_min_wl_m snaps to
  MIN_WL_BANDS_M {0, 32768, 16384, 8192, 4096} before cache key and echo
  (design doc §5's unbounded-key fix), reusing the one true
  OCTAVE_WAVELENGTHS_M array; docstrings now state the server-quantizes/
  client-sends-raw split; same-band cache-sharing test.
- coalescing granularity axis (Hoshe 2): two tests pin different-
  granularity requests as separate in-flight slots and same-granularity
  coalescing unchanged.
- orphaned fixture (Hoshe 3): test_protocol.gd consumer decodes
  atlas_response_ready_with_window.msgpack through the real IPC path and
  asserts the new fields.
- atlas_window_request coverage (Hoshe 4): new test file — stale-drop on
  granularity mismatch, old-server-shape defaults accepted, clamp mirror
  formula + wiring. First draft's quarter-via-request_now test would have
  passed for the wrong reason (request_now resets granularity by design
  until T-1153) — split into formula pin + reachable-path wiring proof.
- granularity type seam (Tyre C2): field + resolver docstrings state
  finer-only integer multiples with resolve_window_granularity as the
  single widening point; matching contract note added to the D-226
  T-1143-rulings amendment.

cargo --lib 1807/1807; goldens bit-identical; gdlint clean.
2026-07-22 00:47:53 +02:00

178 lines
7.9 KiB
GDScript

## T-1150 (PR #191 review, Hoshe 4): atlas_window_request.gd had NO test file
## at all before this — direct coverage of the granularity/min_wl_m staleness
## guard, the n-clamp mirror (Tyre C1), and the old-server-shape default
## disposition. Follows test_atlas_window_viewer.gd's own
## "AtlasWindowRequest — cache reuse" section conventions (same
## instantiation pattern: `AtlasWindowRequest.new(owner_stub)`, `add_child()`
## for the debounce Timer, hand-built response dicts) rather than
## re-inventing a shape.
class_name TestAtlasWindowRequest
extends GdUnitTestSuite
# atlas_window_request.gd has no class_name (review #8 precedent throughout
# this cluster) — preloaded once here, not re-load()ed per test (gdlint
# duplicated-load).
const AtlasWindowRequest := preload("res://ui/implant/apps/atlas/atlas_window_request.gd")
## Build a hand-authored DistrictWindowLayer dict, granularity-aware
## (T-1150) — mirrors test_atlas_window_viewer.gd's own _mock_window(), with
## granularity/min_wl_m added as optional params so callers can build both
## rungs' echo shapes with one helper.
static func _mock_window(
center: Vector2i, n: int = 2, granularity: int = 1, min_wl_m: int = 0
) -> Dictionary:
return {
"center": [center.x, center.y],
"n": n,
"granularity": granularity,
"min_wl_m": min_wl_m,
"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]),
}
static func _mock_response(body_id: String, window: Variant) -> Dictionary:
return {"body_id": body_id, "status": "Ready", "district_window": window}
func _make_request() -> Variant:
var owner_stub := RefCounted.new()
var req = auto_free(AtlasWindowRequest.new(owner_stub))
add_child(req)
return req
# =============================================================================
# (a) granularity mismatch on the echo -> dropped as stale
# =============================================================================
## The mandatory item-(a) case: request_now() asks at the default district
## granularity (1); a response echoing granularity=4 (quarter) for the SAME
## center/n must be dropped as stale, not accepted — a different rung's
## derive answering a request for a different rung is exactly as stale as a
## mismatched center (T-1150 extends §2's guard to this axis).
func test_on_response_with_mismatched_granularity_is_dropped_as_stale() -> void:
var req = _make_request()
req.request_now("GJ380c", Vector2i(2, 2), 2)
assert_bool(req.is_pending()).is_true()
var quarter_window: Dictionary = _mock_window(Vector2i(2, 2), 2, 4, 0)
req.on_response(_mock_response("GJ380c", quarter_window))
assert_bool(req.is_pending()).override_failure_message(
"a granularity-mismatched response must be dropped as stale, leaving the district request still pending"
).is_true()
# =============================================================================
# (b) old-server-shape response (no granularity/min_wl_m keys) -> defaults
# =============================================================================
## A response from a hypothetical pre-T-1150 server (or any response whose
## district_window dict simply omits the new keys) must decode granularity
## as district (1) and min_wl_m as 0 via the same defaulting on_response()
## already applies — and since request_now()'s own defaults are identical,
## the response is ACCEPTED, not treated as stale just because two keys are
## missing.
func test_on_response_missing_granularity_and_min_wl_defaults_and_is_accepted() -> void:
var req = _make_request()
req.request_now("GJ380c", Vector2i(3, 3), 2)
assert_bool(req.is_pending()).is_true()
# Old-shape window: no "granularity"/"min_wl_m" keys at all.
var old_shape_window := {
"center": [3, 3],
"n": 2,
"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]),
}
req.on_response(_mock_response("GJ380c", old_shape_window))
assert_bool(req.is_pending()).override_failure_message(
(
"an old-server-shape response (missing granularity/min_wl_m) must "
+ "default to district/0 and be ACCEPTED, not dropped as stale"
)
).is_false()
var received: Array = []
req.window_ready.connect(func(w: Dictionary) -> void: received.append(w))
# Re-request the same (body, center, n) — must now be a cache hit, proving
# on_response() actually stored the old-shape window under the
# district/0 key, not silently discarding it.
req.request_now("GJ380c", Vector2i(3, 3), 2)
assert_int(received.size()).is_equal(1)
assert_bool(req.is_pending()).is_false()
# =============================================================================
# (c) n-clamp mirror (Tyre C1) — quarter n=32 stores clamped n=16
# =============================================================================
## **Item (c) as literally scoped by the ticket** ("the clamp-mirror from
## item 1"): `_clamp_window_n_mirror()` reproduces the server's
## `clamp_window_n(raw_n, granularity)` bit-for-bit, INCLUDING the quarter
## n=32 -> 16 case — pinned directly against the static helper, independent
## of the request/response plumbing (`request_now()` has no public
## "request quarter" entry point today; T-1150 is struct/key plumbing only,
## requesting quarter is T-1153's job — see the class-level docstring on
## `_clamp_window_n_mirror()` for why calling `request_now()` at district
## granularity can never itself exercise the quarter branch: it unconditionally
## resets `_granularity` to district BEFORE clamping, by design, since no
## caller can ask for quarter yet).
func test_clamp_window_n_mirror_matches_server_formula_at_quarter_n32() -> void:
assert_int(AtlasWindowRequest._clamp_window_n_mirror(32, 4)).is_equal(16)
# District granularity: the per-axis cap (64) governs, matching the
# server's clamp_window_n_district_granularity_uses_per_axis_cap test.
assert_int(AtlasWindowRequest._clamp_window_n_mirror(640, 1)).is_equal(64)
# Small n well under budget at quarter granularity stays unclamped,
# matching clamp_window_n_quarter_granularity_leaves_small_n_unclamped.
assert_int(AtlasWindowRequest._clamp_window_n_mirror(8, 4)).is_equal(8)
## **Item (c), the request/response half:** `request_now()` actually WIRES
## the mirror in (not just defines it) — a request for a district-legal but
## per-axis-oversized `n` (e.g. 640, mirroring the server's own
## `DISTRICT_WINDOW_MAX_N*10` oversized-request test) stores the CLAMPED
## `_n=64`, so a server response echoing the server's OWN clamped n=64 is
## ACCEPTED, not rejected as stale for "not matching" the raw 640 that was
## asked for. This is the exact n-clamp/echo/staleness triangle Tyre C1
## flagged, exercised through the reachable (district) path today; the
## quarter-specific n=32->16 number is pinned by the formula test above since
## no public API can drive quarter through `request_now()` yet.
func test_oversized_n_request_stores_clamped_n_and_accepts_matching_echo() -> void:
var req = _make_request()
req.request_now("GJ380c", Vector2i(4, 4), 640)
assert_int(req._n).override_failure_message(
(
"request_now() must mirror the server's clamp_window_n(640, granularity=1) "
+ "== 64 BEFORE storing _n, not store the raw requested 640"
)
).is_equal(64)
assert_bool(req.is_pending()).is_true()
# The server's real response for this request echoes n=64 (its own
# clamp_window_n() result) — must be ACCEPTED, not stale.
var clamped_echo: Dictionary = _mock_window(Vector2i(4, 4), 64, 1, 0)
req.on_response(_mock_response("GJ380c", clamped_echo))
assert_bool(req.is_pending()).override_failure_message(
(
"a response echoing the CLAMPED n=64 must be accepted, since _n was "
+ "already clamped to 64 before the request fired"
)
).is_false()