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:
@@ -40,6 +40,22 @@ class_name AtlasMapProtocol
|
||||
## it possible to ask, byte-compatible with every existing caller that
|
||||
## doesn't pass them.
|
||||
##
|
||||
## `window_granularity_v2` (T-1152, R5 redesign — see
|
||||
## server/src/atlas/layer_proxy.rs's `WindowGranularity` doc): the ONLY way to
|
||||
## express a coarser-than-district rung (`"Region"`) the legacy `u32` field
|
||||
## cannot encode. A plain STRING variant tag ("Quarter" | "District" |
|
||||
## "Region"), matching `RoadNodeKind`'s existing wire precedent on this same
|
||||
## carrier (a bare `#[derive(Serialize, Deserialize)]` enum with no
|
||||
## `#[serde(rename_all)]` — rmp_serde encodes the Rust variant NAME verbatim,
|
||||
## not an integer discriminant). OMITTED (not sent as "") when
|
||||
## `window_granularity_v2` is the empty string — `#[serde(default)]` on the
|
||||
## Rust side decodes absence as `None`, falling back to the legacy `u32`
|
||||
## field's `resolve_window_granularity_v2()` precedence rule (that field wins
|
||||
## over the legacy one whenever present — see that Rust doc for the full
|
||||
## precedence contract). Every pre-T-1152 caller (and every T-1150 caller that
|
||||
## only ever sends `window_granularity`) omits this field entirely and stays
|
||||
## byte-compatible.
|
||||
##
|
||||
## **Quantization split (PR #191 review, Hoshe 1 / Tyre C3):** `window_min_wl_m`
|
||||
## is sent HERE as a raw, unquantized value — this codec does NOT snap it to
|
||||
## the design doc §5 fixed band set. The SERVER is the one place quantization
|
||||
@@ -56,7 +72,8 @@ static func encode_atlas_layer_request(
|
||||
window_center: Variant = null,
|
||||
window_n: int = 0,
|
||||
window_granularity: int = 0,
|
||||
window_min_wl_m: int = 0
|
||||
window_min_wl_m: int = 0,
|
||||
window_granularity_v2: String = ""
|
||||
) -> PackedByteArray:
|
||||
var msg := {"body_id": body_id, "up_to": up_to}
|
||||
if window_center != null:
|
||||
@@ -67,6 +84,8 @@ static func encode_atlas_layer_request(
|
||||
msg["window_granularity"] = window_granularity
|
||||
if window_min_wl_m != 0:
|
||||
msg["window_min_wl_m"] = window_min_wl_m
|
||||
if not window_granularity_v2.is_empty():
|
||||
msg["window_granularity_v2"] = window_granularity_v2
|
||||
var result = mp.encode(msg)
|
||||
if result.status != null:
|
||||
push_error("Protocol: encode_atlas_layer_request failed: %s" % result.status)
|
||||
@@ -91,7 +110,9 @@ static func encode_atlas_layer_request(
|
||||
## wire decodes to GDScript `null` exactly like every other Option field here.
|
||||
## The response's `center`/`n` echo (inside the layer dict itself) is the
|
||||
## client's race-condition/staleness guard (§2) — read by the window cache,
|
||||
## not unwrapped here.
|
||||
## not unwrapped here. `granularity_v2` (T-1152) rides inside the same dict,
|
||||
## a bare string variant tag ("Quarter"/"District"/"Region") — no separate
|
||||
## top-level unwrap needed, it passes through with everything else.
|
||||
## Key names "road_graph"/"settlements"/"region_grid"/"quarter_footprints"/
|
||||
## "district_window" are the CONFIRMED wire contract — identical to
|
||||
## server/src/atlas/layer_proxy.rs AtlasLayerResponse's field names
|
||||
|
||||
@@ -782,16 +782,27 @@ static func encode_request_bookmark_catalog() -> PackedByteArray:
|
||||
## Omitted callers (every whole-body-layer call site predating T-1138) are
|
||||
## byte-unchanged. window_granularity/window_min_wl_m (T-1150): same
|
||||
## byte-compatibility contract, see atlas_map_protocol.gd.
|
||||
## window_granularity_v2 (T-1152): the R5-redesigned string-tag granularity
|
||||
## ("Quarter"/"District"/"Region") — the only way to request the Region rung.
|
||||
## Omitted (empty string) by every caller that doesn't pass it.
|
||||
static func encode_atlas_layer_request(
|
||||
body_id: String,
|
||||
up_to: String = "Topography",
|
||||
window_center: Variant = null,
|
||||
window_n: int = 0,
|
||||
window_granularity: int = 0,
|
||||
window_min_wl_m: int = 0
|
||||
window_min_wl_m: int = 0,
|
||||
window_granularity_v2: String = ""
|
||||
) -> PackedByteArray:
|
||||
return _amp().encode_atlas_layer_request(
|
||||
_mp(), body_id, up_to, window_center, window_n, window_granularity, window_min_wl_m
|
||||
_mp(),
|
||||
body_id,
|
||||
up_to,
|
||||
window_center,
|
||||
window_n,
|
||||
window_granularity,
|
||||
window_min_wl_m,
|
||||
window_granularity_v2
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user