scale by whole pixels, and make the stage small enough to matter

Two halves of the same problem: the picture was sharp everywhere and wrong
overall.

── whole-number scaling ──

The canvas is now sized to the largest WHOLE number of device pixels per
source pixel that fits the panel, measured with a ResizeObserver and the
device pixel ratio. Below 1x it takes a unit fraction instead, so the rows
that survive are evenly spaced rather than dropped in clumps.

This is a 16px-per-cell bitmap from 1995. At a fractional scale one cell gets
three screen pixels and its neighbour gets two, the grid goes uneven and the
digits wobble — which reads as "not quite right" rather than as blur, and
`image-rendering: pixelated` does not help because unevenness is not blur.
A fractional devicePixelRatio produces exactly this at what looks like 1:1.

It is also allowed to scale UP past the framebuffer's own size, which was the
other half of it: `max-w-full` only ever shrinks, so on a wide panel the
canvas sat at its intrinsic size using a fraction of the pixels available.

── a smaller stage ──

800x600 down to 560x400. The stage is what gets scaled to fill the panel, so
every pixel of empty surround is panel the game does not get, and the whole-
number steps are far apart: in an 1866-device-pixel panel, 800 wide lands on
2x and 560 wide lands on 3x. Same panel, half again as much game.

560x400 is expert (504x342, measured on this machine) plus a margin. A custom
board bigger than the stage is now clipped at the right and bottom rather than
shrunk; `centre` clamps at zero so the menu bar stays reachable. Widen the two
constants if that turns out to be the wrong trade.

Verified: beginner centres at 204,93 on the new stage, which is exact.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Officer-Session: de3ed4e6-f88a-429c-94e6-a760336ba83b
This commit is contained in:
2026-08-23 21:26:10 +00:00
co-authored by Claude Opus 5
parent ad2aa632a5
commit 26c8dd3e1a
3 changed files with 76 additions and 15 deletions
+9 -1
View File
@@ -60,11 +60,19 @@ The check that matters is `ss -lntp` against the **running** process. Reading th
listener that exists because a flag is missing, and running the same arguments by hand did not reproduce
it either.
**The game is centred on a fixed stage, and the whole stage is what you see.** The framebuffer is 800x600
**The game is centred on a fixed stage, and the whole stage is what you see.** The framebuffer is 560x400
whatever the difficulty, `sidecar/session.ts` keeps the window in the middle of it, and the panel draws the
framebuffer entire — scaled to fit, never cropped. So changing difficulty moves the board rather than
resizing the panel, and the surround is teal (`-solid`) rather than the root's black.
**The stage is small because the panel scales it by whole numbers.** Pixel art at a fractional scale gets
uneven cells — some three screen pixels wide, their neighbours two — which reads as "not quite right"
rather than as blur, and `image-rendering: pixelated` does not help because unevenness is not blur. So the
panel picks the largest whole number of DEVICE pixels per source pixel and every pixel of empty stage is
panel the game does not get: in an 1866-device-pixel panel, 800 wide lands on 2x and 560 wide lands on 3x.
560x400 is expert (504x342) plus a margin. A custom board bigger than that is clipped at the right and
bottom rather than shrunk, with the menu bar kept reachable.
It used to park the window at the origin and have the client crop to the window's size, polled every four
seconds. That resized the panel under the player, and it was off by the 3x29 wine puts between the root
origin and the window — a black strip along the top and a row of cut-off cells along the bottom, worst on