Concepts & terms

Every term used in these docs, in plain language. Read this page once and everything after it reads faster — each group below matches a guide chapter.

Glossary

Game terms

variant
The chess ruleset — e.g. standard, chess960. Different variants are different games with separate ratings.
time control
How much clock each player gets. An id like blitz_3_2 means 3 minutes base + 2 seconds added per move. The catalog comes from GET /v1/game-types — render your "choose game" UI from it.
speed
The bucket a time control falls in — bullet (fastest), blitz, rapid. Ratings are kept per speed: being good at rapid says little about bullet.
rated vs casual
A rated game changes the player's rating; a casual game (rated:false) never does — casual uses a simple first-come-first-served pairing pool. Bot games are always casual.
ply
One move by one side. A full move = 2 plies (White then Black). Puzzle solutions and game reviews index moves by ply.
seat
A side of the board: white, black — or spectator for someone only watching.

Rating terms

Elo
The classic single-number chess strength score (higher = stronger). Inachess uses the more precise Glicko-2 underneath, but “rating” colloquially means this same ordering.
Glicko-2
The rating system the engine computes. A rating is a triple: r (the number you show users), rd, and vol. The engine recomputes it after every rated game and hands you the result to store.
rd (rating deviation)
How unsure the engine is about a rating. New or long-idle players have a high rd, so their rating moves fast; established players move slowly. Never invent a rating for a new player — the high rd IS the signal that they are unknown.
vol (volatility)
How erratic the player's results are. You never need to interpret it — just store it and return it.
rating category
Ratings are stored per "<variant>:<speed>" — e.g. standard:blitz. A chess960 blitz rating is not a standard blitz rating; the key keeps pools that don’t transfer apart.
floor
A lower bound linked to the player's peak: you track the peak per category and return floor; the engine never lets the rating sink below it (anti-sandbagging).

Realtime terms

session token
The opaque credential you mint at login with POST /v1/sessions. It authenticates the player's socket for the whole login session. Not a JWT — the engine verifies its own tokens.
session socket
The ONE WebSocket a client opens per login — not per game. The lobby, the player's own game and anything they watch all ride on it.
room
A match/game id used to address socket messages. One socket carries many rooms at once, so every game-scoped message names its room.
intent
A client → engine message. It is called an intent because the client only requests — the engine decides. A move intent may be rejected; the board never changes until the engine broadcasts it.
presence gate
The check that both players are actually at the board before the clock starts. ready_check (matchmaking): each player sends accept, the clock starts once both do. grace_period (tournaments): the room waits for an entrant who may still be offline. Bot games have no gate.
no-show
A player who never confirmed the gate in time. The game aborts (match_aborted, reason no_show) — nobody's rating changes.
subscription = presence
Being subscribed to a room means “at the board”; unsubscribing means “left it”. The socket staying open proves nothing — the user may be browsing the lobby.
busy-state
One active game per user, enforced by the engine. While a player is in a game (or an exclusive tournament), queueing again is rejected with already_in_game.

Reliability terms

callback / webhook
An event the engine POSTs to your platform: game_result, match_found, match_aborted, tournament_complete, fairplay_flag. You host one endpoint and reply 2xx.
outbox
The engine's delivery guarantee: the callback row is written in the same DB transaction as the game result, then a worker retries it until you acknowledge. Nothing is lost to a crash.
eventId / idempotency
Every callback carries a unique eventId. Delivery is at-least-once, so you WILL see duplicates — process each eventId once and reply 2xx (or 409) for repeats. The combined effect is exactly-once.
reconciliation
The pull backstop: GET /v1/matches/results?since=<cursor> returns finished games your webhook may have missed. Run it on a schedule; dedupe by the same eventId.

Principles & analysis

centipawn (cp)
1/100 of a pawn — the engine's evaluation unit. In a game review, cpLoss is how much a move gave up versus the engine's best move.
server-authoritative
The engine validates every move and computes every clock; clients send intent only. This is why nothing in the protocol ever trusts a client-supplied rating, clock, or result.

UCI — the move format, in full

Every move on every layer is a UCI string. It is deliberately tiny: squares only, no piece letters (except promotion), no x/+/#. The SDK sends the string verbatim with client.move(uci) and the engine validates it.

ts
// UCI = from-square + to-square. That's it for a normal move:
e2e4        // pawn e2 → e4
g1f3        // knight g1 → f3

// Promotion — append the promoted piece letter (lowercase): q · r · b · n
e7e8q       // promote to queen  (the default your UI should pre-select)
e7e8n       // promote to knight (underpromotion)

// Castling is encoded as the KING's move (NOT a special token):
e1g1        // white  O-O   (short / king-side)
e1c1        // white  O-O-O (long / queen-side)
e8g8        // black  O-O
e8c8        // black  O-O-O

// En passant — the capturing pawn's diagonal move (the captured pawn is implied):
e5d6        // white pawn e5 captures en passant onto d6

// You send exactly these strings: client.move("e2e4"). The engine validates
// legality — an illegal or malformed move is rejected with error code
// illegal_move (or invalid_move for a string that isn't UCI at all).

Watch UCI drive a board — each client.move(…) is exactly the string on the wire; captures need no special syntax:

User BBlack ♚
♜
♞
♝
♛
♚
♝
♞
♜
♟
♟
♟
♟
♟
♟
♟
♟
♟
♟
♟
♟
♟
♟
♟
♟
♜
♞
♝
♛
♚
♝
♞
♜
LIVE
User AWhite ♔to move
// UCI moves — what the client sendsUser A · WhiteUser B · BlackAclient.move("e2e4");Bclient.move("e7e5");Aclient.move("g1f3");Bclient.move("b8c6");Aclient.move("f1c4");Bclient.move("g8f6");Aclient.move("f3e5");Bclient.move("c6e5");▶ looping · pure HTML/CSS/JS · A = white seat, B = black seat

Promotion is the one 5-character case — the pawn reaches the last rank, your UI asks which piece, and the letter is appended:

User BBlack ♚
♚
♟
♚
LIVE
User AWhite ♔to move
// promotion — the 5th UCI char is the chosen pieceUser A · WhiteUser B · BlackAclient.move("a5a6");Bclient.move("g8f8");Aclient.move("a6a7");Bclient.move("f8g8");Aclient.move("a7a8q"); // promote → ♛▶ looping · pure HTML/CSS/JS · A = white seat, B = black seat

Rule of thumb: from-square + to-square, then a promotion letter only if a pawn reaches the last rank. Castling and en passant need no special syntax — they are just the king's or pawn's own move.

FEN — the position format

Where UCI describes a move, FEN describes a position. You only ever read it — the engine sends the current FEN with every snapshot and every move broadcast, and the SDK parses it for you:

ts
// FEN is a one-line snapshot of a whole position. game_state carries it;
// the SDK's parseBoard(fen) turns it into a square → piece map for rendering:
"rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1"
//  └ piece placement (rank 8 → 1)      └ side to move (b = black)

import { parseBoard, fenTurn } from "@inachess/sdk";
const board = parseBoard(state.fen);   // board["e4"] === "P" (uppercase = white)
const turn  = fenTurn(state.fen);      // "white" | "black"

// You never construct or mutate FEN yourself — the engine sends a fresh one
// with every game_state and every validated move.