Realtime Arena
Live 2-player Fischer games: open and closed lobby tables, and submit_arena_move with EventBridge Scheduler timeouts. Correspondence challenges, ladder games, and dashboard timeout sweeps are unchanged.
Product rules (v1)
| Topic | Rule |
|---|---|
| Eligibility | Meta-games with playercounts including 2 only |
| Ratings | All arena games unrated: true until rated v2 |
| Dashboard | Arena excluded from me_dashboard |
| Vacation | Correspondence vacation does not pause arena clocks; arena play allowed during vacation |
| Game id | tableId === GAME.id when play launches (stable URL + chat) |
| Clock | Server moveReceivedAt at handler entry; grace ARENA_CLOCK_GRACE_MS (750ms) |
| Start sync | arena_launch WS to both players; arena_play_ready when play UI rendered; 3s countdown (arenaClockStartAt) before Fischer time runs; arena_play_stand_down on play leave / WS disconnect during sync window; abort_arena_launch_sync returns table to lobby when stuck (0 moves, clock not live) |
| Lobby activity | arena_table WS to seated opponents on join, leave, or ready-up (event, actorUserId, table ids) |
See Vacation mode for the correspondence vs arena exception.
Storage
| pk | sk | Purpose |
|---|---|---|
ARENATABLE |
tableId |
Lobby state, seats, ready, swap |
ARENAINDEX#<state> |
inverted time + tableId |
Public listing (waiting / seated / playing) |
ARENAUSER |
userId |
One active arena table/game per user |
GAME |
<meta>#0#<tableId> |
Live play (playMode: 'arena', arenaTcId, arenaClock) |
GAMECOMMENTS |
<tableId> |
Lobby + in-game + post-game chat thread |
ARENAFB#<userId> |
<metaGame>#<gameId> |
Optional post-game feedback (soak) |
Details: Database schema (Games section).
API
Public list_arena_time_controls, list_arena_tables. Auth table RPCs and submit_arena_move are documented in Auth queries — Realtime arena.
Post-game soak feedback: submit_arena_feedback (players only, after gameEnded; idempotent per user/game).
Move path
Dedicated submit_arena_move (not submit_move):
- Stamp
moveReceivedAtsynchronously at handler entry. - Out-of-time check at receipt + grace.
- Shared gameslib
applyMove/ simultaneous path. applyArenaMoveClock(Fischer ms, automove increments, no vacation).- Reschedule EventBridge Scheduler →
arenaFlagWorker→timeloss. If the worker runs beforeARENA_CLOCK_GRACE_MShas elapsed (not_flagged_yet), it re-arms the one-shot atnow + 250mswhen(nominal + grace)is already past (pastat()expressions are unreliable). Stale-generation invocations re-arm or end the game if already past grace.
Structured logs: JSON line arenaMoveTiming per move. CloudWatch metrics (namespace AbstractPlay/Arena): ArenaMoveProcessingMs, ArenaMoveOotReject (dimensions tier, stage).
Games with a pie / pie-even flag use auth invoke_pie for in-game seat switch (not lobby seat swap). Arena pie applies applyArenaInvokePieClock (ms Fischer from game.lastMoveTime, no vacation): only the invoker is debited think time and receives the Fischer increment; opponent banks are unchanged. Seat order reverses while banks stay on each player id; lastMoveTime advances and the timeloss flag is rescheduled—correspondence clockInc/clockMax hours and opponent “think time refund” are not used.
Front
Routes: /arena, /arena/table/:meta/:id, /arena/play/:meta/:id, /arena/watch/:meta/:id. Landing polls list_arena_tables. Play uses live Fischer display and optional post-game feedback modal.
v1 soak playbook
Weekly (dev/prod):
node scripts/dump-arena-feedback.mjs --stage prod --from … --to …→ JSONL.- CloudWatch →
AbstractPlay/Arena→ reviewArenaMoveProcessingMsp95 bytier; scanarenaMoveTiminglogs for OOT rejects. - Discuss unfair-clock / responsiveness themes in #dev-curious.
Week 2–4: Tune grace, TC enabled flags, or infra only with data — no per-title server blocks in v1.
Retire feedback: disable modal + RPC; archive dump; node scripts/purge-arena-feedback.mjs --stage prod (after backup).
Records
Completed arena games stamp header.site.playMode, arenaTcId, arenaClock on archived JSON (records cron). Glicko ignores unrated arena games until rated v2 split. Finished arena games also appear in recent_completed_games (COMPLETEDGAMES index) when they meet the usual move-count keep rules (same as correspondence). One-time backfill for games finished before that hook: npm run backfill-arena-completed-index -- --stage dev --dry-run then --apply.
Related
- Games and moves — correspondence vs arena entry
- WebSockets —
watchGames+gameverb for live play and spectate;arena_launch/arena_sync/arena_table(user-targeted) for arena start and lobby activity