Le retour de l'internet !Super Tilt Bro. n'est pas un jeu rétro. Le but à toujours été d'en faire un jeu moderne sur une ancienne console. Et vous savez quoi ? Les jeux modernes, ça se joue en ligne !
Ça fait un bon moment que l'idée me trotte dans la tête, et j'avais même écrit un truc là dessus :
topics/188767-devlog-super-tilt-bro/2#post-34 Les choses ont bien évoluées depuis, on arrive à le faire tourner sur de vraies NES et le protocole s'est bonifie. J'ai même eu l'occasion faire des parties transatlantiques sans aucun souci ! Donc voilà, comment ça se passe sous le capot.
D'un point de vue hardware, basiquement on colle une puce WiFi (ESP8266) dans la cartouche. Je suis loin d'être une brute en hardware, mais c'est juste ça et beaucoup de magie. Je vais surtout parler du netcode de Super Tilt Bro.
Les problèmes avec le jeu en ligneÉcrire un jeu qui se joue en ligne est réputé difficile. À tout moment, on doit s'assurer que les deux joueurs voient la même chose. Lorsque l'action est intense, quelques millisecondes de ping peuvent tout changer.
Considérons Alice et Bob qui jouent ensemble. Bob a préparé sa super attaque et Alice l'a esquivée au tout dernier moment, MAGISTRAL ! Mais il y a 20 millisecondes de ping entre les appartements d'Alice et de Bob, c'est commun, alors qu'une frame fait 16 millisecondes. Alice a esquivé sur la toute dernière frame possible, donc l'esquive est passée, elle est prête pour une contre-attaque sanglante. Malheureusement Bob reçoit l'information à propos de cette esquive la frame d'après, pour lui elle à esquivé trop tard et s'est prise l'attaque de plein fouet. C'est un cas typique de désynchronisation : les deux joueurs voient une issue complètement différente de leur combat.
Alice et Bob vivement maintenant dans des univers parallèles
Le netcode basé sur le rollbackSuper Tilt Bro. implémente un netcode de type "rollback". On n'attend pas de connaître les inputs de l'adversaire, on les devine. Bien sûr il arrive qu'on se trompe. Dans ce cas, on revient à l'état du jeu avant de s'être trompé et on rejoue le plus rapidement possible (sans les afficher) les frames erronées.
Qu'est-ce que ça donne pour Alice et Bob ? Alice esquive à temps et son jeu l'affiche sans problème. Du côté de Bob, le jeu commence par "deviner" qu'Alice n'a pas esquivé a temps (c'est un super play comme on en voit rarement). L'écran de Bob affiche une frame où on peut voir Alice se prendre l'attaque. Quand l'information arrive, moins de 20 millisecondes après, le jeu rejoue les frames mal devinées. Alice n'a jamais été touchée. Ça veut dire qu'il y a eu une frame étrange, où Bob à vu l'attaque réussir, mais tout est revenu à la normale très vite. Le jeu peut continuer.
Le moteur rollback qui, l'air de rien, remonte le temps.
L'algorithme pour deviner les inputs futurs est super simple. On part du principe que rien ne va changer, aucun nouveau bouton pressé, aucun bouton relâché. Même avec un joueur taré qui fait six actions par seconde, sur un jeu à 60 FPS, on devine correctement 90% du temps.
Ajouter de la latence à l'inputUne autre astuce est de délayer les inputs, tout en les envoyant immédiatement sur le réseau. Disons qu'on ajoute artificiellement quatre frames de délai, soit 64 millisecondes. Tant que les messages mettent moins de 60 millisecondes à traverser le réseau, aucun rollback n'est nécessaire.
Voyons la partie entre Alice et Bob, cette fois avec quatre frames de délai à l'input. Quand Alice appuie sur le bouton esquive, l'effet n'est pas immédiat, donc elle se prend le coup en pleine poire. Une ou deux frames plus tard, Bob reçoit le message à propos de la tentative d'esquive, elle prendra effet plus tard. Les deux joueurs voient la même action : Alice a pris cher. C'est moins épique, mais au moins tout le monde voit la même chose et il n'y a pas eu de glitch.
Délayer les inputs : pas de glitch, mais ça peut être frustrant.
Bien entendu, les deux façons de faire peuvent être utilisées conjointement. C'est ce que fait Super Tilt Bro. Les inputs sont légèrement délayées, ça suffit la plupart du temps. Quand il y a un pic de latence sur le réseau, le rollback est là pour rattraper les pots cassés. Simplement dit, le rollback provoque de petites sautilles, le délai d'input rend les persos moins réactifs, il faut trouver le juste équilibre entre les deux.
Ce que font les autresBon, maintenant on a une idée des techniques de mitigation de la latence implémentées dans Super Tilt Bro. La plupart des développeurs ne parlent pas ouvertement de leur netcode, c'est pourtant un sujet important et les différentes approches sont toutes intéressantes. Voici quelques-unes connues.
Super Smash Bros, le vrai. Ça ne se passe pas tout à fait pareil. Il n'y a aucun rollback dans le jeu de Nintendo. Au lieu de ça, le jeu ralenti ou même freeze en attendant les informations nécessaires. On peut facilement voir ces freezes en jouant sur une connexion instable. Pour mitiger l'impact de ces freezes, le jeu semble adapter le délai aux inputs dynamiquement. (Désolé pour ce "il semblerait", les infos disponibles ont été reverse ingénieurées, voir simplement devinées.) On sait pour sûr que Super Smash Bros Brawl note chaque connexion de la meilleure (avec trois frames de délai) à la pire (avec 15 frames de délai.) Des gens ont tenté de mesurer le délai de Ultimate avec une caméra haute vitesse, ils ont échoué pour le mode en ligne, c'était trop instable. Peut-être qu'Ultimate ajuste le délai au cours de la partie pour chaque joueur.
Cette approche qui consiste à éviter les rollbacks, quitte à freezer le jeu, a du bon comme du mauvais. Premièrement, c'est simple à implémenter, le seul cas spécial à gérer est d'attendre les infos nécessaires avant de continuer le jeu. C'est également peu coûteux en CPU et en mémoire. N'ayant pas besoin de pouvoir rejouer le passé, le moteur de jeu peux simplement oublier ce qui s'est passé avant et aller de l'avant. L'effet sur un pic de latence est un freeze, alors qu'un code de type rollback montrera des persos qui se téléportent. Le freeze est plus lisible, mais une petite sautille d'un personage perturbe moins de rythme du jeu. Ce doit être une question de préférence. Le plus gros problème est le jeu compétitif. Même à un niveau moyen, les joueurs ont appris des combos avec des rythmes très précis (parfois même frame-perfect). Si le jeu ralenti, freeze ou change le délai d'input au milieu d'un combo frame-perfect, c'est foutu, même si l'exécution du joueur était bonne. Enfin, les freezes doivent absolument être minimisés, donc en cas de doute, le délai d'input doit être au plus haut.
Une autre solution, bien connue celle-là est GGPO. C'est un netcode de type rollback fait pour pouvoir être utilisé tel quel par les développeurs de jeux. Il est très populaire dans l'émulation d'arcade et est la solution retenue par Skull Girls, jeu reconnu pour son bon netcode. GGPO fait du rollback, sa documentation ne mentionne pas de délai d'input, mais Skull Girls offre l'option. On peut y choisir un nombre de frames de délai qui restera le même pour toutes nos parties. Ainsi, le joueur peux choisir lui-même où placer le curseur entre trop de délai ou trop de rollback.
Ça ressemble beaucoup au netcode de Super Tilt Bro. Le bon point de cette technique est que le délai est fixé et constant. On peut placer nos combos frame-perfect aussi bien qu'en local. Le plus gros problème est lors de gros pics de latence sur le réseau. Les personnages ont tendance à se téléporter dans tous les sens le temps que les jeux réussissent à se resynchroniser. Dans ces cas là, on ne comprend plus rien, mais le jeu continue de se dérouler. Enfin, un moteur de rollback doit être capable de garder des save-states de lui-même et les gérer en mémoire, pour pouvoir revenir dessus en cas de rollback. Ça a un coût non négligeable en CPU et en RAM.
Toutes ces implémentations modernes on un point commun : le peer-to-peer. Éviter aux messages de transiter par un serveur permet de gagner quelques précieuses millisecondes de latence. C'est vraiment une très bonne solution. Super Tilt Bro., lui, utilise un serveur. Voyons pourquoi.
Le serveur de Super Tilt Bro.On a vu que le peer-to-peer c'est le top. Super Tilt Bro. ne fait pas ça. On a aussi vu que le rollback avait un coût CPU et RAM élevé. Il faut gérer une liste de save-states en RAM et être capable de re-jouer plusieurs frames immédiatement à tout moment. La NES, avec son CPU 8 bits à 1,5 Mhz et ses 2 Ko de RAM est très, très, loin des systèmes modernes. Implémenter le système de rollback dans ses condition monopoliserait la plupart des ressources, et c'est le gameplay qui en payerai le prix. Le serveur est là pour aider.
Vous vous souvenez d'Alice et Bob ? Avec le protocole de Super Tilt Bro., quand Alice appuie sur un bouton son jeu envoie l'état de sa manette et un timestamp au serveur. Le serveur connait le jeu, il est capable de simuler des frames et de recalculer l'état du jeu à tout moment. Connaissant la partie et la nouvelle input d'Alice, le serveur peut calculer l'état du jeu au moment où Alice a appuyé sur son bouton et envoyer tout ça à Bob. Bob reçoit donc le timestamp, l'état de la manette d'Alice et l'état de la partie à ce moment là. Si le délai d'input à fait son travail, il peut ignorer l'état du jeu décrit dans le message. Si un rollback est nécessaire, la NES de Bob peut se baser sur l'état fraîchement reçu, qui est juste au moment où la prédiction était mauvaise. Donc la NES n'a aucun besoin de gérer une liste des états passés, ce qui retire presque toute la pression sur la mémoire.
Le serveur peut aussi aider avec le budget CPU. Disons, qu'il y a toujours au moins deux frames qui s'écoulent le temps qu'un message transite d'Alice vers Bob. Quand le serveur reçoit une input d'Alice, au lieu de calculer l'état du jeu au moment de l'input, le serveur peut calculer un état deux frames après. Le serveur se met à prédire le futur, de la même façon que l'aurait fait la NES de Bob, lui épargnant ce travail. Bien entendu, ça nécessite d'implémenter un système de rollback aussi dans le serveur, mais ça aide beaucoup à gérer les grosses latences. A l'heure actuelle, Super Tilt Bro. est capable de rejouer trois frames avant de manquer de temps. Si le serveur peut se charger de le faire pour une ou deux frames, ça aide beaucoup.
UDP, TCP... et les navigateurs webSur internet il y a deux gros protocoles omniprésents TCP et UDP. TCP est le plus commun, il permet à un ordinateur de se connecter à un autre et d'envoyer des données. Avec TCP il n'y a pas de perte de données et les données arrivent a destination dans l'ordre dans lequel elles ont été envoyées. Le gros d'internet est construit sur TCP, c'est super simple à utiliser. UDP de son côté est un protocole beaucoup plus léger. Avec UDP, quand on envoie un paquet, la seule chose assurée est que le paquet a été envoyé. Il peut être perdu en chemin, ou arriver avant un autre pourtant envoyé auparavant. Donc TCP est le meilleur protocole ? Pas du tout ! La magie dont est capable TCP a un coût.
Quand on utilise le protocole de Super Tilt Bro., les paquets perdus ne sont pas un gros problème. Un message du serveur contient l'intégralité de l'état du jeu, donc si on perd un paquet, on retrouvera nos petits avec le prochain. Si on utilisait TCP, qu'on envoyait deux messages à la suite et que le premier se perde. Le premier serait alors inutile, le second étant plus récent il nous intéresse plus. Bien que le paquet intéressant soit physiquement reçut, TCP assure qu'il ne sera pas utilisé tant que le paquet inutile soit reçut. Il faudra donc attendre que le paquet inutile soit ré-émit. J'ai testé d'implémenter ping avec des garanties similaires à TCP et en forçant 10% de pertes de paquets au niveau de mon driver réseau, le ping pouvait monter jusqu'à trois secondes par moments, contre 20 millisecondes normalement.
Vous imaginez avoir trois secondes de latence à cause de la perte d'un paquet inutile ? Hors de question !
Pour ceux qui suivent le jeu, vous avez remarqué l'émulateur HTML5 sur la page itch.io. Ça semble anodin, et ça ne l'est pas du tout. Les navigateurs web se sont construit sur TCP, depuis des décennies. Ils ne laissent pas une page web envoyer des paquets UDP simplement. Super Tilt Bro. est basé sur l'UDP et il en a besoin. La solution à été d'utiliser WebRTC, un protocole fait pour les vidéo conférences. C'est ce qu'utilise votre Skype online, Google Meet, Facebook "appel vidéo",... préféré. Détourner un protocole de vidéo conférence pour y faire passer un prtocole de jeu a été épique. J'écrirai peut-être un truc là-dessus, en attendant, voilà le fil Twitter en temps réèl de mon combat contre la sécurité du web :
https://twitter.com/RogerBidon/status/1259171335135211523Un dernier motImplémenter des possibilités réseau moderne pour la NES à été épique... Et ce n'est pas fini ! Ça à fait son chemin jusqu'à l'ALPHA. Ce n'est pas complet et j'ai terriblement besoin de feedback.
C'est testable ici (HTML5 ou émulateur patché) :
https://sgadrat.itch.io/super-tilt-broIl y a aussi un Discord pour trouver des joueurs :
https://discord.gg/qkxHkfxVoilà, j'espère que ce petit bout de connaissance de l'internet pourras servir à quelqu'un. N'hésitez pas à poser vos questions pour de plus amples détails. Super Tilt Bro. est un projet libre. Il est là four faire de l'internet un meilleur endroit (à son niveau), pas pour protéger des secrets.