What this is: The second room format. Layered on top of Stage 3's infrastructure — same Redis keys, same sync engine, same reconciliation path — but with simultaneous problem reveal and a Lua-scripted atomic claim race.
Owner: Someone comfortable with Redis Lua scripting and concurrent state management.
Tasks
-
Problem reveal at room start
- All problems revealed simultaneously. All
revealedAt values set to startTime.
room:<id>:state does not have a currentProblem field for Arena. Set type = 'arena'.
- Locks are tracked in
room:<id>:locks Hash instead.
-
Lua claim script (the critical piece)
- Implement
claim_problem as a Redis Lua script:
- If
room:<id>:locks has no entry for problemId: claim → HSET room:<id>:locks <problemId> "<teamId>|<cfTimestamp>".
- If an entry exists and the new
cfTimestamp is earlier than the existing one: reclaim → overwrite.
- Else: return
'lost'.
- Called atomically from the
cf_sync_queue worker on every Arena AC.
-
Arena room end condition
- Room ends when all
room:<id>:locks entries are filled (all problems claimed) OR the time limit BullMQ job fires.
- Check lock count after every successful claim. If all locked: enqueue
reconciliation_queue job immediately.
-
SSE events for Arena
room.locked — publish to events:room:<roomId> on every successful claim or reclaim: { problemId, claimedBy: teamId, timestamp }.
room.score — same as Blitz, published after every claim.
room.end — same shape as Blitz.
-
Simultaneous AC arbitration test (part of implementation, not just testing)
- Unit test: 10 concurrent claim attempts for the same problem with varying
cfTimestamp values. Verify the Lua script always awards the claim to the earliest timestamp. Verify no deadlocks or partial writes.
-
Arena UI data contract (for Stage 8 frontend)
- Document per-problem client state shape:
{ status: 'open' | 'claimed_me' | 'claimed_opponent', claimedBy: teamId | null, claimedAt: number | null }.
Testing Gate
Handoff Contract
What this is: The second room format. Layered on top of Stage 3's infrastructure — same Redis keys, same sync engine, same reconciliation path — but with simultaneous problem reveal and a Lua-scripted atomic claim race.
Owner: Someone comfortable with Redis Lua scripting and concurrent state management.
Tasks
Problem reveal at room start
revealedAtvalues set tostartTime.room:<id>:statedoes not have acurrentProblemfield for Arena. Settype = 'arena'.room:<id>:locksHash instead.Lua claim script (the critical piece)
claim_problemas a Redis Lua script:room:<id>:lockshas no entry forproblemId: claim →HSET room:<id>:locks <problemId> "<teamId>|<cfTimestamp>".cfTimestampis earlier than the existing one: reclaim → overwrite.'lost'.cf_sync_queueworker on every Arena AC.Arena room end condition
room:<id>:locksentries are filled (all problems claimed) OR the time limit BullMQ job fires.reconciliation_queuejob immediately.SSE events for Arena
room.locked— publish toevents:room:<roomId>on every successful claim or reclaim:{ problemId, claimedBy: teamId, timestamp }.room.score— same as Blitz, published after every claim.room.end— same shape as Blitz.Simultaneous AC arbitration test (part of implementation, not just testing)
cfTimestampvalues. Verify the Lua script always awards the claim to the earliest timestamp. Verify no deadlocks or partial writes.Arena UI data contract (for Stage 8 frontend)
{ status: 'open' | 'claimed_me' | 'claimed_opponent', claimedBy: teamId | null, claimedAt: number | null }.Testing Gate
room.lockedSSE event fires once with correctclaimedBy. Verify viaHGETALL room:<id>:locks.room.lockedfires again with the updated owner.room.endfires without waiting for the time limit.room.endfires with correct scores based on locks held.Handoff Contract