## 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 TRUE equator pixel row is `(tex_h - 1) * 0.5`, NOT `tex_h * 0.5` (PR ## #187 review, Hoshe): the server's forward map (district_profile.rs:1436) ## divides by `ta.h.saturating_sub(1)`, so row 0 and row (h-1) are the real ## clamped pole endpoints and the midpoint between them is `(h-1)/2`. This ## test originally sampled `tex_h * 0.5`, which is NOT that midpoint for any ## finite tex_h — it happened to still round to district row 0 under BOTH the ## pre-fix buggy formula and the fix, at this specific radius/tex_h pair, ## which is exactly why this equator-only sample masked the ÷tex_h bug ## instead of catching it (see the off-equator round-trip tests below for the ## real regression coverage). Corrected here to the genuine equator pixel so ## this test asserts something true about the fixed formula, not a ## coincidence of the old one. 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 true_equator_py: float = (tex_h - 1.0) * 0.5 var pos: Vector2i = AtlasDescendGeometry.district_pos_at( Vector2(0.0, true_equator_py), tex_w, tex_h, radius_km ) assert_int(pos.y).override_failure_message( "the true equator pixel ((tex_h-1)/2) must map to row 0" ).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) # ============================================================================= # Off-equator round-trip (PR #187 review, Hoshe — BLOCKING, live-repro'd bug): # district_pos_at's row inverse divided by tex_h instead of (tex_h - 1), # matching the WRONG symmetry with the column inverse (which correctly # divides by the plain tex_w, since longitude wraps and has no edge case). # The server's forward map (district_profile.rs:1436) uses # ta.h.saturating_sub(1) for the SAME reason the ground-truth inverse # (aliveness_probe.rs:511, `row / (ta_h - 1) - 0.5`) does: latitude CLAMPS at # the poles, so row 0 / row (h-1) are real endpoints the division must land # on exactly. This class of bug — a client-side twin of a server coordinate # mapping silently drifting out of sync — is exactly what Tyre's C2 review # criterion requires a round-trip drift guard for; that is what every test in # this section is. The suite previously missed it because the only # real-radius latitude sample was py = tex_h*0.5 (test above), the ONE point ## where the buggy (÷tex_h) and correct (÷(tex_h-1)) formulas round to the # same integer district row — never exercising the asymmetry the fix targets. # ============================================================================= ## Forward-maps a district row to its heightmap pixel row using the server's ## OWN formula, transcribed here (district_profile.rs:1436): ## let lat_frac = (dy * DISTRICT_M / meridian_m).clamp(-0.5, 0.5); ## let py = (0.5 + lat_frac) * ta.h.saturating_sub(1); ## `dy` is a DistrictPos.y component (not a pixel) — the caller supplies the ## same `district_rows_half`-scaled value district_pos_at()'s inverse would ## need to recover, so this function and district_pos_at() are meant to be ## exact inverses of one another for any row within the clamped range. static func _server_forward_map_row(district_row: int, tex_h: float, radius_km: float) -> float: var meridian_m: float = PI * radius_km * 1000.0 var district_m: float = AtlasDescendGeometry.DISTRICT_M var lat_frac: float = clampf((float(district_row) * district_m) / meridian_m, -0.5, 0.5) return (0.5 + lat_frac) * (tex_h - 1.0) ## The actual regression: forward-map three off-equator district rows (one ## per hemisphere, plus a near-pole extreme) to pixel rows via the server's ## transcribed formula, then assert district_pos_at() recovers each EXACT ## row from that pixel. Fails outright under the pre-fix ÷tex_h formula for ## every row here except (coincidentally) very near the equator. func test_district_pos_at_round_trips_off_equator_rows_against_server_forward_map() -> void: var radius_km := 6238.4 # GJ380c (server/data/systems.db) — a real body, not a round number var tex_w := 1024.0 var tex_h := 512.0 var col := 400 # arbitrary — this test is about the row axis only # One row per hemisphere (moderate latitude) + one extreme near-pole row. # meridian_m / DISTRICT_M ≈ 9,569 for this radius, so district_rows_half # ≈ 4,785 — these rows sit well inside that range without saturating the # clamp (except the "near pole" case, deliberately close to the edge). var test_rows: Array = [-1200, 900, -4700] for district_row: int in test_rows: var forward_py: float = _server_forward_map_row(district_row, tex_h, radius_km) var recovered: Vector2i = AtlasDescendGeometry.district_pos_at( Vector2(float(col), forward_py), tex_w, tex_h, radius_km ) assert_int(recovered.y).override_failure_message( ( "row round-trip failed: district_row=%d -> forward pixel py=%.4f -> " + "district_pos_at recovered row=%d (drift=%d)" ) % [district_row, forward_py, recovered.y, recovered.y - district_row] ).is_equal(district_row) ## Same round-trip, restated as an explicit non-equator drift guard: the ## pre-fix bug produced a WRONG but still-integer row (Hoshe's live repro: ## forward row 50, buggy inverse returned 45 — a 5-district, ~10.2 km drift) ## — a coarse "is it roughly right" tolerance would have passed that. This ## asserts EXACT equality specifically at a moderate off-equator row, not an ## approximate one, so a reintroduced ÷tex_h regression fails loudly again. func test_district_pos_at_off_equator_row_is_not_off_by_a_few_districts() -> void: var radius_km := 6238.4 var tex_w := 1024.0 var tex_h := 512.0 var district_row := 2500 var forward_py: float = _server_forward_map_row(district_row, tex_h, radius_km) var recovered: Vector2i = AtlasDescendGeometry.district_pos_at( Vector2(512.0, forward_py), tex_w, tex_h, radius_km ) assert_int(recovered.y).is_equal(district_row) # ============================================================================= # 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)) # ============================================================================= # PR #187 review (Araminta — broken promise): the descend reticle must be # hidden while hovering a city marker — _hovered_city already flares white as # the "click here for city data" signal, and _try_click_city wins the click # over descent, so drawing the reticle at the same time promised a descent # the click would never perform. State-level tests (per the review's own # "state-level is fine" allowance): assert _should_draw_descend_reticle()'s # condition directly against _hover_active/_hovered_city/_heightmap_texture, # rather than a pixel-diff on the actual draw call. # ============================================================================= static func _one_pixel_texture() -> ImageTexture: var img := Image.create(1, 1, false, Image.FORMAT_RGB8) return ImageTexture.create_from_image(img) ## Hovering EMPTY map (no city under the cursor) -> reticle SHOWS. func test_descend_reticle_shows_when_hovering_empty_map() -> void: var v: AtlasViewer = auto_free(AtlasViewer.new()) add_child(v) v._heightmap_texture = _one_pixel_texture() v._hover_active = true v._hovered_city = {} assert_bool(v._should_draw_descend_reticle()).override_failure_message( "reticle must show when hovering the open map (no city under cursor)" ).is_true() ## Hovering a CITY marker -> reticle HIDES, even though _hover_active is true ## (this is the exact bug: pre-fix, _hovered_city was never consulted here). func test_descend_reticle_hides_when_hovering_a_city() -> void: var v: AtlasViewer = auto_free(AtlasViewer.new()) add_child(v) v._heightmap_texture = _one_pixel_texture() v._hover_active = true v._hovered_city = {"name": "Ridgeback", "pos": [10, 20]} assert_bool(v._should_draw_descend_reticle()).override_failure_message( "reticle must hide while hovering a city — the click would open city" + " data (_try_click_city wins), not descend" ).is_false() ## Not hovering at all (_hover_active false) -> reticle HIDES regardless of ## _hovered_city — the pre-existing base condition, guarded here so the city ## fix can't accidentally invert it. func test_descend_reticle_hides_when_not_hovering() -> void: var v: AtlasViewer = auto_free(AtlasViewer.new()) add_child(v) v._heightmap_texture = _one_pixel_texture() v._hover_active = false v._hovered_city = {} assert_bool(v._should_draw_descend_reticle()).is_false()