- Shipped
- September 8, 2026 at 12:22 AM UTC
- Author
- Kamo
- Commit
- 3c48418
The rules live in ONE place and it is this one. ChessEngine implements the whole ruleset — move generation, legality under pin and check, castling, en passant, promotion, checkmate, stalemate, threefold repetition, the fifty-move rule and insufficient material — and the browser owns none of it. Every state the server publishes carries `legalMoves` with it, so the board lights up with no round trip and still cannot disagree about what is legal, having never been asked to have an opinion. Proved by perft rather than by inspection: leaf-node counts to depth 3-4 from the four canonical positions. That was not ceremony. The first run failed at depth 3 on a guard keyed to `Math.abs(delta)`, which silently rejected every knight move of the shape "one rank, two files" — a generator right about all 20 opening moves and wrong three plies down. Nothing short of perft finds that. ChessAi is alpha-beta over the same generator, with quiescence (without it a fixed-depth search stops mid-exchange and believes a temporary material count), MVV-LVA ordering, and piece-square tables. Two bugs worth recording: the search was fail-HARD, so a refuted root move returned the alpha bound, which negated into a huge positive score and made the worst move win; and a quiet middlegame at depth 6 took 22 seconds, which is not a hard opponent but a board that looks broken. It is fail-soft now, and every level has a time budget that iterative deepening makes safe — each completed depth leaves a whole usable move behind. Realtime is `chess.member.{memberId}`, per member and NOT per game. That is the only shape StompDestinationAuthz can check: it is a static guard on the interceptor path with no database, so a game-keyed topic could not be authorised at all. Guarded on SEND as well as SUBSCRIBE — the simple broker routes by destination without caring which command carried the frame, so a guard on SUBSCRIBE alone would still let anyone on the socket paint a fabricated board onto a colleague's screen. One live game per pair is a partial unique index, not a check in the service: a check loses to two invitations sent in the same second, which is exactly when it happens. The service reports the race rather than failing it, and returns the game they both wanted.