On disconnect, a player's slot is held for 60s via ReconnectManager.
During the grace window, game.delta frames destined for the absent
slot are buffered in order. If the client reconnects with its
original token (envelope-level), the grace timer is cancelled and
the server replays a fresh game.state snapshot plus every buffered
delta. Timer expiry triggers the original "player_left" game.end
cleanup that previously ran immediately on disconnect.
- new packages/server/src/reconnect.ts: ReconnectManager (no WS refs,
no registry coupling, unref'd timers so tests don't block exit)
- broadcast.ts: unregisterConnection starts grace; handleRoomJoin
routes reconnect path via envelope token + isPending check;
handleGameMove buffers deltas for disconnected opponents
- reconnect.test.ts: 9 unit cases (grace/cancel/buffer/expire/reset)
- broadcast.test.ts: end-to-end reconnect scenario + negative case
Wire the WebSocket message handler to process game.move intents via
GameSession and broadcast game.delta to both room players. Add
broadcast.ts as the message router with handlers for room.create,
room.join (triggers game.state to both players when 2nd player
joins), room.leave, and game.move. Move-intent validation enforces
NOT_YOUR_TURN before applyMove so clients can distinguish turn
errors from illegal moves.
index.ts now extends ClientData with roomCode/token, registers
connections on open and unregisters (+ markDisconnected +
broadcast game.end) on close, and gates inbound frames through
the size cap and rate limiter before dispatch.
broadcast.test.ts drives handleMessage directly with mock
ServerWebSockets (vitest runs on Node, so we can't use Bun.serve)
covering: legal-move broadcast to both players, illegal-move
error-to-sender-only with silent opponent, NOT_YOUR_TURN, malformed
JSON fatal disconnect, VERSION_MISMATCH fatal disconnect,
ROOM_NOT_FOUND, and BAD_TOKEN for unauthenticated game.move.
bun run typecheck, bun run lint, bun run test all pass.
Each room owns a ChessEngine wrapped in a GameSession; only the server
calls insert/retract/fireRules and all EntityIds are minted server-side.
GameSessionRegistry keys sessions by room code so two rooms cannot
observe or collide with each other's working-memory state.
GameSession.applyMove validates algebraic inputs, finds the matching
legal move via ChessEngine.findMove, applies it, and returns a fact-
level diff (inserted/retracted) plus the new turn and terminal state.
Terminal states are sticky: further moves after checkmate/draw return
GAME_OVER rather than silently mutating a dead session.
Exposes @paratype/chess's headless surface (ChessEngine, coord helpers,
schema types) via a new package entry point; the React app continues to
import concrete modules directly.
Introduces PresetRegistry + three pawn-focused preset rules from
RULES.md (pawns-move-backward, double-pawn-sprint,
pawn-diagonal-no-capture). Presets register themselves via
side-effect imports and expose getExtraMoves/filterMoves hooks for
the ChessEngine to invoke during move generation (engine wiring is
P3.11). Registry enforces incompatibility and requires invariants.
- packages/chess/src/engine.ts — ChessEngine integrates all rule modules
(pawn, knight, sliding, king, castling, en-passant, promotion, check,
checkmate, stalemate, draws) into a playable game without the Rete
production network
- packages/chess/src/pgn.ts — minimal SAN/PGN parser with full
disambiguation support (file/rank hints, full from-square)
- packages/chess/tests/fide-games/classic-games.test.ts — 5 game tests:
Fool's Mate, Scholar's Mate, Ruy López, Sicilian Defence, Italian Game
All 5 tests green; typecheck clean.
10 integration tests in packages/rete/tests/golden/ manually wire
AlphaNode → BetaMemory → JoinNode → ProductionNode chains and drive
them via Session.insert/retract. Each test maps to a pararules Nim
reference test (documented in GOLDEN-MAP.md).
Coverage: packages/rete/src at 96.8% statements / 95.4% branch /
97.8% functions — all well above the 90% Phase 1 gate.
Tests: 227 total (166 pre-existing + 61 new golden), all green.