Files
settled-reach/client/tests/test_step_canvas_transport.gd
T
jpmschweitzerandClaude 144d35d2a5 fix(client): size Global's cell count so the display ratio divides exactly
Jeroen: 'still not filling the screen... maybe we should calculate the depth
that needs to be invented based on the canvas size somehow.' That is the fix.

The old flow picked cells from the viewport, let the SERVER impose Global's
2:1 aspect, then fitted the returned extent into the drawable area with an
INTEGER pixels-per-gridunit ratio. When that extent did not divide the area
evenly, floor() dropped a whole step — and at ratio 2 the only step below is
1, i.e. half size. Worse, because the client fitted an extent it had not
chosen, the result could fill NEITHER axis.

Inverted: global_fill_extent() chooses the cell count FROM the ratio, already
shaped 2:1, so cells * RATIO is the drawn size by construction and lands
exactly on the binding axis. Verified across window shapes — 1920x1080 fills
width, 2560x1080 fills height, 2560x1440 width, 3440x1440 height, 1280x720
width. Exactly one axis fills at every shape, which is the most a 2:1
equirectangular canvas can do in an arbitrary viewport; the other letterboxes.

Global keeps its 2:1 aspect because it is 360 degrees of longitude by 180 of
latitude — the aspect cannot follow the viewport without shearing the map.

Tests pin the invariant directly: 2:1 preserved, never overflowing the
drawable area, and never leaving slack on BOTH axes.

Client suite 1832 / 1806 passed / 0 failed / 26 skipped.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-27 21:24:21 +02:00

