Pawnshake v0.1.0 · draft

Protocol

Order and endings

Records can cross on the network. One deterministic order decides what happened, so every device computes the same game from the same records.

Why an order is needed

Records from two players can be in flight at the same time. White resigns while Black's move is on its way. Black accepts a draw at the instant White moves. Without a rule, the two phones could end up with different results. Pawnshake applies records one at a time in a canonical order that every device, the server and any later checker compute the same way.

The three rules

  1. Causality first. A record comes after its own prev and after the opponent record it seen.
  2. Ties by class, then colour. When a white and a black record are both ready (neither had seen the other), the earlier class goes first: MOVE, then END, then everything else. Same class: White first. Two crossing RESIGNs make a draw.
  3. Verdicts where they belong. A server verdict comes right after the player records it had seen, and before anything it had not.

Two moves are never concurrent: a move is only valid on its signer's turn, and the turn only passes through the opponent's move, which the signer must have seen.

Example: the resignation race

The move class goes first, so the move a player made without knowing of the resignation stands.
White BlackBlack to moveRESIGNMOVE Qh4#neither had seen the other: concurrentorder: MOVE before RESIGN → Black mates White BlackBlack to moveRESIGNMOVE Qh4#neither had seen the other: concurrentorder: MOVE before RESIGN → Black mates

Putting moves first in a concurrent pair means a player's move always counts in the position that player actually saw. The same holds for a move against a draw acceptance or a takeback acceptance: the client that signed the move used exactly the position the canonical order gives it.

Checked where it lands

At its place in the order, each record is checked against the game state there. Not valid there? It is void and changes nothing.

RecordValid whenEffect
MOVEsigner's turn, game running, code legal, clock not outplays the move; cancels standing takeback offers and the opponent's draw offer
RESIGNgame runningsigner loses
DRAW_ACCEPTthe opponent's offer stands and is in the signer's seendraw
TAKEBACK_ACCEPToffer stands and is in seenback to the position before the offerer's last move
ABORTfewer than 2 movesaborted, never rated
FLAGGEDgame runningsigner loses on time (draw if the opponent cannot mate)
FLAG_CLAIMopponent to movepeer clocks: valid if the opponent's clock was out at the claim's time; anchored: the server decides
DISPUTEsigner has not signed ENDgame ends disputed, unrated
CANCELgame runningleave (profile decides) or opponent gone (canceled)
LINKgame running, peer clocks, no settle openrestarts the transit estimate; nothing on the board
ENDafter the end, with the computed resultsign-off; both ENDs close the game for good

Optimistic, then corrected

Clients apply records as they arrive and re-run the order when a concurrent record shows up later. The board then corrects itself, identically on both sides. In practice that only happens in races like the one above.

How a game ends

The game ends at the first terminating record in the order: a mating or drawing move, RESIGN, DRAW_ACCEPT, ABORT, FLAGGED, a valid FLAG_CLAIM, a verdict or CANCEL; or when it turns disputed or canceled. Then both clients sign END with the result they computed.

Both END records: the game is closed. Nothing reported later changes it.
genesishash…Qxf7#mateEND1-0 · mate…ACKsaw Qxf7#END1-0 · mateWhiteBlackgenesishash…Qxf7#mateEND1-0 · mate…ACKsaw Qxf7#END1-0 · mateWhiteBlack

Next: how the result reaches the server, on Reports and storage.