Files
settled-reach/client/tests/test_atlas_descend_entry.gd
T
jpmschweitzerandClaude Fable 5 9e1e6db614 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>
2026-07-21 13:32:06 +02:00

163 lines
7.1 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## T-1138 (D-226 T-1124 amendment §5 entry revision): tests for the planetary
## AtlasViewer's click-through descent — the fixed view (no drag-pan/wheel-
## zoom), the pixel-to-DistrictPos inverse mapping (atlas_descend_geometry.gd),
## and the city-click-wins disambiguation rule.
class_name TestAtlasDescendEntry
extends GdUnitTestSuite
const AtlasDescendGeometry := preload("res://ui/implant/apps/atlas/atlas_descend_geometry.gd")
# =============================================================================
# atlas_descend_geometry.gd — pure geometry (no scene tree needed)
# =============================================================================
## The default n=32 window's real footprint is 32 * 2.048 km = 65.536 km, so
## the label reads "~66 x 66 km" (rounded) — pins the number the amendment's
## "honest labeling" resolution depends on.
func test_reticle_label_extent_matches_district_window_default_n() -> void:
var label: Dictionary = AtlasDescendGeometry.reticle_label(Vector2(100.0, 100.0))
var expected_km: float = 32.0 * 2048.0 / 1000.0
assert_str(label["text"]).is_equal("~%.0f × %.0f km" % [expected_km, expected_km])
func test_reticle_label_position_offsets_from_center() -> void:
var center := Vector2(50.0, 60.0)
var label: Dictionary = AtlasDescendGeometry.reticle_label(center)
var pos: Vector2 = label["position"]
assert_float(pos.x).is_greater(center.x) # offset to the right, per §5's resolution
## Eight segments (four L-shaped bracket corners, two arms each) — every
## segment's "from" endpoint is exactly one of the four corner anchors, and no
## segment has zero length (a degenerate reticle would be invisible).
func test_reticle_segments_has_eight_nonzero_segments() -> void:
var segments: Array = AtlasDescendGeometry.reticle_segments(Vector2(200.0, 150.0))
assert_int(segments.size()).is_equal(8)
for seg: Array in segments:
assert_that(seg[0]).override_failure_message(
"a reticle segment must not be zero-length"
).is_not_equal(seg[1])
## The reticle's overall bounding box is centered on `center` and sized by
## DESCEND_RETICLE_SIZE — a regression guard against an off-center or
## mis-scaled bracket.
func test_reticle_segments_centered_on_input_point() -> void:
var center := Vector2(300.0, 300.0)
var segments: Array = AtlasDescendGeometry.reticle_segments(center)
var min_pt := Vector2(INF, INF)
var max_pt := Vector2(-INF, -INF)
for seg: Array in segments:
for p: Vector2 in seg:
min_pt.x = minf(min_pt.x, p.x)
min_pt.y = minf(min_pt.y, p.y)
max_pt.x = maxf(max_pt.x, p.x)
max_pt.y = maxf(max_pt.y, p.y)
var bbox_center: Vector2 = (min_pt + max_pt) * 0.5
assert_float(bbox_center.x).is_equal_approx(center.x, 0.01)
assert_float(bbox_center.y).is_equal_approx(center.y, 0.01)
# =============================================================================
# district_pos_at — pixel-to-DistrictPos inverse mapping
# =============================================================================
## No radius (tiny test body fallback, matches the server's own derive_district
## fallback): the district grid IS the heightmap grid 1:1, so a canvas point
## maps to the nearest integer district coordinate directly.
func test_district_pos_at_no_radius_is_1to1_pixel_mapping() -> void:
var pos: Vector2i = AtlasDescendGeometry.district_pos_at(
Vector2(12.4, 7.6), 100.0, 100.0, 0.0
)
assert_that(pos).is_equal(Vector2i(12, 8))
func test_district_pos_at_zero_texture_size_is_safe() -> void:
var pos: Vector2i = AtlasDescendGeometry.district_pos_at(Vector2(10.0, 10.0), 0.0, 0.0, 6371.0)
assert_that(pos).is_equal(Vector2i.ZERO)
## The equatorial center of the texture (px = tex_w/2) is district column
## district_cols/2 on a body with a radius (equator wraps at district (0,0)
## per the server's own doc: "district (0,0) sits at lon 0 / the equator").
## The vertical center (py = tex_h/2) is the equator row (row 0).
func test_district_pos_at_center_of_texture_is_near_equator_row_zero() -> void:
var radius_km := 6371.0
var tex_w := 1024.0
var tex_h := 512.0
var pos: Vector2i = AtlasDescendGeometry.district_pos_at(
Vector2(0.0, tex_h * 0.5), tex_w, tex_h, radius_km
)
assert_int(pos.y).override_failure_message(
"the vertical texture center must map to row 0 (the equator)"
).is_equal(0)
## Panning across the texture's full width sweeps through the FULL district
## column range (not clamped to a tiny sub-range) — a coarse sanity check that
## district_cols is actually being derived from the body's circumference, not
## left at some degenerate default.
func test_district_pos_at_sweeps_full_column_range_across_texture_width() -> void:
var radius_km := 6371.0
var tex_w := 1024.0
var tex_h := 512.0
var left: Vector2i = AtlasDescendGeometry.district_pos_at(
Vector2(0.0, tex_h * 0.5), tex_w, tex_h, radius_km
)
var right: Vector2i = AtlasDescendGeometry.district_pos_at(
Vector2(tex_w - 1.0, tex_h * 0.5), tex_w, tex_h, radius_km
)
# An Earth-radius body has thousands of equatorial districts (circumference
# ~40,075 km / 2.048 km per district ≈ 19,568) — near-full-width should
# sweep a large fraction of that, not a handful of columns.
assert_int(absi(right.x - left.x)).is_greater(1000)
# =============================================================================
# AtlasViewer — fixed view (no drag-pan/wheel-zoom) + click-through descent
# =============================================================================
## T-1120 capture API (set_view/get_view_offset/get_view_zoom) must survive
## the removal of user pan/zoom — this is the ticket's own explicit note, and
## the visual-golden harness depends on it.
func test_set_view_still_works_after_pan_zoom_removal() -> void:
var v: AtlasViewer = auto_free(AtlasViewer.new())
add_child(v)
v.set_view(3.0, Vector2(50.0, -20.0))
assert_that(v.get_view_zoom()).is_equal_approx(3.0, 0.001)
assert_that(v.get_view_offset()).is_equal(Vector2(50.0, -20.0))
## Clicking (with no heightmap loaded — the guard AtlasViewer's _gui_input
## checks first) must not emit a descend request — there is nothing to
## descend into yet.
func test_no_descend_signal_without_a_loaded_heightmap() -> void:
var v: AtlasViewer = auto_free(AtlasViewer.new())
add_child(v)
var received: Array = []
v.district_descend_requested.connect(func(c: Vector2i) -> void: received.append(c))
# _heightmap_texture stays null (no show_body() call) — _gui_input's
# early-return guard should prevent any click handling at all.
var mb := InputEventMouseButton.new()
mb.button_index = MOUSE_BUTTON_LEFT
mb.pressed = true
mb.position = Vector2(100.0, 100.0)
v._gui_input(mb)
assert_int(received.size()).is_equal(0)
## RegionalScreen forwards AtlasViewer's district_descend_requested verbatim —
## the nav-stack wiring atlas_app.gd depends on.
func test_regional_screen_forwards_district_descend_requested() -> void:
var screen: RegionalScreen = auto_free(RegionalScreen.new())
add_child(screen)
var received: Array = []
screen.district_descend_requested.connect(func(c: Vector2i) -> void: received.append(c))
screen._viewer.district_descend_requested.emit(Vector2i(5, 7))
assert_int(received.size()).is_equal(1)
assert_that(received[0]).is_equal(Vector2i(5, 7))