Projects / CyberRunner
CyberRunner
2025-2026
Browser FPS with 60 Hz server-owned movement and hits, plus ArenaForge, a level-design agent in the same runtime.
- TypeScript
- Colyseus
- RAPIER
- Three.js
- Node.js

CyberRunner is a browser multiplayer first-person shooter. The server owns movement and hits. Clients predict locally so controls feel immediate, then reconcile when the server acknowledges an input.
ArenaForge lives in the same application. You draw an arena boundary, place starts, and give a brief. The agent edits a structured map, runs deterministic gameplay checks, and can launch the result in CyberRunner. Playing a saved map does not call a model.
Play: game.cyberrunnergame.dev. Guest play works.
What I built
- Fixed 60 Hz server-authoritative RAPIER simulation for movement, hits, ammo, and match state.
- Client prediction and snapshot reconciliation on a shared
CharacterController. - 15-byte
input_binand 13-bytefire_bincommand codecs. - Bounded tick-rewind hitscan (RTT/2 plus 1.5 ticks, capped at 250 ms).
- Deathmatch and Search & Destroy, including spike plant and defuse.
- ArenaForge: bounded map-edit tools, deterministic geometry/navigation/spawn/LOS checks, optional scripted playtest, and launch into the live room.
- Recorded Design path that demonstrates the native loop without a model key.
How it works
Client 60 Hz input
→ local RAPIER prediction + 15-byte input_bin
→ server queue (max 24), one command per tick
→ authoritative RAPIER world
→ PlayerState + lastProcessedInputSeq
→ client restore / apply pose / replay leftovers
Firing uses fire_bin. Shot origin comes from rewind history. Aim is client-originated and sanitized. Shared movement, capsule, and map collision live in shared/.
ArenaForge writes GameplayMapDefinition maps that load into the same room.
Evidence
Local synthetic numbers from npm run benchmark on 2026-08-29 (Node v23.11.0, Windows, i9-14900KF):
| Item | Value |
|---|---|
INPUT_CMD size |
15 bytes |
FIRE_CMD size |
13 bytes |
| 60 Hz input payload | 900 B/s |
| Tick time, 1 / 4 / 8 capsules | mean 0.017 / 0.054 / 0.151 ms |
F3 in a match shows RTT, seq/ack, pending replay, and predicted vs server pose.
Historical ArenaForge evaluation: both designers solved simple repair cases in one edit. A harder interaction suite did not show the iterative designer winning overall (one-shot 88.9%, iterative 86.1%).
Limits
- 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.
- Live design is one active job per process. Saving personal maps needs Postgres and a signed-in profile.