Files
settled-reach/client/tests/test_step_canvas_transport.gd
T
jpmschweitzerandClaude Fable 5 d28d24fd26 feat(ui): step-canvas map component — RTT terrain + stepped zoom (D-255, T-1182)
The two-layer client rebuild per D-255(a)(b)(e), replacing the
_canvas.scale continuous-zoom model with one viewer, one path, all six
rungs:

- step_canvas_protocol.gd: StepCanvasRequest/Response codec against
  the T-1181 wire contract — incl. the discovered png_bytes subtlety
  (rmp_serde without serde_bytes emits a msgpack int-array, not bin;
  decode repacks via PackedByteArray before load_png_from_buffer) and
  the extent-echo rule (read the server-clamped extent, never assume
  the requested one).
- step_canvas/ component: transport (six-rung ladder, cursor-anchored
  scroll steps, edge-scroll/WASD pan with re-request on edge crossing,
  hard reset-to-Global), RTT terrain layer (Image.set_pixel colorize
  per the c1 measured ruling, texture.update reuse on step-cross,
  NEAREST coarse / LINEAR fine per rung), unscaled screen-space
  annotation sibling (courses + settlement markers at literal px),
  in-memory LRU cache (Tier 1; T-1183 layers the disk tiers beneath),
  request lifecycle (pending retry, staleness gate, extent echo).
- Full _canvas.scale retirement in the same change: the zoom-scaled
  canvas model, the _zs compensation family, select_rung /
  MAX_COVERAGE_M / compute_tile_grid, the orbital-mosaic-vs-window
  two-path split, _view_zoom/_canonical_fit_zoom — 10 source files
  deleted; their 14 test suites deleted with them (T-1157 dead-goldens
  rule; replacement visual-capture coverage is re-scoped T-1157).
- Surviving surfaces kept per the ticket: atlas_window_cache.gd's LRU
  shape (the ticket's named file atlas_window_tile_set.gd was the
  retiring orchestrator; the real LRU shape lives in
  atlas_window_cache.gd — cited in step_canvas_cache.gd), overlay
  colors, legend/overlay-bar chrome, AtlasViewer descend geometry.

Determinism boundary per D-255(e): the client interpolates only within
the closed server-supplied input set. 7 new gdUnit suites (164 cases)
incl. a real extent-echo bug caught by its own test during
implementation. Full client suite green (exit 0) with the live-gated
suites running against a worktree server build.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 08:53:48 +02:00

191 lines
8.3 KiB
GDScript

