Files
settled-reach/client/tests/test_atlas_window_request.gd
T
jpmschweitzerandClaude Fable 5 8bb09abad0 test(ui): wrong-body Pending must not touch retry state (PR #193 Hoshe finding 1)
Pins on_response()'s guard ordering: body_id check BEFORE the status
branch, so another body's cold Pending can never burn one of our 30
retries or reschedule our timer (multi-body browsing / shared-broadcast
tile fan-out). Revert-verified: reordering the guards makes this test
fail; final own-body Ready assertion proves the request is genuinely
untouched, not just un-retried. 44/44 in the file.

Tickets: T-1163

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 21:15:47 +02:00

653 lines
32 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")
## Dudley's WINDOW_GRANULARITY_REGION_KEY (server/src/atlas/layer_proxy.rs) —
## `u32::MAX`, the RESERVED KEY-SPACE TAG a real server ALWAYS puts in the
## legacy `granularity` slot for every Region response (never a real
## multiplier — District=1/Quarter=4 are the only legal wire multipliers).
## Do NOT "fix" this to 1 — using a convenient value here is EXACTLY the gap
## the live round caught (a mock that diverges from the wire in the one
## field that matters silently un-repros the bug). See
## AtlasWindowRequest's `_echoed_granularity_matches()` doc for the full
## rationale.
const SERVER_LEGACY_GRANULARITY_REGION_SENTINEL: int = 4294967295
## Build a hand-authored DistrictWindowLayer dict, granularity-aware
## (T-1150, extended T-1152/T-1153 for granularity_v2) — mirrors
## test_atlas_window_viewer.gd's own _mock_window(), with
## granularity/min_wl_m/granularity_v2 added as optional params so callers
## can build any rung's echo shape with one helper.
static func _mock_window(
center: Vector2i,
n: int = 2,
granularity: int = 1,
min_wl_m: int = 0,
granularity_v2: String = "District"
) -> Dictionary:
return {
"center": [center.x, center.y],
"n": n,
"granularity": granularity,
"min_wl_m": min_wl_m,
"granularity_v2": granularity_v2,
"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}
## PR #192 cold-start round 3: the WIRE-ACCURATE shape of a cold body's
## first-ever response (server/src/atlas/layer_proxy.rs's
## get_or_generate()/serve_district_window(), whole-body cache MISS branch —
## `AtlasLayerResponse { status: Pending, district_window: None, ... }`,
## confirmed directly against that source). `body_id` is the only
## identifying field.
static func _pending_response(body_id: String) -> Dictionary:
return {"body_id": body_id, "status": "Pending", "district_window": null}
static func _not_found_response(body_id: String) -> Dictionary:
return {"body_id": body_id, "status": "NotFound", "district_window": null}
## Matches atlas_map_protocol.gd's `_decode_status_field()` — the decoded
## `AtlasLayerStatus::Error(String)` variant's status STRING is always just
## "Error" (the message rides in a separate `error` field).
static func _error_response(body_id: String, message: String = "boom") -> Dictionary:
return {"body_id": body_id, "status": "Error", "error": message, "district_window": null}
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).
##
## **Live-round correction:** the mock MUST carry a mismatched
## `granularity_v2` too (explicit `"Quarter"`, not `_mock_window()`'s
## `"District"` default) — a real Quarter response ALWAYS carries
## `granularity_v2: "Quarter"` on the wire, never the District default this
## test's fixture used to leave implicit. Under the v2-authoritative-when-
## present precedence rule (see on_response()'s own doc), a v2-MATCHING
## response is accepted regardless of what the legacy int says — leaving
## granularity_v2 at its District default here would have made this test
## pass for the wrong reason (an accidentally-matching v2 field masking a
## genuinely mismatched legacy int), exactly the class of gap the live round
## caught in the oversized-orbital round-trip test.
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, "Quarter")
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()
# =============================================================================
# (a2) granularity_v2 mismatch on the echo -> dropped as stale (T-1152/T-1153,
# the axis the legacy int alone cannot express — Region has no legacy value)
# =============================================================================
## request_now() can now ask for Region explicitly (T-1153's rung-reselect
## caller) — a response echoing "District" for the SAME center/n must be
## dropped as stale, the granularity_v2 twin of test (a) above, and the
## ONLY guard that can catch this specific mismatch (the legacy int is
## DISTRICT_GRANULARITY=1 on BOTH sides here, since Region has no legacy
## representation — see WindowGranularity::legacy_u32()'s doc).
func test_on_response_with_mismatched_granularity_v2_is_dropped_as_stale() -> void:
var req = _make_request()
req.request_now("GJ380c", Vector2i(0, 0), 6400, AtlasWindowRequest.GRANULARITY_V2_REGION)
assert_bool(req.is_pending()).is_true()
var district_window: Dictionary = _mock_window(Vector2i(0, 0), 6400, 1, 0, "District")
req.on_response(_mock_response("GJ380c", district_window))
assert_bool(req.is_pending()).override_failure_message(
"a granularity_v2-mismatched response (District answering a Region request)"
+ " must be dropped as stale, leaving the request still pending"
).is_true()
## The matching case: request_now() asking for Region, answered by a Region
## echo at the SAME (center, n) — must be ACCEPTED and cached under the
## Region key, retrievable on a follow-up request without a new network round
## trip.
##
## **Live-round correction:** the mock's legacy `granularity` field is now
## Dudley's ACTUAL wire sentinel (`WINDOW_GRANULARITY_REGION_KEY` =
## `u32::MAX` = 4294967295), not a convenient `1` — the original version of
## this test used `1`, which coincidentally matched the request's own
## pinned `_granularity` and therefore never exercised the real mismatch a
## live server actually produces. See _echoed_granularity_matches()'s own
## doc (atlas_window_request.gd) for why this is load-bearing: without the
## v2-authoritative-when-present fix, THIS test would have failed with the
## real sentinel — it only passed before because the mock was wrong.
func test_on_response_matching_granularity_v2_region_is_accepted_and_cached() -> void:
var req = _make_request()
req.request_now("GJ380c", Vector2i(0, 0), 6400, AtlasWindowRequest.GRANULARITY_V2_REGION)
assert_bool(req.is_pending()).is_true()
var region_window: Dictionary = _mock_window(
Vector2i(0, 0), 6400, SERVER_LEGACY_GRANULARITY_REGION_SENTINEL, 0, "Region"
)
req.on_response(_mock_response("GJ380c", region_window))
assert_bool(req.is_pending()).override_failure_message(
"a response carrying the REAL legacy sentinel (u32::MAX) in the old"
+ " granularity slot must still be accepted — v2 is authoritative"
+ " whenever present, the legacy field must not be compared at all"
).is_false()
var received: Array = []
req.window_ready.connect(func(w: Dictionary) -> void: received.append(w))
req.request_now("GJ380c", Vector2i(0, 0), 6400, AtlasWindowRequest.GRANULARITY_V2_REGION)
assert_int(received.size()).override_failure_message(
"a second Region request at the same (center, n) must hit the cache"
).is_equal(1)
assert_bool(req.is_pending()).is_false()
## **The direct precedence-rule proof (live-round finding #2, the sharpest
## case):** a response whose `granularity_v2` MATCHES the request but whose
## LEGACY `granularity` field could never possibly match (the Region
## sentinel) must still be ACCEPTED — proving the legacy comparison is
## SKIPPED entirely when v2 is present, not merely "also checked and
## happens to pass." This is the literal shape of the live bug: real server
## responses ALWAYS carry the Region sentinel in the legacy slot, so any
## code path that still consults the legacy field when v2 is already
## authoritative would drop every single one of these, forever.
func test_on_response_v2_match_is_accepted_regardless_of_legacy_field_value() -> void:
var req = _make_request()
req.request_now("GJ380c", Vector2i(0, 0), 6400, AtlasWindowRequest.GRANULARITY_V2_REGION)
var region_window: Dictionary = _mock_window(
Vector2i(0, 0), 6400, SERVER_LEGACY_GRANULARITY_REGION_SENTINEL, 0, "Region"
)
req.on_response(_mock_response("GJ380c", region_window))
assert_bool(req.is_pending()).override_failure_message(
"v2 match must be sufficient on its own — the legacy sentinel value must"
+ " never be consulted once granularity_v2 is present on the response"
).is_false()
# =============================================================================
# (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()
## **Live-round sibling test (instruction #2's "old-server path stays
## covered"):** a response that carries the LEGACY `granularity` key WITH AN
## EXPLICIT VALUE (1, i.e. genuinely present, not merely defaulted via
## absence — the case test_on_response_missing_granularity_and_min_wl_defaults_and_is_accepted
## above doesn't exercise, since it omits the key entirely) but has NO
## `granularity_v2` key at all — the true "hypothetically old, pre-T-1152
## server" shape — must still be accepted for a plain District request via
## the legacy-comparison FALLBACK branch in `_echoed_granularity_matches()`.
## This is the other half of the v2-authoritative-when-present precedence
## rule: v2 present -> v2 alone decides; v2 ABSENT -> legacy alone decides
## (never both, never neither).
func test_on_response_legacy_only_no_v2_key_still_accepted_for_district() -> void:
var req = _make_request()
req.request_now("GJ380c", Vector2i(4, 4), 2) # defaults to District granularity
assert_bool(req.is_pending()).is_true()
# Legacy-only shape: "granularity" IS present (district=1), "granularity_v2"
# key is absent entirely — not present-with-a-District-value, ABSENT.
var legacy_only_window := {
"center": [4, 4],
"n": 2,
"granularity": AtlasWindowRequest.DEFAULT_GRANULARITY,
"min_wl_m": 0,
"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]),
}
assert_bool(legacy_only_window.has("granularity_v2")).override_failure_message(
"sanity: this fixture must NOT carry granularity_v2 at all — that's the point"
).is_false()
req.on_response(_mock_response("GJ380c", legacy_only_window))
assert_bool(req.is_pending()).override_failure_message(
"a legacy-only response (granularity=1 present, granularity_v2 absent) must"
+ " still be accepted for a District request via the legacy-fallback branch"
).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()
# =============================================================================
# (d) Region clamp mirror (T-1152/T-1153) — mirrors
# server/src/atlas/layer_proxy.rs's clamp_window_n_v2 EXACTLY, including the
# Region branch's bounded halving loop.
#
# PR #192 review (Dudley, server-side analysis): the halving loop is
# PROVABLY UNREACHABLE at current constants — the per-axis clamp to
# SERVER_DISTRICT_WINDOW_MAX_N_REGION (6,400) forecloses it. Brute-forced,
# the max cell_grid_side over ALL reachable (post-per-axis-clamp) n is
# exactly 64 — the wire-cap boundary itself, never over it — so the loop's
# `>` guard is never true for any input. Ruling: the loop STAYS as
# defensive code (a future constant change could make it reachable again),
# but the test suite must not claim it "fires" when it provably doesn't.
# See server/src/atlas/layer_proxy.rs's
# clamp_window_n_v2_region_per_axis_cap_alone_satisfies_wire_cap_for_all_inputs
# for the server-side property-sweep pin this client-side suite mirrors.
# =============================================================================
## District/Quarter through the v2 mirror must be BYTE-IDENTICAL to the
## legacy mirror — the server's own
## `clamp_window_n_v2_delegates_to_legacy_for_district_and_quarter`
## guarantee, restated client-side.
func test_clamp_window_n_mirror_v2_matches_legacy_for_district_and_quarter() -> void:
assert_int(
AtlasWindowRequest._clamp_window_n_mirror_v2(32, AtlasWindowRequest.GRANULARITY_V2_QUARTER)
).is_equal(AtlasWindowRequest._clamp_window_n_mirror(32, 4))
assert_int(
AtlasWindowRequest._clamp_window_n_mirror_v2(640, AtlasWindowRequest.GRANULARITY_V2_DISTRICT)
).is_equal(AtlasWindowRequest._clamp_window_n_mirror(640, 1))
## The clean Region boundary case: n=6,400 (DISTRICT_WINDOW_MAX_N_REGION,
## the per-axis cap exactly) derives cell_grid_side(6400) = round(6400/100) =
## 64, and 64² = 4,096 = WIRE_CAP_CELLS EXACTLY — the halving loop's `>`
## condition is false at the boundary, so this must clamp to EXACTLY 6,400,
## not halve further. This is the server's own
## `clamp_window_n_v2_region_exact_boundary_n6400_uncontested` guarantee,
## restated client-side (WIRE_CAP_CELLS_SQRT * DISTRICTS_PER_REGION is
## DERIVED to land here exactly, per that constant's own doc).
func test_clamp_window_n_mirror_v2_region_boundary_is_exact() -> void:
var n: int = AtlasWindowRequest._clamp_window_n_mirror_v2(
AtlasWindowRequest.SERVER_DISTRICT_WINDOW_MAX_N_REGION, AtlasWindowRequest.GRANULARITY_V2_REGION
)
assert_int(n).is_equal(AtlasWindowRequest.SERVER_DISTRICT_WINDOW_MAX_N_REGION)
## Region's per-axis cap: a raw `n` far over DISTRICT_WINDOW_MAX_N_REGION
## (mirroring the server's own `region_request_oversized_n_clamps_and_echoes_clamped_n`
## test's `DISTRICT_WINDOW_MAX_N_REGION * 10` shape) must clamp DOWN — never
## trust the wire — and the result must satisfy BOTH invariants the server's
## own test asserts: `n <= DISTRICT_WINDOW_MAX_N_REGION` AND
## `cell_grid_side(n)^2 <= WIRE_CAP_CELLS`.
func test_clamp_window_n_mirror_v2_region_oversized_n_clamps_within_both_bounds() -> void:
var oversized: int = AtlasWindowRequest.SERVER_DISTRICT_WINDOW_MAX_N_REGION * 10
var n: int = AtlasWindowRequest._clamp_window_n_mirror_v2(
oversized, AtlasWindowRequest.GRANULARITY_V2_REGION
)
assert_int(n).override_failure_message(
"echoed n must be clamped to DISTRICT_WINDOW_MAX_N_REGION, not the raw oversized value"
).is_less_equal(AtlasWindowRequest.SERVER_DISTRICT_WINDOW_MAX_N_REGION)
var side: int = AtlasWindowRequest._cell_grid_side_region_mirror(n)
assert_int(side * side).override_failure_message(
"clamped cell count must never exceed WIRE_CAP_CELLS at Region granularity either"
).is_less_equal(AtlasWindowRequest.SERVER_WIRE_CAP_CELLS)
## PR #192 review (Dudley's unreachability finding, applied client-side): the
## halving loop's `>` guard is PROVABLY never true at current constants — the
## per-axis clamp to SERVER_DISTRICT_WINDOW_MAX_N_REGION (6,400) happens
## FIRST and unconditionally, and cell_grid_side(6400) = 64 lands EXACTLY on
## the wire-cap boundary (64² = WIRE_CAP_CELLS), never over it. A prior
## version of this test claimed n=6,450 "exercises" the loop firing — it does
## not: 6,450 clamps to 6,400 before the loop ever runs, so the test was
## passing on the per-axis clamp alone, not on anything the loop itself did
## (the same mock-diverges-from-reality class of bug hunted in review round
## 2). Reframed as a property sweep, mirroring the server's own
## `clamp_window_n_v2_region_per_axis_cap_alone_satisfies_wire_cap_for_all_inputs`
## (Dudley): for every raw n across the legal range (including values far
## past the per-axis cap), (i) the per-axis-clamped n never gets modified any
## further by the loop — pre-loop n and post-clamp n are byte-identical —
## and (ii) the wire-cap invariant holds regardless. The loop itself stays as
## defensive code (a future constant change could make it reachable again);
## this test documents that it is a no-op today rather than asserting a
## behavior that never actually happens.
func test_clamp_window_n_mirror_v2_region_per_axis_cap_alone_satisfies_wire_cap_for_all_inputs() -> void:
var sample_raw_ns: Array = [
1, 100, 6399, 6400, 6401, 6450, 6500,
AtlasWindowRequest.SERVER_DISTRICT_WINDOW_MAX_N_REGION * 10,
]
for raw_n: int in sample_raw_ns:
var pre_loop_n: int = clampi(raw_n, 1, AtlasWindowRequest.SERVER_DISTRICT_WINDOW_MAX_N_REGION)
var clamped_n: int = AtlasWindowRequest._clamp_window_n_mirror_v2(
raw_n, AtlasWindowRequest.GRANULARITY_V2_REGION
)
assert_int(clamped_n).override_failure_message(
(
"the per-axis clamp alone must already satisfy the wire cap for"
+ " raw_n=%d — the halving loop is provably unreachable at current"
+ " constants (max cell_grid_side over all reachable n is exactly"
+ " 64, the wire-cap boundary itself), so it must never further"
+ " modify what the per-axis clamp already produced"
) % raw_n
).is_equal(pre_loop_n)
var side: int = AtlasWindowRequest._cell_grid_side_region_mirror(clamped_n)
assert_int(side * side).override_failure_message(
"the wire-cap invariant must hold for raw_n=%d regardless" % raw_n
).is_less_equal(AtlasWindowRequest.SERVER_WIRE_CAP_CELLS)
## n smaller than one region (n < 100) must clamp its cell-grid side to a
## minimum of 1 — cell_grid_side_for_window()'s own `.max(1)` — never a
## degenerate 0x0 grid, matching WindowGranularity::cell_grid_side's own
## documented minimum.
func test_cell_grid_side_region_mirror_minimum_is_one() -> void:
assert_int(AtlasWindowRequest._cell_grid_side_region_mirror(1)).is_equal(1)
assert_int(AtlasWindowRequest._cell_grid_side_region_mirror(50)).is_equal(1)
# =============================================================================
# PR #192 cold-start round 3 — the single-window half of the launch-shape
# gap: the SAME status-gate bug the tile fan-out has (test_atlas_window_tile_set.gd's
# own regressions) applies equally here, since on_response() is the shared
# class both paths use. A first descent onto a cold body with NO tiles
# (District/Quarter rung, or a small Region body) hits the identical
# whole-body-cache-miss -> status:"Pending" wire shape.
# =============================================================================
## The exact bug, single-window shape: a whole-response Pending on a cold
## first request must increment the retry counter and schedule a re-poll —
## not be silently dropped by the OLD `status != "Ready" -> return` gate.
func test_cold_request_whole_response_pending_increments_retry_count() -> void:
var req = _make_request()
req.request_now("GJ380c", Vector2i(2, 2), 2)
assert_bool(req.is_pending()).is_true()
req.on_response(_pending_response("GJ380c"))
assert_bool(req.is_pending()).override_failure_message(
"a whole-response Pending must leave the request still pending, not"
+ " silently give up"
).is_true()
assert_int(req._retries).override_failure_message(
"a whole-response Pending must increment the retry counter — the"
+ " exact bug: the OLD status-gate dropped this before ever reaching"
+ " the retry-scheduling code, leaving retries at 0 forever"
).is_equal(1)
## Full convergence: a whole-response Pending, then a real Ready for the
## RE-REQUEST, must be accepted — proving the retry loop's own re-request
## actually gets picked up, not just that the counter increments. The
## mid-test retries==1 assertion is what makes this genuinely load-bearing:
## without it, a Ready delivered ANY time after a Pending (retried or not)
## trivially passes this test's final assertion, since accepting a fresh
## Ready response was never the broken behavior — only the retry itself was.
func test_cold_request_converges_after_pending_then_real_ready() -> void:
var req = _make_request()
req.request_now("GJ380c", Vector2i(2, 2), 2)
req.on_response(_pending_response("GJ380c"))
assert_bool(req.is_pending()).is_true()
assert_int(req._retries).override_failure_message(
"sanity: the retry must have actually been scheduled before this test"
+ " waits for it to fire — otherwise the final assertion below would"
+ " pass even if the retry never happened at all"
).is_equal(1)
await get_tree().create_timer(0.6).timeout # past the first retry delay
var window: Dictionary = _mock_window(Vector2i(2, 2), 2)
req.on_response(_mock_response("GJ380c", window))
assert_bool(req.is_pending()).override_failure_message(
"a real Ready response after the pending/retry cycle must be accepted"
).is_false()
## NotFound must give up immediately, not retry.
func test_cold_request_not_found_gives_up_immediately_without_retry() -> void:
var req = _make_request()
req.request_now("GJ380c", Vector2i(2, 2), 2)
req.on_response(_not_found_response("GJ380c"))
assert_bool(req.is_pending()).override_failure_message(
"NotFound must give up immediately, not stay pending waiting for a retry"
).is_false()
assert_int(req._retries).is_equal(0)
## Error must give up immediately too.
func test_cold_request_error_gives_up_immediately_without_retry() -> void:
var req = _make_request()
req.request_now("GJ380c", Vector2i(2, 2), 2)
req.on_response(_error_response("GJ380c"))
assert_bool(req.is_pending()).override_failure_message(
"Error must give up immediately, not stay pending waiting for a retry"
).is_false()
assert_int(req._retries).is_equal(0)
## PR #193 review (Hoshe): a whole-response Pending for a DIFFERENT body
## must not touch this request's retry state — the body_id guard runs
## BEFORE the status branch in on_response(), so someone else's cold-body
## pending can never burn one of OUR 30 retries (or reschedule our timer).
## Structurally guaranteed by guard ordering today; this test pins the
## ordering, because a refactor that moves the status branch first would
## silently cross-wire every concurrent cold descent (multi-body Atlas
## browsing, or the orbital tile fan-out where all requests share one
## broadcast signal). The final Ready-for-OUR-body assertion proves the
## request is genuinely unaffected, not just un-retried.
func test_pending_for_a_different_body_does_not_touch_retry_state() -> void:
var req = _make_request()
req.request_now("GJ380c", Vector2i(2, 2), 2)
assert_bool(req.is_pending()).is_true()
req.on_response(_pending_response("OtherBody"))
assert_int(req._retries).override_failure_message(
"a Pending for a DIFFERENT body must not increment OUR retry counter"
+ " — the body_id guard must run before the status branch"
).is_equal(0)
assert_bool(req.is_pending()).override_failure_message(
"a wrong-body Pending must leave the request still pending its own"
+ " response, neither given up nor retried"
).is_true()
var window: Dictionary = _mock_window(Vector2i(2, 2), 2)
req.on_response(_mock_response("GJ380c", window))
assert_bool(req.is_pending()).override_failure_message(
"after ignoring a wrong-body Pending, our OWN Ready must still be"
+ " accepted normally — the request state must be genuinely untouched"
).is_false()
assert_int(req._retries).is_equal(0)
# =============================================================================
# _retry_delay_for() — deterministic exponential backoff + per-tile stagger
# (PR #192 cold-start round 3 hardening: 6 tiles retrying in perfect
# lockstep on a whole-response Pending is a real request-pulse risk even
# though it isn't what caused the starvation bug above).
# =============================================================================
func test_retry_delay_for_first_retry_is_the_initial_delay() -> void:
assert_float(AtlasWindowRequest._retry_delay_for(1, 0)).is_equal_approx(
AtlasWindowRequest.INITIAL_RETRY_DELAY, 0.0001
)
func test_retry_delay_for_doubles_each_retry_until_the_cap() -> void:
assert_float(AtlasWindowRequest._retry_delay_for(1, 0)).is_equal_approx(0.5, 0.0001)
assert_float(AtlasWindowRequest._retry_delay_for(2, 0)).is_equal_approx(1.0, 0.0001)
assert_float(AtlasWindowRequest._retry_delay_for(3, 0)).is_equal_approx(2.0, 0.0001)
assert_float(AtlasWindowRequest._retry_delay_for(4, 0)).is_equal_approx(4.0, 0.0001)
# Retry 5 would double past MAX_RETRY_DELAY (8.0) — must clamp, not keep growing.
assert_float(AtlasWindowRequest._retry_delay_for(5, 0)).is_equal_approx(
AtlasWindowRequest.MAX_RETRY_DELAY, 0.0001
)
assert_float(AtlasWindowRequest._retry_delay_for(20, 0)).is_equal_approx(
AtlasWindowRequest.MAX_RETRY_DELAY, 0.0001
)
## The deterministic stagger: tile index i's delay is offset by
## STAGGER_STEP*i on top of the same backoff schedule — directly assertable,
## not a randomized jitter a test would have to tolerance-check.
func test_retry_delay_for_staggers_deterministically_by_tile_index() -> void:
var base: float = AtlasWindowRequest._retry_delay_for(1, 0)
for i in range(6):
var expected: float = base + AtlasWindowRequest.STAGGER_STEP * float(i)
assert_float(AtlasWindowRequest._retry_delay_for(1, i)).override_failure_message(
"tile index %d's first-retry delay must be exactly base + STAGGER_STEP*%d" % [i, i]
).is_equal_approx(expected, 0.0001)
## Six tiles that all went pending in the same frame must NOT all retry at
## the exact same instant — the anti-storm property this hardening exists
## for, pinned directly: every tile's delay for the SAME retry count must be
## strictly increasing with its stagger index.
func test_retry_delay_for_six_tiles_never_collide_on_the_same_retry() -> void:
var delays: Array = []
for i in range(6):
delays.append(AtlasWindowRequest._retry_delay_for(1, i))
for i in range(1, delays.size()):
assert_float(delays[i]).override_failure_message(
"tile %d's delay must be strictly greater than tile %d's — a storm"
+ " pulse means two tiles retrying at the same instant" % [i, i - 1]
).is_greater(delays[i - 1])