// How wide the ring of seats is allowed to be. // // It sat at a flat 44% by 42% of the seat layer at every size — two numbers picked // against a phone, then asked to hold for a 754x543 felt on a desktop and a 686x141 // one on a phone held sideways. A fixed pair can only be right for one ratio of puck // to felt, so measured on the running page: // // 1280x900, nine-handed five of nine pucks past the table's outer edge, worst 49px // 844x390, six-handed all six, worst 20px // 430x932, six-handed two, worst 12px // // Seats floating off the felt onto the page read as the table being too small for the // game rather than as players at a rail, and it was a different amount of wrong on // every screen — which is what made it look like a mistake instead of a choice. // // The radius is derived from what has to fit now. Whether it *actually* fits is a // question about a laid-out browser, and it is checked there: uiAudit's "seats and // chips stay on the table" runs at every viewport in the sweep. What is checked here // is the contract that function has to honour, including the two ways it is allowed // to give up. const test = require("node:test"); const { describe } = require("node:test"); const assert = require("node:assert"); const { Render } = require("./load"); describe("with no browser to measure", () => { test("it falls back to the ring the tool has always used", () => { // Every call from this suite has no DOM, and so does the very first render, // before the felt has been laid out. Neither should throw or return nonsense. assert.deepEqual(Render.seatRingRadii(), { rx: Render.RING_MAX_RX, ry: Render.RING_MAX_RY }); }); test("so seatPosition still answers", () => { const pos = Render.seatPosition(0, 9, 1); assert.ok(Number.isFinite(pos.left) && Number.isFinite(pos.top)); }); test("and nothing is measured for the puck height either", () => { assert.equal(Render.tooTallForTheTable(), false); }); }); describe("the ceiling and the floor", () => { test("the old ring is the widest it can ever be", () => { // Nothing gets looser than it was; this can only pull seats in. assert.ok(Render.RING_MAX_RX <= 44); assert.ok(Render.RING_MAX_RY <= 42); }); test("and it never collapses the ring into the pot", () => { // A puck wider than the table it sits on has no honest answer. Overhanging is // the lesser evil — seats stacked on the pot would be unreadable, and the // density rule shrinks the pucks before it ever gets this far. assert.ok(Render.RING_MIN_R > 0); assert.ok(Render.RING_MIN_R < Render.RING_MAX_RY); }); test("with a margin that keeps the outermost seat off the rail", () => { assert.ok(Render.SEAT_EDGE_MARGIN_PX > 0); }); }); describe("the ring the radii describe", () => { const spread = (radii, count = 9, aspect = 1.4) => Render.ringPositions(count, radii.rx, radii.ry, aspect); test("a smaller radius puts every seat nearer the middle", () => { const wide = spread({ rx: 44, ry: 42 }); const tight = spread({ rx: 30, ry: 28 }); wide.forEach((seat, i) => { const wideOut = Math.hypot(seat.left - 50, seat.top - 50); const tightOut = Math.hypot(tight[i].left - 50, tight[i].top - 50); assert.ok(tightOut < wideOut, `seat ${i + 1} should have come in`); }); }); test("and the seats stay evenly spread however far in they are", () => { // Pulling the ring in must not bunch them: the spacing rule is arc length, and // shrinking an ellipse uniformly keeps every gap in proportion. const gaps = ring => ring.map((p, i) => { const next = ring[(i + 1) % ring.length]; return Math.hypot(p.left - next.left, p.top - next.top); }); const tight = gaps(spread({ rx: 30, ry: 28 })); const smallest = Math.min(...tight), largest = Math.max(...tight); assert.ok(largest / smallest < 1.35, `gaps ranged ${smallest.toFixed(1)}..${largest.toFixed(1)}`); }); }); describe("the bets follow the seats", () => { test("a chip sits on its own player's spoke, proportionally in", () => { // The chip is a fraction of the seat's reach rather than of the felt, so when // the ring pulls in the chips come with it instead of being left by the rail. const radii = { rx: 30, ry: 28 }; const seat = Render.seatPosition(2, 8, 1.4, radii); const bet = Render.betPosition(2, 8, 1.4, radii); const seatOut = Math.hypot(seat.left - 50, seat.top - 50); const betOut = Math.hypot(bet.left - 50, bet.top - 50); assert.ok(betOut < seatOut, "the chip is nearer the pot than the player is"); assert.ok(betOut > seatOut * 0.4, "but still out on the player's side of the felt"); }); test("and it is the same spoke, not just the same distance", () => { const radii = { rx: 30, ry: 28 }; const seat = Render.seatPosition(5, 9, 1.2, radii); const bet = Render.betPosition(5, 9, 1.2, radii); const angleOf = p => Math.atan2(p.top - 50, p.left - 50); assert.ok(Math.abs(angleOf(seat) - angleOf(bet)) < 0.01); }); });