fix(client): Global drew at half size — request and fit disagreed about the legend

Jeroen: 'that does not fit the viewport'. Correct — Global rendered a real map
into roughly a quarter of the available area.

_letterbox_scale_for() reserves LEGEND_COLUMN_PX before computing the Global
fit, but _request_extent() sized the request against the FULL Control width.
Because that fit is an INTEGER pixels-per-gridunit ratio, the disagreement
does not degrade gracefully: at a 1920-wide window we asked for 960 gridunits
but could only fit floor(1628/960) = 1 px each, so the canvas drew at HALF the
intended scale with room to spare on every side.

The request is now sized to the drawable area, so both sides agree: 814
gridunits at 2 px = 1628 px, plus the 292 px legend column = exactly 1920.
Verified through the capture harness — canvas_scale went 0.5 -> 1.0.

Only Global reserves the column, so only Global adjusts; the fixed rungs are
untouched.

Also corrects the regression test I wrote yesterday, which computed its
expectation from the full viewport and so encoded the bug.

Client suite 1830 / 1804 passed / 0 failed / 26 skipped.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-07-27 21:13:04 +02:00
co-authored by Claude
parent 3a17624ddc
commit d36d458b74
2 changed files with 25 additions and 2 deletions
+9 -1
View File
@@ -810,8 +810,16 @@ func test_request_extent_ignores_a_not_yet_laid_out_viewport() -> void:
for degenerate in [Vector2.ZERO, Vector2(4.0, 4.0), Vector2(1920.0, 2.0)]:
v.size = degenerate
var extent: Vector2i = v._request_extent()
# The fallback is sized to the DRAWABLE area, so Global's reserved
# legend column comes off the width first — the request must match what
# can actually be drawn, or the Global integer fit drops a whole step
# and the map renders at half size in a window with room to spare.
var drawable := Vector2(
StepCanvasViewer.FALLBACK_VIEWPORT_PX.x - StepCanvasTransport.LEGEND_COLUMN_PX,
StepCanvasViewer.FALLBACK_VIEWPORT_PX.y
)
var expected: Vector2i = StepCanvasTransport.viewport_fit_extent(
StepCanvasViewer.FALLBACK_VIEWPORT_PX, v.get_held_rung()
drawable, v.get_held_rung()
)
assert_that(extent).override_failure_message(
"a %s viewport must fall back, not be taken literally — got %s" % [degenerate, extent]
@@ -501,7 +501,22 @@ func _request_extent() -> Vector2i:
# Treat anything below a plausible panel size as "not laid out yet".
if viewport.x < MIN_LAID_OUT_VIEWPORT_PX or viewport.y < MIN_LAID_OUT_VIEWPORT_PX:
viewport = FALLBACK_VIEWPORT_PX
var fit: Vector2i = StepCanvasTransport.viewport_fit_extent(viewport, _held_rung)
# Ask for the area we can actually DRAW into, not the whole Control.
#
# Global reserves LEGEND_COLUMN_PX on the left (_letterbox_scale_for() /
# _recompute_canvas_transform() both do). Sizing the request against the
# full width while fitting the draw against the reduced width makes the two
# disagree, and because the Global fit is an INTEGER px-per-gridunit ratio
# the disagreement does not degrade gracefully: at a 1920-wide window we
# asked for 960 gridunits but could only fit floor(1628/960) = 1 px each,
# so the map drew at HALF SIZE in a window with room to spare (eyeballed on
# Lendel 2026-07-27). Reserving the column here makes the request exactly
# what fits at the intended ratio, so the fit lands on 2 and the canvas
# fills the panel.
var drawable: Vector2 = viewport
if _held_rung == StepCanvasTransport.RUNG_GLOBAL:
drawable.x = maxf(viewport.x - StepCanvasTransport.LEGEND_COLUMN_PX, 1.0)
var fit: Vector2i = StepCanvasTransport.viewport_fit_extent(drawable, _held_rung)
return StepCanvasTransport.cap_extent_to_body(fit, _held_rung, _global_body_extent)