573 lines
27 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")
## GJ380c — the body this session eyeballed throughout.
const BODY_R_KM: float = 6_238.4
# =============================================================================
# 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 rung EXTENT — pinned against the same metre values scale.rs uses.
# Post-inversion (D-255 amendment, pair session 2026-07-26) the D-243 constant
# is the rung's CELL SIZE, not its gridunit spacing; spacing is derived below.
# =============================================================================
func test_rung_extent_matches_d243_metre_values() -> void:
assert_float(StepCanvasTransport.RUNG_EXTENT_M["Region"]).is_equal_approx(204_800.0, 0.01)
assert_float(StepCanvasTransport.RUNG_EXTENT_M["District"]).is_equal_approx(2_048.0, 0.01)
assert_float(StepCanvasTransport.RUNG_EXTENT_M["Quarter"]).is_equal_approx(512.0, 0.01)
assert_float(StepCanvasTransport.RUNG_EXTENT_M["Block"]).is_equal_approx(128.0, 0.01)
assert_float(StepCanvasTransport.RUNG_EXTENT_M["Chunk"]).is_equal_approx(64.0, 0.01)
# Global is deliberately absent — the elastic seam has no constant extent.
assert_bool(StepCanvasTransport.RUNG_EXTENT_M.has("Global")).is_false()
## The inversion's core contract: the SHORTER canvas axis spans exactly one
## cell of the rung's level, whatever the viewport shape. Must agree with the
## server's own StepCanvasRung::spacing_m() — both sides derive it from the
## same three inputs, so this test and its Rust twin pin one contract.
func test_shorter_axis_spans_exactly_one_rung_cell() -> void:
for rung in ["Region", "District", "Quarter", "Block", "Chunk"]:
var cell_m: float = StepCanvasTransport.RUNG_EXTENT_M[rung]
for extent in [Vector2i(960, 540), Vector2i(540, 960), Vector2i(700, 700)]:
var spacing: float = StepCanvasTransport.spacing_for_rung(rung, extent, BODY_R_KM)
var short: float = float(mini(extent.x, extent.y))
assert_float(spacing * short).override_failure_message(
"%s at %s: short axis spans %f m, want %f" % [rung, extent, spacing * short, cell_m]
).is_equal_approx(cell_m, 0.001)
## Spacing follows the canvas, not the rung — halving the cell count over the
## same rung doubles the pitch (a clamped canvas covers the same ground more
## coarsely; it does not cover less ground).
func test_spacing_scales_inversely_with_cell_count() -> void:
var fine: float = StepCanvasTransport.spacing_for_rung(
"District", Vector2i(960, 540), BODY_R_KM
)
var coarse: float = StepCanvasTransport.spacing_for_rung(
"District", Vector2i(480, 270), BODY_R_KM
)
assert_float(coarse).is_equal_approx(fine * 2.0, 0.001)
## Global is the one rung whose spacing comes from the body: the full 2*PI*R
## circumference wraps the canvas WIDTH. This is D-243's elastic seam, and the
## only place a body radius enters the ladder at all.
func test_global_spacing_is_circumference_over_width() -> void:
var extent := Vector2i(960, 480)
var spacing: float = StepCanvasTransport.spacing_for_rung("Global", extent, BODY_R_KM)
var circumference_m: float = TAU * BODY_R_KM * 1000.0
assert_float(spacing * 960.0).is_equal_approx(circumference_m, 1.0)
# Twice the body, twice the pitch at the same cell count.
var double: float = StepCanvasTransport.spacing_for_rung("Global", extent, BODY_R_KM * 2.0)
assert_float(double).is_equal_approx(spacing * 2.0, 0.001)
## A degenerate canvas must not divide by zero — an infinity here would poison
## every world-metre computation downstream.
func test_zero_extent_does_not_divide_by_zero() -> void:
for rung in ["Global", "Chunk"]:
var spacing: float = StepCanvasTransport.spacing_for_rung(rung, Vector2i.ZERO, BODY_R_KM)
assert_bool(is_finite(spacing)).override_failure_message(
"%s produced %f" % [rung, spacing]
).is_true()
# =============================================================================
# Display ratio — uniform 2x2 since the extent inversion, PRESENTATION only
# (D-255(a): never touches a cache key or a wire request)
# =============================================================================
## The four deep rungs share ONE display ratio, whatever its current value —
## asserting the relationship rather than the literal, so tuning
## DISPLAY_RATIO_DEEP (pair session 2026-07-26: 1.0 -> 2.0, the cost/looks
## experiment) doesn't require editing a test that isn't about the number.
## The value itself is a presentation tunable (D-255(a)); what must hold is
## that the deep rungs agree with each other and stay a texel-exact integer.
func test_display_ratio_deep_rungs_all_share_the_deep_ratio() -> void:
var deep: float = StepCanvasTransport.DISPLAY_RATIO_DEEP
for rung in ["District", "Quarter", "Block", "Chunk"]:
assert_float(StepCanvasTransport.display_ratio_for_rung(rung)).is_equal_approx(deep, 0.001)
assert_float(deep).override_failure_message(
"the deep display ratio must be a whole number of screen px per gridunit —"
+ " a fractional ratio reintroduces the sub-pixel blur D-255 texel-exactness prevents"
).is_equal_approx(floorf(deep), 0.0001)
assert_float(deep).is_greater(0.0)
## Every rung reads at the deep ratio since the extent inversion — Region
## because it is a real 262x466 km map rather than an orbital envelope, and
## Global because 5 px/gridunit asked for fewer cells than the heightmap
## already stores. Asserted as a relationship, not a literal, so the ratio
## stays a tunable.
func test_every_rung_uses_the_deep_display_ratio() -> void:
var deep: float = StepCanvasTransport.DISPLAY_RATIO_DEEP
for rung in ["Global", "Region", "District", "Quarter", "Block", "Chunk"]:
assert_float(StepCanvasTransport.display_ratio_for_rung(rung)).override_failure_message(
"%s must read at the deep ratio" % rung
).is_equal_approx(deep, 0.001)
## Region LEFT the orbital set with the extent inversion — it now rides the
## full courses-aware derive, which is most of why the top of the ladder used
## to read flat (no rivers at all above District).
func test_is_orbital_rung_true_only_for_global() -> void:
assert_bool(StepCanvasTransport.is_orbital_rung("Global")).is_true()
for rung in ["Region", "District", "Quarter", "Block", "Chunk"]:
assert_bool(StepCanvasTransport.is_orbital_rung(rung)).override_failure_message(
"%s must take the full derive" % rung
).is_false()
# =============================================================================
# Viewport-fit extent — the client half of "viewport-sized canvas"
# =============================================================================
## A deep-rung request asks for viewport_px / DISPLAY_RATIO_DEEP gridunits —
## derived from the constant, not the literal, so the ratio stays tunable
## (see the deep-ratio test above). This IS the cost lever: doubling the
## ratio quarters the requested cell count.
func test_viewport_fit_extent_at_deep_ratio_divides_by_the_deep_ratio() -> void:
var deep: float = StepCanvasTransport.DISPLAY_RATIO_DEEP
var viewport := Vector2(800.0, 600.0)
var extent: Vector2i = StepCanvasTransport.viewport_fit_extent(viewport, "Chunk")
var expected := Vector2i(int(ceil(viewport.x / deep)), int(ceil(viewport.y / deep)))
assert_that(extent).is_equal(expected)
func test_viewport_fit_extent_at_global_divides_by_the_display_ratio() -> void:
var extent: Vector2i = StepCanvasTransport.viewport_fit_extent(Vector2(1000.0, 500.0), "Global")
assert_that(extent).is_equal(Vector2i(500, 250))
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
# =============================================================================
## Post-inversion the snap lattice is the CANVAS pitch, not a per-rung
## constant. A 1x1-cell District canvas puts the whole 2,048 m cell in one
## gridunit, so this pins the same arithmetic the old test did.
func test_snap_to_gridunit_snaps_to_the_canvas_pitch() -> void:
var one_cell := Vector2i(1, 1) # pitch == the rung's whole cell: 2,048 m
var snapped: Vector2i = StepCanvasTransport.snap_to_gridunit(
Vector2(2100.0, -1000.0), "District", one_cell, BODY_R_KM
)
assert_int(snapped.x).is_equal(2048)
assert_int(snapped.y).is_equal(0)
## Sub-metre pitches must NOT collapse to the origin. The pre-inversion
## implementation multiplied by `int(spacing)`, which truncates to 0 once the
## pitch drops below 1 m — and at a real viewport Chunk's pitch is ~0.12 m, so
## every request centre would have snapped to (0,0) and the viewer would have
## silently panned to the equator on every step. Guards that regression.
func test_snap_to_gridunit_survives_sub_metre_pitch() -> void:
var extent := Vector2i(960, 540) # Chunk pitch here is ~0.119 m
var snapped: Vector2i = StepCanvasTransport.snap_to_gridunit(
Vector2(123_456.0, -7_890.0), "Chunk", extent, BODY_R_KM
)
assert_int(snapped.x).is_equal(123_456)
assert_int(snapped.y).is_equal(-7_890)
func test_snap_to_gridunit_is_idempotent_once_already_on_grid() -> void:
var one_cell := Vector2i(1, 1)
var once: Vector2i = StepCanvasTransport.snap_to_gridunit(
Vector2(4096.0, 6144.0), "District", one_cell, BODY_R_KM
)
var world_again := Vector2(once.x, once.y)
var twice: Vector2i = StepCanvasTransport.snap_to_gridunit(
world_again, "District", one_cell, BODY_R_KM
)
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, BODY_R_KM
)
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, BODY_R_KM
)
var recovered_world: Vector2 = StepCanvasTransport.canvas_local_to_world_m(
local, world_center, rung, extent, BODY_R_KM
)
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("Global", Vector2i(100, 50))
assert_that(footprint).is_equal(Vector2(200.0, 100.0)) # 2x2 deep ratio
## Half-extent is now half the rung's own CELL on the short axis, whatever
## the cell count — that is the inversion restated as an invariant.
func test_half_extent_m_is_half_the_rung_cell() -> void:
for extent in [Vector2i(64, 64), Vector2i(960, 540), Vector2i(7, 3)]:
var half: float = StepCanvasTransport.half_extent_m("District", extent, BODY_R_KM)
assert_float(half).is_equal_approx(2048.0 * 0.5, 0.01)
# =============================================================================
# Rung liveness — replaces the T-1189 extent cap, which the D-255 extent
# inversion superseded (pair session 2026-07-26). A canvas can no longer
# over-request a body: its cell count is viewport-driven and its GROUND extent
# is the rung's own cell size. The residual question is whether a rung's cell
# is bigger than the whole body, which is answered by OMITTING the rung rather
# than by serving a squashed canvas.
# =============================================================================
func test_cap_extent_to_body_is_now_a_no_op() -> void:
var extent := Vector2i(384, 216)
assert_that(StepCanvasTransport.cap_extent_to_body(extent, "Region", Vector2i(177, 88))).is_equal(
extent
)
## All six rungs are live on every populated body in systems.db — the smallest
## is a 734 km-radius moon whose 4,611 km circumference swallows a Region cell
## seventeen times over. Pinned so a future rung resize that would silently
## kill a rung on inhabited worlds fails here first.
func test_every_rung_is_live_on_the_smallest_populated_body() -> void:
for rung in ["Global", "Region", "District", "Quarter", "Block", "Chunk"]:
assert_bool(StepCanvasTransport.is_rung_live_on_body(rung, 733.9)).override_failure_message(
"%s must stay live on the smallest populated body" % rung
).is_true()
## ...but a rung whose cell exceeds the body is not a step down the ladder at
## all — it would zoom OUT. The only two such bodies in systems.db are Phobos
## (11 km) and Deimos (6 km), and Sol is out of generator bounds entirely, so
## this guard never fires on in-scope content. Kept because it is free and the
## failure it prevents is silent.
func test_region_is_not_live_on_a_sub_region_body() -> void:
assert_bool(StepCanvasTransport.is_rung_live_on_body("Region", 6.0)).is_false()
assert_bool(StepCanvasTransport.is_rung_live_on_body("District", 6.0)).is_true()
## An absent radius (an asteroid belt is not a sphere and has no
## equirectangular surface at all) must not silently collapse the ladder —
## the caller has a bigger problem than liveness and should see it.
func test_liveness_is_permissive_without_a_radius() -> void:
assert_bool(StepCanvasTransport.is_rung_live_on_body("Region", 0.0)).is_true()
# =============================================================================
# T-1189/T-1192: shared letterbox/centering mechanism
# =============================================================================
func test_center_offset_centers_a_smaller_canvas_in_a_larger_viewport() -> void:
var offset: Vector2 = StepCanvasTransport.center_offset(
Vector2(800.0, 400.0), Vector2(1920.0, 1080.0)
)
assert_that(offset).is_equal(Vector2((1920.0 - 800.0) * 0.5, (1080.0 - 400.0) * 0.5))
func test_center_offset_is_zero_when_canvas_exactly_fills_the_viewport() -> void:
var offset: Vector2 = StepCanvasTransport.center_offset(
Vector2(1920.0, 1080.0), Vector2(1920.0, 1080.0)
)
assert_that(offset).is_equal(Vector2.ZERO)
func test_center_offset_goes_negative_when_the_canvas_overflows_the_viewport() -> void:
# A canvas bigger than the viewport on an axis crops rather than shrinks
# (matches every fixed rung's own "canvas can exceed the viewport"
# precedent) — a negative offset on that axis is the correct, honest
# result, not clamped to zero.
var offset: Vector2 = StepCanvasTransport.center_offset(
Vector2(2000.0, 400.0), Vector2(1920.0, 1080.0)
)
assert_float(offset.x).is_less(0.0)
assert_float(offset.y).is_greater(0.0)
## D-255 texel-exactness: an integer-px/gridunit canvas (fit_scale_ratio())
## can still land on an ODD-vs-viewport remainder that halves to a .5px
## boundary — GJ1c's own 1593x792 footprint in a 1628x1080 available area
## ((1628-1593)*0.5 = 17.5) is the real case this guards. The offset must be
## FLOORED to a whole pixel, never left fractional (a fractional offset
## would blur the texel grid this whole mechanism exists to keep crisp).
func test_center_offset_floors_a_half_pixel_remainder_to_a_whole_pixel() -> void:
var offset: Vector2 = StepCanvasTransport.center_offset(
Vector2(1593.0, 792.0), Vector2(1628.0, 1080.0)
)
assert_float(offset.x).is_equal_approx(17.0, 0.001)
assert_float(offset.y).is_equal_approx(144.0, 0.001)
# =============================================================================
# T-1192: Global integer PIXELS-PER-GRIDUNIT fit — D-255 amendment
# 2026-07-25 (rung-0's display ratio is viewport-fitted per body to an
# INTEGER px/gridunit ratio; the fractional-fit branch a prior round of this
# ticket carried was a mis-citation of D-255 — the record's actual mandate
# is unqualified texel-exact — and independently a real bug (Hoshe): a
# canvas exceeding the viewport on one axis could fit_scale() down to
# sub-1x, violating the "never below native resolution" invariant. Fixed at
# the root by fitting the INTEGER ratio, not a fraction of the whole
# footprint — this lattice is fine-grained (per gridunit, not per 5px-base
# footprint step), so the coverage-threshold escape hatch this section used
# to need does not come up: the achievable ratios are close enough together
# that the largest one that fits is always a good use of the frame.
# =============================================================================
func test_fit_scale_ratio_picks_the_largest_px_per_gridunit_that_fits_both_axes() -> void:
# GJ1c reference case (T-1183 eyeball): 177x88 gridunits against a full
# 1920x1080 viewport (no legend reservation) — floor(1920/177)=10,
# floor(1080/88)=12, the tighter axis (x) wins.
var ratio: int = StepCanvasTransport.fit_scale_ratio(
Vector2(177.0, 88.0), Vector2(1920.0, 1080.0)
)
assert_int(ratio).is_equal(10)
## The GJ1c reference case AFTER the legend column is reserved (T-1192, the
## StepCanvasViewer end-to-end scenario): available area shrinks to
## 1628x1080 — floor(1628/177)=9, floor(1080/88)=12 — the lead's own cited
## reference number for this exact case.
func test_fit_scale_ratio_matches_the_gj1c_legend_reserved_reference_case() -> void:
var ratio: int = StepCanvasTransport.fit_scale_ratio(
Vector2(177.0, 88.0), Vector2(1628.0, 1080.0)
)
assert_int(ratio).is_equal(9)
## GJ1c at 4K, legend column reserved (3840 - 292 = 3548 available) — the
## lead's own cited reference number for a large viewport.
func test_fit_scale_ratio_matches_the_gj1c_4k_reference_case() -> void:
var ratio: int = StepCanvasTransport.fit_scale_ratio(
Vector2(177.0, 88.0), Vector2(3548.0, 2160.0)
)
assert_int(ratio).is_equal(20)
## Hoshe's repro, now a regression test: a canvas whose BASE footprint
## (885x440 px at the old 5x5-multiple framing) exceeds a narrow 348px-wide
## available viewport used to make fit_scale() return 0.39x — a sub-1x
## downscale violating "never below native resolution". The integer
## px/gridunit ratio floors at 1 instead: draws at native (1 px/gridunit)
## and crops/pans, exactly like every other fixed rung's own
## exceeds-the-viewport precedent.
func test_fit_scale_ratio_floors_at_one_when_gridunits_exceed_a_narrow_viewport() -> void:
var ratio: int = StepCanvasTransport.fit_scale_ratio(
Vector2(177.0, 88.0), Vector2(348.0, 1080.0)
)
assert_int(ratio).is_equal(1)
## A small moon's Global canvas (few gridunits) in a large viewport still
## only wins as large an integer ratio as fits — no special-casing for a
## small canvas, same formula, much larger achievable ratio.
func test_fit_scale_ratio_a_tiny_moon_canvas_wins_a_large_integer_ratio() -> void:
var ratio: int = StepCanvasTransport.fit_scale_ratio(
Vector2(20.0, 20.0), Vector2(1920.0, 1080.0)
)
assert_int(ratio).is_equal(54)
## Exact-fit boundary: the viewport is PRECISELY `extent * 3` on both axes —
## the ratio must land exactly on 3, not overshoot to 4 (floor(exact) must
## not round up) and not undershoot to 2 (an exact multiple is a legal fit,
## not treated as "just barely doesn't fit").
func test_fit_scale_ratio_exact_multiple_boundary_lands_on_the_multiple() -> void:
var ratio: int = StepCanvasTransport.fit_scale_ratio(
Vector2(100.0, 50.0), Vector2(300.0, 150.0)
)
assert_int(ratio).is_equal(3)
## One pixel short of the exact multiple must drop to the NEXT integer down
## — confirms the boundary isn't fuzzy/off-by-one in the other direction.
func test_fit_scale_ratio_one_pixel_short_of_the_multiple_drops_a_step() -> void:
var ratio: int = StepCanvasTransport.fit_scale_ratio(
Vector2(100.0, 50.0), Vector2(299.0, 150.0)
)
assert_int(ratio).is_equal(2)
func test_fit_scale_ratio_is_bounded_by_the_tighter_axis() -> void:
# Wide-but-short viewport: x could fit 10x, y only fits 1x — the smaller
# wins (never overflow either axis).
var ratio: int = StepCanvasTransport.fit_scale_ratio(
Vector2(100.0, 100.0), Vector2(1000.0, 150.0)
)
assert_int(ratio).is_equal(1)
func test_fit_scale_ratio_handles_a_zero_extent_axis_without_dividing_by_zero() -> void:
var ratio: int = StepCanvasTransport.fit_scale_ratio(Vector2.ZERO, Vector2(800.0, 600.0))
assert_int(ratio).is_equal(1)
## fit_scale_from_ratio() converts the INTEGER px/gridunit ratio into the
## `_canvas.scale` multiplier applied on top of a texture already rendered
## at the rung's own base display ratio (5x5 for Global) — this multiplier
## itself may be a non-integer float (9/5 = 1.8), and that is CORRECT:
## texel-exactness is about the final ratio being a whole number, not the
## Node2D scale field.
func test_fit_scale_from_ratio_divides_by_the_base_display_ratio() -> void:
var scale: float = StepCanvasTransport.fit_scale_from_ratio(9, 5.0)
assert_float(scale).is_equal_approx(1.8, 0.001)
func test_fit_scale_from_ratio_at_the_gj1c_4k_reference_case() -> void:
var scale: float = StepCanvasTransport.fit_scale_from_ratio(20, 5.0)
assert_float(scale).is_equal_approx(4.0, 0.001)
func test_fit_scale_from_ratio_handles_a_zero_base_ratio_without_dividing_by_zero() -> void:
var scale: float = StepCanvasTransport.fit_scale_from_ratio(9, 0.0)
assert_float(scale).is_greater(0.0)
# =============================================================================
# T-1192: shared legend-column reservation constant
# =============================================================================
## StepCanvasTransport is the CANONICAL source for this width (T-1192
## review fix) — step_canvas_legend.gd's own RESERVED_COLUMN_PX is now a
## direct read of THIS constant, not an independently-typed literal, so
## there is no separate cross-pin test needed here; this just pins the
## canonical value itself (260 panel width + 16*2 margin = 292).
func test_legend_column_px_is_the_panel_width_plus_margin_on_both_sides() -> void:
assert_float(StepCanvasTransport.LEGEND_COLUMN_PX).is_equal_approx(292.0, 0.01)
## Global must FILL its drawable area on at least one axis, at every window
## shape (Jeroen, 2026-07-27: "it looks unacceptable if it does not fill the
## screen across at least one axis"). Exactly one axis can fill — the canvas is
## equirectangular 2:1 and the aspect cannot follow the viewport — so the other
## letterboxes, and which one binds depends on whether the drawable area is
## wider or narrower than 2:1.
func test_global_fill_extent_fills_one_axis_at_every_window_shape() -> void:
var ratio: float = StepCanvasTransport.DISPLAY_RATIO_DEEP
for drawable in [
Vector2(1628.0, 1080.0), # 1920 window minus legend — wider than 2:1? no
Vector2(2268.0, 1080.0), # 2560 window — wider than 2:1, height binds
Vector2(2268.0, 1440.0),
Vector2(3148.0, 1440.0),
Vector2(988.0, 720.0),
]:
var cells: Vector2i = StepCanvasTransport.global_fill_extent(drawable)
assert_int(cells.x).override_failure_message(
"%s: canvas must stay 2:1, got %s" % [drawable, cells]
).is_equal(cells.y * 2)
var drawn := Vector2(float(cells.x) * ratio, float(cells.y) * ratio)
assert_bool(drawn.x <= drawable.x and drawn.y <= drawable.y).override_failure_message(
"%s: drawn %s overflows the drawable area" % [drawable, drawn]
).is_true()
# The binding axis must be within one ratio-step of exact — i.e. there
# is no room left for another whole gridunit on that axis.
var slack_x: float = drawable.x - drawn.x
var slack_y: float = drawable.y - drawn.y
assert_bool(slack_x < ratio * 2.0 or slack_y < ratio).override_failure_message(
"%s: fills NEITHER axis — drawn %s leaves %s slack" % [drawable, drawn, Vector2(slack_x, slack_y)]
).is_true()
## A degenerate drawable area must still yield a legal 2:1 canvas rather than
## a zero-row one, which would divide by zero downstream in spacing_for_rung().
func test_global_fill_extent_floors_at_one_row() -> void:
var cells: Vector2i = StepCanvasTransport.global_fill_extent(Vector2(1.0, 1.0))
assert_int(cells.y).is_greater_equal(1)
assert_int(cells.x).is_equal(cells.y * 2)