- Shipped
- September 8, 2026 at 6:44 PM UTC
- Author
- Kamo
- Commit
- ab7a25f
The first move of every game was wrong. Pieces were keyed by their position in a FEN scan, and scan order is not identity: after 1.e4 the element that had been on a2 was told to travel to e4 while four of its neighbours each slid one file left, and the piece that faded out on a capture was whichever happened to be last in scan order rather than the piece that was taken. Everything else here is downstream of fixing that. `chessTransition.ts` takes identity from the MOVE, reads what kind of move it was off the two positions, and then PROVES it: the derivation renders a predicted board and compares it against the authoritative FEN, and a mismatch means snap rather than animate. That one check collapses a missed frame, a reload, a rematch and a game opened halfway through into the default path. It is replayed over 130 plies of three real games out of the live database, 43 of them captures, and every one is understood. On top of that identity: a shadow layer under the pieces that separates as they lift, lift-travel-settle with a heavier spring for a capture, a rook coordinated with its king, an en-passant victim that leaves from its own square, drag and drop that follows the pointer with no spring in the way, a refused move that shakes the square it came from, and a check ring that pulses three times and then HOLDS. Every duration lives in `chessMotion.ts`, and a test fails the build on any component that invents one. The pieces are lit rather than filled — one renderer clipping a gradient, a specular and a contact shadow into the piece's own silhouette, so a different set drops in by supplying paths and nothing else. The move list is back without going back on the reason it was removed: a new read endpoint, fetched twice and appended locally in between, so a published frame costs exactly what it did before. Clicking a move reviews that position and cannot touch the live game. Every square is a real labelled button now — "e4, White pawn" — with arrow-key play, a live region that says each move in words, and a promotion card reachable from the keyboard. Two defects found by measuring a rendered board rather than by reading it: - With a near-black tenant brand the Kamo scheme's dark square resolved to luminance 0.136 against its own black piece at 0.097. Making the PIECES literal fixed half of this last time; the square moves too. Both ends are bounded now, and because `color-mix(in srgb, ...)` is plain linear interpolation — predicted 0.413, Chrome painted 0.413 — the whole family of tenant boards is a unit test instead of a comment saying a browser is needed. - `applyView` is the only way a published state reaches the board now, and it drops a frame older than the one on screen. One human move against the computer publishes two frames seconds apart while the HTTP response returns the later of them, so the response could land first and the intermediate frame arrive after it — the board jumping forward and then visibly snapping back. Deliberately not built: clocks. `ChessGame` says there are none on purpose and the invitation copy says "take days if you like"; the brief conditions every clock section on their already existing. What those sections actually want — knowing whose turn it is, and the weight of the moment — is the arriving YOUR TURN, the active seat, and per-move think time, which the ply rows have always had room for and nothing had ever written.