
Video games are a long-standing passion. The Beat Battle format appealed to me, so I transposed it to building: vibe-coding usually happens alone, with no clock and no judge. I wanted to see what survives with an imposed brief, a shared timer and a vote.
01 The problem
A real-time competitive game breaks at its client: if the browser owns the clock or the choice of brief, the first curious player opens devtools and wins.
02 What I built
Every match lives inside one Cloudflare Durable Object, with its server-side alarm timer. The React SPA renders state, it never produces it.
The decision I am most pleased with is invisible on screen. In ranked mode the server draws the brief from a private seed and explicitly refuses to seed it from the battle id: that id is returned to the host, and the brief-picking function ships inside the SPA bundle. Seeding from the id would let the host recompute the brief before the start, then pre-build.
Three scopes were cut to stay on a free plan: the paid auto-clip pipeline, publishing to an external URL, the async gallery. Email auth was dropped in favour of guest-only.
03 What I take from it
MVP deployed on Cloudflare Pages and Workers on 25 July 2026. The full loop runs locally, as a solo demo against two bots. The hosted auth service is paused: the landing screen answers online, but a battle cannot yet be created or joined.
The brief-seeding hole was closed before a single player played.
Arbitrate
The server holds the clock and the five phases. The browser renders, it does not decide.
Pin
Published bytes are pinned at submit: a late edit cannot change what was voted on.
Seal
Vote tallies are never broadcast before the results phase.
Result
321unit tests, plus a smoke test over real WebSockets
- Published bytes pinned at submit: a late edit cannot change what was voted on
- Vote tallies never broadcast before the results phase
- In ranked mode the brief is drawn from a private seed, never from the battle id