Les coulisses de CrazyTaxi : le classique Dreamcast de Sega reconstruit en Three.js
CrazyTaxi est une reconstruction en Three.js du Crazy Taxi de Sega sur Dreamcast. La voiture, la circulation, la caméra, les clients et les menus exécutent le propre code SH-4 du jeu dans un petit interpréteur, identique bit pour bit à la décompilation. Agent! l'a construit en trois jours et 157 commits. Jouez sur ct.gokart.games.
Jouez maintenant : ct.gokart.games. Il faut un navigateur compatible WebGL, et le jeu se joue au clavier ou avec une manette Xbox (ou une autre manette standard).
CrazyTaxi reconstruit le Crazy Taxi de Sega sur Dreamcast (USA, MK-51035) en Three.js. Le premier commit date du 22 octobre. Le soir du 24 octobre, le projet comptait 157 commits : 93 le premier jour, 41 le deuxième et 23 le troisième. Agent! a tout écrit, un commit testé après l'autre, et les messages de commit se lisent comme un cahier de laboratoire, avec des adresses, des comptages et des vérifications dans presque chacun d'eux.
Ce n'est pas un remake construit à partir de captures d'écran. Presque tout ce qui apparaît à l'écran, et l'essentiel de son comportement, vient du disque lui-même.
Premier jour : sortir le jeu du disque
L'essentiel du premier jour a été consacré à l'extraction. Le dépôt ne contient aucune donnée du jeu. Vous extrayez votre propre disque, et une seule commande en sort tout :
python3 tools/extract_all.py '<Crazy Taxi (USA).7z or .chd>' --output local-assets
Derrière cette commande se trouvent 17 convertisseurs dans tools/, chacun livré en trois mêmes étapes : l'outil, ses tests, puis un document décrivant ce qui a été trouvé. Dans l'ordre, ils traitent :
- Le disque : extrait et vérifié par hash depuis la piste haute densité brute.
extract_chd.pylit les images CHD v5 avec ses propres décodeurs, décodeur FLAC compris, et reconstruit la correction d'erreurs des secteurs du CD. Sa sortie correspond octet pour octet au dump.7z. - Textures et sprites : les banques de textures TEXDC, ainsi que les banques de sprites, dont les palettes ont été retrouvées dans le code de chargement SH-4 de l'exécutable.
- Modèles et villes : les modèles POLDC et les archives de ville BINC, convertis en glTF texturé.
- Personnages et animations : assemblés en suivant la propre routine de dessin du jeu, y compris les animations par courbes clés et l'IK à deux os dont elles ont besoin.
- Circulation et taxis : construits à partir de la table des voitures USA et des tables des taxis, chaque couleur de la circulation étant une variante de matériau glTF.
- Collisions, tracés de parcours, objets de décor, clients, arrêts, images de destination et replays, chacun vérifié par rapport aux propres tables de l'exécutable.
- Audio : la musique et les voix ADX, ainsi que les banques de sons DTPK, avec leurs fréquences de lecture et leurs programmes d'instruments lus comme le pilote son de la Dreamcast les lit.
Une exécution complète écrit 4,744 fichiers (255 MB) en deux minutes environ.
Exécuter le propre code du jeu
Le jeu three.js a démarré le soir même avec la ville, un taxi provisoire et une caméra de poursuite réglée à la main.
Moins d'une heure plus tard, le commit 02bf19e a jeté la physique provisoire. src/sh4.js est un petit interpréteur SH-4, le processeur de la Dreamcast, et src/dc-car.js s'en sert pour exécuter init_CarMain et exec_CarMain directement depuis le 1ST_READ.BIN du jeu. Ils tournent sur les points de parcours et les données de collision du disque, chargés à leurs adresses Dreamcast d'origine. Le dessin, le son et les vibrations sont remplacés par des stubs ou capturés, et three.js dessine tout ce que le jeu a demandé.
Le reste du jeu est passé dans l'interpréteur, un système après l'autre :
- La circulation : chaque voiture, train, tramway et feu est un événement du jeu, géré par les propres
trafficControletnlExecuteEventdu jeu. Le taxi percute la circulation, et la circulation riposte. - La caméra : la caméra de poursuite de
camUpdateet le survol du taxi pendant le compte à rebours. Three.js reprend simplement la matrice de vue du jeu. - Les clients : qui attend où, la montée, la course, puis la note et le bonus de temps à la dépose.
- Les menus : l'écran titre, le menu principal, les options, les records et la sélection du chauffeur. Tous sont le code du jeu qui dessine des sprites, que
src/menu-view.jsplace sur un canvas.
Chacun d'eux est vérifié par rapport au propre simulateur SH-4 de la décompilation. tools/car_trace.py enregistre une course entière : le compte à rebours, la prise en charge, la conduite, la dépose. Chaque image du taxi, de la caméra, de chaque voiture de la circulation, de l'horloge du jeu et de chaque client correspond bit pour bit dans les deux villes.
Cette vérification a révélé un vrai bug. Deux parties différentes des calculs du jeu divergeaient, jusqu'à ce que les déplacements de flottants 64 bits de l'interpréteur soient corrigés pour stocker les paires de registres dans l'ordre réellement utilisé par le SH-4. Le simulateur de la décompilation reçoit le même correctif dans car_trace.py.
Crazy Box
Le deuxième jour, Crazy Box est arrivé : les mini-jeux du jeu, tous les 16 gérés par Init_DCmini et execDCmini. Chaque nouvelle pièce du jeu a été trouvée de la même façon. Trouver la routine dans l'exécutable, l'exécuter dans l'interpréteur et capturer ce qu'elle dessine :
- Les quilles de Crazy Bowling viennent de
ExecSetObject. Elles s'envolent quand on les percute, et le jeu les compte. - La flèche de guidage verte vient de
ExecArrow, dessinée dans l'espace de vue puis replacée dans le monde. - La fenêtre TRY AGAIN est la propre
windowTryAgaindu jeu, curseur de menu compris. - Les épreuves réussies sont sauvegardées par le propre
CheckRankingdu jeu et débloquent les boîtes suivantes. La sauvegarde correspond aux mêmes 0x25B0 octets que la Dreamcast sauvegarde, stockés danslocalStorage.
Une découverte de ce travail : la routine nommée carRumble ne gère pas que les vibrations. C'est elle qui exécute le Crazy Dash. Désactivée, Crazy Jump plafonnait à 132 m. Activée, un dash au GO atteint 173 m.
60 fps et textures haute résolution
Une fois correct, il fallait que ce soit rapide. La ville arrivait sous forme de 13,779 maillages séparés, et le navigateur faisait 3,000 à 4,500 appels de dessin par image. Sur une branche Auto-Pilot, src/static-batch.js fusionne le monde statique. Les 374 textures de la ville vont dans un seul atlas, et les maillages sont fusionnés par cellules de 4,000 unités. Cela ramène le tout à environ 300 appels de dessin par image. Dans l'app Mac en 2560 × 1920, cela donne 60 fps stables (minimum 58), contre 37 à 47 auparavant.
Appuyez sur H (ou Y sur une manette) pour le pack de textures haute résolution. tools/match_texture_pack.py recalcule le hash de textures personnalisées de Flycast pour chaque texture du jeu, ce qui fait correspondre un pack de textures Flycast à 1,793 des 2,090 textures du jeu. Avec le pack activé, la ville regroupée puise dans des atlas quatre fois plus grands (8192 × 8192). La version publiée livre ces textures en KTX2 : 51 MB contre 551 MB de JPEG et PNG pour le pack.
Le reste des finitions du troisième jour :
- TAA : des images décalées par jitter fusionnées dans un historique reprojeté (
src/taa.js), en plus du MSAA 4×, pour que la ville au loin ne scintille plus. - Fini le scintillement : les détails coplanaires superposés, comme les ombres des bâtiments sur le trottoir, sont rapprochés de l'œil pour qu'ils cessent de se disputer la profondeur.
- Téléchargements plus légers : musique et voix en Opus et AAC (d'environ 150 MB à 37 MB), et Brotli pour tout le reste (de 103 MB à 28 MB transférés).
- Nouveaux extras : la prise en charge des manettes Xbox, une mini-carte de la ville avec l'itinéraire routier le plus court vers la destination en vert, et un bonus de temps à la dépose avec les règles Arcade.
- Apps de bureau : une app Mac signée et notarisée, ainsi que des lanceurs Windows et Linux, qui exécutent tous la version web sur ct.gokart.games.
Comment tout a été vérifié
Rien de tout cela n'a été pris pour argent comptant. Les fixtures de traces de la décompilation vérifient la logique du jeu image par image. Chrome en mode headless joue une course dans les deux villes après chaque modification et compte les erreurs. tools/draw-order-check.mjs conduit le taxi avec un pilote automatique et compare chaque image à une référence dont la profondeur est correcte : 66 échantillons sur 95 étaient faux avant la correction de l'ordre de dessin, et 0 sur 565 après. tools/mac-probe.sh pilote l'app Mac sans surveillance et affiche les fps et les appels de dessin chaque seconde.
Essayez-le
- Jouer : ct.gokart.games
- Code source : AgentiLoop/CrazyTaxi. Pour le lancer en local, il vous faut votre propre disque Crazy Taxi (USA), Node.js et Python 3.
Arrêtez-vous à côté d'un client, à l'intérieur de son cercle, pour le prendre en charge. Suivez ensuite la flèche jusqu'au lieu de l'image et arrêtez-vous là.
CrazyTaxi est un projet de fan indépendant, ni affilié à Sega ni approuvé par Sega. Crazy Taxi est une marque déposée de Sega. Le dépôt ne contient aucune donnée du jeu.