## T-1182 tests: step_canvas_transport.gd — the D-255(a) six-rung stepped
## transport state machine (rung ladder, cursor-anchored step math,
## viewport-fit extent, world<->canvas-local projection). All pure
## functions, no scene tree needed.
class_name TestStepCanvasTransport
extends GdUnitTestSuite
const StepCanvasTransport := preload("res://ui/implant/apps/atlas/step_canvas/step_canvas_transport.gd")
# =============================================================================
# Rung ladder — index <-> name, scroll clamping
# =============================================================================
func test_rung_at_index_zero_is_global() -> void:
assert_str(StepCanvasTransport.rung_at_index(0)).is_equal(StepCanvasTransport.RUNG_GLOBAL)
func test_rung_at_index_five_is_chunk_the_deepest() -> void:
assert_str(StepCanvasTransport.rung_at_index(5)).is_equal(StepCanvasTransport.RUNG_CHUNK)
func test_rung_at_index_clamps_out_of_range_indices() -> void:
assert_str(StepCanvasTransport.rung_at_index(-3)).is_equal(StepCanvasTransport.RUNG_GLOBAL)
assert_str(StepCanvasTransport.rung_at_index(99)).is_equal(StepCanvasTransport.RUNG_CHUNK)
func test_index_for_rung_round_trips_every_ladder_entry() -> void:
for i in range(StepCanvasTransport.RUNG_LADDER.size()):
var rung: String = StepCanvasTransport.rung_at_index(i)
assert_int(StepCanvasTransport.index_for_rung(rung)).is_equal(i)
func test_index_for_rung_unrecognized_returns_negative_one() -> void:
assert_int(StepCanvasTransport.index_for_rung("Sector")).is_equal(-1)
func test_scroll_step_descends_one_notch_at_a_time() -> void:
assert_int(StepCanvasTransport.scroll_step(0, 1)).is_equal(1)
assert_int(StepCanvasTransport.scroll_step(2, 1)).is_equal(3)
func test_scroll_step_ascends_one_notch_at_a_time() -> void:
assert_int(StepCanvasTransport.scroll_step(3, -1)).is_equal(2)
func test_scroll_step_clamps_at_the_deepest_rung() -> void:
assert_int(StepCanvasTransport.scroll_step(5, 1)).is_equal(5)
func test_scroll_step_clamps_at_the_global_opener() -> void:
assert_int(StepCanvasTransport.scroll_step(0, -1)).is_equal(0)
func test_scroll_step_zero_direction_is_a_no_op() -> void:
assert_int(StepCanvasTransport.scroll_step(2, 0)).is_equal(2)
# =============================================================================
# D-243 gridunit spacing — pinned against the same metre values scale.rs uses
# =============================================================================
func test_spacing_for_rung_matches_d243_metre_values() -> void:
assert_float(StepCanvasTransport.spacing_for_rung("Global")).is_equal_approx(204_800.0, 0.01)
assert_float(StepCanvasTransport.spacing_for_rung("Region")).is_equal_approx(204_800.0, 0.01)
assert_float(StepCanvasTransport.spacing_for_rung("District")).is_equal_approx(2_048.0, 0.01)
assert_float(StepCanvasTransport.spacing_for_rung("Quarter")).is_equal_approx(512.0, 0.01)
assert_float(StepCanvasTransport.spacing_for_rung("Block")).is_equal_approx(128.0, 0.01)
assert_float(StepCanvasTransport.spacing_for_rung("Chunk")).is_equal_approx(64.0, 0.01)
# =============================================================================
# Display ratio — deep/mid 1x1, shallow ~5x5, PRESENTATION only (D-255(a))
# =============================================================================
func test_display_ratio_deep_rungs_are_one_to_one() -> void:
for rung in ["District", "Quarter", "Block", "Chunk"]:
assert_float(StepCanvasTransport.display_ratio_for_rung(rung)).is_equal_approx(1.0, 0.001)
func test_display_ratio_shallow_rungs_use_the_five_x_five_fallback() -> void:
for rung in ["Global", "Region"]:
assert_float(StepCanvasTransport.display_ratio_for_rung(rung)).is_equal_approx(5.0, 0.001)
func test_is_orbital_rung_true_only_for_global_and_region() -> void:
assert_bool(StepCanvasTransport.is_orbital_rung("Global")).is_true()
assert_bool(StepCanvasTransport.is_orbital_rung("Region")).is_true()
assert_bool(StepCanvasTransport.is_orbital_rung("District")).is_false()
assert_bool(StepCanvasTransport.is_orbital_rung("Chunk")).is_false()
# =============================================================================
# Viewport-fit extent — the client half of "viewport-sized canvas"
# =============================================================================
func test_viewport_fit_extent_at_deep_ratio_matches_viewport_pixels() -> void:
# 1x1 ratio -> extent in gridunits == viewport px, 1:1.
var extent: Vector2i = StepCanvasTransport.viewport_fit_extent(Vector2(800.0, 600.0), "Chunk")
assert_that(extent).is_equal(Vector2i(800, 600))
func test_viewport_fit_extent_at_shallow_ratio_divides_by_the_display_ratio() -> void:
var extent: Vector2i = StepCanvasTransport.viewport_fit_extent(Vector2(1000.0, 500.0), "Region")
assert_that(extent).is_equal(Vector2i(200, 100))
func test_viewport_fit_extent_clamps_to_the_fixed_canvas_max_axis() -> void:
var extent: Vector2i = StepCanvasTransport.viewport_fit_extent(
Vector2(20_000.0, 20_000.0), "Chunk"
)
assert_int(extent.x).is_equal(StepCanvasTransport.FIXED_CANVAS_MAX_AXIS)
assert_int(extent.y).is_equal(StepCanvasTransport.FIXED_CANVAS_MAX_AXIS)
func test_viewport_fit_extent_never_produces_a_zero_axis() -> void:
var extent: Vector2i = StepCanvasTransport.viewport_fit_extent(Vector2(0.0, 0.0), "Chunk")
assert_int(extent.x).is_greater_equal(1)
assert_int(extent.y).is_greater_equal(1)
# =============================================================================
# Gridunit snapping — cache-key stability for repeated "same spot" requests
# =============================================================================
func test_snap_to_gridunit_snaps_to_the_rungs_own_spacing() -> void:
var snapped: Vector2i = StepCanvasTransport.snap_to_gridunit(Vector2(2100.0, -1000.0), "District")
# District spacing = 2048 m: 2100 rounds to 1*2048=2048, -1000 rounds to 0.
assert_int(snapped.x).is_equal(2048)
assert_int(snapped.y).is_equal(0)
func test_snap_to_gridunit_is_idempotent_once_already_on_grid() -> void:
var once: Vector2i = StepCanvasTransport.snap_to_gridunit(Vector2(4096.0, 6144.0), "District")
var world_again := Vector2(once.x, once.y)
var twice: Vector2i = StepCanvasTransport.snap_to_gridunit(world_again, "District")
assert_that(once).is_equal(twice)
# =============================================================================
# World <-> canvas-local projection — the shared transform both the terrain
# and annotation layers agree on by construction
# =============================================================================
func test_world_m_to_canvas_local_centers_the_world_center_on_the_canvas_center() -> void:
var extent := Vector2i(64, 64)
var rung := "District"
var world_center := Vector2(10_000.0, 20_000.0)
var local: Vector2 = StepCanvasTransport.world_m_to_canvas_local(
world_center, world_center, rung, extent
)
var expected_center: Vector2 = StepCanvasTransport.canvas_footprint_px(rung, extent) * 0.5
assert_that(local).is_equal_approx(expected_center, Vector2(0.01, 0.01))
## world_m_to_canvas_local() and canvas_local_to_world_m() must be exact
## inverses of one another — a round trip through both must recover the
## original world point (within float tolerance). This is the invariant the
## cursor-anchored scroll step depends on: whatever point the cursor reads
## as "under it" before a scroll must be the SAME point after re-deriving
## from the new step's own frame.
func test_world_to_local_and_back_round_trips() -> void:
var extent := Vector2i(128, 96)
var rung := "Quarter"
var world_center := Vector2(50_000.0, -30_000.0)
var original_world := Vector2(51_200.0, -29_500.0)
var local: Vector2 = StepCanvasTransport.world_m_to_canvas_local(
original_world, world_center, rung, extent
)
var recovered_world: Vector2 = StepCanvasTransport.canvas_local_to_world_m(
local, world_center, rung, extent
)
assert_that(recovered_world).is_equal_approx(original_world, Vector2(0.5, 0.5))
func test_canvas_footprint_px_is_extent_times_display_ratio() -> void:
var footprint: Vector2 = StepCanvasTransport.canvas_footprint_px("Region", Vector2i(100, 50))
assert_that(footprint).is_equal(Vector2(500.0, 250.0)) # 5x5 shallow ratio
func test_half_extent_m_is_half_the_cell_count_times_spacing() -> void:
var half: float = StepCanvasTransport.half_extent_m("District", 64)
assert_float(half).is_equal_approx(64.0 * 0.5 * 2048.0, 0.01)