Files
settled-reach/client/tests/test_step_canvas_transport.gd
T
jpmschweitzerandClaude 0a0419abcc feat(client): invert the Atlas rung relation — rung sets extent, not spacing
A rung used to fix the gridunit SPACING, with the canvas extent falling out
of spacing x cell count. That is why the top of the ladder was unusable: at
REGION_M spacing a viewport-sized canvas spanned ~251,658 km — six times
around a rocky body — so the Region rung capped to the body and redrew the
Global picture pixel-for-pixel. "Global and region look the same" was not a
rendering bug; it was this relation, stated in metres.

Inverted: a rung fixes the EXTENT and the spacing falls out of the canvas
size. The shorter viewport axis spans exactly one cell of the rung's level,
so a widescreen window shows more ground on the long axis rather than less
on the short one. Every rung now shows the ground its name promises —
Region 262x466 km, District 4.1x7.3 km — and the canvas cell count is
viewport-driven and identical at every rung, so derive cost no longer varies
with depth and resize is free.

Consequences that fell out of the inversion rather than being chosen:

- Region leaves the orbital derive set. It was envelope-only because at
  251,658 km nothing finer made sense; at 262 km it is a genuine provincial
  map and takes the full courses-aware derive. Region having no rivers at
  all was much of why the top of the ladder read flat. It also joins the
  deep display ratio for the same reason.
- The S2 station-spacing floor is deleted, not retuned. It guarded an
  O(1/spacing) blowup that the inversion makes structurally impossible (the
  canvas cell count is now constant across rungs, so stations-per-course is
  bounded however deep you scroll). Kept, it would do active harm in the
  opposite direction: a 2,048 m pitch across a 3.6 km District canvas places
  two stations and draws every river as a straight line. Station placement
  gets its own generator pass.
- cap_extent_to_body is superseded and now a documented no-op. A canvas can
  no longer over-request a body by construction. The residual question —
  whether a rung's cell exceeds the whole body — is liveness, not capping,
  and is_rung_live_on_body() answers it by omitting the rung. Empirically it
  never fires on inhabited content: all six rungs are live on all 271
  populated bodies with a radius.
- snap_to_gridunit no longer truncates its multiplier to int. Post-inversion
  the deep rungs run sub-metre (Chunk ~0.12 m at a 1080 px short axis), where
  int(spacing) floors to zero and would collapse every request centre onto
  the origin.

Both sides derive spacing from the same three inputs (rung, echoed cell
extent, body radius) rather than one telling the other, so there is nothing
to keep in sync beyond the constant table itself. Body radius already
reaches the viewer via enter(); no wire change.

Pair session with Jeroen, 2026-07-26. D-243/D-255 amendments to be backfiled.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-26 21:38:08 +02:00

528 lines
24 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 — deep/mid 1x1, shallow ~5x5, PRESENTATION only (D-255(a))
# =============================================================================
## 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)
## Only Global keeps the shallow fallback now — Region joined the deep band
## with the extent inversion (it is a real 262x466 km map, not an orbital
## envelope).
func test_only_global_uses_the_shallow_display_ratio() -> void:
assert_float(StepCanvasTransport.display_ratio_for_rung("Global")).is_equal_approx(5.0, 0.001)
assert_float(StepCanvasTransport.display_ratio_for_rung("Region")).is_equal_approx(
StepCanvasTransport.DISPLAY_RATIO_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_shallow_ratio_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(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
# =============================================================================
## 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(500.0, 250.0)) # 5x5 shallow 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. systems.db has two such rocks (6 km and 11 km
## radius, both uninhabited).
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)