A cel-shaded arcade boat racer through flooded Indian cities, built from scratch in TypeScript and Three.js. Real Bengaluru streets from OpenStreetMap, online races for four, runs on PC and phone.
The Story
Every monsoon, the same video does the rounds. A Bengaluru road, knee-deep in brown water, a bike pushing through it, an auto stalled in the middle, someone filming from a first-floor balcony. The comments are always the same: "Venice of the East." "Swimming pool on Outer Ring Road." "Silk Board finally has a water feature."
At some point the joke stopped being a joke in my head and turned into a question: if the roads are going to be rivers anyway, why not race on them?
So that's the game. Flooded Indian cities, turned into race tracks. You ride a jet ski or a hovercraft down MG Road at night, past the shops that are really there, around the Brigade Road hairpin, through cobbled Church Street, and flat out down Queen's Road past Chinnaswamy Stadium. Then you do it again in Mumbai, along Marine Drive with the sea wall on your left. Then in Kerala, where the flooding is less a disaster and more the whole point of the place.
It runs in a browser tab. No download, no install, no account. Send a friend a four-letter room code and you're racing each other in ten seconds, one of you on a laptop and the other on a phone.
What You Actually Do
Throttle, steer, and slide. Hold the powerslide through a corner and it charges boost in three tiers: the longer you hold, the bigger the kick when you let go.
Wave surge is the other boost, and it works backwards: you don't earn it, you spend it. Hold Shift and the boat goes to twice the speed it was doing when you pulled the lever, capped at 209 km/h so stabbing it repeatedly converges instead of compounding. You get four seconds of burn, and it's a budget, not a timer: it only drains while you hold it. One long pull, or six short stabs out of six corners. Run dry and it refills, all at once, after twenty seconds.
Hit the swells at speed and you get air. Land flat or lose time.
The Maps
Each map borrows the layout of a famous F1 circuit and drops it onto a real Indian place.
SPA-FRANCORCHAMPS
Flooded MalleshwaramMonsoon street race, laid out like Spa
OPENSTREETMAP ROUTE
MG Road & Church StThe real streets, routed from OpenStreetMap
CIRCUIT DE MONACO
Marine DriveThe Mumbai Grand Prix, Monaco-style
MARINA BAY STREET CIRCUIT
Kerala BackwatersAlappuzha to Fort Kochi, laid out like Marina Bay
The MG Road map is the one I'm proudest of. The lap is the real road network, routed over OpenStreetMap data: MG Road, Brigade Road, Church Street, Museum Road, St Mark's Road, Queen's Road, Cubbon Road, Link Road, and the sweeps under the Purple Line metro viaduct. The shops are the real shops, the ones OpenStreetMap lists on those streets, so you pass the actual cafés and pubs of Church Street. St Joseph's gate is where it really stands, the grid starts beside Chinnaswamy Stadium, and Kingfisher Towers glows over the rooftops in the distance, on its true bearing.
Screenshots
How It's Built
Everything you see is made in code. There are no 3D models, no texture files, no downloaded assets in the world. Every hull, rider, building, palm tree, bus and shop sign is geometry generated in TypeScript, coloured from one palette and drawn with cel shading and ink outlines so the whole thing looks like a comic. The only files are the loading-screen artwork and the music.
One wave field, everywhere. The water is a sum of Gerstner waves. The GPU uses it to draw the sea; the CPU samples exactly the same function to float the boats, so what you see is exactly what you're riding on.
Procedural riders, zero keyframes. Layers of signals (lean, throttle, landing, celebration) blend into a pose, and inverse kinematics put the hands on the handlebars. They lean into corners, brace for landings, and celebrate when they win.
Cloudflare multiplayer. Each room is a Durable Object: a tiny server that exists only for that room code, relays positions between up to four players and enforces the rules (host, ready-up, rematch). Drop off the Wi-Fi mid-race and your seat is held for thirty seconds, so a flaky phone connection doesn't kick you out.
Dynamic resolution scaling. It holds frame rate on a phone. The renderer watches how long frames really take and drops the resolution when the GPU falls behind, then steps back up when it can.
Problems I Had To Solve
Real streets don't make race tracks
OpenStreetMap draws a dual carriageway as two separate roads, splits streets at every junction, and has no road at all under the metro viaduct. The lap is routed as a shortest path between the corners over a graph of every road, with nearby road ends snapped together to bridge the carriageways. The viaduct leg is traced by hand along the Parade Ground's edge. Then the route is made "street-legal" for boats: corners eased to a minimum radius, and streets that run too close together pushed apart so two parts of the lap never overlap.
The park kept growing onto the track
Cubbon Park's lawns were laid in straight chords along the road, which is fine on a straight and terrible on a bend: the grass cut straight across the inside of the corner. Same with a railing that happily ran right across the Brigade Road hairpin. Now each lawn is only as deep as the space before the next stretch of road, runs shorten on bends, and the railing splits itself in half until every piece is back on the footpath.
The resolution scaler never fired on the phones that needed it
It was budgeting against how long the CPU took to submit a frame. But on a phone, the GPU is the slow part: the CPU finished in no time, so the scaler thought everything was fine while the game stuttered. It now measures the real interval between frames, drops the resolution when frames run long, and leaves 30 Hz screens sharp instead of mistaking them for slow ones.
Making the trailer without screen recording
Screen-recording a 3D game drops frames, so the trailer is made in code too. A test harness drives the game in a headless browser and steps the physics at a fixed 1/60 s per frame, so the footage plays back at a true 30 fps however slowly it renders. A Python script then cuts it on the beat of the music. Yes, that includes all four riders doing the Macarena on the start grid.
The Numbers
LINES OF CODE
~37,000
TypeScript
3D MODELS
Zero
All procedural geometry
MAPS
4 Cities
4 F1 circuit layouts
PLAYERS
Up to 4
Per online multiplayer room
DEVICES
PC & Phone
Keyboard / gamepad and touch controls
INSTALL
None
Instant play in any browser tab