how to build

Project · ★★★ · Advanced

Arena

A real multiplayer game. Make a room, send the four-letter code to friends, and up to eight of you are bumping each other off a shrinking platform from your own phones. The last one standing wins the round. A tiny server per room runs the game twenty times a second, so everyone sees the same thing and nobody can cheat by editing their copy. If someone's Wi-Fi drops for a moment, they come straight back in. This is how real online games work, scaled down to something you can read.

You need

What you'll learn

What this shows the agent can do

The brief

Make a new folder for the project. Copy the brief below into a file called BRIEF.md in that folder, or download BRIEF.md. Open the agent in the folder and say:

Read BRIEF.md and start with milestone 1. Stop after each milestone so I can check it.
# Arena — real-time multiplayer sumo (★★★)

Build a real-time multiplayer game that friends join with a four-letter room code. Up to eight players are discs on a round platform. You steer with the keys or a thumb joystick and dash with a button. Bump other players off the edge. The platform shrinks over time, and the last one standing wins the round. One Cloudflare Durable Object per room runs the game: it holds the state, talks to every phone over WebSockets, and steps the physics 20 times a second, so the server decides what happened and nobody can cheat by editing their own copy. Extra people can watch as spectators. If a phone drops off Wi-Fi for a moment, it reconnects and keeps its place.

## Done looks like

- The home screen has "Create room" and "Join" with a code box. Creating a room shows a code like `KTVM` and a share link `/?room=KTVM`.
- Up to 8 players per room, each with a name (up to 12 letters or digits) and a colour. A ninth person joins as a spectator and sees the game but cannot move.
- Controls: arrow keys or WASD and Space to dash on a laptop. On a phone, drag on the left half for a joystick and tap a dash button on the right. Dash has a 2 second cooldown shown on the button.
- The server runs the game at 20 ticks per second. Players have momentum and push each other. Falling off the edge eliminates you for the round.
- The platform radius shrinks steadily after 20 seconds. Last player standing wins. A scoreboard counts round wins. A new round starts 5 seconds later.
- Other players move smoothly on screen, not in jumps, even though the server only sends 20 updates a second.
- A debug overlay (press F3) shows ping, server tick, players connected and snapshot age.
- Pull a phone off Wi-Fi for 5 seconds: it reconnects and the player is still in the game.
- An empty room stops ticking. It is cleaned up after 10 minutes with nobody in it.
- Live at the address `npx wrangler deploy` prints, on the Cloudflare free plan.

## Constraints

- One Cloudflare Worker with static assets, plus a Durable Object class `Room` (SQLite-backed, which is what the free plan offers). Use wrangler.
- Routes: `POST /api/rooms` makes a new code and returns it. `GET /ws/<code>` upgrades to a WebSocket and is forwarded to the `Room` named by the code (`idFromName(code)`).
- Room codes: 4 letters from `BCDFGHJKMNPQRSTVWXZ` (consonants only, so no rude words and no lookalikes).
- Server authority: clients send only inputs, `{"t":"in","seq":n,"x":-1..1,"y":-1..1,"dash":bool}`, at most 30 a second and only when they change. The room keeps the latest input per player, steps physics every 50 ms, and broadcasts `{"t":"st","tick":n,"ring":r,"p":[[id,x,y,vx,vy,alive],...]}`.
- Clients draw other players about 100 ms in the past, interpolating between the two snapshots around that time.
- On join the server gives each player a random token, kept in `sessionStorage`. Reconnect with backoff (0.5, 1, 2, 4 seconds, then every 5). The room keeps a disconnected player's slot for 15 seconds.
- Stop the tick loop when nobody is connected, so an empty room does not use your free allowance.
- Validate every message on the server: right shape, numbers in range, names cleaned. Drop anything else. Close a socket that sends more than 60 messages a second.
- Canvas 2D for drawing. No game engine, no frameworks, no build step for the front end.
- No accounts, no tracking. Phone works.

## Milestones

1. **The game, offline.** Single-player version in the browser: the platform, your disc with momentum, dash with cooldown, falling off, three dumb bots that drift around. Keyboard and phone joystick.
   Check: on a phone you can steer with your thumb, dash into a bot and knock it off the edge.
> Milestone 1 is a game you can play on your own. Everything after it is the server that lets friends play together, which is the point of this project.
2. **Room and socket.** The Worker, the `Room` Durable Object and the two routes. Each socket gets a hello message and the room broadcasts how many sockets are connected.
   Check: open the room link in three tabs; each tab shows "3 connected", and closing one makes the others show 2.
3. **Lobby.** Join with a name. A player list with colours. Up to 8 players, the rest become spectators.
   Check: with nine tabs open, eight are players in the list and the ninth says "Watching".
