Armed cars, six arenas, a live broadcast — twelve cars, one survivor.
YOVNOK is a vehicle-combat battle royale that runs in the browser, on a computer or a phone. You drive and shoot at the same time: twin machine guns in the headlights, an RPG on a roof turret, a closing ring of danger, and eleven other cars that want you gone. Every match is broadcast live.
Play now: play.yovnok.com
It is free software (GPL-3.0-or-later) and every third-party asset in it is free to share — see Assets and licences.
A browser with WebGL 2 (current Chrome, Edge, Firefox or Safari), played with a keyboard and mouse, a gamepad, or touch on a phone or tablet (held sideways; add it to the home screen for full screen).
Press PLAY, then:
- SOLO — twelve armed cars, every car for itself.
- DUOS — six cars, each a driver and a gunner leaning out of the window.
- SQUADS — three SUVs, each a driver and three window gunners. The SUV carries 1.6× the armour but pulls and turns slower.
Every mode is a battle royale: the ring closes, the last car standing wins. Bots fill whatever seats players do not (people are seated together). Alone on the server, a match starts in seconds; when others are online, it waits up to a minute so people pressing PLAY around the same time share it.
In duos and squads the driver only drives and the gunners only shoot. Every car carries its people — a driver at the wheel and, in team modes, gunners who sit tucked inside and lean out of their window to aim, gun at the shoulder, with an over-the-shoulder view — all the way round: aiming across the car they climb onto the sill and shoot over the roof, where more of them shows. Tucked in they cannot shoot or be shot; leaning out they can do both. A gunner holds a gun and a grenade launcher and swaps between them, can move to another window (not the wheel), and when shot down is knocked back inside for 8 seconds. The car is out when its hull is.
- PRIVATE ROOM — create a room and send friends its five-letter code or invite link. 2–12 players, no bots; the host picks the mode and the map and starts, and everyone picks their car and seat.
When the server is at capacity, PLAY shows a waiting card and goes in as soon as a seat frees up.
| Keyboard + mouse | Gamepad | Touch | |
|---|---|---|---|
| Throttle · brake / reverse | W · S |
RT · LT |
left thumb: stick up · down |
| Steer | A / D |
left stick | left thumb: stick sideways |
| Aim | mouse | right stick | right thumb: drag (with aim assist) |
| Twin machine guns | left click | RB |
MG (slide to aim) |
| Roof RPG | right click | LB |
RPG |
| Handbrake | Space |
X |
DRIFT |
| Boost | Shift |
B or L3 |
BOOST |
| Reload | R |
Y |
↻ |
| Lean out and aim (gunner) | hold right click | hold LT |
thumb on the right |
| Fire (gunner) | left click | RT or RB |
FIRE |
| Swap gun / launcher (gunner) | Q or the wheel |
LB |
SWAP |
| Change window (gunner) | F |
D-pad left/right | SEAT |
| Menu | Esc |
Start (D-pad + A/B to choose) |
☰ |
| Performance overlay | F3 or ` |
— | — |
The arenas (public matches rotate between them after every match):
| Arena | |
|---|---|
| The Stadium | floodlit night; container cover, a mesa, a two-deck crowd that cheers the kills |
| Dusk Canyon | sunset; sandstone mesas, a dry wash, a rock shelf |
| Rainy Dockyard | night and rain; a maze of container lanes, warehouses, gantry cranes |
| Snowbound Base | overcast; runways, hangars, banked bunkers, frozen ponds with no grip |
| Quarry | clear day; an open pit on three levels joined by haul roads |
| Pine Forest | misty morning; trees to weave through, tracks, a logging camp, a bog |
In a match
- The guns swing ±20° off the nose — aim them mostly by aiming the car. The roof RPG turns much further and hits hard, but reloads slowly.
- The ring closes in stages. Outside it, you burn.
- Damage shows: smoke, then black smoke and flames, then a car on fire. Repair crates and wreck salvage patch you up if you sit still on them.
- Close or reload the tab mid-match and a bot keeps your car going for 30 seconds — come back in time and REJOIN puts you back behind the wheel.
The garage. Paint, a pattern (stripes, hazard, camo, tiger, checker) in a colour of your choice, rim colour, a body kit (bull bar, light bar, rear wing, side armour, side exhausts), a door decal with your race number, underglow, and your outfit (SWAT, Mechanic or Soldier, in your paint colour) — some unlocked by playing (matches, kills, wins). And your guns, all open from the start:
| Bumper guns | Roof weapon | ||
|---|---|---|---|
| Twin .50 | a steady stream, all-rounder | RPG | one heavy rocket, big splash |
| Scattergun | seven pellets a shot, deadly up close, nothing past 70 m | Swarm | four rockets at once, fanned out |
| Lancer | slow, heavy, pin-point rail shots at long range |
Your callsign, look and loadout live in your browser; there are no accounts yet.
Requires Node.js 24 (or newer) and npm.
npm install
MODE=solo npm run devThen open http://localhost:5173. That starts two processes:
| Process | Port | |
|---|---|---|
| game server | 8787 |
the authoritative simulation and WebSocket (/ws) |
| Vite | 5173 |
the client, proxying /ws to the game server |
Useful switches (environment variables for the server):
| Variable | Default | |
|---|---|---|
MODE |
duel |
solo is the game as released (one-car teams, bots, the ring) |
BOTS |
fill in solo |
off, or a number to fill the field to |
SOLO_CARS |
12 |
cars per solo match |
MAX_ROOMS |
3 |
simultaneous matches in one process, public and private |
MAX_PLAYERS |
16 |
players at once; past it, PLAY waits for a seat |
REJOIN_SECONDS |
30 |
how long a dropped player's car is held |
ALLOWED_ORIGINS |
(any) | comma-separated origins allowed to open sockets |
TRUST_PROXY |
off | 1 behind a reverse proxy (client IP from X-Forwarded-For) |
To try a bad connection, add ?lag=120&jitter=40&loss=0.05 to the page URL.
Production build: npm run build && npm start serves the built client and
the socket from one process on port 8787. Or with the container:
docker build -t yovnok . && docker run -p 8787:8787 -e MODE=solo yovnok.
npm run checkruns the whole suite — about 400 checks — and the production build:
| What it guards | |
|---|---|
typecheck |
TypeScript, strict |
licensecheck |
every asset listed, credited and under an allowed licence |
assetcheck |
built models against their size and LOD budget |
simcheck |
driving physics, collision, and every map's invariants (spawns, symmetry, crates, reachability) |
combattest, weapontest |
hit detection, damage, weapons and their mounts |
matchtest |
the match lifecycle end to end against a real server |
bottest |
bot driving, aiming and weapon discipline |
cosmeticstest |
paints and unlocks are look-only; callsign rules |
roomstest |
several matches at once, rejoin, private rooms, the player limit, and abuse cannot crash the server |
netheadless |
client prediction and reconciliation over a real socket |
Diagnostics that are not pass/fail: npm run solobench plays real bot matches
and reports match length, accuracy and damage by weapon (DEV_MAP=<map id> picks
the arena); scalebench and
driftbench measure load and handling.
- Client: TypeScript and three.js (WebGL 2), built with Vite. Models are glTF with meshopt geometry and KTX2 textures.
- Server: Node.js and
ws. The server is authoritative: it simulates every car at 60 Hz; clients predict their own car and reconcile, and draw everyone else slightly in the past, interpolated. - Shared: the simulation, arena, weapons and rules are one codebase used by
both sides (
src/shared), so client and server cannot disagree.
src/
shared/ simulation, arena, weapons, match rules, protocol
server/ rooms (matches), bots, the HTTP + WebSocket server
client/ rendering, input, camera, audio, HUD and menus
scripts/ tests, benchmarks, and the asset pipeline
assets-src/ source models and textures, with their prep scripts
public/ built assets served to the browser
More depth: docs/ARCHITECTURE.md (how the server, client, rooms, netcode and statistics work), ASSET_SPEC.md (the asset pipeline) and DEPLOY.md (hosting and operations).
The code is GPL-3.0-or-later — see LICENSE.
Third-party assets are used only under CC0, CC-BY or CC-BY-SA
(never non-commercial or no-derivatives), each credited with its author,
source and licence in ASSETS.md and in the game's Credits
screen. npm run licensecheck fails the build if anything is unlisted,
uncredited or under another licence. Models come from Poly Haven (CC0) and
Sketchfab (CC-BY); music from Kevin MacLeod (CC-BY); sound effects from
OpenGameArt (CC0 / CC-BY-SA).
Sponsor names in the arena are fictional.
Issues and pull requests are welcome.
- Run
npm run checkbefore opening a pull request. - Commit messages follow Conventional Commits:
feat(bots): lead moving targets,fix: …,docs: …,chore: …. Acommit-msghook (installed bynpm install) and a GitHub check enforce it. - New assets must be added to
assets.jsonwith their licence, or the build refuses them.
