fix(client): T-1153/T-1152 round 3 — unified rung coverage model, reselect clamp mirror, orbital tile-set mosaic
Bug A (zoom reselection dead): select_rung() redesigned from the two-gate split to a unified per-rung coverage-ceiling walk (MAX_COVERAGE_M, finest-first) — which also restores District as a reachable rung (33-131km extent band; the two-gate model had made it structurally unreachable). Plus the THIRD instance of the PR #191 C1 clamp-mirror bug class: _maybe_reselect_rung fed the old rung's _held_n raw into the new rung's request — the server clamped, the held value didn't, every cross-rung response stale-dropped. Re-clamped at the boundary; the leaving_tile_mode flag also fixes a stale request-granularity edge case. Bug B (orbital = whole body): genuine tile-set model. compute_tile_grid composes ceil-divided Region-tile centers (canonicalized column wrap, row clamp, Dictionary-set dedup; Lendel = 3x2 = 6 tiles); AtlasWindowTileSet owns one AtlasWindowRequest per tile (reusing all tested request/cache/debounce machinery), fans responses out, per-tile tile_ready — genuinely progressive; the overlay draws arrived tiles as a mosaic under the one view transform. enter_orbital branches to tile mode when >1 tile; single-window path unchanged below the coverage ceiling. E/W wrap at tile seams live-confirmed (col -6400 wraps to 12739). Pole- row dedup verified correct but flagged honestly as currently-unreachable defensive logic (no real radius triggers row collision). All fixes + fallout verified via revert/confirm-fails/restore. Targeted suites 256/256; gdlint clean; new 20-test tile-set suite.
This commit is contained in:
@@ -268,84 +268,75 @@ func test_pole_wall_rows_half_matches_canonicalize_rows_half() -> void:
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# T-1153: select_rung() — the §5 rung-selection rule, split into TWO tests
|
||||
# per select_rung()'s own doc: a COVERAGE ceiling decides Region (can a
|
||||
# District window even span this much world), and the `2x` visual-tolerance
|
||||
# rule (design doc §5: "select the coarsest rung whose cell spacing <=
|
||||
# 2*(E/C)") decides District vs. Quarter for whatever's under that ceiling.
|
||||
# T-1153: select_rung() — REDESIGNED (live round 3 finding) per-rung
|
||||
# single-window COVERAGE CEILING model, superseding the original
|
||||
# `2x`-visual-tolerance-only reading of design doc §5. Select the FINEST
|
||||
# rung whose own single-window coverage ceiling (MAX_COVERAGE_M) still
|
||||
# covers the current world extent: Quarter <= 32,768 m; District <=
|
||||
# 131,072 m; Region otherwise (including tiled coverage beyond its own
|
||||
# single-window ceiling, a viewer-level concern — see select_rung()'s own
|
||||
# doc for the full derivation and why this REPLACES the earlier two-gate
|
||||
# design entirely, not just patches it).
|
||||
# =============================================================================
|
||||
|
||||
|
||||
## A tight sample spacing (deep zoom-in — small E over a large C) must select
|
||||
## Quarter (512 m), the finest legal rung — 2*(E/C) is far below District's
|
||||
## 2,048 m spacing at this ratio.
|
||||
func test_select_rung_picks_quarter_at_a_tight_sample_spacing() -> void:
|
||||
# E=2000m over C=1000px -> sample spacing 2 m/px -> threshold 4 m. Even
|
||||
# Quarter (512 m) is coarser than the threshold, so select_rung() falls
|
||||
# through to the FINEST legal rung (its own documented fallback) rather
|
||||
# than returning something even finer that doesn't exist — Quarter.
|
||||
## Deep zoom-in (a tiny extent) selects Quarter — comfortably under its own
|
||||
## 32,768 m ceiling.
|
||||
func test_select_rung_picks_quarter_well_under_its_ceiling() -> void:
|
||||
var rung: String = AtlasWindowGeometry.select_rung(2000.0, 1000.0)
|
||||
assert_str(rung).is_equal("Quarter")
|
||||
|
||||
|
||||
## A sample spacing that satisfies BOTH District's own `2x` band AND the
|
||||
## coverage ceiling selects District — the coarsest rung whose spacing still
|
||||
## satisfies the fine-end rule, without exceeding what a District window can
|
||||
## physically cover.
|
||||
func test_select_rung_picks_district_at_a_moderate_sample_spacing() -> void:
|
||||
# E=120,000m (under the 64*2048=131,072m coverage ceiling) over C=100px ->
|
||||
# threshold = 2*120000/100 = 2,400m — satisfies District's 2,048m spacing.
|
||||
var rung: String = AtlasWindowGeometry.select_rung(120_000.0, 100.0)
|
||||
## An extent past Quarter's own ceiling but under District's selects
|
||||
## District — the finest rung that can still cover it in one window.
|
||||
func test_select_rung_picks_district_between_the_two_ceilings() -> void:
|
||||
# 60,000 m is past Quarter's 32,768 m ceiling but well under District's
|
||||
# 131,072 m one.
|
||||
var rung: String = AtlasWindowGeometry.select_rung(60_000.0, 100.0)
|
||||
assert_str(rung).is_equal("District")
|
||||
|
||||
|
||||
## An extent past the COVERAGE ceiling (more world than a District window can
|
||||
## physically span, regardless of how generous the visual tolerance would
|
||||
## otherwise be) must select Region — the coverage test, not the `2x` visual
|
||||
## one, is what decides this (select_rung()'s own doc: "the coverage ceiling
|
||||
## wins whenever the two disagree").
|
||||
func test_select_rung_picks_region_past_the_coverage_ceiling() -> void:
|
||||
# E = full Earth-like circumference (~40,075 km) — far past the
|
||||
# 64*2048=131,072m District coverage ceiling regardless of canvas_px.
|
||||
## An extent past BOTH Quarter's and District's ceilings selects Region —
|
||||
## neither finer rung's single window can cover this much world.
|
||||
func test_select_rung_picks_region_past_both_finer_ceilings() -> void:
|
||||
var rung: String = AtlasWindowGeometry.select_rung(40_075_264.0, 1920.0)
|
||||
assert_str(rung).is_equal("Region")
|
||||
|
||||
|
||||
## Exactly AT the coverage ceiling (E == 64*2048 = 131,072m) must still
|
||||
## select District if the `2x` band also agrees — the ceiling is `>`, not
|
||||
## `>=`, so the boundary value itself stays under District's own test.
|
||||
func test_select_rung_coverage_ceiling_boundary_stays_district() -> void:
|
||||
## Exactly AT Quarter's own ceiling (32,768 m) must still select Quarter —
|
||||
## the rule is `<=`, not `<`.
|
||||
func test_select_rung_quarter_ceiling_boundary_is_inclusive() -> void:
|
||||
var rung: String = AtlasWindowGeometry.select_rung(32_768.0, 100.0)
|
||||
assert_str(rung).is_equal("Quarter")
|
||||
|
||||
|
||||
## One metre past Quarter's ceiling must flip to District — confirms the
|
||||
## ceiling bites right at its own boundary, not one cell short of it.
|
||||
func test_select_rung_one_past_quarter_ceiling_is_district() -> void:
|
||||
var rung: String = AtlasWindowGeometry.select_rung(32_769.0, 100.0)
|
||||
assert_str(rung).is_equal("District")
|
||||
|
||||
|
||||
## Exactly AT District's own ceiling (131,072 m) must still select District.
|
||||
func test_select_rung_district_ceiling_boundary_is_inclusive() -> void:
|
||||
var rung: String = AtlasWindowGeometry.select_rung(131_072.0, 100.0)
|
||||
assert_str(rung).is_equal("District")
|
||||
|
||||
|
||||
## One metre past the coverage ceiling must flip to Region — confirms the
|
||||
## ceiling actually bites right at its own boundary, not one district-window
|
||||
## short of it.
|
||||
func test_select_rung_one_past_the_coverage_ceiling_is_region() -> void:
|
||||
## One metre past District's ceiling must flip to Region.
|
||||
func test_select_rung_one_past_district_ceiling_is_region() -> void:
|
||||
var rung: String = AtlasWindowGeometry.select_rung(131_073.0, 100.0)
|
||||
assert_str(rung).is_equal("Region")
|
||||
|
||||
|
||||
## Exactly AT District's `2x` threshold (spacing_m == 2*(E/C)) must select
|
||||
## District, not the next-finer rung — the rule is `<=`, not `<`.
|
||||
func test_select_rung_district_threshold_boundary_is_inclusive() -> void:
|
||||
# District spacing = 2048 m. Choose E/C such that 2*(E/C) == 2048 exactly:
|
||||
# E=1024, C=1.0 -> E/C=1024 -> threshold=2048. E=1024 is also comfortably
|
||||
# under the coverage ceiling (131,072), so the `2x` test is what's
|
||||
# actually being exercised here.
|
||||
var rung: String = AtlasWindowGeometry.select_rung(1024.0, 1.0)
|
||||
assert_str(rung).is_equal("District")
|
||||
|
||||
|
||||
## Degenerate canvas_px (<=0, an unlaid-out viewport) must fall back to the
|
||||
## FINEST rung, never crash or pick the coarsest by dividing by zero — the
|
||||
## documented "under-resolve is the safe failure direction" disposition (and
|
||||
## must be checked BEFORE the coverage ceiling could otherwise route a
|
||||
## degenerate small extent toward Region by accident).
|
||||
func test_select_rung_degenerate_canvas_px_falls_back_to_finest() -> void:
|
||||
var rung: String = AtlasWindowGeometry.select_rung(1000.0, 0.0)
|
||||
assert_str(rung).is_equal("Quarter")
|
||||
## canvas_px is unused by the coverage rule (kept for signature stability,
|
||||
## see select_rung()'s own doc) — degenerate/zero values must not change the
|
||||
## selected rung at all, unlike the old `2x`-tolerance design's special-cased
|
||||
## fallback.
|
||||
func test_select_rung_canvas_px_does_not_affect_selection() -> void:
|
||||
var with_real_canvas: String = AtlasWindowGeometry.select_rung(2000.0, 1000.0)
|
||||
var with_zero_canvas: String = AtlasWindowGeometry.select_rung(2000.0, 0.0)
|
||||
assert_str(with_zero_canvas).is_equal(with_real_canvas)
|
||||
|
||||
|
||||
## spacing_for_rung() is select_rung()'s inverse lookup — pin the three known
|
||||
@@ -363,16 +354,26 @@ func test_spacing_for_rung_unknown_tag_falls_back_to_district() -> void:
|
||||
assert_float(AtlasWindowGeometry.spacing_for_rung("Nonsense")).is_equal_approx(2048.0, 0.001)
|
||||
|
||||
|
||||
## The exact scenario that surfaced the coverage-vs-visual-tolerance
|
||||
## distinction (live-testing enter_orbital()'s own fit zoom): a whole
|
||||
## Earth-like body's circumference (~40,075 km, matching
|
||||
## AtlasDescendGeometry.district_extent()'s own cols*DISTRICT_M for
|
||||
## radius=6371km) fitted to a 1920px-wide viewport at CELL_PIXEL_SIZE=16 must
|
||||
## select Region — this is the direct regression guard for the bug this
|
||||
## implementation found and fixed (an earlier version of select_rung()
|
||||
## selected District here, which would have meant the canonical orbital
|
||||
## frame requests a District-tier derive spanning an entire planet — the
|
||||
## exact R1-catastrophe cost scenario the design doc §4 rejects).
|
||||
## MAX_COVERAGE_M's three values, pinned directly against the formulas
|
||||
## select_rung()'s own doc derives them from — a regression guard
|
||||
## independent of select_rung()'s own boundary tests above, so a future
|
||||
## accidental edit to the constants table itself (not just the selection
|
||||
## logic) is caught here too.
|
||||
func test_max_coverage_m_matches_derived_formulas() -> void:
|
||||
assert_float(AtlasWindowGeometry.MAX_COVERAGE_M["Quarter"]).is_equal_approx(32_768.0, 0.001)
|
||||
assert_float(AtlasWindowGeometry.MAX_COVERAGE_M["District"]).is_equal_approx(131_072.0, 0.001)
|
||||
assert_float(AtlasWindowGeometry.MAX_COVERAGE_M["Region"]).is_equal_approx(13_107_200.0, 0.001)
|
||||
|
||||
|
||||
## The exact scenario that surfaced the original design flaw
|
||||
## (live-testing enter_orbital()'s own fit zoom): a whole Earth-like body's
|
||||
## circumference (~40,075 km, matching AtlasDescendGeometry.district_extent()'s
|
||||
## own cols*DISTRICT_M for radius=6371km) fitted to a 1920px-wide viewport at
|
||||
## CELL_PIXEL_SIZE=16 must select Region — the direct regression guard for
|
||||
## the bug an early version of select_rung() had (picking District here,
|
||||
## which would have meant the canonical orbital frame requests a
|
||||
## District-tier derive spanning an entire planet — the exact R1-catastrophe
|
||||
## cost scenario the design doc §4 rejects).
|
||||
func test_select_rung_at_orbital_fit_zoom_selects_region() -> void:
|
||||
var radius_km := 6371.0
|
||||
var extent: Dictionary = AtlasDescendGeometry.district_extent(radius_km)
|
||||
@@ -390,50 +391,29 @@ func test_select_rung_at_orbital_fit_zoom_selects_region() -> void:
|
||||
).is_equal("Region")
|
||||
|
||||
|
||||
## Pinned capture-resolution boundary numbers (1600x900, the coordinator's
|
||||
## requested eyeball-capture viewport) — a live executable regression guard
|
||||
## for select_rung()'s own doc's worked example. Region releases District's
|
||||
## coverage ceiling at _view_zoom ~= 1.5625; District's own `2x` band edge
|
||||
## sits at _view_zoom ~= 0.125 — i.e. BELOW (not above) the coverage-ceiling
|
||||
## crossing, confirming the two never overlap at this (or any real) canvas
|
||||
## size — see select_rung()'s "Tuning knobs" paragraph for what would need
|
||||
## to change (DISTRICT_WINDOW_MAX_N, a server-side wire-budget change) to
|
||||
## open a real District band.
|
||||
func test_select_rung_1600x900_region_district_boundary_zoom() -> void:
|
||||
var viewport := Vector2(1600.0, 900.0)
|
||||
var canvas_px: float = maxf(viewport.x, viewport.y)
|
||||
var boundary_zoom := 1.5625
|
||||
var just_inside: float = AtlasWindowGeometry.world_extent_m(
|
||||
CELL_PIXEL_SIZE, boundary_zoom * 1.001, viewport
|
||||
)
|
||||
var just_outside: float = AtlasWindowGeometry.world_extent_m(
|
||||
CELL_PIXEL_SIZE, boundary_zoom * 0.999, viewport
|
||||
)
|
||||
assert_str(AtlasWindowGeometry.select_rung(just_inside, canvas_px)).override_failure_message(
|
||||
"zoomed IN past ~1.5625 at 1600x900 must have released the Region coverage ceiling"
|
||||
).is_not_equal("Region")
|
||||
assert_str(AtlasWindowGeometry.select_rung(just_outside, canvas_px)).override_failure_message(
|
||||
"zoomed OUT past ~1.5625 at 1600x900 must still be under the Region coverage ceiling"
|
||||
).is_equal("Region")
|
||||
|
||||
|
||||
func test_select_rung_1600x900_district_quarter_boundary_zoom_confirms_no_overlap() -> void:
|
||||
var viewport := Vector2(1600.0, 900.0)
|
||||
var canvas_px: float = maxf(viewport.x, viewport.y)
|
||||
var boundary_zoom := 0.125
|
||||
var just_inside: float = AtlasWindowGeometry.world_extent_m(
|
||||
CELL_PIXEL_SIZE, boundary_zoom * 1.001, viewport
|
||||
)
|
||||
var just_outside: float = AtlasWindowGeometry.world_extent_m(
|
||||
CELL_PIXEL_SIZE, boundary_zoom * 0.999, viewport
|
||||
)
|
||||
# Both sides of the District/Quarter `2x`-band boundary read "Region" at
|
||||
# 1600x900, NOT "District" — confirming the coverage ceiling (which
|
||||
# releases at zoom~=1.5625, far above this boundary) has already forced
|
||||
# Region long before the `2x` band's own edge is reached. This is the
|
||||
# literal "no overlap" finding, pinned as an executable assertion.
|
||||
assert_str(AtlasWindowGeometry.select_rung(just_inside, canvas_px)).is_equal("Region")
|
||||
assert_str(AtlasWindowGeometry.select_rung(just_outside, canvas_px)).is_equal("Region")
|
||||
## **Live round 3 regression, the direct fix target:** at 1600x900 (the
|
||||
## coordinator's capture viewport), zooming IN from the orbital fit all the
|
||||
## way to Quarter's own ceiling must pass through District along the way —
|
||||
## a wheel-zoom gesture crossing world_extent_m from Region's territory down
|
||||
## to Quarter's must select District for SOME real span of extent in
|
||||
## between, not skip straight from Region to Quarter (the exact "money shot"
|
||||
## the coordinator wants capture-worthy: a visible SHARPEN in place, not a
|
||||
## jump).
|
||||
func test_select_rung_district_is_reachable_between_region_and_quarter() -> void:
|
||||
# An extent comfortably between District's and Quarter's ceilings (e.g.
|
||||
# the midpoint) must select District — proving the band is non-empty,
|
||||
# unlike the old two-gate design where it was empty by construction at
|
||||
# every real viewport (see git history / the coordinator's live-round
|
||||
# finding for the retired analysis).
|
||||
var midpoint: float = (
|
||||
(AtlasWindowGeometry.MAX_COVERAGE_M["Quarter"] as float)
|
||||
+ (AtlasWindowGeometry.MAX_COVERAGE_M["District"] as float)
|
||||
) * 0.5
|
||||
var rung: String = AtlasWindowGeometry.select_rung(midpoint, 1600.0)
|
||||
assert_str(rung).override_failure_message(
|
||||
"District must be reachable between Quarter's and District's own"
|
||||
+ " coverage ceilings — the redesigned rule must not skip it"
|
||||
).is_equal("District")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
@@ -598,3 +578,102 @@ func test_edge_scroll_direction_points_west_near_left_edge() -> void:
|
||||
)
|
||||
assert_float(direction.x).is_less(0.0)
|
||||
assert_float(direction.y).is_equal_approx(0.0, 0.001)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# T-1153, live round 3 (Jeroen's ruling, design doc §4): compute_tile_grid()
|
||||
# — the orbital rest state's multi-window mosaic.
|
||||
# =============================================================================
|
||||
|
||||
|
||||
## The exact live-round scenario: GJ380c/Lendel (radius 6238.4 km) needs a
|
||||
## 3x2 = 6-tile grid — the coordinator's own estimate, confirmed here as an
|
||||
## executable regression.
|
||||
func test_compute_tile_grid_lendel_produces_six_tiles() -> void:
|
||||
var tiles: Array = AtlasWindowGeometry.compute_tile_grid(6238.4)
|
||||
assert_int(tiles.size()).override_failure_message(
|
||||
"GJ380c/Lendel must tile into 3x2=6 windows, matching the coordinator's own"
|
||||
+ " live-round finding (13,107.2 km single-window coverage vs. 39,198 km"
|
||||
+ " circumference)"
|
||||
).is_equal(6)
|
||||
|
||||
|
||||
## A tiny body whose whole circumference fits in ONE Region window's
|
||||
## coverage ceiling must produce exactly ONE tile — tiling degenerates
|
||||
## gracefully to the pre-existing single-window behavior when it isn't
|
||||
## actually needed.
|
||||
func test_compute_tile_grid_tiny_body_produces_one_tile() -> void:
|
||||
# radius small enough that circumference << MAX_COVERAGE_M["Region"]
|
||||
# (13,107,200 m) — a few hundred km radius comfortably qualifies.
|
||||
var tiles: Array = AtlasWindowGeometry.compute_tile_grid(50.0)
|
||||
assert_int(tiles.size()).is_equal(1)
|
||||
assert_that(tiles[0]).is_equal(Vector2i.ZERO)
|
||||
|
||||
|
||||
## A no-radius body (tiny test body) must produce exactly one tile at the
|
||||
## canonical origin — matching enter_orbital()'s own no-radius fallback
|
||||
## disposition (no circumference/tiling concept without a radius).
|
||||
func test_compute_tile_grid_no_radius_produces_single_origin_tile() -> void:
|
||||
var tiles: Array = AtlasWindowGeometry.compute_tile_grid(0.0)
|
||||
assert_int(tiles.size()).is_equal(1)
|
||||
assert_that(tiles[0]).is_equal(Vector2i.ZERO)
|
||||
|
||||
|
||||
## Every tile center must be a LEGAL canonicalized DistrictPos — column
|
||||
## wrapped into [0, cols), row clamped into [-rows_half, rows_half] — the
|
||||
## same range canonicalize_district_center() enforces everywhere else in
|
||||
## this cluster (pan refetch, entry, rung-reselect). A raw, uncanonicalized
|
||||
## tile center would fail the server's own normalize_window_center() (or
|
||||
## silently alias to a different tile than intended).
|
||||
func test_compute_tile_grid_tiles_are_all_canonicalized() -> void:
|
||||
var radius_km := 6238.4
|
||||
var extent: Dictionary = AtlasDescendGeometry.district_extent(radius_km)
|
||||
var cols: int = int(extent["cols"])
|
||||
var rows_half: int = int(extent["rows_half"])
|
||||
var tiles: Array = AtlasWindowGeometry.compute_tile_grid(radius_km)
|
||||
for tile: Vector2i in tiles:
|
||||
assert_int(tile.x).override_failure_message(
|
||||
"tile column %d must be wrapped into [0, %d)" % [tile.x, cols]
|
||||
).is_greater_equal(0)
|
||||
assert_int(tile.x).is_less(cols)
|
||||
assert_int(tile.y).override_failure_message(
|
||||
"tile row %d must be clamped into [-%d, %d]" % [tile.y, rows_half, rows_half]
|
||||
).is_greater_equal(-rows_half)
|
||||
assert_int(tile.y).is_less_equal(rows_half)
|
||||
|
||||
|
||||
## No two tiles may share the same canonicalized center — compute_tile_grid()
|
||||
## must dedupe (a pole-row clamp or column-wrap collision producing the exact
|
||||
## same DistrictPos twice would otherwise request/draw the same tile twice,
|
||||
## wasting a request and drawing one tile over another).
|
||||
func test_compute_tile_grid_has_no_duplicate_centers() -> void:
|
||||
var tiles: Array = AtlasWindowGeometry.compute_tile_grid(6238.4)
|
||||
var seen: Dictionary = {}
|
||||
for tile: Vector2i in tiles:
|
||||
assert_bool(seen.has(tile)).override_failure_message(
|
||||
"tile center %s appears more than once in the grid" % str(tile)
|
||||
).is_false()
|
||||
seen[tile] = true
|
||||
|
||||
|
||||
## The tile grid's own center of mass must land on the canonical origin
|
||||
## (0,0) — the tile-set's symmetric layout (each axis' centers computed as
|
||||
## `(index - (count-1)/2) * TILE_N`) is centered on the SAME canonical origin
|
||||
## enter_orbital() uses, so the tile-set's overall framing agrees with
|
||||
## single-window enter_orbital()'s own "center on (0,0)" contract.
|
||||
func test_compute_tile_grid_is_centered_on_the_canonical_origin() -> void:
|
||||
var tiles: Array = AtlasWindowGeometry.compute_tile_grid(6238.4)
|
||||
var sum_col := 0
|
||||
var sum_row := 0
|
||||
for tile: Vector2i in tiles:
|
||||
sum_col += tile.x
|
||||
sum_row += tile.y
|
||||
# Column centers wrap (periodic), so a raw average isn't meaningful there
|
||||
# the way it is for rows — assert row symmetry directly instead (rows
|
||||
# never wrap, so their average must be very close to 0 for a
|
||||
# symmetric grid).
|
||||
var avg_row: float = float(sum_row) / float(tiles.size())
|
||||
assert_float(avg_row).override_failure_message(
|
||||
"the tile grid's row centers must average to ~0 (symmetric around the"
|
||||
+ " canonical origin's equator row)"
|
||||
).is_equal_approx(0.0, float(AtlasWindowGeometry.TILE_N))
|
||||
|
||||
@@ -23,10 +23,14 @@ static func _mock_window(n: int = 2) -> Dictionary:
|
||||
|
||||
|
||||
## Minimal viewer stub — AtlasWindowOverlay only reaches the viewer through
|
||||
## get_district_window()/is_overlay_visible()/get_cell_pixel_size(), so a
|
||||
## bare stub with just those three methods is a legitimate "viewer" for
|
||||
## these tests, matching the duck-typed-viewer precedent this whole overlay
|
||||
## cluster already relies on (atlas_overlay_bar.gd/atlas_legend_panel.gd).
|
||||
## get_district_window()/is_overlay_visible()/get_cell_pixel_size()/
|
||||
## is_tile_mode(), so a bare stub with just those methods is a legitimate
|
||||
## "viewer" for these tests, matching the duck-typed-viewer precedent this
|
||||
## whole overlay cluster already relies on (atlas_overlay_bar.gd/
|
||||
## atlas_legend_panel.gd). is_tile_mode() always returns false — this suite
|
||||
## covers the single-window composite-cache path only; the tile mosaic path
|
||||
## is covered separately by test_atlas_window_tile_set.gd + the viewer's own
|
||||
## is_tile_mode()-branching tests.
|
||||
class _ViewerStub:
|
||||
var window: Variant = null
|
||||
var active_overlay: String = ""
|
||||
@@ -40,6 +44,9 @@ class _ViewerStub:
|
||||
func get_cell_pixel_size() -> float:
|
||||
return 16.0
|
||||
|
||||
func is_tile_mode() -> bool:
|
||||
return false
|
||||
|
||||
|
||||
func test_composite_smooth_defaults_true() -> void:
|
||||
assert_bool(AtlasWindowOverlay.COMPOSITE_SMOOTH).override_failure_message(
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
## T-1153, live round 3 (Jeroen's ruling, design doc §4): tests for
|
||||
## AtlasWindowTileSet — the orbital rest-state multi-window mosaic
|
||||
## orchestration. Same hand-built-response-dict conventions as
|
||||
## test_atlas_window_request.gd/test_atlas_zoom_ladder.gd; this file is
|
||||
## about the ORCHESTRATION (N tiles, progressive per-tile arrival,
|
||||
## teardown), not the tile-grid MATH (already covered directly against
|
||||
## AtlasWindowGeometry.compute_tile_grid() in test_atlas_window_geometry.gd).
|
||||
class_name TestAtlasWindowTileSet
|
||||
extends GdUnitTestSuite
|
||||
|
||||
const AtlasWindowTileSet := preload("res://ui/implant/apps/atlas/atlas_window_tile_set.gd")
|
||||
const AtlasWindowRequest := preload("res://ui/implant/apps/atlas/atlas_window_request.gd")
|
||||
|
||||
|
||||
static func _mock_window(center: Vector2i, n: int) -> Dictionary:
|
||||
return {
|
||||
"center": [center.x, center.y],
|
||||
"n": n,
|
||||
"granularity_v2": "Region",
|
||||
"morphology": PackedByteArray([1, 2, 3, 4]),
|
||||
"elev_q": PackedByteArray([10, 20, 30, 40]),
|
||||
"temp_dc": [0, 0, 0, 0],
|
||||
"moisture_q": PackedByteArray([0, 0, 0, 0]),
|
||||
"vegetation": PackedByteArray([0, 0, 0, 0]),
|
||||
"glaciation": PackedByteArray([0, 0, 0, 0]),
|
||||
}
|
||||
|
||||
|
||||
static func _mock_response(body_id: String, window: Variant) -> Dictionary:
|
||||
return {"body_id": body_id, "status": "Ready", "district_window": window}
|
||||
|
||||
|
||||
func _make_tile_set() -> Variant:
|
||||
var owner_stub := RefCounted.new()
|
||||
var ts = auto_free(AtlasWindowTileSet.new(owner_stub))
|
||||
add_child(ts)
|
||||
return ts
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# enter() — tile grid computation + one request per tile
|
||||
# =============================================================================
|
||||
|
||||
|
||||
## enter() on a real, tiling-sized body must produce the SAME tile count
|
||||
## compute_tile_grid() would — 6 for GJ380c/Lendel, the coordinator's own
|
||||
## live-round number.
|
||||
func test_enter_produces_the_expected_tile_count_for_lendel() -> void:
|
||||
var ts = _make_tile_set()
|
||||
ts.enter("GJ380c", 6238.4)
|
||||
assert_int(ts.get_tile_count()).is_equal(6)
|
||||
assert_bool(ts.is_multi_tile()).is_true()
|
||||
|
||||
|
||||
## A tiny (non-tiling) body produces exactly ONE tile — the degenerate case
|
||||
## compute_tile_grid() itself already covers; this confirms the ORCHESTRATION
|
||||
## (not just the grid math) handles it without crashing or requesting zero
|
||||
## tiles.
|
||||
func test_enter_tiny_body_produces_one_tile() -> void:
|
||||
var ts = _make_tile_set()
|
||||
ts.enter("TinyBody", 50.0)
|
||||
assert_int(ts.get_tile_count()).is_equal(1)
|
||||
assert_bool(ts.is_multi_tile()).is_false()
|
||||
|
||||
|
||||
## Every tile must start with a null window (nothing has arrived yet) and
|
||||
## the tile set must not report "fully arrived" before any response lands.
|
||||
func test_enter_all_tiles_start_unarrived() -> void:
|
||||
var ts = _make_tile_set()
|
||||
ts.enter("GJ380c", 6238.4)
|
||||
for tile: Dictionary in ts.get_tiles():
|
||||
assert_that(tile["window"]).is_null()
|
||||
assert_bool(ts.is_fully_arrived()).is_false()
|
||||
|
||||
|
||||
## An empty tile set (never entered) must not report "fully arrived" either
|
||||
## — an empty AND-over-nothing must not vacuously read true.
|
||||
func test_empty_tile_set_is_not_fully_arrived() -> void:
|
||||
var ts = _make_tile_set()
|
||||
assert_bool(ts.is_fully_arrived()).is_false()
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Progressive per-tile arrival (design doc §4: "with visible refinement as
|
||||
# tiles complete") — each tile's response is independent of every other's.
|
||||
# =============================================================================
|
||||
|
||||
|
||||
## Delivering ONE tile's response must populate ONLY that tile's window,
|
||||
## leaving every other tile still null — the direct "progressive, not
|
||||
## block-on-all" regression.
|
||||
func test_one_tile_arriving_does_not_affect_the_others() -> void:
|
||||
var ts = _make_tile_set()
|
||||
ts.enter("GJ380c", 6238.4)
|
||||
var tiles: Array = ts.get_tiles()
|
||||
var first_center: Vector2i = tiles[0]["center"]
|
||||
var window: Dictionary = _mock_window(first_center, AtlasWindowRequest.SERVER_DISTRICT_WINDOW_MAX_N_REGION)
|
||||
SimBridge.atlas_layers_received.emit(_mock_response("GJ380c", window))
|
||||
|
||||
var updated_tiles: Array = ts.get_tiles()
|
||||
assert_that(updated_tiles[0]["window"]).override_failure_message(
|
||||
"the tile whose response arrived must have its window populated"
|
||||
).is_equal(window)
|
||||
for i in range(1, updated_tiles.size()):
|
||||
assert_that(updated_tiles[i]["window"]).override_failure_message(
|
||||
"tile %d must still be unarrived — only tile 0's response was delivered" % i
|
||||
).is_null()
|
||||
|
||||
|
||||
## tile_ready must fire with the INDEX of the tile that actually arrived —
|
||||
## the viewer/overlay needs this to know WHICH tile to redraw, not just
|
||||
## "something changed".
|
||||
func test_tile_ready_signal_fires_with_the_correct_index() -> void:
|
||||
var ts = _make_tile_set()
|
||||
ts.enter("GJ380c", 6238.4)
|
||||
var received_indices: Array = []
|
||||
ts.tile_ready.connect(func(index: int) -> void: received_indices.append(index))
|
||||
|
||||
var tiles: Array = ts.get_tiles()
|
||||
var second_center: Vector2i = tiles[1]["center"]
|
||||
var window: Dictionary = _mock_window(second_center, AtlasWindowRequest.SERVER_DISTRICT_WINDOW_MAX_N_REGION)
|
||||
SimBridge.atlas_layers_received.emit(_mock_response("GJ380c", window))
|
||||
|
||||
assert_int(received_indices.size()).is_equal(1)
|
||||
assert_int(received_indices[0]).is_equal(1)
|
||||
|
||||
|
||||
## Delivering EVERY tile's response must flip is_fully_arrived() to true —
|
||||
## the mosaic-complete signal the viewer/legend chrome can use.
|
||||
func test_all_tiles_arriving_flips_fully_arrived() -> void:
|
||||
var ts = _make_tile_set()
|
||||
ts.enter("GJ380c", 6238.4)
|
||||
var tiles: Array = ts.get_tiles()
|
||||
for tile: Dictionary in tiles:
|
||||
var window: Dictionary = _mock_window(
|
||||
tile["center"], AtlasWindowRequest.SERVER_DISTRICT_WINDOW_MAX_N_REGION
|
||||
)
|
||||
SimBridge.atlas_layers_received.emit(_mock_response("GJ380c", window))
|
||||
|
||||
assert_bool(ts.is_fully_arrived()).override_failure_message(
|
||||
"once every tile's response has arrived, the tile set must report fully arrived"
|
||||
).is_true()
|
||||
|
||||
|
||||
## A response for a body the tile set is NOT currently showing (a stale
|
||||
## response from a body the player has since navigated away from) must not
|
||||
## be adopted by any tile — the SAME body_id staleness guard every other
|
||||
## AtlasWindowRequest-based path already relies on (this is inherited for
|
||||
## free since each tile IS an AtlasWindowRequest, but pinned here as an
|
||||
## orchestration-level regression too).
|
||||
func test_response_for_a_different_body_is_ignored() -> void:
|
||||
var ts = _make_tile_set()
|
||||
ts.enter("GJ380c", 6238.4)
|
||||
var tiles: Array = ts.get_tiles()
|
||||
var window: Dictionary = _mock_window(
|
||||
tiles[0]["center"], AtlasWindowRequest.SERVER_DISTRICT_WINDOW_MAX_N_REGION
|
||||
)
|
||||
SimBridge.atlas_layers_received.emit(_mock_response("GJ_wrong_body", window))
|
||||
|
||||
assert_that(ts.get_tiles()[0]["window"]).is_null()
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Teardown — re-entering (a fresh body, or the same body again) must not
|
||||
# leave stale tile request nodes wired up.
|
||||
# =============================================================================
|
||||
|
||||
|
||||
## Calling enter() a SECOND time (e.g. re-entering the orbital frame, or
|
||||
## switching to a different body) must replace the tile set entirely — the
|
||||
## OLD tiles' indices/centers must not linger.
|
||||
func test_second_enter_replaces_the_tile_set() -> void:
|
||||
var ts = _make_tile_set()
|
||||
ts.enter("GJ380c", 6238.4)
|
||||
var first_count: int = ts.get_tile_count()
|
||||
assert_int(first_count).is_equal(6)
|
||||
|
||||
ts.enter("TinyBody", 50.0)
|
||||
assert_int(ts.get_tile_count()).override_failure_message(
|
||||
"a second enter() must fully replace the tile set, not append to it"
|
||||
).is_equal(1)
|
||||
|
||||
|
||||
## A response matching an OLD tile set's (body, center) — arriving AFTER a
|
||||
## second enter() has already torn it down — must not be adopted (or crash):
|
||||
## the old tile's AtlasWindowRequest node is queue_free()'d, and _tiles no
|
||||
## longer references it, so a stale signal (if it could somehow still fire)
|
||||
## has no live entry left to update.
|
||||
func test_stale_response_after_second_enter_does_not_crash_or_leak() -> void:
|
||||
var ts = _make_tile_set()
|
||||
ts.enter("GJ380c", 6238.4)
|
||||
var old_tiles: Array = ts.get_tiles()
|
||||
var old_center: Vector2i = old_tiles[0]["center"]
|
||||
|
||||
ts.enter("GJ380c", 50.0) # same body_id, different (tiny) radius -> different tile grid
|
||||
|
||||
# A response shaped like it's answering the OLD tile set's first tile —
|
||||
# must not crash, and must not corrupt the NEW tile set's single tile.
|
||||
var stale_window: Dictionary = _mock_window(
|
||||
old_center, AtlasWindowRequest.SERVER_DISTRICT_WINDOW_MAX_N_REGION
|
||||
)
|
||||
SimBridge.atlas_layers_received.emit(_mock_response("GJ380c", stale_window))
|
||||
|
||||
assert_int(ts.get_tile_count()).is_equal(1)
|
||||
@@ -73,81 +73,64 @@ func test_enter_orbital_requests_region_granularity() -> void:
|
||||
assert_str(v._held_granularity_v2).is_equal("Region")
|
||||
|
||||
|
||||
## enter_orbital()'s `n` intent is the body's full equatorial circumference
|
||||
## in districts (district_extent().cols) — the whole body fitted to the
|
||||
## canvas, per Jeroen's HARD condition wording — BUT `_held_n` is what
|
||||
## actually gets STORED/SENT, and that must be the CLAMPED value
|
||||
## (live-round finding: the raw cols value, routinely tens of thousands at
|
||||
## Region granularity, was stored unclamped while AtlasWindowRequest clamped
|
||||
## before sending — see _enter_at_rung()'s own doc for the full C1-one-layer-up
|
||||
## story). GJ380c's real cols (~19,139 per the live repro) exceeds
|
||||
## DISTRICT_WINDOW_MAX_N_REGION's clamp ceiling, so this body is the exact
|
||||
## regression case, not a hypothetical.
|
||||
func test_enter_orbital_n_is_the_clamped_value_not_raw_circumference() -> void:
|
||||
## **Superseded by live round 3's tiling fix — retargeted, not deleted.**
|
||||
## GJ380c/Lendel (radius 6238.4 km) was the ORIGINAL single-window C1 repro
|
||||
## (raw cols ~19,139 vs. the 6,400 clamp ceiling) — but that SAME threshold
|
||||
## (`DISTRICT_WINDOW_MAX_N_REGION * DISTRICT_M` = the coverage ceiling
|
||||
## `compute_tile_grid()` tiles past) means any body needing the n-clamp ALSO
|
||||
## needs tiling: there is no real body where enter_orbital() takes the
|
||||
## single-window path with a raw `n` big enough to require clamping.
|
||||
## GJ380c now correctly enters TILE mode (test_enter_orbital_n_is_the_clamped_value_not_raw_circumference's
|
||||
## old assertion on a single clamped `_held_n` no longer applies — see
|
||||
## test_enter_orbital_tile_mode_held_n_is_the_whole_body_extent below for
|
||||
## what `_held_n` means in tile mode instead). The single-window clamp-mirror
|
||||
## fix itself remains covered: `_enter_at_rung()`'s own doc/the clamp
|
||||
## mirror's unit tests (test_atlas_window_request.gd) pin the formula
|
||||
## directly, and test_zoom_crossing_fires_request_and_accepts_wire_accurate_refinement
|
||||
## exercises the SAME clamp-mirror lesson at the reselect (not entry)
|
||||
## boundary, which single-window mode still reaches on the way DOWN from a
|
||||
## tile-mode zoom-in.
|
||||
func test_enter_orbital_tile_mode_held_n_is_the_whole_body_extent() -> void:
|
||||
var v: AtlasWindowViewer = auto_free(AtlasWindowViewer.new())
|
||||
add_child(v)
|
||||
var radius_km := 6238.4 # GJ380c (Lendel) — the live-repro body
|
||||
var radius_km := 6238.4 # GJ380c (Lendel)
|
||||
var extent: Dictionary = AtlasDescendGeometry.district_extent(radius_km)
|
||||
var raw_cols: int = int(extent["cols"])
|
||||
v.enter_orbital({"body_id": "GJ380c", "body_radius_km": radius_km}, {})
|
||||
|
||||
var expected_clamped: int = AtlasWindowRequest._clamp_window_n_mirror_v2(raw_cols, "Region")
|
||||
var failure_msg: String = (
|
||||
"_held_n must be the CLAMPED n (%d), matching what the server will echo —"
|
||||
+ " not the raw circumference (%d), which the server would clamp down and"
|
||||
+ " every response would then fail the w_n != _held_n staleness check"
|
||||
) % [expected_clamped, raw_cols]
|
||||
assert_int(v._held_n).override_failure_message(failure_msg).is_equal(expected_clamped)
|
||||
# GJ380c's raw circumference must actually exceed the clamp — otherwise this
|
||||
# test would pass trivially without exercising the clamp at all.
|
||||
assert_int(raw_cols).override_failure_message(
|
||||
"GJ380c's raw district-column count must exceed the Region clamp ceiling"
|
||||
+ " for this to be a real regression guard, not a no-op"
|
||||
).is_greater(expected_clamped)
|
||||
assert_bool(v.is_tile_mode()).override_failure_message(
|
||||
"GJ380c/Lendel needs tiling — enter_orbital() must have entered tile mode"
|
||||
).is_true()
|
||||
# In TILE mode, _held_n is the WHOLE body's extent (unclamped) — each
|
||||
# TILE clamps its own request independently inside AtlasWindowTileSet
|
||||
# (see that file's own tests), so _held_n here is NOT expected to equal
|
||||
# any single clamped value the way single-window mode's is.
|
||||
assert_int(v._held_n).is_equal(raw_cols)
|
||||
|
||||
|
||||
## **The live-round regression, end to end (fix #1: the n-clamp mirror one
|
||||
## layer up):** enter_orbital() on a real-sized body (GJ380c/Lendel, radius
|
||||
## 6238.4 km, raw cols far past the Region clamp ceiling) followed by a
|
||||
## server response echoing the CLAMPED n + "Region" granularity must be
|
||||
## ACCEPTED and become the held window — not silently dropped as stale
|
||||
## forever (the exact live bug: `wv._held_n = 19139` vs. echoed `6400`,
|
||||
## blank ladder on every real-sized body). This is the round-trip the
|
||||
## existing suite never exercised — every prior enter_orbital() test
|
||||
## asserted on request-side state only, never delivered a response.
|
||||
##
|
||||
## **WIRE-ACCURATE response shape (fix #2, second live-round finding):** the
|
||||
## response dict below carries `"granularity":
|
||||
## SERVER_LEGACY_GRANULARITY_REGION_SENTINEL` explicitly — the ACTUAL byte a
|
||||
## real server sends, not the field's absence. The first version of this
|
||||
## test omitted the legacy key entirely, which let `w.get("granularity",
|
||||
## DEFAULT)` silently default to `1` (matching `_granularity`'s own pinned
|
||||
## value) — an ACCIDENTAL pass that never exercised the real sentinel
|
||||
## mismatch, exactly the class of gap the live round exists to catch. This
|
||||
## version fails without the v2-authoritative-when-present fix in
|
||||
## `_echoed_granularity_matches()`.
|
||||
func test_enter_orbital_oversized_body_accepts_the_clamped_region_response() -> void:
|
||||
## **The live-round-3 regression, end to end for TILE mode:** enter_orbital()
|
||||
## on GJ380c/Lendel followed by delivering ONE tile's wire-accurate response
|
||||
## (clamped n=6,400, "Region" granularity_v2, the legacy sentinel in the old
|
||||
## granularity slot — exactly what a real server sends) must be ACCEPTED
|
||||
## into that tile's own slot — not silently dropped. This exercises BOTH
|
||||
## live-round fixes (the v2-authoritative precedence AND per-tile clamping)
|
||||
## through the tile-set path specifically, complementing
|
||||
## test_atlas_window_tile_set.gd's own more granular orchestration tests.
|
||||
func test_enter_orbital_tile_mode_accepts_a_wire_accurate_tile_response() -> void:
|
||||
var v: AtlasWindowViewer = auto_free(AtlasWindowViewer.new())
|
||||
add_child(v)
|
||||
var radius_km := 6238.4 # GJ380c (Lendel) — the live-repro body
|
||||
var extent: Dictionary = AtlasDescendGeometry.district_extent(radius_km)
|
||||
var raw_cols: int = int(extent["cols"])
|
||||
var clamped_n: int = AtlasWindowRequest._clamp_window_n_mirror_v2(raw_cols, "Region")
|
||||
# Sanity: this body must actually need clamping, or the test proves nothing.
|
||||
assert_int(raw_cols).is_greater(clamped_n)
|
||||
|
||||
v.enter_orbital({"body_id": "GJ380c", "body_radius_km": radius_km}, {})
|
||||
assert_that(v.get_district_window()).override_failure_message(
|
||||
"no response delivered yet — must still be null"
|
||||
).is_null()
|
||||
assert_bool(v.is_tile_mode()).is_true()
|
||||
|
||||
# The server's REAL response: echoes the CLAMPED n, "Region" granularity_v2
|
||||
# (String), center (0,0), AND the legacy sentinel in "granularity" — exactly
|
||||
# what handle_atlas_request/clamp_window_n_v2 actually produces on the wire
|
||||
# for an oversized orbital request (confirmed against Dudley's contract).
|
||||
var region_window: Dictionary = {
|
||||
"center": [0, 0],
|
||||
"n": clamped_n,
|
||||
var tile_set = v.get_tile_set()
|
||||
var tiles: Array = tile_set.get_tiles()
|
||||
assert_int(tiles.size()).is_greater(1)
|
||||
var first_tile_center: Vector2i = tiles[0]["center"]
|
||||
|
||||
var tile_window: Dictionary = {
|
||||
"center": [first_tile_center.x, first_tile_center.y],
|
||||
"n": AtlasWindowRequest.SERVER_DISTRICT_WINDOW_MAX_N_REGION,
|
||||
"granularity": SERVER_LEGACY_GRANULARITY_REGION_SENTINEL,
|
||||
"granularity_v2": "Region",
|
||||
"morphology": PackedByteArray([8, 14, 0, 1]),
|
||||
@@ -157,19 +140,12 @@ func test_enter_orbital_oversized_body_accepts_the_clamped_region_response() ->
|
||||
"vegetation": PackedByteArray([2, 1, 6, 3]),
|
||||
"glaciation": PackedByteArray([0, 0, 1, 2]),
|
||||
}
|
||||
SimBridge.atlas_layers_received.emit(_mock_response("GJ380c", region_window))
|
||||
SimBridge.atlas_layers_received.emit(_mock_response("GJ380c", tile_window))
|
||||
|
||||
var failure_msg: String = (
|
||||
"a response echoing the server's own clamped n + Region granularity_v2 (with"
|
||||
+ " the legacy sentinel u32::MAX in the old granularity slot) must be ACCEPTED"
|
||||
+ " and become the held window — the live bug left this permanently null"
|
||||
+ " (w_n=%d never matched a stale unclamped _held_n=%d, THEN the legacy"
|
||||
+ " sentinel never matched the stored _granularity=1) on every real-sized body"
|
||||
) % [clamped_n, raw_cols]
|
||||
assert_that(v.get_district_window()).override_failure_message(failure_msg).is_equal(
|
||||
region_window
|
||||
)
|
||||
assert_str(v._held_granularity_v2).is_equal("Region")
|
||||
assert_that(tile_set.get_tiles()[0]["window"]).override_failure_message(
|
||||
"a wire-accurate response (clamped n, Region granularity_v2, the legacy"
|
||||
+ " sentinel) for the first tile must be ACCEPTED into that tile's slot"
|
||||
).is_equal(tile_window)
|
||||
|
||||
|
||||
## A no-radius body (tiny test body) has no circumference concept —
|
||||
@@ -364,19 +340,20 @@ func test_zoom_out_past_district_threshold_requests_a_coarser_rung() -> void:
|
||||
assert_that(v.get_district_window()).is_equal(district_window)
|
||||
|
||||
|
||||
## Zooming IN on a District-rung window (well within its own legal spacing
|
||||
## band) must NOT trigger a rung change — this is the "zoom is client-side on
|
||||
## the already-held composite" case, unchanged for in-rung zoom. Sets
|
||||
## _view_zoom DIRECTLY to a value inside District's legal band (rather than
|
||||
## relying on enter()'s COVER auto-fit, which for a small n can already sit
|
||||
## right at Quarter's own threshold — a fit's zoom level is a display-density
|
||||
## choice independent of what rung selection would pick from scratch, and
|
||||
## this test is specifically about a SINGLE zoom-in STEP not crossing a
|
||||
## boundary, not about where the auto-fit itself lands). District's legal
|
||||
## band (select_rung()'s own doc: the coverage ceiling and the `2x` visual
|
||||
## band only overlap at small viewports — `canvas_px <= DISTRICT_WINDOW_MAX_N
|
||||
## * DISTRICT_SPACING_M / 1024 = 128px`) requires a SMALL viewport here,
|
||||
## unlike most of this suite's 800x600/1920x1080 fixtures.
|
||||
## Zooming IN on a District-rung window (well within its own legal coverage
|
||||
## band, `(32,768 m, 131,072 m]` per select_rung()'s redesigned per-rung
|
||||
## ceiling model — viewport-independent since `canvas_px` no longer affects
|
||||
## selection) must NOT trigger a rung change — this is the "zoom is
|
||||
## client-side on the already-held composite" case, unchanged for in-rung
|
||||
## zoom. Sets _view_zoom DIRECTLY to a value inside District's band (rather
|
||||
## than relying on enter()'s COVER auto-fit, which for a small n can already
|
||||
## sit right at Quarter's own threshold — a fit's zoom level is a
|
||||
## display-density choice independent of what rung selection would pick from
|
||||
## scratch, and this test is specifically about a SINGLE zoom-in STEP not
|
||||
## crossing a boundary, not about where the auto-fit itself lands). The
|
||||
## small 100x80 viewport here is incidental (any size works under the new
|
||||
## viewport-independent model) — kept small only because that's what the
|
||||
## original version of this test used.
|
||||
func test_zoom_in_within_district_threshold_does_not_change_rung() -> void:
|
||||
var v: AtlasWindowViewer = auto_free(AtlasWindowViewer.new())
|
||||
add_child(v)
|
||||
@@ -394,6 +371,84 @@ func test_zoom_in_within_district_threshold_does_not_change_rung() -> void:
|
||||
).is_equal("District")
|
||||
|
||||
|
||||
## **Live round 3 regression, the direct end-to-end fix target:** a real
|
||||
## wheel-zoom gesture (many `_zoom_at()` ticks, matching the shape a
|
||||
## continuous mouse-wheel scroll actually produces) crossing from the
|
||||
## Region rest state down through District into Quarter territory must (i)
|
||||
## fire a request at the NEW granularity — `_window_request.get_granularity_v2()`
|
||||
## must have changed by the end of the gesture — and (ii) accept a
|
||||
## WIRE-ACCURATE response for that request: echoing the REQUEST's own
|
||||
## (already re-centered, already re-clamped) center/n, which the live round
|
||||
## found DIFFERS from the ORIGINAL held center (screen-center-anchored
|
||||
## refinement re-centers on wherever the cursor currently maps to, not
|
||||
## wherever the player started) — this is the "second latent drop" the
|
||||
## coordinator specifically flagged: comparing the echo against a STALE
|
||||
## `_held_center` (frozen at the pre-crossing value) rather than the
|
||||
## request's own center would silently drop this response too.
|
||||
## **Live round 3 update:** GJ380c/Lendel now enters TILE mode via
|
||||
## enter_orbital() (bug B's fix), so this test starts from THERE — zooming
|
||||
## in far enough crosses Region's coverage ceiling and must LEAVE tile mode
|
||||
## for the single-window path at the new (finer) rung, exactly the
|
||||
## `_maybe_reselect_rung()` "leaving_tile_mode" branch this test exercises.
|
||||
func test_zoom_crossing_fires_request_and_accepts_wire_accurate_refinement() -> void:
|
||||
var v: AtlasWindowViewer = auto_free(AtlasWindowViewer.new())
|
||||
add_child(v)
|
||||
v.size = Vector2(1600.0, 900.0)
|
||||
var radius_km := 6238.4 # GJ380c (Lendel) — the live-repro body
|
||||
v.enter_orbital({"body_id": "GJ380c", "body_radius_km": radius_km}, {})
|
||||
assert_bool(v.is_tile_mode()).override_failure_message(
|
||||
"GJ380c/Lendel must enter tile mode at the orbital rest state (live round 3)"
|
||||
).is_true()
|
||||
|
||||
# A real wheel-zoom gesture: many ticks, cursor OFF-CENTER (so cursor-
|
||||
# anchored zoom genuinely drifts the screen-to-district mapping away from
|
||||
# the canonical origin, not just scaling in place) — matching the live
|
||||
# drive's actual input shape, not a single synthetic jump. Zooming in far
|
||||
# enough must cross OUT of Region's coverage ceiling, leaving tile mode.
|
||||
var cursor_pos := Vector2(1100.0, 300.0) # off-center, biased toward one quadrant
|
||||
for _i in range(60):
|
||||
v._zoom_at(cursor_pos, 1.15)
|
||||
if not v.is_tile_mode():
|
||||
break
|
||||
|
||||
# (i) Tile mode must have been LEFT, and a request must have gone out at
|
||||
# a NEW (finer) granularity via the single-window path.
|
||||
assert_bool(v.is_tile_mode()).override_failure_message(
|
||||
"zooming in far enough must leave tile mode for the single-window path"
|
||||
).is_false()
|
||||
var request_granularity: String = v._window_request.get_granularity_v2()
|
||||
assert_str(request_granularity).override_failure_message(
|
||||
"leaving tile mode must fire a request at a new (finer) granularity"
|
||||
).is_not_equal("Region")
|
||||
|
||||
# (ii) The request's own center/n — read AFTER leaving tile mode, so this
|
||||
# is whatever _maybe_reselect_rung() actually computed — is what a
|
||||
# wire-accurate response must echo to be accepted.
|
||||
var request_center: Vector2i = v._window_request._center
|
||||
var request_n: int = v._window_request._n
|
||||
|
||||
var refinement_window: Dictionary = {
|
||||
"center": [request_center.x, request_center.y],
|
||||
"n": request_n,
|
||||
"granularity_v2": request_granularity,
|
||||
"morphology": PackedByteArray([1, 2, 3, 4]),
|
||||
"elev_q": PackedByteArray([10, 20, 30, 40]),
|
||||
"temp_dc": [0, 0, 0, 0],
|
||||
"moisture_q": PackedByteArray([0, 0, 0, 0]),
|
||||
"vegetation": PackedByteArray([0, 0, 0, 0]),
|
||||
"glaciation": PackedByteArray([0, 0, 0, 0]),
|
||||
}
|
||||
SimBridge.atlas_layers_received.emit(_mock_response("GJ380c", refinement_window))
|
||||
|
||||
assert_that(v.get_district_window()).override_failure_message(
|
||||
"a wire-accurate refinement response (echoing the REQUEST's own center/n/"
|
||||
+ " granularity after leaving tile mode) must be ACCEPTED — comparing"
|
||||
+ " against a stale/wrong reference instead of the request's own would"
|
||||
+ " silently drop this response forever"
|
||||
).is_equal(refinement_window)
|
||||
assert_str(v._held_granularity_v2).is_equal(request_granularity)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# T-1153: E/W wrap and pole-wall clamps at EVERY rung — both are extent-
|
||||
# relative (CELL_PIXEL_SIZE-based district-space math, unchanged regardless
|
||||
|
||||
Reference in New Issue
Block a user