Comment j'ai créé un mode training pour un jeu Digimon sur PS1

Publié le 17/01/2021

Un jour Andrea Demetrio a relancé le jeu de combat de son enfance, Digimon Rumble Arena. Ce qui aurait dû être une chouette madeleine de Proust de quelques heures s'est transformé en une obsession de plusieurs jours visant à créer un mode entrainement afin d'apprendre l'infini de Stingmon.

Digimon Rumble Arena, initialement connu sous le nom de Digimon Tamers - Battle Evolution, est un jeu de la fin de l’ère Playstation sorti entre 2001 et 2003. C’est un platform fighter assez basique, avec des barres de vie traditionnelles, des contrôles simples et quelques mécaniques supplémentaires telles que des objets, des pièges dans les stages et des power-ups. Le jeu comprend 23 personnages tirés des 3 premières saisons de l’anime Digimon (jusqu’à Tamers), plus un personnage original créé pour le jeu : Reapermon, ou Gokumon en japonais.

J’ai beaucoup joué à ce jeu quand il est sorti. Avec mon frère et un ami, nous avons passé un nombre incalculable d'après-midi à se battre, à bannir Seraphimon (parce qu’on était nuls, mais aussi parce que mon frère était un peu trop fort avec), et on organisait parfois des tournois tous les trois ; dont un que j’ai, lâchement mais sûrement, gagné en jouant la montre avec la fameuse tactique de Patamon dans le stage de Reapermon.

Dix-huit ans plus tard, une fois adulte, j’ai trouvé le moyen de jouer de nouveau à ce jeu, cette fois en ligne, en utilisant l’émulateur Mednafen et son jeu en ligne intégré. Partant de là, je suis entré en contact avec une communauté encore active qui organise des tournois locaux au Japon et en Nouvelle-Zélande, et qui m'a montré les combos les plus fous qu’il m’ait été donné de voir sur le jeu (dont certains que je n’aurais même pas imaginé possible de réaliser).

Pris d’excitation, j’ai relancé le jeu et décidé de tester moi-même des choses avec mon vieux main, Stingmon. J’avais entendu parler d’un infini avec son standing melee et il fallait absolument que j’essaie ça ! Sauf que… Le jeu n’avait pas de mode training. J’ai donc lancé le mode 2 joueurs, placé les deux persos dans un coin, et commencé à tester le combo.

À ma grande surprise, tout s’est très bien passé. Le compteur de combo a grimpé, la vie est descendue et j’étais content d’avoir trouvé un combo 0-to-death, donc je suis tout de suite allé le poster sur le Discord de la communauté… Pour me voir répondre que “le compteur de combo in-game ne marche pas vraiment” et que mon loop infini “pouvait en fait être bloqué après le second hit”, mais pouvait devenir un vrai infini avec un timing précis. Je vous épargne mes jurons.

J’ai relancé le jeu, je suis retourné dans le mode 2 joueurs, avec un setup bizarre qui me permettait de forcer le joueur 2 à bloquer en appuyant sur un bouton, pendant que de l’autre main, je m’entrainais à mon timing. Ce n’était vraiment pas la meilleure solution, c’était même complètement impraticable, en plus du fait que ça impliquait l’utilisation de beaucoup trop de savestates. C’était la goutte d’eau.

Malgré les apparences ceci n'est pas un vrai combo.

Je suis un physicien. Je résous des problèmes. Et mon problème, c’était de m’entrainer sur un combo infini de Stingmon sur un jeu vieux de 18 ans, sorti sur une console vieille de 25 ans.

Ma première réaction fut donc de faire ce que n’importe quel adulte de 30 ans sain d’esprit aurait fait à ma place : lancer Visual Studio 2019 pour créer un programme en C++ qui me permettrait d’envoyer des inputs de clavier à Mednafen pour “simuler” la garde du joueur 2, sans que j’aie à faire quoi que ce soit sur ma manette. Si l’idée semblait bonne, et avait l’air de marcher (en essayant dans Notepad ++, les inputs étaient envoyés au rythme désiré), en pratique Mednafen méprisait complètement mon programme et se riait bien de moi et de mes tentatives pathétiques de le contrôler.

Ce n’était pas juste une provocation. C’était une déclaration de guerre.

BizHawk, LUA, et pourquoi vous ne devriez jamais mettre au défi un physicien