4. **Server-run game.** Move the physics from milestone 1 into the room. Clients send inputs and draw snapshots straight away, with no smoothing yet.
   Check: two browsers in one room can push each other off, and both screens agree on who fell.
5. **Smooth movement.** Interpolation 100 ms in the past and the debug overlay.
   Check: other players glide instead of stepping, and the overlay shows 20 ticks a second and a ping number.
6. **Rounds.** Shrinking ring, eliminations, last one standing, round wins, countdown to the next round.
   Check: a full round plays out on its own: the ring shrinks, the winner's name shows, a new round starts after 5 seconds.
7. **Reconnect, cleanup, bots.** Tokens and reconnect with backoff. Stop ticking when empty, clean up after 10 minutes. A `bots.mjs` script (Node 22 has `WebSocket` built in) that joins a room with N bots pressing random inputs.
   Check: `node bots.mjs KTVM 7` fills the room to 8 with you; turn your phone's Wi-Fi off for 5 seconds and on again, and you are back in the same game.
8. **Go live.** Deploy. Play a real game with friends on their own phones on mobile data.
   Check: three people on three different networks finish a round, and nobody sees other players jump or freeze.

## Stretch

- Client prediction for your own disc: apply your input at once, then correct towards the server snapshot using `seq`.
- A tag mode: one player is "it", touching passes it on, whoever is "it" the least time wins.
- Power-ups that spawn on the platform: heavy (harder to push), bouncy, speed.
- Sound with Web Audio for bumps, dashes and falls.
- Room settings for the creator: ring speed, round count, private room that hides the share link.
- Quick emotes (a few fixed words, no free text chat).

## Leave out

- Accounts, global leaderboards, matchmaking between strangers.
- Free text chat.
- Anti-cheat beyond server authority and message checks.
- Game engines, Phaser, frameworks.

Start by reading WRITING.md and the guide chapter on data if you need storage.

Milestones

The same milestones as the brief. Tick each one off when its check passes. Do one per sitting if you like. The dot in the list marks the point where you have something that works on its own. Everything after it makes it remember things or work for more than one person. Stop at the dot and use what you built before you go on.

  1. The game, offline. Single-player version in the browser: the platform, your disc with momentum, dash with cooldown, falling off, three dumb bots that drift around. Keyboard and phone joystick.

    Check: on a phone you can steer with your thumb, dash into a bot and knock it off the edge.

  2. Milestone 1 is a game you can play on your own. Everything after it is the server that lets friends play together, which is the point of this project.
  3. Room and socket. The Worker, the Room Durable Object and the two routes. Each socket gets a hello message and the room broadcasts how many sockets are connected.

    Check: open the room link in three tabs; each tab shows "3 connected", and closing one makes the others show 2.

  4. Lobby. Join with a name. A player list with colours. Up to 8 players, the rest become spectators.

    Check: with nine tabs open, eight are players in the list and the ninth says "Watching".

  5. Server-run game. Move the physics from milestone 1 into the room. Clients send inputs and draw snapshots straight away, with no smoothing yet.

    Check: two browsers in one room can push each other off, and both screens agree on who fell.

  6. Smooth movement. Interpolation 100 ms in the past and the debug overlay.

    Check: other players glide instead of stepping, and the overlay shows 20 ticks a second and a ping number.

  7. Rounds. Shrinking ring, eliminations, last one standing, round wins, countdown to the next round.

    Check: a full round plays out on its own: the ring shrinks, the winner's name shows, a new round starts after 5 seconds.

  8. Reconnect, cleanup, bots. Tokens and reconnect with backoff. Stop ticking when empty, clean up after 10 minutes. A bots.mjs script (Node 22 has WebSocket built in) that joins a room with N bots pressing random inputs.

    Check: node bots.mjs KTVM 7 fills the room to 8 with you; turn your phone's Wi-Fi off for 5 seconds and on again, and you are back in the same game.

  9. Go live. Deploy. Play a real game with friends on their own phones on mobile data.

    Check: three people on three different networks finish a round, and nobody sees other players jump or freeze.

Make it yours

Once it works, pick one of these and ask for it as a new milestone.

If it goes wrong

Other players jump around instead of moving smoothly.

The client is drawing each snapshot the moment it arrives. Say this to the agent:

Other players stutter. Buffer snapshots and draw other players 100 ms in the past, interpolating between the two snapshots around that time. Show snapshot age in the debug overlay.

The two screens disagree about who fell off.

Some physics is still running in the browser and deciding things on its own. Say this to the agent:

My two screens disagree about who fell. Make sure eliminations happen only in the Room and the client only draws what the snapshot says.

After a phone sleeps, it joins as a new player and the old one stands still.

The reconnect is not sending the saved token, or the slot was dropped too soon. Say this to the agent:

Reconnecting makes a new player. Send the sessionStorage token on reconnect and keep the old slot for 15 seconds. Show me the token in the debug overlay.