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:
@@ -16,17 +16,23 @@ const AtlasWindowRequest := preload("res://ui/implant/apps/atlas/atlas_window_re
|
||||
|
||||
|
||||
## Build a hand-authored DistrictWindowLayer dict, granularity-aware
|
||||
## (T-1150) — mirrors test_atlas_window_viewer.gd's own _mock_window(), with
|
||||
## granularity/min_wl_m added as optional params so callers can build both
|
||||
## rungs' echo shapes with one helper.
|
||||
## (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
|
||||
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],
|
||||
@@ -70,6 +76,54 @@ func test_on_response_with_mismatched_granularity_is_dropped_as_stale() -> void:
|
||||
).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.
|
||||
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")
|
||||
req.on_response(_mock_response("GJ380c", region_window))
|
||||
assert_bool(req.is_pending()).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()
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# (b) old-server-shape response (no granularity/min_wl_m keys) -> defaults
|
||||
# =============================================================================
|
||||
@@ -175,3 +229,87 @@ func test_oversized_n_request_stores_clamped_n_and_accepts_matching_echo() -> vo
|
||||
+ "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 (no closed form, per Dudley's own
|
||||
# doc — "replicate the loop exactly").
|
||||
# =============================================================================
|
||||
|
||||
|
||||
## District/Quarter through the v2 mirror must be BYTE-IDENTICAL to the
|
||||
## legacy mirror — the server's own
|
||||
## `clamp_window_n_v2_matches_legacy_for_finer_than_district_rungs`
|
||||
## 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
|
||||
## `region_per_axis_cap_lands_exactly_on_wire_cap_when_uncontested`-shaped
|
||||
## boundary (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)
|
||||
|
||||
|
||||
## A REGION request whose derived grid would exceed WIRE_CAP_CELLS (n set so
|
||||
## cell_grid_side(n) rounds to 65, just over the 64-per-axis wire-size
|
||||
## ceiling) must actually HALVE — this is the case the boundary test above
|
||||
## deliberately sits just below, so this one confirms the loop body actually
|
||||
## fires, not just that its guard conditions are correct at the edges.
|
||||
func test_clamp_window_n_mirror_v2_region_halves_when_over_wire_cap() -> void:
|
||||
# n=6,450 -> cell_grid_side = round(64.5) = 65 (round-half-away-from-zero,
|
||||
# matching Rust's f64::round() and GDScript's roundi() for non-negative
|
||||
# inputs) -> 65² = 4,225 > WIRE_CAP_CELLS (4,096) -> must halve at least once.
|
||||
var n: int = AtlasWindowRequest._clamp_window_n_mirror_v2(
|
||||
6450, AtlasWindowRequest.GRANULARITY_V2_REGION
|
||||
)
|
||||
assert_int(n).override_failure_message(
|
||||
"a Region request whose grid exceeds WIRE_CAP_CELLS must be halved down, not left at 6,450"
|
||||
).is_less(6450)
|
||||
var side: int = AtlasWindowRequest._cell_grid_side_region_mirror(n)
|
||||
assert_int(side * side).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)
|
||||
|
||||
Reference in New Issue
Block a user