J’étais particulièrement salé. Toute une après-midi gâchée à cause d’un émulateur arrogant qui ne voulait pas accepter mes simulations d’inputs. Ce soir-là, je demandais à la communauté de Digimon Rumble Arena si quelqu’un avait la moindre piste… Et c’est comme ça que je croisai pour la première fois la route de Bizhawk. J’avais déjà entendu parler de BizHawk, grâce à un article à propos d’un groupe de buisness analysts qui travaillaient sur le machine learning appliqué à la finance, et qui utilisaient Bizhawk pour apprendre à une IA à jouer à Street Fighter II.

Bizhawk n’est pas juste un émulateur : c’est toute une suite d’outils pour le speedrun automatisé, avec des routines pour surveiller et modifier la RAM de la machine émulée, ainsi qu’un environnement poussé pour éditer des scripts dans le langage LUA. Dès que j’ai commencé à creuser le sujet, j’ai compris que c’était l’arme qu’il me fallait pour mener mon combat.

Je n’y connaissais absolument rien à LUA, mais je suis un programmeur relativement compétent ; ayant grandi avec le langage Ruby (coucou RPG Maker XP), j’ai transféré mes connaissances vers le langage C++, construit une application avec interface graphique en C# pour une collaboration entre physiciens (sans connaître le langage au préalable).

Et je suis aussi ce type qui a dû maintenir une énorme base de code LabView pour une expérience (si vous ne savez pas ce qu’est LabView, imaginez un langage de programmation fait par Fisher Price, avec des blocs à faire glisser qui représentent des actions, des câbles qui les relient pour construire un flot de données, le tout sans outils vraiment pratiques à utiliser pour zoomer ou dézoomer sur la grille. Si ça vous évoque un cauchemar sans nom, c’est parce que c’en est un).

Tout ce qu’il me fallait, c’était un tutoriel de base sur LUA, et j’étais paré. Les premiers tests se sont étonnamment bien passés. Je pouvais envoyer des inputs du joypad via le script LUA et contrôler le joueur 2 avec du code, le faisant bloquer, mettre des balayettes, garder et attaquer sans problème. Créer une interface graphique basique fut aussi relativement rapide, grâce aux librairies LUA de BizHawk. En moins d’une après-midi de boulot, j’avais un script d’ennemi un peu brut mais fonctionnel.

Et puis, je suis arrivé à l’étape numéro deux : geler les barres de vie. On ne peut pas parler de training mode si le timer continue et qu’on risque de mettre l’adversaire KO au 15ème essai raté de l’infini de Stingmon. Bien sûr, recharger des sauvegardes de l'état du jeu peuvent aider, mais puisque j’avais commencé quelque chose, je voulais le faire du mieux que je le pouvais.

Et pour faire ça, j’ai plongé dans ce monde merveilleux qu’est l’exploration de la RAM. C'est peu de dire que j’allais en avoir pour mon argent.

Des barres de vie, une fausse piste, et d’obscurs sites de codes pour GameShark

La première chose qui vous vient à l’esprit, quand vous vous retrouvez à contempler des tableaux sans fin, qui contiennent quelque 500 000 valeurs hexadécimales bizarres et en bazar, c’est de remettre en question vos choix de vie.

Parce que quand vous vous mettez à faire de la rétro-ingénierie sur des adresses mémoire brutes d’un jeu si vieux qu’il pourrait légalement boire et voter, c’est qu’un truc a du merder, quelque part, à un moment. Ensuite seulement, vous vous ressaisissez, vous vous rappelez pourquoi vous êtes là, à fixer ces nombres, tel Neo contemplant le code source de la Matrice : vous êtes là pour lab cet infini de Stingmon dans Digimon Rumble Arena.

Et il s’avère que tout compte fait, fouiller dans des adresses RAM, en tout cas pour les jeux de combat, n’est pas si douloureux que ça en a l’air dans la mesure où on peut rapidement identifier les valeurs dont on a besoin. Vous cherchez le timer ? Trouvez la valeur qui continue de baisser à chaque frame, de manière régulière. Vous cherchez la vie ? Faites une recherche après chaque coup et comparez les valeurs de RAM avec les précédentes, jusqu’à ce que vous en trouviez une qui ne baisse qu’une fois que vous avez frappé l’adversaire.

Il y a une certaine logique et une certaine beauté là-dedans.

