JevArena
September 18, 2026 · View on GitHub
Jev fights Jev in parallel universe — on Snake.
JevArena is a public, open-source spectator demo by Raihan Khan. Two independent TypeSafe Jev agents drive two live Snake browsers side by side using only indexed direction-button clicks.
- Classic Snake, no custom game explanation required
- Two independent same-origin browser contexts
- Click-only: no typing and no
TEXT_MODEL - One server-only
TYPESAFE_API_KEY - Live operation and direction probabilities
- Railway-ready standalone Next.js image
How the duel works
Each pane embeds a separate /play/snake page in its own iframe browsing
context. Every game owns its own loop, snake, food seed, DOM direction buttons,
score, and crash state.
Snake iframe A state ─→ Jev ─→ CLICK direction button in iframe A
Snake iframe B state ─→ Jev ─→ CLICK direction button in iframe B
The iframe streams structured board snapshots to the spectator page with
same-origin postMessage. The parent sends each snapshot to the server-only
/api/agent/step route. Jev receives:
- head, body, food, score, current direction, and tick
- only legal/safe on-screen direction buttons as indexed targets
- recent actions for that agent
The selected target is sent back to that specific iframe, which resolves the
current data-element-id and calls its real direction button. The avatar
flashes over the board and clicked control so viewers can see who acted.
Why iframe browsers instead of Playwright in production?
The first design considered two Playwright Chromium contexts plus screenshot or CDP screencast streaming. That requires persistent browser processes and a stateful stream per viewer, making the public Railway demo materially heavier and less reliable.
The shipped driver uses two real, isolated same-origin iframe browsing contexts. It preserves the important browser-agent boundary—live page state, indexed DOM targets, and real button clicks—without bundling Chromium or running server-side browser workers. A Playwright driver can replace the iframe transport later without changing the Jev request contract.
Match rules
- The match runs for 60 seconds.
- A snake automatically advances every 230 ms.
- Jev can click
UP,DOWN,LEFT, orRIGHT, or wait. - Eating food adds one point and grows the snake.
- The first crash loses immediately.
- If both survive, the higher score at time-up wins.
- Each browser uses an independent deterministic food seed.
- Rematch aborts both old loops and opens fresh browser instances.
Anonymous fight counter
When both iframe browsers are ready and a match is about to start, JevArena
calls POST /api/fights. The endpoint atomically increments a local JSON file
and writes one Railway log line:
fight_click total=42 persistence=volume
There is no identity, tracking cookie, UTM capture, or analytics SDK.
GET /api/fightsreturns the current count and persistence mode./statsshows Fights started: N./data/fights.jsonis preferred when/datais writable./tmp/jevarena/fights.jsonis the automatic non-durable fallback.
For durable production counts, open the Railway project, select the
jevarena service, add a Volume, and mount it at /data. Redeploy once; the
container entrypoint assigns the mounted directory to the unprivileged
nextjs runtime user, and the stats page will show that Volume storage is
active. No database or paid data service is required.
Run locally
Requires Node.js 20 or newer.
git clone https://github.com/raihankhan-rk/jevarena.git
cd jevarena
npm install
cp .env.example .env.local
Set the only credential:
TYPESAFE_API_KEY=your_key_here
Then:
npm run dev
Open http://localhost:3000.
Without a key, JevArena uses a clearly labeled deterministic direction policy
for UI and game testing. A configured Jev failure never silently falls back;
that browser becomes BLOCKED.
Verify
npm run typecheck
npm run lint
npm run build
Railway
The included multi-stage Dockerfile emits Next.js standalone output,
respects PORT, and serves / with HTTP 200. The iframe driver does not need
Chromium packages. Connect the repository, set TYPESAFE_API_KEY, and deploy
from main. Mount a Railway Volume at /data if the fight count must survive
deploys and restarts.
Project map
app/play/snake/page.tsx independent Snake browser route
app/api/agent/step/route.ts validated server-only Jev endpoint
app/api/fights/route.ts anonymous file-backed counter API
app/stats/page.tsx private-by-URL count display
components/snake-game.tsx canvas game + indexed DOM controls
components/agent-pane.tsx browser chrome, iframe, and telemetry
components/arena.tsx match clock and two independent agent loops
lib/snake/engine.ts deterministic Snake simulation
lib/arena/games.ts board observation and action-space builder
lib/server/fight-counter.ts atomic /data counter with /tmp fallback
lib/server/jev.ts @typesafe-ai/sdk operation/direction policy
Security
@typesafe-ai/sdk, TypeSafeClient, and systemOne are imported only by the
server module. .env* files are ignored; .env.example contains only
TYPESAFE_API_KEY=.
License
MIT © 2026 Raihan Khan