Three programs, one vocabulary
Everything is Swift. The app and the server both link ChessKit, so
“is this move legal?” has exactly one answer on both sides of the socket.
Select a module to inspect it; its dependencies light up dashed.
1. e4 — traced end to end
The client never moves its own pieces. It sends a frame, the
GameCoordinator actor rules on it, and the board only changes when the verdict
comes back. Step through the honest case — then flip the toggle and watch the server
swat away an injected illegal move.
One protocol, compiled twice
The ChessOnline target is the single source of truth for every
message — linked by the app and the server, so the schema cannot drift. Each frame is
self-describing JSON with a "type" discriminator. It deliberately depends on
nothing, not even ChessKit: strings and UUIDs only, so the protocol stays stable while
engine types evolve.
A negamax you can test to the node
NegamaxEngine is 465 lines of alpha-beta search over ChessKit’s
evaluation. Fixed-seed Zobrist hashing and zero randomness (unless an opening book is attached)
make it deterministic — regression tests pin exact node counts, not vibes. A search visits
these stations in order:
GameReview · move judgment
Exactly the thresholds in GameReview.swift —
<25 best · <60 good · <120 inaccuracy · <250 mistake · else blunder.
The evaluator is pluggable: the default is a cheap one-ply lookahead; the app’s
ReviewView plugs in the real engine for trustworthy numbers. Works identically for
engine games and online games.
Three rules the whole system leans on
The server is authoritative
Clients mirror the game locally for SAN and highlights, but every online move is legality-checked server-side with the same ChessKit before it’s broadcast. Clocks, timeouts, draw offers, Elo, and results are all enforced inside one actor — clients are mirrors, not referees.
One wire protocol, compiled twice
The ChessOnline target is linked by both the app and the server, so
client/server messages have a single definition and no JSON drift. It depends on
nothing — strings and UUIDs only — so the wire stays stable as engine types evolve.
The engine is deterministic
Fixed-seed Zobrist hashing, no randomness unless an opening book is attached. Search behavior is testable down to exact node counts, which is how blunder-detection and search regressions stay pinned in CI.