Du coup, avec la naïveté très sincère du débutant qui a des étoiles plein les yeux, j’ai concentré mes efforts sur la recherche de ces valeurs. J’ai pris Gatomon et Agumon, choisi certains des stages les moins pénibles (croyez-moi, vous ne voulez clairement pas faire ça dans Recycling ou Revolution), et j’ai lancé mon outil permettant d'observer la mémoire du jeu. En quelques minutes, j’avais trouvé deux valeurs prometteuses, qui descendaient de 120 à 0 en adéquation avec les barres de vie des personnages. J’ai cru avoir visé juste, j’ai sauvegardé les adresses et changé de stage pour confirmer mon intuition. Les valeurs étaient toujours les mêmes : Boom, jackpot ! Puis j’ai fait le dernier test : changer les personnages. Et c’est alors que mon monde s’est écroulé, dans un éboulement de débris et une montagne de poussière.

Ce que je m’attendais à trouver : une valeur entre 0 et 120.

Ce que j’ai réellement trouvé : un charabia aléatoire qui pourrait probablement invoquer Cthulhu ou un autre Ancien si on le lisait à l’envers, en face d’un miroir, trois fois de suite.

Ces adresses mémoires dépendaient des personnages. Et, cerise sur le gâteau, elles n’étaient même pas les vraies valeurs de vie, mais la taille en pixels à l’instant T des barres de vie sur l'interface du jeu. Un peu de vulgarité s’en est suivie. BEAUCOUP, en fait. J’étais revenu au point de départ. Je n’avais trouvé aucune règle sur la position de ces valeurs et je n’avais aucune autre idée. Je décidais donc d’utiliser mon arme secrète : des sites de cheat code pour GameShark.

Quelqu’un avait forcément trouvé une manière d’avoir des points de vie infinis sur un personnage. C’était la base des cheat codes. Score infini, timer infini, vie infinie et 1-hit KO. On peut s’attendre à ce que des gens aient trouvé ces codes pour n’importe quel ancien jeu de combat sur console qui se respecte ! Et j’étais chanceux. Sur un site assez louche au look début 2000, j’ai trouvé quelle adresse mémoire titiller pour geler les barres de vie et le timer autant que je le voulais. Ce n’était pas bien joli, ce n’était pas parfait, et ça faisait crasher le jeu si le code était envoyé pendant un écran de chargement; mais ça marchait.

Rapide, crade, peut-être même carrément dépravé, mais ça faisait le boulot à merveille : j’avais un script LUA de training mode basique chargé dans Digimon Rumble Arena pour contrôler l’adversaire et geler les barres de vie.

Alors que je terminais le script pour les coups de base, j’ai décidé d’ajouter une deuxième fonction pour forcer l’adversaire à exécuter un mouvement spécifique, que ce soit marcher, dasher, sauter ou autres joyeusetés. Tout s’est bien passé, à première vue, mis à part sur un point tout à fait futile : je ne pouvais pas faire se déplacer l'adversaire vers le joueur ou loin de lui, seulement à droite ou à gauche de manière absolue.

Forcément, je n’avais pas accès aux valeurs des positions, donc c’était à prévoir. Mais c’était pénible. Très pénible. Pire en fait, puisque le script était quasiment inutilisable en l’état, avec l’adversaire qui avançait vaillamment dans une direction donnée sans aucune forme d’intérêt pour le joueur. J’avais trouvé mon prochain problème à résoudre, et je peux vous dire que j’étais prêt à tout retourner.

De l'espoir, des positions, et de la lumière au bout du tunnel

Après une bonne nuit de sommeil, j’étais prêt à plonger de nouveau dans la mémoire du jeu. Je devais trouver ces valeurs de position en X. C’était la première étape vers une option de mouvement fonctionnelle, et je n’étais pas prêt à me contenter de moins. Trouver ces positions ne fut pas le plus dur, en tout cas au début. J’ai choisi deux personnages, le stage du volcan, et j’ai joué avec la mémoire, jusqu’à ce que je trouve une valeur (un entier de 4 bytes pour être plus précis) qui bougeait de manière synchronisée avec le joueur 1, et une autre valeur similaire qui bougeait avec le joueur 2.

Si vous vous rappelez de la première fausse piste, j’ai été cette fois BEAUCOUP plus prudent. J’ai changé le stage ainsi que le deuxième personnage, et répété l'expérience. Même valeurs. Puis, j’ai changé le premier personnage et vérifié si leurs positions étaient toujours là où elles devaient être. Tout était bon.

