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:
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user