fix(client): T-1153 — v2 granularity is authoritative in the staleness guard; legacy compared only when v2 absent

Second live-round blocker: the T-1150 legacy granularity comparison
stayed armed alongside the v2 check, and the server ALWAYS sends the
u32::MAX Region sentinel in the legacy slot — which can never equal the
client's pinned legacy value, so every Region response was stale-dropped
after the v2 check passed. _echoed_granularity_matches() now branches on
PRESENCE of granularity_v2: present -> v2 is the only comparison; absent
(old server) -> legacy fallback.

Mock-fleet audit while fixing: three tests had responses diverging from
the real wire — the oversized-orbital round-trip omitted the legacy
sentinel (passed for the wrong reason), the Region-accept test used
legacy=1, and the mismatch-drop test left v2 at a masking default that
would have inverted under the new rule. All wire-accurate now with a
named SERVER_LEGACY_GRANULARITY_REGION_SENTINEL const; +2 tests (legacy-
only old-server acceptance; v2-wins-regardless-of-legacy precedence
proof). All three verified to fail against the reverted fix. Full suite
3524/3524.
This commit is contained in:
2026-07-22 12:07:33 +02:00
parent 1c87b6a712
commit 11f7927f24
3 changed files with 198 additions and 35 deletions
+108 -3
View File
@@ -14,6 +14,17 @@ extends GdUnitTestSuite
# 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
@@ -63,12 +74,24 @@ func _make_request() -> Variant:
## 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)
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(
@@ -106,14 +129,30 @@ func test_on_response_with_mismatched_granularity_v2_is_dropped_as_stale() -> vo
## 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, 1, 0, "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()).is_false()
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))
@@ -124,6 +163,30 @@ func test_on_response_matching_granularity_v2_region_is_accepted_and_cached() ->
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
# =============================================================================
@@ -170,6 +233,48 @@ func test_on_response_missing_granularity_and_min_wl_defaults_and_is_accepted()
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
# =============================================================================
+44 -16
View File
@@ -14,6 +14,20 @@ extends GdUnitTestSuite
const AtlasWindowRequest := preload("res://ui/implant/apps/atlas/atlas_window_request.gd")
const AtlasDescendGeometry := preload("res://ui/implant/apps/atlas/atlas_descend_geometry.gd")
## Dudley's WINDOW_GRANULARITY_REGION_KEY (server/src/atlas/layer_proxy.rs) —
## `u32::MAX`, a RESERVED KEY-SPACE TAG the 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 — that would silently un-repro the live-round bug
## this constant exists to guard against (a real server's actual wire byte,
## not a convenient test value). See _echoed_granularity_matches()'s own doc
## (atlas_window_request.gd) for why this value can NEVER equal a client's
## stored `_granularity` (which stays pinned at DISTRICT_GRANULARITY=1 for
## every rung a T-1152-aware client requests) — that mismatch is exactly
## what silently dropped every Region response before the v2-authoritative
## fix.
const SERVER_LEGACY_GRANULARITY_REGION_SENTINEL: int = 4294967295
## Build a hand-authored DistrictWindowLayer dict (n=2 by default) — mirrors
## test_atlas_window_viewer.gd's own _mock_window().
@@ -92,15 +106,26 @@ func test_enter_orbital_n_is_the_clamped_value_not_raw_circumference() -> void:
).is_greater(expected_clamped)
## **The live-round regression, end to end:** enter_orbital() on a
## real-sized body (GJ380c/Lendel, radius 6238.4 km, raw cols far past the
## Region clamp ceiling) followed by a server response echoing the CLAMPED
## n + "Region" granularity must be ACCEPTED and become the held window — not
## silently dropped as stale forever (the exact live bug: `wv._held_n =
## 19139` vs. echoed `6400`, blank ladder on every real-sized body). This is
## the round-trip the existing suite never exercised — every prior
## enter_orbital() test asserted on request-side state only, never delivered
## a response.
## **The live-round regression, end to end (fix #1: the n-clamp mirror one
## layer up):** enter_orbital() on a real-sized body (GJ380c/Lendel, radius
## 6238.4 km, raw cols far past the Region clamp ceiling) followed by a
## server response echoing the CLAMPED n + "Region" granularity must be
## ACCEPTED and become the held window — not silently dropped as stale
## forever (the exact live bug: `wv._held_n = 19139` vs. echoed `6400`,
## blank ladder on every real-sized body). This is the round-trip the
## existing suite never exercised — every prior enter_orbital() test
## asserted on request-side state only, never delivered a response.
##
## **WIRE-ACCURATE response shape (fix #2, second live-round finding):** the
## response dict below carries `"granularity":
## SERVER_LEGACY_GRANULARITY_REGION_SENTINEL` explicitly — the ACTUAL byte a
## real server sends, not the field's absence. The first version of this
## test omitted the legacy key entirely, which let `w.get("granularity",
## DEFAULT)` silently default to `1` (matching `_granularity`'s own pinned
## value) — an ACCIDENTAL pass that never exercised the real sentinel
## mismatch, exactly the class of gap the live round exists to catch. This
## version fails without the v2-authoritative-when-present fix in
## `_echoed_granularity_matches()`.
func test_enter_orbital_oversized_body_accepts_the_clamped_region_response() -> void:
var v: AtlasWindowViewer = auto_free(AtlasWindowViewer.new())
add_child(v)
@@ -116,12 +141,14 @@ func test_enter_orbital_oversized_body_accepts_the_clamped_region_response() ->
"no response delivered yet — must still be null"
).is_null()
# The server's real response: echoes the CLAMPED n, "Region" granularity,
# center (0,0) — exactly what handle_atlas_request/clamp_window_n_v2
# actually produces for an oversized orbital request.
# The server's REAL response: echoes the CLAMPED n, "Region" granularity_v2
# (String), center (0,0), AND the legacy sentinel in "granularity" — exactly
# what handle_atlas_request/clamp_window_n_v2 actually produces on the wire
# for an oversized orbital request (confirmed against Dudley's contract).
var region_window: Dictionary = {
"center": [0, 0],
"n": clamped_n,
"granularity": SERVER_LEGACY_GRANULARITY_REGION_SENTINEL,
"granularity_v2": "Region",
"morphology": PackedByteArray([8, 14, 0, 1]),
"elev_q": PackedByteArray([40, 90, 5, 60]),
@@ -133,10 +160,11 @@ func test_enter_orbital_oversized_body_accepts_the_clamped_region_response() ->
SimBridge.atlas_layers_received.emit(_mock_response("GJ380c", region_window))
var failure_msg: String = (
"a response echoing the server's own clamped n + Region granularity must be"
+ " ACCEPTED and become the held window — the live bug left this permanently"
+ " null (w_n=%d never matched a stale unclamped _held_n=%d) on every"
+ " real-sized body"
"a response echoing the server's own clamped n + Region granularity_v2 (with"
+ " the legacy sentinel u32::MAX in the old granularity slot) must be ACCEPTED"
+ " and become the held window — the live bug left this permanently null"
+ " (w_n=%d never matched a stale unclamped _held_n=%d, THEN the legacy"
+ " sentinel never matched the stored _granularity=1) on every real-sized body"
) % [clamped_n, raw_cols]
assert_that(v.get_district_window()).override_failure_message(failure_msg).is_equal(
region_window