- Shipped
- September 9, 2026 at 4:15 AM UTC
- Author
- Kamo
- Commit
- a02dd1d
Four rules, all of them things a member reported seeing broken. EMPTY TABLES. A lobby showed three tables while one game was being played. Two had no seats at all: one created before opening a table meant buying into it, one emptied by a path that predated the check. closeIfEmpty already covers the two ways a seat goes away, and it is correct — the gap is that nothing ever looks at a table AGAIN once the last thing that cared about it stopped. findDueToDeal only sees tables with a deal pending and an empty table has none, so an empty table is not merely wrong, it is unreachable: no request, no timer and no member will ever cause it to be examined. HoldemTableReaper closes them every thirty seconds, and lobbyFor decoders to advertise a seatless table even in the half-minute before that runs. Closing the row is the fix; not offering it is the guarantee, because only one of the two can lose a race. The reaper publishes CLOSED, which is what sends a spectator back to the lobby. Spectators are members without seats, so they are exactly the people still pointed at a dead table and exactly the people no other path would tell. NUMBERED, NOT NAMED AFTER ANYBODY. "Sage's table" claims ownership of a thing that is shared the moment a second person sits down, and it was the same string three times over. Tables are Table 1, Table 2, Table 3, taking the LOWEST free number so closing Table 1 means the next table opened is Table 1 again — a lobby of Table 1, Table 4, Table 9 has been counting since the feature shipped rather than describing what is in front of you. Only OPEN tables hold a number and only exact names count, so renaming one gives its number back. RENAMING. Whoever opened the table, and nobody else. A name is the one thing here that is not a game action. ONE TABLE AT A TIME. Two seats means two thirty-second clocks running on one person, and the table they are not looking at times out hand after hand in front of people waiting on them. Refused on both sit and create — but refused with a QUESTION, not a wall: SeatedElsewhereException carries the table they are at, the client asks "leave Table 2 and join Table 4?", and the answer comes back as leaveOthers on a retry of the very call that was refused. One extra round trip, no extra endpoint. A distinct exception type rather than a recognisable sentence, because the sentence is translated and the code is not. A live hand is where the swap stops. Chips in front of somebody in a pot are already at risk and are not theirs to carry to another table, so they are told to finish the hand — a few seconds, against either a stolen pot or a seat they are not at. 22 new tests, and they are not vacuous: removing the naming, the one-table guard and the lobby filter fails seven of them, hitting exactly the ones that name each rule. 682 green overall.