Small on purpose
The whole game downloads in about two megabytes. It is written in plain JavaScript on three.js, with no engine, no framework and no audio files — every sound is synthesised in the browser from noise and oscillators. The rider, trees, rocks and rails are low-poly models; the mountain is not a model at all.
WebGPU, with a net
Powderline renders through WebGPU where the browser has it and falls back to WebGL2 where it does not — one scene, one set of materials, the backend picked at boot. That fallback is not optional: the game runs on phones and inside game portals, and a WebGPU-only build would be a black screen for a large share of players.
Because both backends must draw the same picture, nothing in the project hand-writes a shader. Every surface — the procedural sky, the distant ranges, the snow with its per-vertex ice gloss — is described once as a shader graph and compiled to WGSL or GLSL by whichever backend is running. The two were held to pixel parity against the previous renderer before the switch shipped.
A mountain from arithmetic
The terrain is a height function built from layered value noise — see how the mountain is built — and the noise's hash is integer arithmetic, not the usual fractional-sine trick. That choice is why two browsers can build the identical mountain for a race, and why a GPU can be asked to build a chunk and produce the same vertices, to the last bit, as the CPU the physics runs on. The world streams in 80 m chunks; the physics samples the same function directly, so the surface you see and the surface you land on are one thing.
Snow that remembers
The board's edge writes into a depth field that follows the rider — a render target the snow material reads back as relief in the surface, so a track lights like a groove: bright on the lip facing the sun, blue in the trough. Landings punch a crater sized by impact. It is real relief in the shading rather than displaced geometry; the terrain's vertices sit two metres apart, and a board track is thirty centimetres wide.
On top of the frame there is a small post stack: valley fog that knows which way is down, so the air pools in the bowls below you; a blizzard that thickens with depth, layered into that same air; and a radial speed blur that only appears once the run is genuinely quick. All of it is off on the low quality setting, and the blur has its own switch.
A backend that trusts nobody
Accounts, quests, the leaderboard and the wheel live on a Supabase backend in Frankfurt. The browser is treated as hostile: coin payouts, drop odds, quest validation and rider-name rules are all enforced in the database, so nothing can be minted, rolled or renamed by editing the page. The public API key in the game can read what it is allowed to read and write only what the server-side rules permit. Development runs against a separate self-hosted copy of that backend, so testing never touches real riders' data.
Races are the exception that proves it: there is no server in a race at all. The two browsers connect directly over WebRTC and each is authoritative over its own rider — see 1v1 races.
How it is tested
The physics runs headless in Node against a stub world, with checks for the things that have broken before: that a scripted run over a row of kickers ends within two percent of the same spot at 30 Hz and 240 Hz, that every authored rider pose keeps both boots on the deck, that a kicker can only be ridden from its entry. Rendering changes are filmed headlessly on both backends and compared region by region against the previous build. The rider name filter has its own suite, and half of it is names that must be allowed.
Your first run starts in seconds
Free in your browser on a laptop, a phone or a gamepad. No download, no sign-up.
PLAY FREE