←all writing
0123 Oct 2026/9 min read

I built a city so you could drive a taxi through it

Startup City is a 3D taxi game that runs in the browser. Twelve days of building it: a drone that became a taxi, traffic that had to be rewritten, crashes rated like real ones, and a police chase.

Next.jsReact Three FiberGame
Illustration for the post: I built a city so you could drive a taxi through it

Press Enter. You are behind an orange taxi at dusk, on 14th Street, with the Manthan T. HQ to your left and a video screen the size of a building telling you that ducks cross at the river and have right of way. Two passengers are waiting nearby. Nothing happens until you stop for one of them. Then the clock starts.

The start of a shift in Startup City: the taxi on 14th Street at dusk, street signs for Washington Road and City Museum, a city channel screen reading "Three late rides and the shift is over", the round mini-map bottom left and two waiting passengers listed above it. The first seconds of a shift. city.mnthan.com, 23 October 2026.

That is Startup City, at city.mnthan.com. It is a Next.js 16 app with three.js through React Three Fiber, and the whole city runs in your browser. This post is about the twelve days it took to go from an empty grid to a city with a police helicopter in it, and the parts that fought back.

It was a drone for about a day

On 4 October I had a drone delivery game. You flew over towers and landed parcels on roofs against a timer. It worked. By the evening I had deleted it.

The city exists so that startups can put their name on it, and a drone pilot spends the game looking down at roofs. A driver looks along the street, which is where a shop sign, a billboard or a screen would be. Everything about the drone was pulling attention to the one place no ad would go. So the drone became a taxi, the roof pads became doors at the kerb, and nothing of the flying version is left.

The city

Right now there are 592 buildings in five districts, 29 named places (a stadium, an airport, a casino, three hotels, a golf club by the ring road, two parks), a river with five bridges and a boulevard on each bank, a railway with two trains, and the sea on every side with a beach before it. Every street has a name and a sign at each lit corner, and the bottom right of the screen tells you which one you are on. Those numbers are from 17 October and they drift.

The taxi on the river boulevard at sunset, the downtown towers across the water, boats on the river and a wooden jetty on the right. The river road. The towers across the water are downtown; the bridges have billboards standing in the river beside them. city.mnthan.com, 23 October 2026.

The mini-map is round and turns with the car, nose up, the way Vice City’s did. It is a plain 2D canvas, not a second three.js view. The city is painted into a picture once, a few rows at a time while the browser is idle, and every frame that picture is drawn rotated under a round clip. A dot is the waiting passenger, a square is the address, and either one is pinned to the rim when it is out of range.

The traffic was fake, so I threw it out

The first traffic was a formula. Every car ran one street for ever and wrapped around at the edge. It looked fine for about a minute. Then I saw the same car on the same route again, and then a car appear out of the sea.

Now the traffic is a simulation that runs every frame, start screen included. A car drives out of a building’s gate, keeps to its lane, stops at a red light, queues behind the car in front (or behind you), turns at junctions, and eventually turns into another gate and disappears into the building, where the walls hide it. How many are out follows the city’s clock: a peak in the morning and evening, two thirds of that at midday, half in the small hours. A visit starts at dusk, so you arrive in the evening rush.

Junctions ate most of the month. The lights had a 4-second green at first. A car needs about two seconds of green left to clear a junction, so that let two or three through per cycle, and I watched a queue of six stand through three cycles, over half a minute. I also watched cars drawn straight through each other, because a turn landed on whatever was standing there, and two cars turning right across each other could each wait for the other for ever.

The green is 7 seconds now. A car at the stop line goes only if it can clear the far side before the cross street’s green, if nothing is over the junction box, if there is room for it past the box, and if the oncoming lane is clear when it is turning across it. The test for all this runs the whole city for five minutes of game time and fails if any two vehicles ever overlap or anything stands still for more than a cycle and a half. The dice are seeded, so a failing run is the same run every time. Without that I would still be debugging it.

