A three.js reconstruction of the Sega Dreamcast Crazy Taxi (USA, MK-51035), built from data extracted from the user's own disc. No game assets are in this repository.
You need your own copy of the game. No game data ships with this repo. Rip
your own Crazy Taxi (USA, MK-51035) Dreamcast disc to a .7z, .chd or the raw
(Track 3).bin, then pull the assets out of it.
What you need: Node.js, Python 3, and on a Mac, Xcode or the Command Line Tools if you want the app.
# 1. Pull the assets off your disc image (once, about 2 minutes)
python3 tools/extract_all.py '<Crazy Taxi (USA).7z or .chd>' --output local-assets
# 2. Install and play in the browser
npm install
npm run dev # open http://127.0.0.1:5173/ (add ?city=original for the Original city)
# Optional: run the tests
npm testmac/ is a small macOS app that plays the game without a browser or a dev server.
npm run build && mac/build.sh # builds mac/build/CrazyTaxi.app
open mac/build/CrazyTaxi.appAny Xbox-style (standard layout) controller works. Plug it in and press a button.
Driving
| Action | Keyboard | Controller |
|---|---|---|
| Steer | ← → or A / D | Left stick or D-pad |
| Gas | ↑ or W | A or RT |
| Brake | ↓, S or Space | B or LT |
| Drive gear | X | RB or right stick forward |
| Reverse gear | Z | LB or right stick back |
| Pause | P | Menu (≡) |
| Restart | R | View |
| Hi-res textures on/off | H | Y |
Menus
| Action | Keyboard | Controller |
|---|---|---|
| Move | Arrows or WASD | D-pad or left stick |
| Choose | Enter, Space or Z | A |
| Back | Esc, Backspace or X | B |
| Start | Q | Menu (≡) |
How to play: stop beside a customer (inside their circle) to pick them up, then follow the arrow to the place in the picture and stop there to drop them off.
The world (city and set objects) is merged into a few draw calls at load
(src/static-batch.js: one texture atlas, one mesh per cell and way of drawing) -
~300 draw calls a frame instead of ~3,000, 60 fps in the app on an M4 at 2560 x 1920
where it was 40-47. ?batch=0 draws the single meshes again. The hi-res pack (?hires=1,
or H in game) draws the same batches from atlases four times the size with the pack's
pictures in the tiles (a 64 x 64 game texture is the pack at 256; 8192 x 8192, mipmapped).
Edges are anti-aliased over time as well as by 4x MSAA (src/taa.js: jittered frames
blended into a reprojected history), so the far city doesn't crawl; ?taa=0 is MSAA only.
tools/mac-probe.sh 30 runs an unattended 60 fps check in the Mac app.
src/collision.js- the game's ground and wall lookups (GetCollision2D,CheckColliWall) ported from the decomp overlocal-assets/collision/*.json. Checked against the extracted courses: all 25,712 Arcade stage course points land on#roadwithin 2 units.src/start.js- the start spots and headingsinit_CarMainuses (Arcade: course 0's last point moved 135 along z, heading 0x5000; Original fixed values).src/dc-car.js+src/sh4.js- the game runs by its own code.init_CarMain/exec_CarMain(carGearbox, carWheelForces, carAngularMotion, carWheelCollision, carGroundCheck...),entryCarPut,trafficControl(where cars appear and vanish: a circle of 1100 units around a point 500 ahead of the cab, sections of 15 course points, the density level),nlExecuteEvent(every traffic car, train, cable car, signal and customer task is an event: pcarDrive, carSteering, carResistance, carBalance, the course occupancy grid, kyakuTaskExec),ExecColliObj(the collision boxes: the cab hits the traffic and the traffic hits back),initCamera/camUpdate(the chase camera: the eye eased by homing vectors to 60 behind and 15 above the cab plus a smoothed speed term, the target 17 ahead and 5 up, the eye pulled in front of walls byGetCollision, the view matrix and the 60 degree perspective; the get-in and get-out cuts, modes 9 and 10),SetStartCamera/ExecStartCamera(the countdown's swoop around the cab, widening to 70 degrees),init_GameSystem/exec_GameSystem(the 366 frame countdown that releases the cab when 60 remain, the 50 s clock from the settings' defaults, the customer's time, time up),init_KyakuMain/exec_KyakuMainand the customer tasks (who waits where, the stop beside them, GS_GuestIn's time and fare, Psg_Execute's state machine: the get-in that holds the cab, the ride, the drop-off's rating and time money, the get-out),Act_Init/Act_Execute(the driver) from1ST_READ.BINrun in a small SH-4 interpreter over the disc's course points (SPLDCn.BIN), collision (COLDCn.BIN), motions (MOTDC.BIN) and people models (POLDC0.BIN) at their Dreamcast addresses, with the drawing, sound, rumble, effects and HUD calls stubbed or captured. Every frame of a recorded fare - the countdown, the cab moved beside a customer, the get-in, 60 frames of driving, the drop-off at the stop: the cab, the camera, every traffic car, GSystem, every customer task and every figure drawn - matches the decomp's SH-4 simulator bit for bit in both cities (tests/fixtures/car-trace-*.json, recorded bytools/car_trace.py; the simulator's 64-bitfmovpairs are corrected there: the SH-4 keepsFR[2k]at the lower address, which is hownlPushMatrix's two load paths agree and howMultiRot4Vecbuilds a rotation). The file documents the Car_Data fields the game uses (position +0x78, angles +0x84, speed +0x15C in units per frame, gear +0x120, control +0x54, the four wheel records at +0x1A0 - all four wheels steer, the collision object at +0x00 whose rotationputPlayerCar'sGetRotVecstores), GSystem (0x0C1EC4A8: time, cash, fare, the customer's time and limits), a customer task (the record, the passenger struct, the phase, the destination's stop entry), the traffic car record (+0x78 type, +0x7C colour, +0x84 flags, +0x8C position, +0xA4 angles, +0xC8 brake, +0xCC speed, +0xD4 course point) and the pad mapping (TaxiSW: stick, triggers, B = Drive, A = Reverse). The HUD's fare roll, which pays the cash in the game, does not run: the cash is paid the frame the customer is delivered.src/traffic-view.js- the traffic drawn asputCarModeldraws it: the extracted vehicle models (local-assets/vehicles/traffic, near/far detail by the game's 400 unit rule, the camera's clipping cone and 1800 unit draw distance applied byentryCarPutitself from the game's own view matrix), the body tinted with the car's colour (KHR_materials_variants paints), lifted and tilted by its suspension, wheels spun by 0.2 x the speed, brake lamps.src/people.js- the people drawn as the game draws them: the interpreter captureshumanDraw/VSklIpPlay(the V motions of the customers at the kerb, played frommotions.glbat the game's frame) andFcvPlayBuffer(the key-curve poses of the driver, the get-in and get-out and the passenger in the cab, rebuilt from the game's 17-vector pose buffer with the same root, torso, head and two-bone solver math astools/extract_fcv_motions.py) with each figure's world matrix, and poses the extracted characters (local-assets/characters/char_NN.glb) from them; the pick-up circle (100 - 60k) and the destination's drop circle.src/hud.js- clock, cash, customer time bar (green/yellow/red at the rating limits), destination picture + name (local-assets/landmarks), arrow, fare card, countdown and TIME UP, all read from the game's GSystem and customer tasks.src/main.js,src/scene.js- the city (BINCn.glb), its set objects (OBJDCn.glb) andcab_0.glbdrawn unlit (texture x vertex colour, as the Dreamcast does) through the game's camera: its view matrix on the three.js camera, its horizontal field of view across a 4:3 picture whose vertical view is kept while the width fills the window (near 3, far 15000, the decomp's reading of the projection); the game's own culling of the traffic and the people (SetNewClippingAngle) is widened with the window, so nothing pops in at the sides; the HUD scales with the height and sits in the corners.src/audio.js- the sound, from what the game's code asks its sound driver for (the interpreter hooks the calls), over the WAVstools/extract_audio.pywrote (local-assets/audio) with Web Audio: the effects (Sound_Request's words name the DTPK requests of the loaded packs - horns, bells, the skids, the fare's jingles; note-on velocity, playback rate, looping samples held until their note-off), the cab's engine and tyres (init_CarSound/exec_CarSoundrun and drivenlsndIcsControl: the engine program ofSNDDC3.BINkeyed from the rpm, its splits looped at the pitch curve's rate, the tyre programs ofSNDDC1.BIN), the voices (Voice_Requestqueues a script,Voice_Executestreams its clips ofVOICE01.AFSon four channels through the ADX stream calls) and the music (gdc_PlayStreamSongDiskat the countdown's 60, the jukebox of four songs ofSONG01.AFS,gdc_ExecStreamSongDisk's fade over the last 120 frames,ADXT_SetOutVol's decibels). Browsers start audio on the first key or click; a song cued before that starts where it would be.tools/browser-check.mjs- headless Chrome smoke run (drives, then runs one fare by moving the cab beside a customer and on to their stop; screenshots).
Not yet: the sound's finer points (velocity and key volume curves, pans, the
AICA envelopes), pedestrians, the trick
bonuses' display (Crazy Through etc. are counted: car.tricks), turn signals,
HUD sprites, Crazy Box.
Asset extraction is documented in docs/asset-extraction.md.