feat(client): T-1153 + T-1152 client half — continuous cursor-anchored zoom ladder, Region-rung orbital entry, click-through retired

The atlas 'regional' screen now opens the LADDER at the canonical orbital
frame (Region granularity, whole body fitted and centered) and wheel zoom
descends continuously — cursor-anchored, unclamped across rungs, with
progressive refinement (held composite keeps drawing, finer rung swaps in
place on arrival; no blank frame, no mode flip). Full-zoom-out resets to
the canonical planetary frame per Jeroen's HARD condition
(is_fully_zoomed_out = extent >= body circumference, not a zoom-value
heuristic). The district_screen nav hop is deleted — D-013 restored:
descent is a zoom gesture, not a nav push. AtlasViewer's heightmap-texture
path is unreachable from nav (code intact; overlay surface deferred, see
report/tickets).

Rung selection: design doc §5's literal formula has NO legal District band
at any real viewport (visual-tolerance band and n=64 coverage ceiling
never overlap — pinned by executable boundary tests at 1600x900);
select_rung() splits it into a coverage ceiling (decides Region) then the
2x visual tolerance (District vs Quarter), documented at the function.
In practice the ladder steps Region -> Quarter directly.

Wire: window_granularity_v2 encoded (omitted at District for byte-compat),
granularity_v2 echoed value keyed + staleness-guarded end to end; Region
clamp mirror replicates the server's bounded halving loop (no closed
form). MIN/MAX_ZOOM widened to [0.0005, 64] — the old 0.5 floor would
have clamped a real body's canonical fit zoom, violating the reset
condition.

Real pre-existing bug fixed in atlas_window_overlay.gd: the draw path used
echoed n as both cell-grid dimension and district extent — only
coincidentally correct at District granularity; Quarter/Region would have
read wrong array offsets. cell_grid_side_for_window() now mirrors the
server's WindowGranularity::cell_grid_side.

Tests: +26 pure-function geometry tests, new 30-test zoom-ladder suite,
extensions across the window cache/request/overlay/delivery suites.
Full suite 3518 green; cold-parse clean.
This commit is contained in:
2026-07-22 11:41:37 +02:00
parent d356b09926
commit ce90d69ae8
19 changed files with 2144 additions and 336 deletions
+60
View File
@@ -240,6 +240,36 @@ func test_encode_atlas_layer_request_carries_granularity_and_min_wl() -> void:
assert_that(decoded.value.get("window_min_wl_m")).is_equal(512)
## T-1152/T-1153: window_granularity_v2 is OMITTED (not sent as "") when at
## its empty-string default — same byte-compatibility contract as
## window_granularity/window_min_wl_m's own default-omission.
func test_encode_atlas_layer_request_omits_granularity_v2_by_default() -> void:
var bytes := Protocol.encode_atlas_layer_request(
"GJ1c", "Topography", Vector2i(140, 260), 32
)
var decoded = Messagepack.decode(bytes)
assert_bool(decoded.value.has("window_granularity_v2")).is_false()
## T-1152/T-1153: a Region-rung request carries window_granularity_v2 as the
## bare string "Region" — the ONLY way to express the coarser-than-district
## rung (WindowGranularity's Rust doc: "the ONLY way to actually request
## Region is window_granularity_v2 = Some(WindowGranularity::Region)"), a
## plain rmp_serde variant-name encoding matching RoadNodeKind's existing
## wire precedent, NOT an integer discriminant.
func test_encode_atlas_layer_request_carries_granularity_v2_region() -> void:
var bytes := Protocol.encode_atlas_layer_request(
"GJ1c", "Topography", Vector2i(0, 0), 6400, 0, 0, "Region"
)
var decoded = Messagepack.decode(bytes)
assert_that(decoded.value.get("window_granularity_v2")).is_equal("Region")
# The legacy window_granularity field is independently omittable — a
# Region request sends ONLY the v2 tag, never a legacy value pretending
# to mean something for Region (WINDOW_GRANULARITY_REGION_KEY is a
# key-space tag the SERVER echoes, never a legal wire INPUT).
assert_bool(decoded.value.has("window_granularity")).is_false()
## §2: district_window is a distinct payload (echoes center/n for the
## client's staleness guard) but the codec passthrough is the same shape as
## every sibling layer — raw.get(), no reshaping. Field types follow the
@@ -265,6 +295,36 @@ func test_atlas_response_district_window_passthrough() -> void:
assert_that((decoded as Dictionary).get("district_window")).is_equal(window)
## T-1152/T-1153, design doc §6 encoding-continuity acceptance: a Region-rung
## response passes through the EXACT SAME codec path as District/Quarter —
## granularity_v2 is just another field in the same dict, no special-cased
## decode branch for the coarser rung. This is the direct regression test for
## "one colorizer family, no per-rung palettes": the wire contract itself
## draws no distinction, so nothing downstream (the overlay's
## cell_grid_side_for_window()/_cell_color()) needs a rung-specific decode
## path either.
func test_atlas_response_district_window_passthrough_region_rung() -> void:
var window := {
"center": [0, 0],
"n": 6400,
"granularity": 4294967295, # WINDOW_GRANULARITY_REGION_KEY (u32::MAX) — key-space tag, not a real multiplier
"granularity_v2": "Region",
"min_wl_m": 0,
"morphology": PackedByteArray([8, 14, 0, 5]),
"elev_q": PackedByteArray([40, 62, 5, 88]),
"temp_dc": [120, 95, AtlasOverlayColors.REGION_TEMP_NONE_DC, 60],
"moisture_q": PackedByteArray([50, 30, 90, 20]),
"vegetation": PackedByteArray([2, 1, 6, 3]),
"glaciation": PackedByteArray([0, 0, 1, 2]),
}
var raw := {"body_id": "GJ1c", "status": "Ready", "district_window": window}
var decoded: Variant = Protocol.atlas_response_from_raw(raw)
assert_that(decoded).is_not_null()
var district_window: Dictionary = (decoded as Dictionary).get("district_window")
assert_that(district_window).is_equal(window)
assert_str(str(district_window.get("granularity_v2"))).is_equal("Region")
## A body with no window requested (or not yet derived — §1's background-queue
## serving model: the completion may not have landed yet) must decode with
## district_window absent -> null, same "layer hasn't produced yet" contract