A browser multiplayer FPS. The server owns movement and hits. ArenaForge is a level-design agent that edits a map you can then play in the same runtime.
Play: game.cyberrunnergame.dev
Guest play works. Two tabs can share a Deathmatch with the 6-letter HUD code.
The server runs movement and hits at a fixed 60 Hz RAPIER tick. The client predicts locally, reconciles on each ack, and fires through a bounded rewind window.
Draw an arena boundary, place the starting positions, and give ArenaForge a brief. The agent publishes a plan, builds the map through bounded level-editing tools, reads deterministic gameplay checks, and can revise before the result is saved and launched in CyberRunner.
Search & Destroy and Deathmatch are both supported. Playing a saved map does not call a model.
- Public map: Shoot House Neon
- Deathmatch: first to 5 kills, 10 minutes
- Search & Destroy: 3 lives, first to 3 rounds, spike plant and defuse
- Eight weapons, hitscan and projectile
- Crouch, prone, slide, sprint
- Quick Play and hosted rooms
map-contract-smoke is an internal fixture. It is not in the Create Game picker.
Open Arena Forge in the lobby.
- Recorded Design replays a complete native S&D design from a blank user-defined arena, including the plan, construction, deterministic checks, route inspection, playtest feedback, and revision. It runs without a model key.
- New Design is the live product path: mouse-draw a rectangle, place starts, write a brief, Generate.
Saved maps show up in Create Game as FORGE · <name>. Quick Play stays official maps only.
Human design setup
↓
ArenaForge agent
↓
ArenaMap
↓
evaluator / scripted playtest
↓
GameplayMapDefinition
↓
┌───────────────┐
│ │
Play now Save
│ │
Room Postgres
↓
Lobby
↓
Room
To run live design on your machine, put an OpenAI or Anthropic key on the server. See docs/arena-forge-live.md. Guests can Save Map for the current session. Keeping maps on a profile needs Postgres and a signed-in user. The public site keeps live design off unless you turn it on with sign-in and daily caps.
Recorded Design is a product demonstration of the current native loop. Historical P4 and P5 runs remain evaluation and research evidence. They are not a public Forge tab.
Both historical designers solved the simple repair cases (blocked spawn, broken routes) in one edit. A harder suite, where a useful edit can break something else, did not show the iterative designer winning overall. The historical P5 session queried the playtest, overcorrected, then revised.
Numbers: server/arena-forge-evaluation.md, server/arena-forge-evaluation-p4b.md, server/arena-forge-playtest.md.
The camera flythrough is presentation only. The scripted playtest is an offline navigation proxy, not bots in a live room.
- Client samples input on a 60 Hz tick and assigns a unique seq.
- Local RAPIER applies that command and stores a controller snapshot.
- The same command is sent as a 15-byte
input_bin. - Server queues it (max 24), applies one command per sim tick, writes
PlayerState, ackslastProcessedInputSeq. - Client restores the snapshot for that ack, applies the server pose, replays leftovers once, then smooths the camera.
Firing uses a 13-byte fire_bin. Shot origin comes from rewind history (RTT/2 plus 1.5 ticks, capped at 250 ms). Aim is client-originated and sanitized. Shared CharacterController, capsule, and shoot-house colliders live in shared/.
From npm run benchmark on 2026-08-29 (Node v23.11.0, Windows, i9-14900KF). Raw file: benchmarks/latest.json. Method: benchmarks/README.md.
These are local synthetic numbers, not a production load test.
| Item | Value | Kind |
|---|---|---|
INPUT_CMD size |
15 bytes | measured (encodeInputCmd) |
FIRE_CMD size |
13 bytes | measured (encodeFireCmd) |
| 60 Hz input payload | 900 B/s | calculated from 15 × 60 |
| Pending inputs at 0 / 50 / 100 / 150 ms | 0 / 3 / 6 / 9 ticks | synthetic local prediction harness |
| Tick time, 1 / 4 / 8 capsules | mean 0.017 / 0.054 / 0.151 ms | synthetic local RAPIER only |
F3 in a match, or debug.overlay() in the console, shows RTT, seq/ack, pending replay, and predicted vs server pose.
npm install
npm test
npm run dev:server # ws://localhost:2567
npm run dev:client # http://localhost:5173npm run build
npm run previewCopy server/.env.example to server/.env only if you want auth, a database, or live Forge. Guest play works without that.
Live Forge setup: docs/arena-forge-live.md. npm run forge:doctor checks configuration without calling a model.
Production (Caddy, systemd, Postgres): server/DEPLOY.md.
| Layer | Choice |
|---|---|
| Render | Three.js |
| Physics | RAPIER 3D (WASM), client and server |
| Rooms | Colyseus schema sync plus binary input_bin / fire_bin |
| Server | Node.js, Express, optional Postgres + Google OAuth |
| Client build | Vite + TypeScript |
| Hosting | DigitalOcean, Caddy (HTTPS), systemd |
| Live Forge | OpenAI or Anthropic, server-side only |
- Single-region rooms. Default cap is 8 players.
- Remotes use chase-latest lerp, not a delayed snapshot buffer.
- Client prediction does not collide with other players.
- Hitscan rewinds. Projectiles use current poses.
- Aim direction is client-trusted after a sanity check.
- Live design is one active job per process. Saving personal maps needs Postgres and a signed-in profile.
The source is public to read. It is not open source. See LICENSE. Shipped media: ASSETS.md.