The felt grows with the window and the pucks did not: 96px was the only ceiling there was. Nine-handed, the seats covered 19% of the felt on a phone, 11% on a 1280px desktop and 8% at 1440px and beyond — and the stack figure a dealer reads sat at 10px on a 2560px monitor, the same 10px as on a 360px phone, well under every published floor for text meant to be read. Two things stop this costing more than it buys. Every candidate width is a forced layout. Simply raising the ceiling took the layout pass from 24ms to 73ms twelve-handed on a desktop, so a screen that cannot use the wider widths must not measure them: the extra pass is offered only when the arithmetic already says the seats have room to spare, and a phone measures exactly the ten widths, in the same order, that it measured before. Timed with the order alternated so neither carries the warm-up: 10.7ms against 10.4ms on a phone, and +3 to +6ms where the widths are actually used. And a wide puck on a narrow felt is worse than a small one. The first version of this let phones through, and a 144px puck on an iPhone's 278px ring is half the width of the table: six-handed, all six seats sat over the rail by 15px against 5px before, reading as a stack of cards rather than a table with people around it. So the ceiling is a fifth of the felt's shorter span, capped at 144. Five is measured rather than chosen — the shorter span is 233-303px on every phone in the sweep including sideways and 617-714px on tablets and desktops, so any divisor between 3.2 and 6.4 separates them — and the test pins both ends. Above the ordinary ceiling the puck takes the larger type as well, which is the point of the extra width. Nine-handed: an iPad goes 96px/12px to 134px/15px, a 1440px desktop 96px/12px to 140px/15px, and the seats go from 8% of the felt to 12%. Every phone, at every seat count, is untouched. The sweep is byte-identical to before this change: same six configurations, same 63 offenders, empty diff. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
130 lines
6.7 KiB
JavaScript
130 lines
6.7 KiB
JavaScript
// How wide a seat puck is allowed to get, and on which screens.
|
|
//
|
|
// The felt grows with the window and the pucks did not: 96px was the only ceiling there
|
|
// was, so nine-handed the seats covered 19% of the felt on a phone, 11% on a 1280px
|
|
// desktop and 8% at 1440px and beyond — with the stack figure a dealer reads sitting at
|
|
// 10px on a 2560px monitor, the same 10px as on a 360px phone.
|
|
//
|
|
// Two things stop the fix from costing more than it buys, and both are checked here.
|
|
//
|
|
// Every candidate width is a forced layout, and raising the ceiling for everybody took
|
|
// the layout pass from 24ms to 73ms on a twelve-handed desktop — so a screen that cannot
|
|
// use the extra widths must not measure them. And a 144px puck on an iPhone's 278px ring
|
|
// is half the width of the table: the first version of this let phones through, and
|
|
// six-handed all six seats sat over the rail by 15px against 5px before, reading as a
|
|
// stack of cards rather than a table with people around it.
|
|
const test = require("node:test");
|
|
const { describe } = require("node:test");
|
|
const assert = require("node:assert");
|
|
const { Render } = require("./load");
|
|
|
|
// Ring boxes read off the running page, not invented. #seatsLayer, nine-handed, at the
|
|
// viewports the sweep visits.
|
|
const RINGS = {
|
|
"Galaxy S8": { width: 233, height: 282 },
|
|
"iPhone 14": { width: 263, height: 470 },
|
|
"Pro Max": { width: 303, height: 535 },
|
|
"phone sideways": { width: 565, height: 257 },
|
|
iPad: { width: 689, height: 689 },
|
|
desktop: { width: 857, height: 617 },
|
|
"desktop 1440": { width: 992, height: 714 },
|
|
};
|
|
const SMALL = ["Galaxy S8", "iPhone 14", "Pro Max", "phone sideways"];
|
|
const LARGE = ["iPad", "desktop", "desktop 1440"];
|
|
|
|
describe("which felts may have wider pucks at all", () => {
|
|
test("no phone does, held either way up", () => {
|
|
// The guarantee the phones are owed: they measure the widths they always
|
|
// measured and settle on the puck they always settled on.
|
|
SMALL.forEach(name => {
|
|
assert.ok(Render.roomyWidthCeiling(RINGS[name]) <= Render.SEAT_WIDTH_MAX,
|
|
`${name} would be allowed a ${Render.roomyWidthCeiling(RINGS[name])}px puck`);
|
|
[2, 4, 6, 9, 12].forEach(count => assert.equal(
|
|
Render.roomForWiderPucks(count, RINGS[name]), false,
|
|
`${name} ${count}-handed`));
|
|
});
|
|
});
|
|
|
|
test("and every tablet and desktop does", () => {
|
|
LARGE.forEach(name => {
|
|
assert.ok(Render.roomyWidthCeiling(RINGS[name]) > Render.SEAT_WIDTH_MAX, name);
|
|
});
|
|
});
|
|
|
|
test("with a comfortable margin either side of the line", () => {
|
|
// The rule is a fifth of the shorter span, and the point of five is that it is
|
|
// not near either group. If a change to the felt ever moves these together,
|
|
// this says so before the phones quietly start paying for it.
|
|
const biggestSmall = Math.max(...SMALL.map(n => Math.min(RINGS[n].width, RINGS[n].height)));
|
|
const smallestLarge = Math.min(...LARGE.map(n => Math.min(RINGS[n].width, RINGS[n].height)));
|
|
assert.ok(smallestLarge > biggestSmall * 1.8,
|
|
`${biggestSmall}px and ${smallestLarge}px are too close for one divisor to separate`);
|
|
// Every divisor that keeps the phones out is at least the widest phone span over
|
|
// the ordinary ceiling; every one that lets the tablets in is below the narrowest
|
|
// large span over the same. 303/96 = 3.2 and 617/96 = 6.4, and five is between.
|
|
const floor = biggestSmall / Render.SEAT_WIDTH_MAX;
|
|
const cap = smallestLarge / Render.SEAT_WIDTH_MAX;
|
|
assert.ok(Render.SEAT_ROOMY_SPAN_RATIO >= floor,
|
|
`${Render.SEAT_ROOMY_SPAN_RATIO} lets a phone through; it has to be at least ${floor.toFixed(2)}`);
|
|
assert.ok(Render.SEAT_ROOMY_SPAN_RATIO < cap,
|
|
`${Render.SEAT_ROOMY_SPAN_RATIO} shuts the tablets out; it has to be under ${cap.toFixed(2)}`);
|
|
});
|
|
|
|
test("and a felt with no size at all is not one of them", () => {
|
|
assert.equal(Render.roomForWiderPucks(9, null), false);
|
|
assert.equal(Render.roomForWiderPucks(9, { width: 0, height: 0 }), false);
|
|
});
|
|
});
|
|
|
|
describe("the widths a felt actually measures", () => {
|
|
const widthsFor = (name, count) => Render.seatWidthCandidates(count, RINGS[name]);
|
|
|
|
test("a phone measures exactly the ten it measured before", () => {
|
|
const expected = [];
|
|
for (let w = Render.SEAT_WIDTH_MAX; w > Render.SEAT_WIDTH_MIN; w -= Render.SEAT_WIDTH_STEP) {
|
|
expected.push(w);
|
|
}
|
|
expected.push(Render.SEAT_WIDTH_MIN);
|
|
SMALL.forEach(name => [6, 9, 12].forEach(count =>
|
|
assert.deepEqual(widthsFor(name, count), expected, `${name} ${count}-handed`)));
|
|
});
|
|
|
|
test("a desktop measures a handful more, and no more than a handful", () => {
|
|
const extra = widthsFor("desktop 1440", 9).length - widthsFor("Galaxy S8", 9).length;
|
|
assert.ok(extra > 0, "the wider widths never got offered");
|
|
assert.ok(extra <= 6, `${extra} extra forced layouts is more than this is worth`);
|
|
});
|
|
|
|
test("never wider than the felt's own ceiling", () => {
|
|
Object.keys(RINGS).forEach(name => [2, 6, 9, 12].forEach(count => {
|
|
const widest = Math.max(...widthsFor(name, count));
|
|
assert.ok(widest <= Math.max(Render.SEAT_WIDTH_MAX, Render.roomyWidthCeiling(RINGS[name])),
|
|
`${name} ${count}-handed offered ${widest}px`);
|
|
assert.ok(widest <= Render.SEAT_WIDTH_ROOMY_MAX);
|
|
}));
|
|
});
|
|
|
|
test("always widest first, always down to the floor", () => {
|
|
// The search takes the first width that fits, so the order is the whole
|
|
// algorithm — and the floor has to be on the list or a crowded table has
|
|
// nothing left to fall back to.
|
|
Object.keys(RINGS).forEach(name => [2, 9, 12].forEach(count => {
|
|
const widths = widthsFor(name, count);
|
|
widths.forEach((w, i) => {
|
|
if (i === 0) return;
|
|
assert.ok(w < widths[i - 1], `${name}: ${widths.join(", ")} is not descending`);
|
|
});
|
|
assert.equal(widths[widths.length - 1], Render.SEAT_WIDTH_MIN);
|
|
}));
|
|
});
|
|
|
|
test("and a crowded table on a big felt is told no like any other", () => {
|
|
// Room on the felt is necessary, not sufficient: the seats still have to clear
|
|
// each other. Enough of them on any ring and the wider widths stop being
|
|
// offered, which is the arithmetic doing the refusing rather than the
|
|
// measurement having to.
|
|
const crowded = [24, 30, 40].map(count => Render.roomForWiderPucks(count, RINGS.desktop));
|
|
assert.deepEqual(crowded, [false, false, false]);
|
|
});
|
|
});
|