J’ai laissé échapper un sourire. Pour une fois, quelque chose se passait comme prévu. J’ai édité mon script, changé l’horrible mouvement droite/gauche pour un “en avant/en arrière” bien plus élégant, et j’étais bon pour ma journée. J’ai donc essayé d’enregistrer un clip pour montrer la fonctionnalité aux membres du Discord Digimon Rumble Arena. C’est là que tout s’est écroulé une nouvelle fois.

Remontez au paragraphe précédent, relisez-le : j’ai changé de stage plusieurs fois, et j’ai changé le 2ème personnage. Puis j’ai changé le premier. Mais je n’ai pas vérifié l’adresse du deuxième personnage à nouveau, quand j’ai fait le dernier test. Et ça... c’était une erreur.

Il se trouve que, si la localisation de la position du premier personnage dans la mémoire est complètement indépendante du stage et du personnage lui-même, l’adresse du second personnage ne l’est pas. J’ai lâché un paquet de grossièretés, frappé ma tête contre le mur, un bon paquet de fois. La mauvaise nouvelle : je n’étais pas sorti de là. La bonne : j’avais une piste.

L’adresse de la position du second personnage dépendait seulement de qui était le premier personnage. Et l’adresse du premier personnage était, elle, toujours fixe. Ce n’était pas aussi grave que je le pensais. Il y avait toujours un espoir.

Parce que j'avais déterminé que cette valeur était indépendante du stage, j’ai simplement pris une fois chacun des 24 personnages, contre un seul adversaire choisi au hasard, dans le stage du volcan. J’ai trouvé la valeur de départ de la position X du joueur 2 (327680, un nombre que je n’oublierai jamais après cette expérience), lancé une recherche dans la mémoire et, lentement mais sûrement, rempli un tableau de valeurs qui reliait la position en mémoire du joueur 1 à la position en mémoire du joueur 2.

La position dans la mémoire indiquant où étaient stockés les positions des personnages fut de nouveau trouvée sur le site douteux déjà mentionné, puisque “jouer n’importe quel personnage sans avoir à grinder le mode arcade” est un autre membre premium du club très select des “cheats essentiels à n’importe quel jeu de baston des années 90”.

Avec ces nouvelles connaissances et un peu de boulot, le script faisait finalement ce qui était prévu : bouger l’adversaire près ou loin du joueur en effectuant une action définie. J’appelais ça : une victoire.

J’avais plongé dans la matrice et j’en été revenu avec le trésor que je convoitais. Ça aurait suffit à la plupart des gens, mais je sentais que je pouvais faire plus. J’avais le sentiment d’avoir ouvert une brèche, et que j’étais très proche de goûter au fruit défendu du savoir.

Il était temps de ravager les enfers. Avec l’artillerie lourde.

Quel Wargreymon est le plus fort ?

Il y a 18 ans, mon frère et moi n’arrivions pas à nous mettre d’accord sur quel WarGreymon, le classique ou le noir, avait les coups speciaux les plus forts. Terra Force et Terra Destroyer. Yin et Yang. Il n’y avait aucun mode training et encore moins de valeurs numériques dans Digimon Rumble Arena ; et mesurer les barres de vie avec une règle sur l’écran incurvé d’une télé 4:3 cathodique n’était pas des plus pratique (vous pouvez toujours faire le test chez vous et m’envoyer les résultats via pigeon voyageur, je vous attends). D'où la discussion sans fin sur qui était le meilleur WarGreymon. Il était temps de cracker le code. Il était temps de savoir, pour de bon, à la lumière des chiffres et de la science.

Mon succès avec les valeurs des positions m’avait donné un coup de fouet. Je pensais pouvoir cracker ce code, et je jetais de nouveau un œil à ces valeurs de 0 à 120 qui m’avaient échappé la première fois. Elles ne dépendaient que des deux personnages choisis, et étaient complètement indépendantes du stage. Il n’y avait pas d’adresse fixe, mais il devait y avoir un truc, une sorte de règle pour les organiser.

Le premier essai fut vain : j’ai essayé d’utiliser les mêmes variations que pour les positions, mais ça ne fonctionnait pas du tout. C’est là que les maths sont entrées en jeu. J’ai commencé à faire des essais avec trois personnages : Gatomon, Agumon et Guilmon. J’ai lancé des matchs dans toutes les combinaisons possibles et noté les valeurs mémoires qui me semblaient les plus pertinentes.