The taxi with a passenger aboard, orange route arrows painted on the road ahead, a bus stop with two people waiting, and the fare panel reading "Take a groom in a hurry", Mast Tower 4, 0:25. Passenger in, 25 seconds left. The orange arrows are the route; the mini-map carries the same address. city.mnthan.com, 23 October 2026.

What a crash costs

My first playtest went badly. The car was too hard to handle, so I retuned it to be grippy and forgiving, and later made it faster (top speed 20, up from 17) with a stronger brake, so a stop from top speed takes as long as it did.

A crash is rated the way crash investigators rate one, by the taxi’s delta-v: the closing speed along the line of the hit, times M / (m + M), where the taxi and a car both weigh 1, a bus weighs 8, and a building does not give way at all. Damage grows with the square of the delta-v past a dead zone, so a scrape along a wall or a nudge at the lights is free, and six head-on hits at top speed wreck the taxi. A wrecked taxi burns for a few seconds with the keys dead, then blows up, and the blast damages whatever is near it. A car you hit hard enough is thrown off its lane and spins; a bus mostly stays put.

The taxi blowing up against a two-storey building: a fireball of orange sparks and flying debris in front of an orange "Square 5" sign and a green City Museum road sign. The end of a shift. It took sixteen runs under boost at the same wall; the taxi burned for a few seconds first, then this. city.mnthan.com, 23 October 2026.

The street furniture behaves like the real thing. Lamps shear at the base and fall, because most real street lights are built to. A tree bends or snaps depending on how hard you hit it. A billboard’s post does more damage than a wall at the same speed, because a narrow thing takes the whole hit into one spot, which is also what happens to real cars and real poles.

A ride with no crash earns a tip of a quarter of the fare. One crash, a tenth. More than that, nothing, and the passenger has something to say about it.

The people, the ambulance and the police

There are people on the pavements, and they can be hit. A light hit knocks them down and they get up and limp off. A fast one is fatal. Six seconds later an ambulance comes out of the hospital with its siren on, picks its way to the body through the traffic, and two paramedics carry a stretcher out and work the scene.

A death in sight of a police car counts towards a wanted star, and a spree brings stars whether anyone saw it or not. From three stars there are roadblocks across the street ahead of you; from four a helicopter follows you with a searchlight, and at five it shoots. Stop next to a police car for long enough and you are busted, which ends the shift with a fine.

The damaged taxi pinned against a building by two black-and-white police cars, two wanted stars in the top right corner. Two stars, two police cars, and I was standing still. Busted, with a 5-coin fine. city.mnthan.com, 23 October 2026. The cheat codes are ASPIRINE and LEAVEMEALONE, typed while driving, for anyone who remembers where they come from.

Everything that decides lives outside React

The car physics, the traffic, the fares, the crash damage, the police: all of it is plain TypeScript in src/game/ with no React, three.js or browser APIs in it. The renderer reads the same clock the engine uses, so what is drawn is what is hit. This is what makes the five-minute traffic test possible, and it means the same code can run on a server later to check a submitted score, which I will need once there is a leaderboard.

The ad space

The reason for all of this is that startups can put their name in the city, and I kept that scarce on purpose: six video screens around the centre square, twelve billboards over the avenues and in the river, and 24 storefronts, 42 slots in all, plus four sponsored fares that pay double and come up twice as often. Fares are weighted so a taken slot gets its share of drivers; nobody should pay for a board no taxi passes.

The taxi parked in the ring in front of a city channel screen reading "Keep left. As the traffic does", with the HUD bar at the bottom saying the ad was watched and paid. Park in the ring in front of a screen and watch it, and the game pays you a few coins. Empty screens run the city channel: tips and jokes. city.mnthan.com, 23 October 2026.

No startup has a slot yet. Coins add up across shifts, and at 50,000 you can request a storefront for free, which is how I would like the first ones to arrive.

What is missing: a leaderboard, online payment, and a proper launch. The controls are W/S and A/D, Space for the handbrake, Shift or E to boost, H for the horn, and a thumb stick on a phone. If you try it, the thing I most want to know is whether the car feels right, because so far it has been tuned by one driver.

← all writing nextBuilding a help search that finds the right article, without a search server →