Chess on the ATmosphere checkmate.blue
chess
13
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 140 lines 6.5 kB view raw view rendered
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```