A Trackmania-style time-attack racer with a hand-painted, Arcane / anime look. Loops, wall-rides, ballistic jumps, boost pads, drift mini-turbos, checkpoints with splits, ghosts and medals. It runs in any WebGL2 browser, so the same build plays natively on Apple Silicon and x86 Macs/PCs.
Everything is procedural. There are no model, texture or audio files: the tracks, city, car, brush-stroke textures, engine sound and soundtrack are all generated at load time.
Play it in your browser: https://enqam.github.io/riftline/
npm install
npm run dev # http://localhost:5199Other scripts:
npm run build # optimised multi-file build in dist/ (serve with: npm run preview)
npm run build:single # ONE self-contained HTML file in dist-single/ (double-click to play, works offline)
npm run sim # headless check: builds every track, lets the AI drive it, reports times/fallsAppend ?fps to the URL for an FPS / resolution readout. The dev and preview servers listen on
0.0.0.0:5199, so other devices on your network can join at http://<this-mac's-ip>:5199.
| Action | Keyboard | Gamepad |
|---|---|---|
| Accelerate | W / ↑ | RT (or A) |
| Brake / reverse | S / ↓ | LT (or X) |
| Steer | A D / ← → | Left stick / d-pad |
| Drift | Space / Shift (hold through a corner, release for a mini-turbo) | RB / LB |
| Respawn at last checkpoint (keeps your speed) | Enter / Backspace | B |
| Instant restart | R / Delete | Y |
| Camera (chase / far / hood) | C | View |
| Toggle ghost | G | |
| Music on / off | M | |
| Pause | Esc / P | Start |
Braking while steering hard at speed also starts a drift. A drift charges through blue, then orange, then pink. Release it to cash in a mini-turbo.
- Gilded Ascent: golden hour over a clockwork city of spires, domes and airships. It has a loop, a kicker jump and a 48° banked hairpin.
- Neon Undercroft: a toxic night undercity with neon signs, a near-vertical 150° wall-ride, a loop and a jump.
- Skyshard Rift: twilight among floating isles and giant crystals, with a drop start, a double loop, a 170° wall-ride and two big jumps.
Each track's author medal time comes from the built-in AI driver. Gold, silver and bronze are scaled from it.
You enter a driver name before your first race. Change it any time with the name button at the top of the main menu. Every finish is posted to the leaderboard (main menu → Leaderboard, ← → to switch tracks). Each driver keeps their best time per track. The results screen shows your rank and the top five.
While racing you chase up to two ghosts:
- Gold: the track record holder, with a name tag.
- Cyan: your own best.
If a track has no record yet, you race the author's (AI) ghost.
Where the board lives:
npm run dev/npm run preview. The Vite server also serves a small API (server/leaderboard.js). Everyone playing against that machine, for example over the network athttp://<this-mac's-ip>:5199, shares one board and one record ghost per track. It's stored indata/leaderboard.json. To reset the board, stop the server, delete that file, and start it again.- Single-file build, static hosting (including the GitHub Pages link above) or the Artifact. There is no server, so the board and ghosts are kept in that browser's local storage instead.
The API is a LAN-party convenience with basic input checks. It isn't hardened against someone deliberately posting fake times.
- Painterly toon shading (
src/render/materials.js). Two cel bands whose edges are broken up by a generated brush-stroke texture, coloured ambient in the shadows, a warm terminator band, a hard anime specular, and a rim light that flares up when a subject is backlit. - One-pass G-buffer. Every material writes HDR colour and view-space normal + depth to two render targets (MRT, with MSAA).
- Post (
src/render/pipeline.js):- ink outlines from depth/normal discontinuities, with wobbly line weight so they look hand-inked
- mip-chain bloom
- sun shafts and an anime lens flare
- filmic tonemapping, then split-tone grading
- anime speed lines, plus zoom blur and chromatic aberration while boosting
- paper grain and vignette
- Sky (
src/render/sky.js). A gradient sky with an anime sun and halo rings, stepped painted clouds, skyline silhouettes, stars and aurora. Toon water, a toxic sea or a sea of clouds sits below, with cel-shaded cumulus puffs above. - Instanced scenery (
src/render/environment.js):- towers with procedural windows, domes, spires, clock faces and airships
- neon signs and pipes
- floating isles with trees and crystals
- Track builder (
src/track/builder.js). Tracks are chains of blocks (straight, turn, loop, ramp, ballistic jump, landing) integrated like a turtle with yaw / pitch / bank, then sampled every metre. Jump gaps follow the exact ballistic arc for a design speed. - Surface queries (
src/track/surface.js). Closest point and road frame against the ribbon. A local search keeps continuity through loops, and a spatial hash handles landings on other sections. - Car (
src/physics/car.js):- fixed 120 Hz step, with render interpolation for smooth 60/120 Hz displays
- spring/damper suspension plus speed-squared downforce, so the car sticks through loops and wall-rides
- tyres with a capped sideways force, so cornering hard at speed slides rather than tracking like rails
- speed-preserving drift, boost pads and air control
- Collision. The road slab is solid from below, from the sides and at its end caps (miss a jump
and you hit the landing ramp's face). Barriers are solid from both sides. Buildings, pillars, isles,
crystals, pylons and lamps are solid (
src/physics/world.js), and flat roofs can be driven on. Touching the sea, smog or cloud floor respawns you. - Autopilot (
src/game/autopilot.js). Brakes to a curvature-based speed envelope. It drives the attract mode and sets author times.
Measured on Apple M4 Pro at 1536×1152 internal resolution with 4× MSAA: about 3 ms per frame, CPU and GPU combined. The game adapts to slower x86 integrated GPUs on its own:
- In Auto quality it starts on High and scales the internal resolution down (to 50–70%) when frames go over budget. If that isn't enough, it drops a preset (Medium: no MSAA; Low: 1× pixel ratio, smaller shadow map, fewer bloom levels).
- Scenery is instanced. The whole road with its walls is one draw call.
- Post effects run at reduced resolution where they can.
You can force a preset in Settings. If a laptop struggles, try Low and turn off ink outlines.
src/
main.js game loop, state machine, FX wiring, ghosts, medals, dynamic resolution
track/ track layouts, block integrator, surface queries
physics/ car model + tuning constants
render/ materials/shaders, pipeline, sky, track mesh, environment, car, particles
game/ camera rig, input, race/checkpoints, autopilot, storage/ghosts, leaderboard client
audio/ procedural engine, SFX and generative music (WebAudio)
ui/ menus, HUD, results (DOM + CSS)
server/leaderboard.js leaderboard + record-ghost API (Vite plugin)
tools/sim.mjs headless track validator (writes top-down maps to tools/out/)