- maybeAbandon Promise no longer floats from setTimeout
- broadcastSinceLast loses dead extra parameter
- bot-slot token is randomized so a third party can't hijack the
bot's color by guessing a fixed placeholder
Replace broadcastNewAnnouncements/broadcastUpdate with watermark-based
broadcastSinceLast; add pokeBot helper; make all state-mutating handlers
async; hook pokeBot after every mutation so the CasualBrain fires on
each turn without oracle access.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extract endGame/finalizeIfEnded to game-end.ts so driver.ts can call
finalizeIfEnded after an applied move (fix: bot checkmate was not
setting game.status='finished'). Wrap entire dispatch() call in
try/catch for exception safety. Move lastSeenAnnouncementCount advance
to after successful dispatch so retry attempts see FSM rejection
announcements. Add checkmate-finalize test; lock retry-cap at 5 calls.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wires Brain to Game: init/onStateChange/dispose lifecycle, in-flight
mutex, 5-attempt retry loop with attemptHistory, resign-on-cap. Also
adds Game.aiOpponent? field to state.ts for Task 5.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>