feat(ui): T-1138 regional map screen — click-through descent, fixed planetary view, windowed composite (D-226 T-1124 SS5)
Entry per Jeroen's 2026-07-21 revision: planetary heightmap is now FIXED — all drag-pan/wheel-zoom input removed (set_view/get_view_* capture API survives for the golden harness); hover shows a not-to-scale bracket reticle with the real extent labeled (a true n=32 rectangle is sub-pixel on the planetary canvas — the honest representation given the morph transition is deferred), and a click that misses every city marker descends (city-click wins — one gesture, two contextual reads, no modifier). Descent pushes a new 'district' nav screen centered on the click point's DistrictPos via atlas_descend_geometry.district_pos_at (the pixel-to-district inverse of the server mapping, verified against scale.rs). Regional mode: atlas_window_viewer draws the composite (morphology x elev_q lightness base; temp/moisture/veg toggles — temp reuses the region-ramp colorizer exactly; Marine=6 transparent; glaciation always-on tint matching apply_ice_tint's REAL gate, None|Light no-op, over the amendment's looser prose — documented); pan-on-held-composite with edge-crossing refetch + border-fade during the queue-based derive wait; zoom never refetches. atlas_window_cache: LRU keyed (body_id, center, n), touch-on-read, evict-only, no freshness (D-227). atlas_window_request mirrors the generation-proxy pending-retry shape for None-until-derived. Codec: window params omitted from the wire when absent — byte-identical for every existing caller. Live-verified against the T-1137 server in-worktree: real round-trip on a GJ380c coastal district (6 fields x 1024 cells), echo staleness guard, genuine ~1.4s background-derive wait, pan-edge refetch to an adjacent window, cache-hit on re-descent with zero network. Full client suite 3194/3194; gdlint clean on all 18 files. Open follow-ups flagged in-code: header location label always falls back to coordinates (nearest-settlement needs a join the district window does not carry); atlas_standalone.gd's 'atlas_app.gd is never modified' doc line is now imprecise. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -16,10 +16,33 @@ class_name AtlasMapProtocol
|
||||
## A bare map {body_id, up_to} — NOT the Vec<PlayerInput> array — so the server's
|
||||
## frame demux routes it to the atlas proxy. up_to is a CascadeLayer unit variant
|
||||
## (bare string: "Heightmap" | "Topography").
|
||||
##
|
||||
## `window_center`/`window_n` (T-1138, D-226 T-1124 amendment §1): the windowed
|
||||
## district-resolution regional-map query. Both are OMITTED from the encoded
|
||||
## map (not sent as null) when window_center is null — this is what makes
|
||||
## `#[serde(default)]` on the Rust side decode absence as `window_center: None`
|
||||
## for every whole-body-only caller (request_atlas_layers()'s existing call
|
||||
## sites), byte-identical to pre-T-1138 wire traffic. window_center is a
|
||||
## DistrictPos, wire-encoded as the same [row, col] int-pair convention every
|
||||
## other position field on this channel already uses (road_graph node
|
||||
## positions, settlement positions, Layer-1 river-cell positions) — there is
|
||||
## no separate DistrictPos struct-map on the wire, just a 2-element array.
|
||||
## window_n is left unclamped here — §1 is explicit the server clamps to
|
||||
## [1, DISTRICT_WINDOW_MAX_N] itself and never trusts the wire value; the
|
||||
## client-side default/cap constants (DISTRICT_WINDOW_DEFAULT_N/MAX_N) live on
|
||||
## the regional-window viewer, not duplicated into the codec.
|
||||
static func encode_atlas_layer_request(
|
||||
mp, body_id: String, up_to: String = "Topography"
|
||||
mp,
|
||||
body_id: String,
|
||||
up_to: String = "Topography",
|
||||
window_center: Variant = null,
|
||||
window_n: int = 0
|
||||
) -> PackedByteArray:
|
||||
var msg := {"body_id": body_id, "up_to": up_to}
|
||||
if window_center != null:
|
||||
var center: Vector2i = window_center
|
||||
msg["window_center"] = [center.x, center.y]
|
||||
msg["window_n"] = window_n
|
||||
var result = mp.encode(msg)
|
||||
if result.status != null:
|
||||
push_error("Protocol: encode_atlas_layer_request failed: %s" % result.status)
|
||||
@@ -37,12 +60,22 @@ static func encode_atlas_layer_request(
|
||||
## the L4 quarter-footprint aggregates, same passthrough pattern —
|
||||
## QuarterFootprintLayer.entries is a BTreeMap<u64, QuarterFootprintEntry> on
|
||||
## the wire, decoding to a Dictionary with int keys (city_id), no reshaping.
|
||||
## Key names "road_graph"/"settlements"/"region_grid"/"quarter_footprints" are
|
||||
## the CONFIRMED wire contract — identical to server/src/atlas/layer_proxy.rs
|
||||
## AtlasLayerResponse's field names (region_grid pinned 2026-07-14,
|
||||
## quarter_footprints pinned 2026-07-18; round-tripped by
|
||||
## test_atlas_overlays.gd and the server's msgpack round-trip tests). This
|
||||
## remains the one client-side spot to touch if the contract ever changes.
|
||||
## district_window (T-1138, D-226 T-1124 amendment §2): the windowed
|
||||
## DistrictWindowLayer — a DISTINCT payload by design (keyed on the request's
|
||||
## (body, center, n), not the body alone), but the wire passthrough is the
|
||||
## same shape as every sibling: raw.get() with no reshaping, `None` on the
|
||||
## 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.
|
||||
## 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
|
||||
## (region_grid pinned 2026-07-14, quarter_footprints pinned 2026-07-18,
|
||||
## district_window per the D-226 T-1124 amendment §2 struct; round-tripped by
|
||||
## test_atlas_overlays.gd/test_atlas_data_delivery.gd and the server's msgpack
|
||||
## round-trip tests). This remains the one client-side spot to touch if the
|
||||
## contract ever changes.
|
||||
static func atlas_response_from_raw(raw: Variant) -> Variant:
|
||||
if not raw is Dictionary or not raw.has("status"):
|
||||
return null
|
||||
@@ -64,6 +97,7 @@ static func atlas_response_from_raw(raw: Variant) -> Variant:
|
||||
"settlements": raw.get("settlements"),
|
||||
"region_grid": raw.get("region_grid"),
|
||||
"quarter_footprints": raw.get("quarter_footprints"),
|
||||
"district_window": raw.get("district_window"),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -776,16 +776,24 @@ static func encode_request_bookmark_catalog() -> PackedByteArray:
|
||||
## above. Every function below is a thin delegate under its ORIGINAL public
|
||||
## name — external callers (sim_bridge.gd, test_atlas_overlays.gd,
|
||||
## test_atlas_data_delivery.gd) are unaffected by the move.
|
||||
## window_center/window_n (T-1138, D-226 T-1124 amendment §1): optional
|
||||
## windowed district-resolution regional-map query — see
|
||||
## atlas_map_protocol.gd's encode_atlas_layer_request doc for the wire shape.
|
||||
## Omitted callers (every whole-body-layer call site predating T-1138) are
|
||||
## byte-unchanged.
|
||||
static func encode_atlas_layer_request(
|
||||
body_id: String, up_to: String = "Topography"
|
||||
body_id: String,
|
||||
up_to: String = "Topography",
|
||||
window_center: Variant = null,
|
||||
window_n: int = 0
|
||||
) -> PackedByteArray:
|
||||
return _amp().encode_atlas_layer_request(_mp(), body_id, up_to)
|
||||
return _amp().encode_atlas_layer_request(_mp(), body_id, up_to, window_center, window_n)
|
||||
|
||||
|
||||
## Decode an AtlasLayerResponse (#969, D-225). Returns a Dictionary
|
||||
## {body_id, status, error, layer1, district_grid, road_graph, settlements,
|
||||
## region_grid, quarter_footprints}, or null if the bytes are not an atlas
|
||||
## response (no "status" key — e.g. an ObserverSnapshot).
|
||||
## region_grid, quarter_footprints, district_window}, or null if the bytes are
|
||||
## not an atlas response (no "status" key — e.g. an ObserverSnapshot).
|
||||
static func decode_atlas_layer_response(bytes: PackedByteArray) -> Variant:
|
||||
return atlas_response_from_raw(decode_raw(bytes))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user