Así se hizo CrazyTaxi: el clásico de Sega para Dreamcast reconstruido en Three.js
CrazyTaxi es una reconstrucción en Three.js del Crazy Taxi de Sega Dreamcast. El coche, el tráfico, la cámara, los clientes y los menús ejecutan el propio código SH-4 del juego en un pequeño intérprete, coincidiendo bit a bit con la decompilación. Agent! lo construyó en tres días y 157 commits. Juega en ct.gokart.games.
Juega ahora: ct.gokart.games. Necesita un navegador con WebGL y se juega con teclado o con un mando de Xbox (u otro mando estándar).
CrazyTaxi reconstruye el Crazy Taxi de Sega Dreamcast (USA, MK-51035) en Three.js. El primer commit se hizo el 22 de octubre. En la noche del 24 de octubre ya sumaba 157 commits: 93 el primer día, 41 el segundo y 23 el tercero. Agent! lo escribió todo, un commit probado tras otro, y los mensajes de commit parecen un cuaderno de laboratorio, con direcciones, recuentos y comprobaciones en casi todos.
No es un remake hecho a partir de capturas de pantalla. Casi todo lo que aparece en pantalla, y la mayor parte de cómo se comporta, procede del propio disco.
Día uno: sacar el juego del disco
Casi todo el primer día se dedicó a la extracción. En el repositorio no hay ningún dato del juego. Tú extraes tu propio disco y un solo comando lo saca todo:
python3 tools/extract_all.py '<Crazy Taxi (USA).7z or .chd>' --output local-assets
Detrás de ese comando hay 17 conversores en tools/, cada uno enviado en los mismos tres pasos: la herramienta, sus pruebas y después un documento con lo que se encontró. Por orden, se encargan de:
- El disco: extraído y verificado por hash desde la pista de alta densidad en bruto.
extract_chd.pylee imágenes CHD v5 con sus propios decodificadores, incluido un decodificador FLAC, y reconstruye la corrección de errores de los sectores del CD. Su salida coincide byte a byte con el volcado.7z. - Texturas y sprites: los bancos de texturas TEXDC, más los bancos de sprites, cuyas paletas se recuperaron del código cargador SH-4 del ejecutable.
- Modelos y ciudades: los modelos POLDC y los archivos de ciudad BINC, convertidos a glTF con texturas.
- Personajes y movimientos: montados siguiendo la propia rutina de dibujo del juego, incluidos los movimientos por curvas clave y el IK de dos huesos que necesitan.
- Tráfico y taxis: construidos a partir de la tabla de coches de USA y las tablas de taxis, con cada color del tráfico como variante de material glTF.
- Colisiones, líneas de recorrido, objetos de escenario, clientes, paradas, imágenes de destino y repeticiones, cada uno comprobado con las propias tablas del ejecutable.
- Audio: la música y las voces ADX y los bancos de sonido DTPK, con sus velocidades de reproducción y programas de instrumentos leídos tal como los lee el driver de sonido de la Dreamcast.
Una ejecución completa escribe 4,744 archivos (255 MB) en unos dos minutos.
Ejecutar el propio código del juego
El juego en three.js arrancó esa misma tarde con la ciudad, un taxi provisional y una cámara de persecución ajustada a mano.
Menos de una hora después, el commit 02bf19e tiró a la basura la física provisional. src/sh4.js es un pequeño intérprete de SH-4, la CPU de la Dreamcast, y src/dc-car.js lo usa para ejecutar init_CarMain y exec_CarMain directamente desde el 1ST_READ.BIN del juego. Funcionan sobre los puntos de recorrido y los datos de colisión del disco, cargados en sus direcciones originales de Dreamcast. El dibujo, el sonido y la vibración se simulan con stubs o se capturan, y three.js dibuja lo que el juego haya pedido.
El resto del juego pasó al intérprete sistema a sistema:
- Tráfico: cada coche, tren, tranvía y semáforo es un evento del juego, gestionado por los propios
trafficControlynlExecuteEventdel juego. El taxi choca con el tráfico y el tráfico responde. - La cámara: la cámara de persecución de
camUpdatey el barrido alrededor del taxi durante la cuenta atrás. Three.js simplemente toma la matriz de vista del juego. - Clientes: quién espera dónde, la subida, el trayecto y la valoración y el bonus de tiempo al dejarlos.
- Menús: la pantalla de título, el menú principal, las opciones, los récords y la selección de conductor. Todos son el código del juego dibujando sprites, que
src/menu-view.jscoloca en un canvas.
Cada uno de ellos se comprueba con el propio simulador SH-4 de la decompilación. tools/car_trace.py registra una carrera completa: la cuenta atrás, la recogida, la conducción y la entrega. Cada fotograma del taxi, de la cámara, de cada coche del tráfico, del reloj del juego y de cada cliente coincide bit a bit en ambas ciudades.
Esa comprobación detectó un error real. Dos partes distintas de las matemáticas del juego no coincidían hasta que se corrigieron los movimientos de float de 64 bits del intérprete para guardar los pares de registros en el orden que realmente usa la SH-4. El simulador de la decompilación recibe el mismo parche en car_trace.py.
Crazy Box
El segundo día llegó Crazy Box: los minijuegos del juego, los 16 gestionados por Init_DCmini y execDCmini. Cada nueva pieza del juego se encontró de la misma manera. Localizar la rutina en el ejecutable, ejecutarla en el intérprete y capturar lo que dibuja:
- Los bolos de Crazy Bowling vienen de
ExecSetObject. Salen volando cuando los golpeas y el juego los cuenta. - La flecha guía verde viene de
ExecArrow, dibujada en el espacio de vista y devuelta al mundo. - La ventana TRY AGAIN es la propia
windowTryAgaindel juego, cursor de menú incluido. - Las pruebas superadas se guardan con el propio
CheckRankingdel juego y desbloquean las siguientes cajas. La partida guardada son los mismos 0x25B0 bytes que guarda la Dreamcast, almacenados enlocalStorage.
Un hallazgo de este trabajo: la rutina llamada carRumble no es solo vibración. Es la que ejecuta el Crazy Dash. Con ella anulada, Crazy Jump llegaba como máximo a 132 m. Con ella en marcha, un dash en el GO alcanza 173 m.
60 fps y texturas en alta resolución
Una vez correcto, tenía que ser rápido. La ciudad llegaba como 13,779 mallas separadas y el navegador hacía entre 3,000 y 4,500 llamadas de dibujo por fotograma. En una rama de Auto-Pilot, src/static-batch.js fusiona el mundo estático. Las 374 texturas de la ciudad van a un único atlas y las mallas se fusionan por celdas de 4,000 unidades. Eso lo reduce a unas 300 llamadas de dibujo por fotograma. En la app de Mac a 2560 × 1920 son 60 fps estables (mínimo 58), frente a los 37 a 47 de antes.
Pulsa H (o Y en un mando) para el pack de texturas en alta resolución. tools/match_texture_pack.py recalcula el hash de texturas personalizadas de Flycast para cada textura del juego, lo que asigna un pack de texturas de Flycast a 1,793 de las 2,090 texturas del juego. Con el pack activado, la ciudad agrupada dibuja desde atlas cuatro veces más grandes (8192 × 8192). La versión publicada incluye esas texturas en KTX2: 51 MB frente a los 551 MB de JPEG y PNG del pack.
El resto del pulido del tercer día:
- TAA: fotogramas con jitter mezclados en un historial reproyectado (
src/taa.js), sobre MSAA 4×, para que la ciudad lejana deje de parpadear. - Adiós al parpadeo: los detalles coplanares superpuestos, como las sombras de los edificios sobre la acera, se acercan hacia el ojo para que dejen de pelearse por la profundidad.
- Descargas más pequeñas: música y voces en Opus y AAC (de unos 150 MB a 37 MB) y Brotli para todo lo demás (de 103 MB a 28 MB por la red).
- Nuevos extras: soporte para mando de Xbox, un minimapa de la ciudad con la ruta más corta por carretera hasta el destino en verde y un bonus de tiempo al dejar al cliente en las reglas Arcade.
- Apps de escritorio: una app de Mac firmada y notarizada, además de lanzadores para Windows y Linux, todos ejecutando la versión web en ct.gokart.games.
Cómo se comprobó
Nada de esto se dio por bueno sin más. Los fixtures de trazas de la decompilación comprueban la lógica del juego fotograma a fotograma. Chrome en modo headless juega una carrera en ambas ciudades tras cada cambio y cuenta los errores. tools/draw-order-check.mjs conduce el taxi con un piloto automático y compara cada fotograma con una referencia con profundidad correcta: 66 de 95 muestras eran incorrectas antes de corregir el orden de dibujo y 0 de 565 después. tools/mac-probe.sh maneja la app de Mac sin supervisión e imprime los fps y las llamadas de dibujo cada segundo.
Pruébalo
- Jugar: ct.gokart.games
- Código fuente: AgentiLoop/CrazyTaxi. Para ejecutarlo en local necesitas tu propio disco de Crazy Taxi (USA), Node.js y Python 3.
Detente junto a un cliente, dentro de su círculo, para recogerlo. Después sigue la flecha hasta el lugar de la imagen y detente allí.
CrazyTaxi es un proyecto independiente de fans y no está afiliado ni respaldado por Sega. Crazy Taxi es una marca registrada de Sega. El repositorio no contiene ningún dato del juego.