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>
This commit is contained in:
2026-07-27 21:24:21 +02:00
co-authored by Claude
parent d36d458b74
commit 144d35d2a5
6 changed files with 85 additions and 3 deletions
@@ -531,3 +531,42 @@ func test_fit_scale_from_ratio_handles_a_zero_base_ratio_without_dividing_by_zer
## 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)
+3 -3
View File
@@ -818,9 +818,9 @@ func test_request_extent_ignores_a_not_yet_laid_out_viewport() -> void:
StepCanvasViewer.FALLBACK_VIEWPORT_PX.x - StepCanvasTransport.LEGEND_COLUMN_PX,
StepCanvasViewer.FALLBACK_VIEWPORT_PX.y
)
var expected: Vector2i = StepCanvasTransport.viewport_fit_extent(
drawable, v.get_held_rung()
)
# Global sizes via global_fill_extent (2:1, chosen so the display ratio
# divides exactly), not the generic viewport fit.
var expected: Vector2i = StepCanvasTransport.global_fill_extent(drawable)
assert_that(extent).override_failure_message(
"a %s viewport must fall back, not be taken literally — got %s" % [degenerate, extent]
).is_equal(expected)