J’avais l’intuition que, puisque les positions ne dépendaient pas du stage, l’adresse du premier joueur devait être stockée dans la mémoire du jeu en tant que valeur spécifique respectant cette forme : a + x + y ; a étant une adresse mémoire relative contenant des données (sous forme de variable) propres au personnage ; x étant la taille des variables du premier personnage et y la taille des variables du second personnage. C’était une hypothèse que je devais vérifier. La méthode scientifique, vous comprenez ? a devait avoit une valeur immuable (en programmation on appelle ça une constante), sans quoi toute ma réflexion n’avait pas de sens.

C’était mon hypothèse la plus probable. J’ai assigné x à Gatomon, y à Guilmon et z à Agumon, en construisant un système linéaire à 3 équations et 4 variables. (ndt: en mathématiques, un système d'équations linéaires consiste, pour faire simple, à trouver des valeurs inconnues utilisées simultanément par plusieurs équations. C'est ce qui se passe au Sudoku où, pour trouver le bon chiffre, il faut qu'il fonctionne avec le reste du tableau. Pour un exemple concret voir ici).

Trois équations.

Quatre variables.

Des systèmes linéaires à 4 variables ?!

Si vous avez une compréhension ne serait-ce que basique des mathématiques, cela devrait faire sonner en vous une alarme au moins aussi bruyante qu’un compteur Geiger durant la catastrophe de Tchernobyl : il y a plus de variables que d'équations, ce qui rend ce système linéaire indéterminé, et donc impossible à résoudre.

Il me fallait une quatrième équation, et c’est là que j’ai eu l’idée d’utiliser en plus les matchs miroir. Dans ces cas-là, l’adresse mémoire, si mon hypothèse tenait debout, devait être égale à 2x + a. Je me suis empressé de compléter mon système linéaire, et je l’ai donné à résoudre à un calculateur en ligne (ne me jugez pas, résoudre des systèmes linéaires à 6 chiffres le dimanche après-midi n’est pas franchement mon passe-temps préféré). J’ai rentré les équations, cliqué sur “résoudre”, et attendu. Tout ça pour découvrir qu’une des équations était linéairement dépendante des trois autres.

Première réaction : comment c’est possible ?! Quelle autre équation je peux trouver pour résoudre ce système ? Je dois trouver la valeur de a !

Éclair de génie quelques instants plus tard : si une des équations dépend en effet des trois autres, ça signifie que ma théorie est valable !

Je ne m’en étais pas aperçu immédiatement, mais c’était la preuve que mon idée tenait debout : si la quatrième équation pouvait être créée en utilisant les trois autres, ça voulait dire que le système avait une infinité de solutions, quelle que soit la valeur que je prenne comme adresse mémoire relative. D’habitude, quand on a un système avec une équation redondante, le système est soit impossible, soit possède une infinité de solutions.

Celui-ci appartenait à la seconde catégorie, ce qui signifiait que je pouvais ne pas me préoccuper de la valeur de a, que je plaçais donc à 0. Ça a eu pour effet de déplacer la moitié de la valeur de a, quelle qu’elle soit, à l’intérieur des autres variables, ce qui a rendu possible la résolution du système pour les autres variables. Et en plus, je n’avais plus besoin d’un système d’équations : un simple match miroir pouvait me donner les bonnes valeurs pour chaque personnage, puisque l’équation devenait simplement 2x = adresse. Et du coup, il ne me restait plus qu’à trouver la taille du bloc de chaque personnage une seule fois, pour pouvoir traiter tous ses autres match-ups.

Pour vérifier que ma théorie était vraiment solide, j’ai lancé un match miroir avec Renamon, calculé la valeur, puis je l’ai ajoutée à la valeur que j’avais trouvée pour Gatomon, j’ai croisé les doigts et noté l’adresse mémoire ainsi trouvée. Puis, j’ai lancé un match Renamon vs Gatomon, et j’ai surveillé la valeur que je venais de calculer au début du match.

Je ne peux pas parler pour mes voisins, mais vu comme les murs de mon appartement sont fins, je suis à peu près certain qu’ils ont entendu mon hurlement de joie quand j’ai vu que la valeur était effectivement 120. Boom, headshot ! Je suis revenu frénétiquement à mon outil permettant d'observer la mémoire du jeu, pour me rendre compte que la localisation du joueur 2 dans la mémoire était toujours à la même distance (132 bytes pour être précis) de celle du joueur 1. Les valeurs de la Digi bar (la barre de super du jeu) suivaient le même modèle.

Je n’avais plus qu’à lancer 19 matchs miroir pour récupérer les valeurs manquantes. J'ai commencé avec BlackWarGreymon. Puis Wormon, le personnage avec la plus faible valeur de défense selon la communauté. Puis, WarGreymon.

J’ai paramétré mon script de test, fait apparaître les barres de vie en pourcentages, chargé ma super avec les deux WarGreymon et balancé la sauce sur un pauvre petit Wormon innocent.

Terra Force infligea un burst dévastateur de 52% de dégâts, qui fit trembler l’écran. Mais Terra Destroyer était encore plus impitoyable, dévorant 68% de la barre de vie du pauvre petit Digimon insecte. Soixante. Huit. Pourcents. En un coup. Je laissais échapper un Wooooo à la Ric Flair. Je l’avais. Ma victoire, pure, et sans compromis.

On était dimanche, tard dans la nuit. J’avais travaillé sur un script pour montrer le total de vie de chaque personnage et le faire apparaître sur le HUD. Et, après 18 ans, j'avais enfin clos le débat avec mon frère.

Le dernier obstacle, ou pourquoi vous devriez prendre en compte les canaris

Passé toute cette excitation, je me rendis compte que, après avoir réglé toutes ces questions passionnantes, j’avais négligé le problème initial qui m‘avait mené à tout ça : m’entrainer à faire l’infini de Stingmon.

Je pouvais forcer l’adversaire à garder, d’accord, mais pas après le premier hit. Ce qui était, par définition, l’élément précis dont j’avais besoin pour m'entraîner à faire un combo infini. J’y ai réfléchi pendant un moment. Je ne savais pas comment récupérer les valeurs d'étourdissement aka hitstun. Je n’avais pas particulièrement envie de replonger dans cet enfer. Et du coup, j’ai fait ce que n’importe quel physicien aurait fait à ma place : j’ai choisi l’option de la flemme et j’ai cherché un canari.

Il est jaune, mais Angumon n'est pas un canari

Avant que vous n’appeliez la police pour me faire interner, je ne parle pas du petit oiseau jaune. J’ai eu trois canaris dans ma vie, Titti, Ozio et Kimi, mais tout ça n’a rien à voir avec eux. Vous connaissez l’expression “un canari dans une mine de charbon”, pour parler d’un danger imminent ? C’est de ce canari là dont je parle : un objet indépendant qui change d’état uniquement quand un personnage se fait toucher.

J’ai imaginé deux ou trois possibilités, la première étant “la taille de la barre de vie”, la seconde étant “la taille de la Digi barre”. La première option avait un gros inconvénient : quand les barres de vie étaient gelées par le code du mode training, ça ne pouvait pas fonctionner. La seconde option avait un souci similaire, bien qu’un peu différent : la Digi barre a un palier maximum, après lequel elle n’augmente plus. C’est comme ça que j’en suis arrivé à la possibilité #3 : le score.

Dans ce jeu, chaque coup a un score qui lui est propre, qui vous est accordé si le coup touche l’adversaire et n’est pas bloqué. Le score du joueur attaquant augmente et son coup inflige des dégâts à l’adversaire. Par pure coïncidence, les adresses des scores faisaient partie des quelques adresses qui étaient complètement indépendantes des personnages, et je les avais déjà trouvées par hasard lors d’une de mes plongées précédentes.

J’ai ajouté une option au script pour déclencher l’action désirée lorsque le score du joueur 1 augmente, croisé les doigts, et chargé ça dans le jeu.

Lorsque l’adversaire s’est mis à bloquer le 3ème coup de mon pseudo-infini de Stingmon, mon cœur s’est emballé à la fois d’une joie intense et d’une tristesse profonde.

De joie, car j’avais finalement résolu le problème qui avait été au départ de tout cela, et crée un training mode complètement fonctionnel pour un vieux jeu de Playstation émulé.

De tristesse car, comme je m’en rendais compte, j’étais bien loin de réussir à faire cette boucle plus de deux fois de suite avec mon niveau actuel.

Tant pis : si j’avais réussi à faire tout ça en trois jours, je devais bien être capable de bosser mon timing et de réussir ce combo 0-to-death au moins une fois, non ?

Merci à Andrea pour son humour et sa disponibilité. Le code pour son mode training mode est disponible gratuitement sur GitHub. Traduit de l'anglais par Pr0sk, correction par Neithan.

Aidez Bas Gros Poing

INSERT COIN ?

Aidez BGP à se développer !

Fichier 1