Skip to content

Stage 4 — Arena Mechanics #7

Description

@KineticTactic

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

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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

  • Race condition: Two players race for the same Arena problem. Earlier CF timestamp wins. room.locked SSE event fires once with correct claimedBy. Verify via HGETALL room:<id>:locks.
  • Reclaim: Player A's sync job processes first despite a later CF timestamp. Player B's job arrives with an earlier timestamp. Lua reclaims for B. room.locked fires again with the updated owner.
  • 10-concurrent load test: As described in task 5.
  • All locked → room ends: All problems claimed → room.end fires without waiting for the time limit.
  • Time limit ends partially claimed room: Some problems unclaimed when timer fires → room.end fires with correct scores based on locks held.

Handoff Contract

  • All testing gate items pass.
  • Lua script is committed in the repo. The worker calls the Lua script — no inline JavaScript claim logic.
  • Both Blitz and Arena end-to-end flows work simultaneously (full Stage 3 + Stage 4 regression).

Metadata

Metadata

Assignees

Labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions