Authoritative Colyseus server that powers the /walk page on three.ws.
This process runs outside the Vercel deploy — Vercel doesn't host long-lived WebSockets, so this server lives next to the static site (Fly.io, Railway, Render, or a small VPS). The client at three.ws/walk connects to it over a WebSocket and exchanges player state through the WalkRoom defined in src/rooms/WalkRoom.js.
# From the repo root
npm install # installs this workspace too
npm run dev:multi # boots the Colyseus server on :2567
# Or, in another terminal, both servers together:
npm run dev:walk-all # Vite (:3000) + Colyseus (:2567)The Vite dev page at http://localhost:3000/walk will autodiscover the server at ws://localhost:2567.
| Var | Default | Purpose |
|---|---|---|
PORT |
2567 |
TCP port to bind |
HOST |
0.0.0.0 |
Interface to bind |
ALLOWED_ORIGINS |
localhost:3000-3003,three.ws,www.three.ws |
Comma-separated origin allow-list for the WS upgrade. *.vercel.app and *.three.ws are always allowed so preview deploys connect. |
| Route | Purpose |
|---|---|
/health, /healthz |
Liveness probe — returns {ok:true} |
/colyseus |
Admin monitor UI (@colyseus/monitor) — protect this behind a reverse proxy or basic auth in prod |
| WS upgrade | Colyseus protocol — clients connect with new Client('ws://host:2567') |
Every move message is validated server-side in WalkRoom.js:
- Max-step clamp: positions farther than
1.2 mfrom the player's last position are rejected (legit deltas at 15Hz × 4 m/s run speed are ~0.27 m). - World bounds: a 60 m radius around origin; out-of-bounds positions are clamped.
- Y clamp: vertical position pinned to
[-10, 10]. - Rate limit: 30 moves/sec per client window — well above the 15Hz the client sends, so it absorbs jitter without dropping legit traffic.
- Field types: every numeric field is
Number.isFinite-checked; motion strings are validated against an allow-list.
cd multiplayer
fly launch --no-deploy # reads fly.toml (already in this dir)
fly secrets set ALLOWED_ORIGINS=https://three.ws,https://www.three.ws
fly deployAfter deploy, point the client at the new host by adding a meta tag to walk.html:
<meta name="walk-server" content="wss://three-ws-multiplayer.fly.dev">- A single Node process holds many rooms (each room = one WalkRoom instance).
- Each room caps at 50 clients (
MAX_CLIENTS_PER_ROOMin WalkRoom.js). Colyseus's matchmaker creates a new room when the current one fills. - Across machines: add
@colyseus/redis-presence+ a Redis instance so matchmaking is cluster-aware. This is a config-only change tosrc/index.js; add it when you cross ~200 concurrent players. - Memory budget: ~5 MB per 50-player room on Node 22. The default Fly VM (256 MB) holds plenty of rooms.
See src/schemas.js. Each Player is 8 fields, encoded as a binary delta: only the fields that changed since the last patch are sent. At 15Hz × 50 players × ~24 bytes/player avg = ~18 KB/s outbound per fully-busy room — fine on any VPS.