// 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]); }); });