
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 
STATEframes. - 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 
STATEframes (~10–15 Hz). Client reconciles and renders HUD/breadcrumbs. - Finish: reaching exit emits a 
Finishevent; 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
/metricsexposes 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