Take the chair number off the corner it was never alone in

A seat's number was a grey digit in the top-left corner of the puck, and the
top-left corner is also where the blind badge drops off the rim and where a
long centred name reaches. Measured across 348 seats on 36 tables: 117 of them
had it printed under something else. All 66 seats posting a blind had it
covered outright — a 22px badge at z-index 4 over a 6px digit — and 51 more had
it sitting on the first letter of the player's own name, the whole glyph in the
worst case, on a 54px puck.

There is nowhere on the felt to move it to. The button's chip has the other end
of the top rim. Bets land on the rim between a seat and the pot, which is the
bottom edge for the seats above centre and the top edge for the ones below. The
middle of the puck is name, bet and stack, and all three are about this hand.
The chair number is the only mark on a puck that isn't, so it is the one that
gave way.

Nothing is lost that a tap doesn't return. The seat editor's heading reads
"Mike from work · Seat 2" now and the seat menu's first line leads with the
chair; a seat nobody has named still calls itself "Seat 5"; and an open chair
still wears its number large and centred, which is the one moment the number is
the thing being looked for rather than a footnote on somebody else's name.

The sweep had never mentioned any of this. The check that asks whether a puck
covers itself up knew about two of the three things a puck wears, and only ever
held them against writing — so the mark sitting on the names was invisible to
it, and so was a badge painted straight over another mark. Both gaps are
closed: put the number back and it reports five offenders on a Galaxy S8
ten-handed, naming Christopher, Alexandra, Jonah and the SB.

1148 tests, 418 audits clean.
This commit is contained in:
2026-09-11 19:19:03 -06:00
parent 49c2d745eb
commit b096f054f1
6 changed files with 233 additions and 25 deletions
+53 -22
View File
@@ -1258,36 +1258,67 @@ const LayoutAudit = {
// right without any of them being modelled. Line boxes rather than one bounding box,
// too: a two-line name that clears the chip on the line beside it is not covered
// because its *other* line happens to sit under where the chip is.
//
// Two things a puck wears were missing from this for eleven rounds, and between them
// they are why the chair number could sit on the felt covered up without the sweep
// ever saying so.
//
// The first is the chair number itself. This check has always asked whether the
// button or a blind badge sits on a name, and the mark that was sitting on names was
// neither of those: measured across 348 seats on 36 tables, 51 of them had the number
// printed on the first letter of the player's own name.
//
// The second is that marks were only ever tested against the *writing*, never against
// each other — so a badge painted straight over the chair number, which is what all
// 66 seats posting a blind were doing, was invisible to a check whose whole subject
// is a puck covering itself up.
seatLabelsClear() {
const offenders = [];
// What a puck wears, as opposed to what it writes.
const MARKS = ".dealerChip, .blindBadge, .seatChairNumber";
const painted = mark => {
const box = mark.getBoundingClientRect();
if (!box.width || !box.height) return null;
if (!(mark.textContent || "").trim()) return null;
const style = getComputedStyle(mark);
if (style.display === "none" || style.visibility === "hidden" ||
parseFloat(style.opacity) < 0.05) return null;
return box;
};
const across = (a, b) => {
const wide = Math.min(a.right, b.right) - Math.max(a.left, b.left);
const deep = Math.min(a.bottom, b.bottom) - Math.max(a.top, b.top);
return wide > 1 && deep > 1 ? wide : 0;
};
document.querySelectorAll(".seat").forEach(seat => {
const marks = [...seat.querySelectorAll(MARKS)]
.map(mark => ({ mark, box: painted(mark) }))
.filter(entry => entry.box);
const name = seat.querySelector(".seatPuckName");
if (!name || !name.textContent.trim()) return;
const range = document.createRange();
range.selectNodeContents(name);
const lines = [...range.getClientRects()].filter(line => line.width > 1 && line.height > 1);
if (!lines.length) return;
seat.querySelectorAll(".dealerChip, .blindBadge").forEach(mark => {
const over = mark.getBoundingClientRect();
if (!over.width || !over.height) return;
const marked = getComputedStyle(mark);
if (marked.display === "none" || marked.visibility === "hidden" ||
parseFloat(marked.opacity) < 0.05) return;
let worst = 0;
lines.forEach(line => {
const across = Math.min(line.right, over.right) - Math.max(line.left, over.left);
const down = Math.min(line.bottom, over.bottom) - Math.max(line.top, over.top);
if (across > 1 && down > 1) worst = Math.max(worst, across);
if (name && name.textContent.trim()) {
const range = document.createRange();
range.selectNodeContents(name);
const lines = [...range.getClientRects()].filter(line => line.width > 1 && line.height > 1);
marks.forEach(({ mark, box }) => {
const worst = lines.reduce((most, line) => Math.max(most, across(line, box)), 0);
if (worst > 1) {
offenders.push(`${this.describe(name)} :: ${Math.round(worst)}px under ${this.describe(mark)}`);
}
});
}
// And the marks against each other. The rim holds three of them — the blinds
// at one end, the button at the other — and there is not room for a third.
marks.forEach((a, i) => marks.slice(i + 1).forEach(b => {
const worst = across(a.box, b.box);
if (worst > 1) {
offenders.push(`${this.describe(name)} :: ${Math.round(worst)}px under ${this.describe(mark)}`);
offenders.push(`${this.describe(a.mark)} :: ${Math.round(worst)}px across ${this.describe(b.mark)}`);
}
});
}));
});
return { name: "seat names aren't painted under their own badges", offenders };
return { name: "nothing a seat wears covers its name or another mark", offenders };
},
// A seat's writing, covered by something belonging to a different seat.