Labyrinth Racer (MazeRPC)
Fast‑loop, seed‑deterministic FPV maze racer. Monorepo with a Next.js client, a Node Room Authority (WS + event log), shared types/PRNG, a tiny AI helper, and infra bits (Redis, Postgres, Prometheus).
Project repo: J0YY/mazerpc
The gist
- Deterministic seed → same maze for everyone. Client renders locally using the shared generator; server is authoritative for physics and state.
- Clients send high‑rate inputs with sequence numbers. Authority ticks at 20 Hz, applies inputs, handles pickups/finish, broadcasts
STATE
frames. - Event sourcing: append significant events to Redis Streams; periodic snapshots to Postgres for restart/replay.
- Observability: Prometheus metrics at
/metrics
(tick duration, WS connects, frame bytes). - Optional AI: server can request “rival radio” banter or post‑run recaps via OpenAI Responses API (env‑gated, off by default).
Background / inspo
Speedrunning + distributed systems. The thrill is a live authority that never lies: your client can speculate, but the server keeps you honest. The maze is verifiable from a signed seed, so runs are shareable and replayable.
What’s in the box
apps/web
: Next.js + React Three Fiber client. FPV/Orbit toggle, warm dungeon palette, start/exit beacons, candles, breadcrumb trail, hot/cold proximity panel, local input sampling.apps/room-authority
: Express + ws authority. Deterministic maze from seed, 20 Hz loop, collision, pickups, Redis Streams logging, metrics, Prisma snapshots.packages/shared
: zod‑typed contracts, mulberry32 PRNG, seed/hash utils.packages/sim
: maze generation (recursive backtracker + loop carving), path metric, simple physics with sub‑stepped per‑axis sweeps (anti‑tunneling).packages/ai
: minimal OpenAI Responses wrapper (rival banter / coach recap), env‑gated.infra/compose
: docker‑compose for Redis + Postgres.
Controls (client)
- F: Toggle FPV (pointer‑lock) / Orbit (peek). Esc unlocks in FPV.
- WASD (view‑relative), Shift to sprint.
- Left panel: escape proximity meter (hot/cold). Right panel: rival radio.
- Settings: FOV, brightness, grid toggle, candle density, speed.
How it works (high‑level)
- Seed notarization:
GET /seed/:mode
→{ seed, roomId, ws }
. Seed issha256(mode|salt|roomUUID)
. - WS connect:
/ws?room=<roomId>&player=<uuid>
; first client instantiates the authority with that seed. - INIT: server sends
{ kind: "INIT", seed, layoutHash, tickRate, ... }
; client builds the same maze locally. - Inputs: client sends
InputMsg { t_client, seq, move, action }
every frame; server applies inputs at 20 Hz (sub‑steps prevent tunneling). - Broadcast: authority emits
STATE
frames (~10–15 Hz). Client reconciles and renders HUD/breadcrumbs. - Finish: reaching exit emits a
Finish
event; client may request a signed receipt (seed, timeMs, path hash).
Determinism and fairness
- Maze = f(seed). Seed is announced and reused on both client/server → verifiable runs.
- Physics: authority resolves collisions. Client prediction is allowed but authoritative state wins.
Event log and snapshots
- Events → Redis Streams
match:<roomId>:events
(fast path). - Snapshots every ~5s → Postgres via Prisma for rapid recovery.
- Replay plan: load latest snapshot, apply events since offset.
Observability
/metrics
exposes Prometheus counters/histograms: tick duration, frame bytes, WS connects, etc.
AI hooks (optional)
- Rival banter and coach recap via OpenAI Responses API; both behind env flags.
Repo layout
apps/
room-authority/ # Node 20 authority (Express + ws + Redis + Prisma)
web/ # Next.js + R3F client
packages/
shared/ # zod contracts, PRNG, seed utils
sim/ # maze gen, path metric, physics
ai/ # OpenAI helpers (Responses API)
infra/
compose/ # docker-compose for Redis + Postgres