fix(ui): PR #205 review round — integer px-per-gridunit fit replaces fractional (T-1189, T-1192)
The fractional fit branch is deleted, resolving both review findings at the root: tyre showed its comments cited D-255 for an exception the record does not contain (the language came from the lead's ticket text, not governance), and hoshe showed it returned sub-1x for a canvas exceeding the viewport on one axis. Replacement: fit_scale_ratio() chooses the largest integer pixels-per-gridunit R fitting both legend-reserved axes, floored at 1 (over-viewport draws native and crops like every fixed rung) — the fine-grained integer lattice (GJ1c 1080p -> 9px/gu = 1593x792, ~98% width; 4K -> 20) that makes the fractional hatch unnecessary. center_offset() floors to whole pixels (half-pixel centering would blur the texel grid). Doc comments cite the real sanction (D-255 amendment 2026-07-25, this branch). Legend column constant is now canonical in transport, read directly by the legend (was an independently-typed literal); its test asserts real geometry. New regression test proves _global_body_extent clears on body switch and never caps another body's requests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -71,12 +71,24 @@ func test_reposition_sets_a_fixed_panel_margin_position() -> void:
|
||||
assert_that(legend.position).is_equal(Vector2(LegendScript.PANEL_MARGIN, 60.0))
|
||||
|
||||
|
||||
## T-1192: StepCanvasTransport.LEGEND_COLUMN_PX (the Global fit-scale
|
||||
## reservation StepCanvasViewer applies) must stay derived from this SAME
|
||||
## panel's own width/margin — a drift here would silently reopen the
|
||||
## "legend overlaps the canvas" defect on one side while the OTHER side
|
||||
## thinks it already reserved enough room.
|
||||
func test_reserved_column_px_matches_the_transport_sides_own_constant() -> void:
|
||||
assert_float(LegendScript.RESERVED_COLUMN_PX).is_equal_approx(
|
||||
StepCanvasTransport.LEGEND_COLUMN_PX, 0.01
|
||||
)
|
||||
## T-1192 review fix: RESERVED_COLUMN_PX is now a direct read of
|
||||
## StepCanvasTransport.LEGEND_COLUMN_PX (not an independently-typed
|
||||
## literal), so a const-vs-const cross-pin test would be structurally
|
||||
## unable to fail — it is the SAME value by construction. What's still
|
||||
## worth proving directly is the geometric guarantee that constant is FOR:
|
||||
## the legend's own ACTUAL laid-out right edge (`reposition()`'s position.x
|
||||
## + the panel's real minimum width) must land at or before the reserved
|
||||
## column, with room to spare — never right up against it, and certainly
|
||||
## never past it into where the canvas is centered from.
|
||||
func test_legend_actual_right_edge_stays_inside_the_reserved_column() -> void:
|
||||
var v: StepCanvasViewer = auto_free(StepCanvasViewer.new())
|
||||
add_child(v)
|
||||
var legend = LegendScript.new(v)
|
||||
auto_free(legend)
|
||||
legend.reposition()
|
||||
|
||||
var actual_right_edge: float = legend.position.x + legend.custom_minimum_size.x
|
||||
assert_float(actual_right_edge).override_failure_message(
|
||||
"the legend's real laid-out right edge must stay inside the column"
|
||||
+ " StepCanvasViewer reserves for it, with margin to spare"
|
||||
).is_less(StepCanvasTransport.LEGEND_COLUMN_PX)
|
||||
|
||||
@@ -280,72 +280,143 @@ func test_center_offset_goes_negative_when_the_canvas_overflows_the_viewport() -
|
||||
assert_float(offset.y).is_greater(0.0)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# T-1192: Global integer-fit scale — D-255 texel-exactness
|
||||
# =============================================================================
|
||||
|
||||
|
||||
func test_integer_fit_scale_picks_the_largest_multiple_that_fits_both_axes() -> void:
|
||||
# GJ1c reference case: 177x88 texels at the 5x5 shallow display ratio =
|
||||
# 885x440 px footprint, fit against a full 1920x1080 viewport.
|
||||
var scale: int = StepCanvasTransport.integer_fit_scale(
|
||||
Vector2(885.0, 440.0), Vector2(1920.0, 1080.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_int(scale).is_equal(2)
|
||||
assert_float(offset.x).is_equal_approx(17.0, 0.001)
|
||||
assert_float(offset.y).is_equal_approx(144.0, 0.001)
|
||||
|
||||
|
||||
func test_integer_fit_scale_is_bounded_by_the_tighter_axis() -> void:
|
||||
# Wide-but-short viewport: x could fit 4x, y only fits 1x — the smaller
|
||||
# =============================================================================
|
||||
# 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 scale: int = StepCanvasTransport.integer_fit_scale(
|
||||
var ratio: int = StepCanvasTransport.fit_scale_ratio(
|
||||
Vector2(100.0, 100.0), Vector2(1000.0, 150.0)
|
||||
)
|
||||
assert_int(scale).is_equal(1)
|
||||
assert_int(ratio).is_equal(1)
|
||||
|
||||
|
||||
func test_integer_fit_scale_never_drops_below_one() -> void:
|
||||
# A canvas larger than the viewport still gets scale 1 (draw at native
|
||||
# size and let it exceed/crop), never a shrink below native.
|
||||
var scale: int = StepCanvasTransport.integer_fit_scale(
|
||||
Vector2(3000.0, 3000.0), Vector2(800.0, 600.0)
|
||||
)
|
||||
assert_int(scale).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)
|
||||
|
||||
|
||||
func test_integer_fit_scale_handles_a_zero_canvas_axis_without_dividing_by_zero() -> void:
|
||||
var scale: int = StepCanvasTransport.integer_fit_scale(Vector2.ZERO, Vector2(800.0, 600.0))
|
||||
assert_int(scale).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_matches_the_integer_fit_when_coverage_is_high() -> void:
|
||||
# The GJ1c full-viewport case: integer 2x covers well over the
|
||||
# FIT_MIN_COVERAGE_RATIO bar, so fit_scale() must agree with
|
||||
# integer_fit_scale() exactly (no fractional fallback).
|
||||
var scale: float = StepCanvasTransport.fit_scale(
|
||||
Vector2(885.0, 440.0), Vector2(1920.0, 1080.0)
|
||||
)
|
||||
assert_float(scale).is_equal_approx(2.0, 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)
|
||||
|
||||
|
||||
## The GJ1c reference case AFTER the legend column is reserved (T-1192):
|
||||
## available area shrinks to 1628x1080, so the 2x integer candidate (1770 px
|
||||
## wide) no longer fits — integer_fit_scale() drops to 1x, which only covers
|
||||
## ~41% of the tighter available axis, well under FIT_MIN_COVERAGE_RATIO —
|
||||
## fit_scale() must fall back to the non-integer uniform fit that fills the
|
||||
## tighter (x) axis exactly, not settle for the sparse 1x frame.
|
||||
func test_fit_scale_falls_back_to_fractional_fit_on_excessive_letterboxing() -> void:
|
||||
var scale: float = StepCanvasTransport.fit_scale(
|
||||
Vector2(885.0, 440.0), Vector2(1628.0, 1080.0)
|
||||
)
|
||||
assert_float(scale).is_greater(1.0)
|
||||
assert_float(scale).is_less(2.0)
|
||||
# The fractional fit fills the tighter (x) axis exactly.
|
||||
assert_float(885.0 * scale).is_equal_approx(1628.0, 0.01)
|
||||
|
||||
|
||||
func test_fit_scale_handles_a_zero_canvas_axis_without_dividing_by_zero() -> void:
|
||||
var scale: float = StepCanvasTransport.fit_scale(Vector2.ZERO, Vector2(800.0, 600.0))
|
||||
assert_float(scale).is_equal_approx(1.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)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
@@ -353,9 +424,10 @@ func test_fit_scale_handles_a_zero_canvas_axis_without_dividing_by_zero() -> voi
|
||||
# =============================================================================
|
||||
|
||||
|
||||
func test_legend_column_px_is_positive_and_matches_the_legend_panels_own_sizing() -> void:
|
||||
# Pinned against step_canvas_legend.gd's own RESERVED_COLUMN_PX (260 +
|
||||
# 16*2 = 292) — the two constants must never drift apart, since the
|
||||
# "beside, never over" guarantee depends on both sides agreeing on the
|
||||
# SAME reserved width.
|
||||
## 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)
|
||||
|
||||
@@ -529,6 +529,42 @@ func test_district_request_extent_is_never_capped_by_the_global_extent() -> void
|
||||
assert_that(extent).is_equal(uncapped)
|
||||
|
||||
|
||||
## Hoshe (review round 2): _global_body_extent must NOT leak across a body
|
||||
## switch — land a Global extent for body A, enter() body B, and prove BOTH
|
||||
## that the cap source itself reads back ZERO for the new body AND that a
|
||||
## Region request for body B before ITS OWN Global echo lands goes out
|
||||
## UNCAPPED (never silently capped by body A's leftover grid). The reset
|
||||
## already exists at StepCanvasViewer.enter() ("a new body has its own
|
||||
## region grid") — this proves it, through the real enter()/land/enter()
|
||||
## sequence rather than asserting the field directly only.
|
||||
func test_global_body_extent_resets_on_a_different_body_and_does_not_leak() -> void:
|
||||
var v: StepCanvasViewer = auto_free(StepCanvasViewer.new())
|
||||
add_child(v)
|
||||
v.size = Vector2(1920.0, 1080.0)
|
||||
|
||||
v.enter({"body_id": "T1189_extent_letterbox_test_body", "body_radius_km": 6371.0}, {})
|
||||
TestStepCanvasViewer._land_global_canvas(v, GJ1C_GLOBAL_EXTENT.x, GJ1C_GLOBAL_EXTENT.y)
|
||||
assert_that(v._global_body_extent).override_failure_message(
|
||||
"test setup: body A must actually have a landed cap source"
|
||||
).is_equal(GJ1C_GLOBAL_EXTENT)
|
||||
|
||||
v.enter({"body_id": "T1189_extent_cap_body_b", "body_radius_km": 3000.0}, {})
|
||||
|
||||
assert_that(v._global_body_extent).override_failure_message(
|
||||
"entering a DIFFERENT body must reset the cap source to ZERO — body"
|
||||
+ " A's region grid must never leak into body B's requests"
|
||||
).is_equal(Vector2i.ZERO)
|
||||
|
||||
v._scroll_rung(1, Vector2(960.0, 540.0)) # Region, for body B — no Global echo yet
|
||||
var extent: Vector2i = v._request_extent()
|
||||
var uncapped: Vector2i = StepCanvasTransport.viewport_fit_extent(v.size, "Region")
|
||||
assert_that(extent).override_failure_message(
|
||||
"body B's Region request, before body B's own Global echo has"
|
||||
+ " landed, must go out UNCAPPED — never capped by body A's stale"
|
||||
+ " leftover region-grid extent"
|
||||
).is_equal(uncapped)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# T-1189/T-1192: shared letterbox mechanism — centering + Global fit scale.
|
||||
# =============================================================================
|
||||
@@ -551,28 +587,46 @@ func test_global_canvas_arrival_centers_the_view_not_top_left() -> void:
|
||||
).is_not_equal(Vector2.ZERO)
|
||||
|
||||
|
||||
## D-255 texel-exactness (T-1192): at a large (4K-class) viewport, GJ1c's
|
||||
## 885x440 raw footprint (177x88 texels x 5x5 shallow display ratio) clears
|
||||
## the coverage bar even AFTER the legend column is reserved, so the
|
||||
## INTEGER fit wins outright — verified end-to-end through the real viewer
|
||||
## wiring, not just the pure transport function this mirrors. (The T-1183
|
||||
## reference 1920x1080 viewport is deliberately NOT used here — at that
|
||||
## size the legend-column reservation starves the integer candidate below
|
||||
## the coverage bar and the fractional escape hatch fires instead, covered
|
||||
## separately by test_global_canvas_uses_fractional_fit_when_legend_column_
|
||||
## starves_the_integer_fit() below.)
|
||||
func test_global_canvas_scale_is_an_integer_multiple_of_the_raw_footprint() -> void:
|
||||
## D-255 amendment 2026-07-25 texel-exactness: the `_canvas.scale` value
|
||||
## itself is NOT required to be a whole number (9/5 = 1.8 is entirely
|
||||
## legitimate) — what MUST be exact is the resulting on-screen
|
||||
## pixels-per-gridunit ratio. Verified end-to-end through the real viewer
|
||||
## wiring: `_canvas_scale * base_display_ratio` (the rung's own display
|
||||
## ratio, 5.0 for Global) must land on an exact integer, for both the
|
||||
## legend-reserved 1920x1080 case (R=9, scale=1.8) AND the 4K case (R=20,
|
||||
## scale=4.0) — the SAME formula, no separate coverage-threshold branch.
|
||||
func test_global_canvas_scale_yields_an_exact_integer_pixels_per_gridunit_ratio() -> void:
|
||||
var v: StepCanvasViewer = auto_free(StepCanvasViewer.new())
|
||||
add_child(v)
|
||||
v.size = Vector2(1920.0, 1080.0)
|
||||
v.enter({"body_id": "T1189_extent_letterbox_test_body", "body_radius_km": 6371.0}, {})
|
||||
TestStepCanvasViewer._land_global_canvas(v, GJ1C_GLOBAL_EXTENT.x, GJ1C_GLOBAL_EXTENT.y)
|
||||
|
||||
assert_that(v._canvas.scale).is_equal(Vector2(v._canvas_scale, v._canvas_scale))
|
||||
var base_ratio: float = StepCanvasTransport.display_ratio_for_rung("Global")
|
||||
var effective_px_per_gridunit: float = v._canvas_scale * base_ratio
|
||||
assert_float(effective_px_per_gridunit).override_failure_message(
|
||||
"the FINAL on-screen pixels-per-gridunit ratio must be an exact"
|
||||
+ " integer even when the _canvas.scale multiplier itself is not"
|
||||
).is_equal_approx(roundf(effective_px_per_gridunit), 0.001)
|
||||
# The lead's own cited reference number for this exact scenario.
|
||||
assert_float(effective_px_per_gridunit).is_equal_approx(9.0, 0.001)
|
||||
|
||||
|
||||
## Same invariant at a large (4K-class) viewport, where the integer ratio
|
||||
## (20) happens to make the _canvas.scale multiplier itself a whole number
|
||||
## too (20/5 = 4.0) — confirms the 1080p case above isn't a coincidence of
|
||||
## a small viewport, just the same formula at a different achievable ratio.
|
||||
func test_global_canvas_scale_at_4k_also_yields_an_exact_integer_ratio() -> void:
|
||||
var v: StepCanvasViewer = auto_free(StepCanvasViewer.new())
|
||||
add_child(v)
|
||||
v.size = Vector2(3840.0, 2160.0)
|
||||
v.enter({"body_id": "T1189_extent_letterbox_test_body", "body_radius_km": 6371.0}, {})
|
||||
TestStepCanvasViewer._land_global_canvas(v, GJ1C_GLOBAL_EXTENT.x, GJ1C_GLOBAL_EXTENT.y)
|
||||
|
||||
assert_that(v._canvas.scale).is_equal(Vector2(v._canvas_scale, v._canvas_scale))
|
||||
assert_float(v._canvas_scale).override_failure_message(
|
||||
"the Global fit scale must be a whole number of texture pixels"
|
||||
+ " when it clears the coverage bar (D-255 texel-exactness)"
|
||||
).is_equal_approx(roundf(v._canvas_scale), 0.001)
|
||||
var base_ratio: float = StepCanvasTransport.display_ratio_for_rung("Global")
|
||||
var effective_px_per_gridunit: float = v._canvas_scale * base_ratio
|
||||
assert_float(effective_px_per_gridunit).is_equal_approx(20.0, 0.001)
|
||||
|
||||
|
||||
## Fixed rungs must NEVER receive the Global fit multiplier — `_canvas.scale`
|
||||
@@ -632,23 +686,23 @@ func test_resize_recenters_an_already_held_global_canvas() -> void:
|
||||
).is_not_equal(offset_before)
|
||||
|
||||
|
||||
## Small-canvas fallback (D-255's own escape hatch): once the legend column
|
||||
## eats enough of the available width that the integer fit falls under
|
||||
## FIT_MIN_COVERAGE_RATIO, the viewer must fall back to the fractional fit
|
||||
## rather than settling for a sparse integer frame — end-to-end through the
|
||||
## real viewer, mirroring test_fit_scale_falls_back_to_fractional_fit_on_
|
||||
## excessive_letterboxing in test_step_canvas_transport.gd.
|
||||
func test_global_canvas_uses_fractional_fit_when_legend_column_starves_the_integer_fit() -> void:
|
||||
## Hoshe (review round 2): a narrow viewport where GJ1c's canvas exceeds
|
||||
## the available width on the gridunit lattice itself (177 gridunits >
|
||||
## available px after the legend column is reserved) used to make the OLD
|
||||
## fractional-fit branch return a sub-1x scale (0.39x) — a real downscale
|
||||
## below native resolution, violating "never below native". The integer
|
||||
## px/gridunit ratio floors at 1 instead: `_canvas_scale` must never drop
|
||||
## under 1.0, end-to-end through the real viewer wiring, not just the pure
|
||||
## transport function this mirrors.
|
||||
func test_global_canvas_scale_never_drops_below_native_on_a_narrow_viewport() -> void:
|
||||
var v: StepCanvasViewer = auto_free(StepCanvasViewer.new())
|
||||
add_child(v)
|
||||
v.size = Vector2(1920.0, 1080.0)
|
||||
v.size = Vector2(348.0 + StepCanvasTransport.LEGEND_COLUMN_PX, 1080.0)
|
||||
v.enter({"body_id": "T1189_extent_letterbox_test_body", "body_radius_km": 6371.0}, {})
|
||||
TestStepCanvasViewer._land_global_canvas(v, GJ1C_GLOBAL_EXTENT.x, GJ1C_GLOBAL_EXTENT.y)
|
||||
|
||||
# GJ1c at 1920x1080 with the legend column reserved: 2x no longer fits
|
||||
# (1770 > 1628 available), 1x covers only ~41% of the tighter axis — well
|
||||
# under the 75% coverage bar, so a non-integer fit must have been chosen.
|
||||
assert_float(v._canvas_scale).override_failure_message(
|
||||
"the reserved legend column must starve the 2x integer candidate at"
|
||||
+ " this reference viewport, forcing the fractional fit"
|
||||
).is_greater(1.0)
|
||||
var base_ratio: float = StepCanvasTransport.display_ratio_for_rung("Global")
|
||||
assert_float(v._canvas_scale * base_ratio).override_failure_message(
|
||||
"the effective pixels-per-gridunit ratio must floor at 1 (native),"
|
||||
+ " never a sub-1x downscale, even on a viewport this narrow"
|
||||
).is_equal_approx(1.0, 0.001)
|
||||
|
||||
Reference in New Issue
Block a user