← Home
ProjectPersonal project · 2026

BuildBattle

Two to eight players, one brief, twenty minutes, and a match built to hold up against the player who opens devtools.

5 phaseslobby, build, showcase, vote and results, all arbitrated server-side
Cloudflare Durable ObjectsWebSocketSupabaseReact 18TypeScript
BuildBattle in the build phase: the phase bar reading lobby, build, showcase, vote, results; the brief drawn for the match, "Magic 8-Ball"; the server-held clock 26 seconds from lock-in; and the constraints imposed on every player.
01

Context

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.

02

Where it breaks

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.

03

State on the server

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 crypto.randomUUID seed and explicitly refuses to seed it from the battle id. The reason is written in the code: 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 START, then pre-build.

04

Deployed, not joinable

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.

05

What was cut

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.

Highlights

  • Published bytes pinned at submit: a late edit cannot change what was voted on
  • Vote tallies never broadcast before the results phase
  • 321 unit tests and a smoke test running three players and three submissions over real WebSockets
The takeaway

In ranked mode the server refuses to seed the brief from the battle id: the host knows that id and the picking function ships in the client bundle. The hole was closed before a single player played.

More work