Went to audit the seat editor and couldn't open it: Playwright refused to click the middle of seat 1 because seat 9's dealer chip was sitting there. On a nine-seat table, tapping the centre of one player handed the button to another. I shipped that. The enlarged chip hit area grew outward from a chip already hung over its seat's corner at -10px/-10px -- and on an oval, a seat's corner is the seat next to it. Measured on the nine-seat table: seat 1 10 of 49 sampled points taken by seat 9's chip, including its centre seat 4 9 of 49 taken by seat 5's chip seat 5 3 of 49 taken by seat 6's chip I claimed at the time that no seat tap was stolen, and I had checked -- with an audit that exempted anything inside a `.seat` as harmless crowding. The thief was inside a seat. The check walked straight past it. Two fixes, and the second matters more The chip sits inside its seat now rather than on the rim, and its hit area grows inward over its own seat, which is a big target that can spare a corner. Padding inward alone wasn't enough -- I tried it and the numbers didn't move, because the chip's own box was the problem, not just the pad. Isolating that took disabling the pad and then the overhang separately: pad off gave 2 seats affected, overhang off gave zero. And the audit now asks whether a seat's centre belongs to that seat, as its own check. The occlusion check couldn't answer it: it was built for overlays on top of the table, and this was two pieces of the table fighting over a tap. It exempts a neighbour's body -- seats do overlap by design -- but not a neighbour's controls, which was the exact hole. Seven of eight sizes now have no seat whose centre goes elsewhere, and all four seats I tap open their own editor. What's left, stated rather than buried At 360x640 and 375x667 one seat still loses a single sample point of its rim to a neighbour's chip. Centres are correct; this is the rim of a crowded oval. At 844x390 two seats have centres belonging to the seat next door -- and those are seat bodies, not chips. Nine seats on a 163px-tall landscape table overlap almost completely. That's the landscape felt sizing, it predates all of this, and it wants fixing on its own terms rather than being folded in here. Both are now reported by the audit instead of hidden by it, which is the part I actually trust. 152 tests still passing; contrast clean at every size. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_015bnp7xKUPFFSpwkYNFaPgb
Hold'em dealer tests
Unit tests for the money logic behind /Casino/TexasHoldem — the betting engine, the
side-pot calculator, the split calculator and the table state.
node --test tests/holdem/*.test.js
No install step, no package.json, no dependencies. The browser modules are plain
script-tag files that each end with a module.exports guard, so Node can require them
as-is; load.js puts them on globalThis under the same names they use to find each
other in the page, in the load order from TexasHoldem.cshtml.
What's covered
| File | Covers |
|---|---|
potCalculator.test.js |
Main and side pots, dead money, uncalled bets, odd-cent distribution |
betting.test.js |
Blinds, street completion, raises reopening the action, all-in run-outs |
state.test.js |
Button rotation, which seats post blinds, session standings |
splitCalculator.test.js |
Chopped pots, odd chips, unmakeable denominations |
invariants.test.js |
1000 randomised hands per property (see below) |
The layout audit
The unit tests above cover the money. uiAudit.js covers the thing you can only see
in a browser: whether a dealer can actually reach every control.
node tests/holdem/runUiAudit.js # needs Playwright and the site running
It checks WCAG AA contrast, touch target sizes, touch-action and tap highlights, that fields stay selectable and are large enough not to trigger iOS zoom, that nothing important is covered or off-screen with no way to scroll to it, that no grid column has collapsed to nothing, and that no text is painted outside its box. It runs over the table, all five overlays and Quick Split, at thirteen viewports from a 360px phone to a 1600px desktop and the same phones on their side.
Deliberately not in CI: the unit suite is dependency-free and runs in a second, while
this one needs a browser and a running site. Run it after any change that adds or
resizes a control. With no Playwright to hand, paste uiAudit.js into devtools and
call LayoutAudit.runAll().
The contrast check is built so it cannot report a false positive: backgrounds
composite through alpha, and where a gradient is involved every colour stop is tried
and the most flattering one used. A finding means the text fails even under the
reading most generous to it. Two earlier attempts at this got it wrong in both
directions — one read only background-color and scored text on a gradient against
nothing, the other treated low-alpha stops as opaque — so uiAudit.js is checked
against known-answer cases before it is believed.
Touch targets are measured by probing outward with elementFromPoint rather than by
reading the element box, because the box lies in both directions: a small control can
be padded out with a transparent ::after and respond well beyond its painted size,
and a checkbox inside a label is hit by tapping the label. The 44px rule is only
enforced where hover: none says there's no mouse.
It exists because these checks were being re-typed into a console every time something changed, and each time they only covered whatever had just been touched. That is how the standings table shipped with a player-name column zero pixels wide and kept it for two rounds.
Why the randomised suite exists
Every correctness bug found in this tool so far has been shape-dependent: blinds posted to empty chairs, the button skipping past players, heads-up acting out of order, an overbet announced as a side pot. None of them show up on a full six-handed table with equal stacks, which is the table anyone writes a fixed example for.
invariants.test.js deals a thousand hands across random seat counts, random empty
chairs, random stack depths and random dealer input, and checks the things a dealer
would notice being wrong:
- chips are never created or destroyed during play
- the pot breakdown plus the uncalled returns always equal the money that went in
- paying the pot out settles it to the cent
- once uncalled chips are handed back, nobody is still betting above the whole table
- the button and both blinds always land on players who are actually in the hand
The generator is seeded, so a failure reproduces from the seed printed in the message rather than being a flake to re-run.