smaller stage, half steps above 2x — bigger, still even
Trimming the stage alone does nothing, and that is worth writing down: the scale is snapped, so at 3x the game is 1512 device pixels wide whether the stage is 560 or 512. Shrinking the surround only pays when it crosses a step. So both halves land together. ── the stage ── 560x400 down to 512x352, which is expert (504x342, measured) plus four pixels a side. There is nothing left to give — intermediate is 342 tall too, so anything shorter clips a board people actually play. ── half steps from 2x ── Not a retreat from whole-number scaling. The unit that has to stay regular is the CELL, not the pixel: this is a 16px grid, so at 3.5x a cell is exactly 56 screen pixels — as even as 3x or 4x — and only sub-cell pixels alternate between three and four. Below 2x that alternation is a third of a pixel rather than a seventh, so whole numbers still apply there. The case that started all this, a fractional devicePixelRatio at nominal 1:1, put the CELL boundaries at 26.4px, which is a different thing entirely. Together, on a 1800x1400 device-pixel panel: 3.0x becomes 3.5x, and the game goes from 1512 to 1764 device pixels wide. Known cost, and it is in the README: the Extras > Auto Options dialog is 328 wide and opens 25px right of the game, so it needs a 554-wide stage to fit whole from beginner. It cannot be closed from the panel anyway — no window manager, no title bar, no OK button — so that is the thing to fix before this number is revisited. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Officer-Session: de3ed4e6-f88a-429c-94e6-a760336ba83b
This commit is contained in:
@@ -77,7 +77,7 @@ 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 560x400
|
||||
**The game is centred on a fixed stage, and the whole stage is what you see.** The framebuffer is 512x352
|
||||
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.
|
||||
@@ -85,10 +85,16 @@ resizing the panel, and the surround is teal (`-solid`) rather than the root's b
|
||||
**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.
|
||||
panel snaps the scale, in DEVICE pixels, to whole numbers below 2x and half steps above it. Halves are safe
|
||||
because the unit that has to stay regular is the CELL: at 3.5x a cell is exactly 56 screen pixels, as even
|
||||
as at 3x, and only sub-cell pixels alternate.
|
||||
|
||||
That makes the size QUANTISED, which is the thing to understand before changing the stage: at 3x the game
|
||||
is the same width whether the stage is 512 or 560. Trimming the surround is worth nothing on its own — it
|
||||
only helps when it crosses a step. 512x352 is expert (504x342) plus four pixels a side, and there is no
|
||||
room left: intermediate is 342 tall too. A custom board bigger than that is clipped at the right and bottom
|
||||
rather than shrunk, with the menu bar kept reachable, and the Auto Options dialog needs a 554-wide stage to
|
||||
fit whole from beginner.
|
||||
|
||||
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
|
||||
|
||||
+11
-7
@@ -54,14 +54,18 @@ export type SessionStatus = {
|
||||
* so the size here decides which step it lands on, and the steps are far apart. In a 1866-device-pixel
|
||||
* panel: 800 wide gives 2x, 560 wide gives 3x. Same panel, half again as much game.
|
||||
*
|
||||
* 560x400 is expert (504x342) plus a margin, which is what the three standard difficulties need. A
|
||||
* CUSTOM board larger than this is clipped at the right and bottom edges rather than shrunk — `centre`
|
||||
* clamps at zero so the menu bar stays reachable. That is the trade: the sizes people actually play are
|
||||
* as large as they can be, and the one that is not is still playable. Widen these two numbers if that
|
||||
* turns out to be the wrong way round.
|
||||
* 512x352 is expert (504x342, measured) plus four pixels a side. There is no room left to give: expert
|
||||
* and intermediate are both 342 tall, so anything shorter clips a board somebody actually plays.
|
||||
*
|
||||
* Two things get clipped at this size, and both are deliberate. A CUSTOM board larger than the stage
|
||||
* loses its right and bottom edges — `centre` clamps at zero, so the menu bar stays reachable. And the
|
||||
* Extras → Auto Options dialog is 328 wide and opens 25px right of the game window, which needs a stage
|
||||
* 554 wide to fit whole when the board is beginner. That dialog cannot be closed from the panel anyway
|
||||
* (no window manager, so no title bar, and it has no OK button) — see the README. Fix that first, then
|
||||
* revisit this number.
|
||||
*/
|
||||
const SCREEN_W = 560;
|
||||
const SCREEN_H = 400;
|
||||
const SCREEN_W = 512;
|
||||
const SCREEN_H = 352;
|
||||
|
||||
/**
|
||||
* How often to check whether the board changed size.
|
||||
|
||||
+20
-12
@@ -20,7 +20,7 @@ type SessionStatus = {
|
||||
//
|
||||
// Only a placeholder until `onReady`, which sizes the canvas from the real framebuffer. Drift with the
|
||||
// sidecar's stage costs one frame of the wrong aspect ratio and nothing else.
|
||||
const PLACEHOLDER = { width: 560, height: 400 };
|
||||
const PLACEHOLDER = { width: 512, height: 352 };
|
||||
|
||||
const BUTTON_MASK: Record<number, number> = { 0: 1, 1: 2, 2: 4 };
|
||||
|
||||
@@ -82,25 +82,33 @@ export const MinesweeperPanel = () => {
|
||||
}, [status?.running]);
|
||||
|
||||
/**
|
||||
* How big to draw the stage — and the answer is always a WHOLE number of screen pixels per source
|
||||
* pixel, or a whole number of source pixels per screen pixel.
|
||||
* How big to draw the stage. Computed in DEVICE pixels, so a 2x display gets twice the detail rather
|
||||
* than the same picture twice the size — and allowed to scale UP past the framebuffer's own size,
|
||||
* without which a wide panel leaves the canvas sitting at a fraction of the pixels available to it.
|
||||
*
|
||||
* This is a 16px-per-cell bitmap from 1995. At a fractional scale some cells get three screen pixels
|
||||
* and their neighbours get two, so the grid goes uneven and the digits wobble: sharp everywhere and
|
||||
* wrong overall, which is what "not quite right" looks like on pixel art. `image-rendering: pixelated`
|
||||
* does not save you — it removes the blur, not the unevenness.
|
||||
* ── Why the scale is snapped, and to what ──
|
||||
*
|
||||
* Computed in DEVICE pixels, so a 2x display gets 2x the detail rather than the same picture twice as
|
||||
* large. And it is allowed to scale UP past the framebuffer's own size, which is the part that was
|
||||
* missing: on a 1x display the canvas sat at 800 CSS px in a panel twice that wide, using a quarter of
|
||||
* the pixels available to it.
|
||||
* This is a 16px-per-cell bitmap from 1995. At an arbitrary scale a cell is 26.4 screen pixels, so the
|
||||
* grid lines themselves land unevenly and the digits wobble: sharp everywhere and wrong overall, which
|
||||
* is what "not quite right" looks like on pixel art. `image-rendering: pixelated` does not save you —
|
||||
* it removes the blur, not the unevenness. A fractional devicePixelRatio produces this at what looks
|
||||
* like 1:1, which is the case that is easiest to miss.
|
||||
*
|
||||
* HALF steps are allowed from 2x up, and that is not a compromise on the above. The unit that has to
|
||||
* stay regular is the CELL, not the pixel: at 3.5x a cell is exactly 56 screen pixels, so the grid is
|
||||
* as even as it is at 3x or 4x, and only sub-cell pixels alternate between three and four. Below 2x
|
||||
* that alternation is a third of the pixel rather than a seventh, so it stays whole-number there.
|
||||
*
|
||||
* The steps are what make the size quantised — at 3x the game is the same width whether the stage is
|
||||
* 512 or 560 wide. Shrinking the stage is only worth anything when it crosses a step, which is why
|
||||
* these two changes landed together.
|
||||
*/
|
||||
const stage = useMemo(() => {
|
||||
if (!frame || !box || box.width <= 0 || box.height <= 0) return null;
|
||||
const fit = Math.min((box.width * box.dpr) / frame.width, (box.height * box.dpr) / frame.height);
|
||||
// Below 1 there is nothing to floor to, so take a unit fraction: the rows that survive are then
|
||||
// evenly spaced instead of dropped in clumps.
|
||||
const scale = fit >= 1 ? Math.floor(fit) : 1 / Math.ceil(1 / fit);
|
||||
const scale = fit < 1 ? 1 / Math.ceil(1 / fit) : fit < 2 ? 1 : Math.floor(fit * 2) / 2;
|
||||
return { width: (frame.width * scale) / box.dpr, height: (frame.height * scale) / box.dpr };
|
||||
}, [frame, box]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user