Two rounds of asking where the tool disagrees with itself, and here is the last of it — the one that was actually creating money. There are two ways a contribution gets in. Tapping the action moves chips: the bet leaves the stack, sits in front of the player, and is swept into their hand total. The other is the "without live betting" panel — a base pot amount, or chips counted out on a seat — for a dealer recording a hand they are watching rather than driving. Nothing leaves anybody's stack there. The pot wizard prices both the same way, and awarding it credits the winner. So in the second mode the table simply grew. Measured at a three-handed table with a $50 base pot: stacks 200, 200, 200 before, pot priced at $150, and afterwards 350, 200, 200 — six hundred dollars of chips became seven hundred and fifty, and the bank check, newly able to see it, blamed a rebuy nobody had made. So before the pot is pushed, whatever was assumed rather than taken comes off the stacks that assumed it, and is recorded where a swept bet lives. Chips already committed or swept are left alone — they have been paid once, and taking them twice is the same bug the other way round. A base pot on top of a live bet collects only the difference. Nobody is charged more than they are holding, and an empty chair or a seat sitting out is charged nothing at all. The same hand now ends 300, 150, 150: the table has the six hundred it started with, and the bank check is balanced before, during and after. 13 new tests, with the already-paid test disabled on purpose to confirm five of them catch the double charge. Walked through the UI in base-pot mode: pot badge $150, wizard pricing three seats at $50 each, and afterwards the felt holding exactly what it held before. 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 — each viewport twice, once with a mouse and once with a thumb.
That last part, and two of those checks, are newer than this paragraph claiming them.
runAll() did not include the contrast check at all, and touch targets sat behind an
opt-in flag nothing passed, so "the audit is clean" meant nine checks rather than
eleven for a long run of rounds. Both were written and tested and simply never called.
The mouse/thumb pair matters for the same reason: the 44px rule stands itself down
where hover: hover says there is a pointer, and a headless desktop browser is a
pointer, so a single pass had never measured a touch target at any viewport. Between
them they found a 9px seat label at 3.39:1, a 37px-tall amount field and a 30px delete
button on Quick Split.
A check that decides it doesn't apply says so in the output rather than reporting no offenders, which reads identically to a pass. The same goes for the two exemptions that exist: a disabled control, which WCAG itself excuses, and a link inside a sentence, whose size is set by the line it sits in.
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.