Chess on the ATmosphere
checkmate.blue
chess
1# How checkmate.blue Works
2
3checkmate.blue is a real-time chess game built entirely on the ATmosphere. There is no application server, no database, no WebSocket server. The browser is the app, each player's PDS is their database, and AT Protocol is the infrastructure.
4
5This document walks through how a single move flows through the system -- from one player's browser to the other's.
6
7## The Setup
8
9Both players have the game page open in their browsers. Each player has their own `blue.checkmate.game` record on their own PDS (Personal Data Server). These records contain the full PGN (Portable Game Notation) -- the complete move history. The players are connected to each other via Jetstream, a WebSocket service that broadcasts AT Protocol record changes in real-time.
10
11## Player A Makes a Move
12
13Player A drags a piece on the board. The app asks chess.js: "is this legal?" If it's a pawn reaching the back rank, a promotion modal appears first. Otherwise, the move is applied to the local chess.js instance immediately -- the board updates before anything hits the network.
14
15The app generates the updated PGN from chess.js (including standard headers like player DIDs and the event name) and writes it to Player A's own PDS via `putRecord`. This is the only write -- one record update containing the full PGN, the game status, and a timestamp. Player A cannot write to Player B's PDS. AT Protocol enforces this: you can only write to your own repository.
16
17## The Move Reaches Player B
18
19Jetstream, which indexes the AT Protocol firehose, sees that Player A's `blue.checkmate.game` record changed. Player B's browser has an open WebSocket to Jetstream filtered to Player A's DID and the `blue.checkmate.game` collection. Jetstream pushes the event.
20
21Player B's app receives the updated record. It extracts the PGN and compares it to the local chess.js state. If the incoming PGN has more moves (it should -- exactly one more), the app replaces the local chess.js instance with the new PGN. The board updates. A sound plays.
22
23If Jetstream disconnects (network issues, server restart), the app falls back to polling Player A's PDS directly every 3 seconds until the WebSocket reconnects. Either way, the data source is the same: Player A's record on their PDS.
24
25## Player B Responds
26
27Now it's Player B's turn. The same flow happens in reverse. Player B moves, chess.js validates, the PGN is written to Player B's PDS, Jetstream delivers it to Player A.
28
29## Why Two Records?
30
31Each player maintains their own copy of the game because AT Protocol is built around personal data repositories -- you own your data, you write your data. There's no shared database to update. This means at any given moment, one record is one move ahead of the other (whichever player moved last). When the game page loads or reloads, the app reads both records and uses the longer PGN, ensuring no moves are lost.
32
33## When the Game Ends
34
35If chess.js detects checkmate, stalemate, or a draw condition after a move, the writing player includes `status: "completed"` and the result in their record update. The opponent receives this via Jetstream, verifies the result by replaying the PGN through chess.js, and mirrors the completion to their own record. Resignations and draw agreements follow the same pattern -- the acting player writes to their record, the opponent verifies and syncs.
36
37## The Flow
38
39```mermaid
40sequenceDiagram
41 participant A as Player A (Browser)
42 participant CJ as chess.js
43 participant A_PDS as Player A's PDS
44 participant JS as Jetstream
45 participant B_PDS as Player B's PDS
46 participant B as Player B (Browser)
47
48 Note over A,B: Both players have the game open.<br/>Each has their own game record on their own PDS.<br/>Both are connected to Jetstream.
49
50 rect rgb(30, 40, 55)
51 Note over A,CJ: Player A makes a move
52 A->>CJ: Drag piece (orig, dest)
53 CJ->>CJ: Is this legal?
54 CJ-->>A: Yes -- board updates instantly
55 end
56
57 rect rgb(30, 40, 55)
58 Note over A,A_PDS: Write to own PDS
59 A->>A_PDS: putRecord(blue.checkmate.game)<br/>Updated PGN + status + timestamp
60 Note over A,A_PDS: Player A can only write to<br/>their own repository
61 end
62
63 rect rgb(40, 35, 50)
64 Note over A_PDS,B: Move delivered via Jetstream
65 A_PDS-->>JS: Record change event
66 JS-->>B: Filtered by Player A's DID<br/>+ blue.checkmate.game collection
67 end
68
69 rect rgb(30, 40, 55)
70 Note over B,CJ: Player B receives the move
71 B->>CJ: Compare PGN lengths
72 CJ-->>B: Incoming PGN is longer -- accept
73 B->>B: Update board + play sound
74 end
75
76 Note over A,B: Now it's Player B's turn.<br/>The same flow happens in reverse.
77
78 rect rgb(30, 40, 55)
79 Note over B,CJ: Player B responds
80 B->>CJ: Drag piece (orig, dest)
81 CJ->>CJ: Is this legal?
82 CJ-->>B: Yes -- board updates instantly
83 end
84
85 rect rgb(30, 40, 55)
86 Note over B,B_PDS: Write to own PDS
87 B->>B_PDS: putRecord(blue.checkmate.game)<br/>Updated PGN + status + timestamp
88 end
89
90 rect rgb(40, 35, 50)
91 Note over A,B_PDS: Move delivered via Jetstream
92 B_PDS-->>JS: Record change event
93 JS-->>A: Filtered by Player B's DID
94 end
95
96 rect rgb(30, 40, 55)
97 Note over A,CJ: Player A receives the move
98 A->>CJ: Compare PGN lengths
99 CJ-->>A: Incoming PGN is longer -- accept
100 A->>A: Update board + play sound
101 end
102```
103
104## Two Records, One Game
105
106```mermaid
107flowchart TB
108 subgraph A_PDS["Player A's PDS"]
109 A_REC["blue.checkmate.game/3abc...<br/><br/>pgn: 1. e4 e5 2. Nf3<br/>status: active<br/>white: did:plc:playerA<br/>black: did:plc:playerB"]
110 end
111
112 subgraph B_PDS["Player B's PDS"]
113 B_REC["blue.checkmate.game/3xyz...<br/><br/>pgn: 1. e4 e5 2. Nf3 Nc6<br/>status: active<br/>parentGameUri: at://playerA/..."]
114 end
115
116 A_REC -.-|"Player B's record points<br/>back via parentGameUri"| B_REC
117
118 LOAD["On page load or reconnect:<br/>Read BOTH records.<br/>Use the longer PGN."]
119
120 A_REC --> LOAD
121 B_REC --> LOAD
122```
123
124## Jetstream Fallback
125
126```mermaid
127stateDiagram-v2
128 [*] --> Connected: WebSocket opens to Jetstream
129
130 Connected --> Connected: Receive opponent's moves
131 Connected --> Disconnected: WebSocket closes
132
133 Disconnected --> Polling: Start polling opponent's PDS (3s interval)
134 Disconnected --> Reconnecting: Exponential backoff (1s to 30s)
135
136 Polling --> Connected: WebSocket reopens
137 Reconnecting --> Connected: WebSocket reopens
138
139 Note right of Polling: Same data source either way --<br/>the opponent's record on their PDS
140```