1v1 Jev

September 17, 2026 · View on GitHub

Server-authoritative browser FPS: queue up, fight Jev (TypeSafe System One) in a Rust-like industrial yard, first to 5 kills. Spectators watch and chat from the sidelines. Final kill gets a killcam before the next challenger.

Stack

  • Client: Vite + Three.js + TypeScript
  • Server: Node.js + ws, 60 Hz sim, 20 Hz snapshots
  • AI: @typesafe-ai/sdk (jev-latest) with heuristic fallback

Setup

npm install
cp .env.example .env
# Put your key in .env:
# TYPESAFE_API_KEY=...
npm run dev

Production-style (single Node host serves API + built client):

npm run build
npm start

Serves the built client from the Node server on PORT (default 3001).

Controls

ActionInput
MoveWASD
LookMouse (pointer lock)
ADSRight mouse
FireLeft mouse
JumpSpace
ChatT

Game rules

  • One human vs Jev at a time; others queue / spectate
  • First to 5 kills
  • Bolt-action sniper: inaccurate hipfire, accurate once ADS settles (~65% into ADS) — classic MW2 quickscope timing
  • Shared radar (no fog of war)
  • Safe respawns with brief spawn protection
  • Final kill → killcam (full motion replay) → next queued player countdown
  • Solo players are re-queued automatically after each match

Jev

The server builds structured JSON state each decision tick (~9 Hz) and fans out Choice/Noul questions (move, yaw, pitch, ADS, fire, jump). API key stays on the server. Calls run only while a match is in playing and both fighters are alive. If TypeSafe is unreachable, a deterministic heuristic uses the same action interface so matches never stall.

Deploy (Railway)

Recommended: one Railway service runs the Node game server and serves the built client. Same origin means the browser uses wss://your-domain/ws automatically — no Vercel split, no VITE_WS_URL.

Browser ──HTTPS──► Railway (UI from client/dist)
       └──WSS /ws──► Railway (match sim + Jev)

Vercel alone cannot host this: serverless/edge functions are short-lived and do not support sticky WebSocket rooms + a 60 Hz sim.

Prerequisites

Steps

  1. Open railway.appNew ProjectDeploy from GitHub repo → select OneVOneJev.

  2. Important service settings (Service → Settings):

    • Root Directory: leave empty / repo root — do not set it to client
    • Builder: Dockerfile (uses root Dockerfile)
    • Custom Start Command: leave empty, or set exactly npm start (never npm run dev / vite)
  3. Railway builds the image; start command is npm start (Node game server serves client/dist + /ws).

  4. Open the service → Variables → add:

    VariableValue
    TYPESAFE_API_KEYyour TypeSafe key

    Railway sets PORT for you. The container already uses HOST=0.0.0.0.

  5. Settings → Networking → Generate Domain (HTTPS + WSS on the same host).

  6. Open the public URL — you should see the 1v1 JEV lobby.

  7. Sanity checks:

    • https://YOUR_DOMAIN/health{"ok":true}
    • Join Queue → countdown → match (WebSocket on /ws)

If you see vite: not found / npm run dev

Railway tried to run the Vite client instead of the production server. Fix:

  1. Root Directory = repo root (not client)
  2. Start Command = npm start (clear any dev / vite override)
  3. Redeploy

Local production parity

npm run build
npm start
# open http://localhost:3001

Cost notes

Railway’s hobby/trial tier is enough for a small public demo. TypeSafe usage scales with live match time (~9 Jev decisions/sec while playing).