60

Et c'est reparti !

La base de code commence à se stabiliser et mine de rien, le jeu est mieux qu'avant. Ça serait con de n'en faire profiter que les geeks qui le recompile depuis le dépôt github. Donc voilà, petit message pour annoncer qu'on relance le cycle ALPHA -> BETA -> release. Avec cette première ALPHA, toujours disponible gratuitement ici : https://sgadrat.itch.io/super-tilt-bro.

Ce que j'entends par ALPHA, c'est que le jeu est toujours en gros chantier. Il est jouable, mais il y a plein de choses qui devrait arriver au fur et à mesure des ALPHAs : le support du réseau, plus de musiques, de meilleurs bruitages,... En fait, à peu près tout ce qui me semblera cool, qui vivra verra smile



Changements notables par rapport à la version 1.1 (celle sur les cartouches):

* Un nouveau perso jouable
* Nouveaux graphismes par Martin Le Borgne, qui a bossé sur Twin Dragons (ce mec est super cool !)
* On peut descendre des plateformes en appuyant brièvement sur bas
* On peut sortir de l'écran sans mourir, les limites sont juste un peu plus loin
* L'IA est un peu moins conne (elle arrive enfin à nous frapper si on ne bouge pas)
avatar

61

king
avatar
@originalfei
Homebrews Connexion
In pixels we trust.
ORE WO DARE DA TO OMOTTE YAGARU !

62

Hop, une jolie petite nouvelle à propos du projet : le prototype online.

Il y a bien longtemps j'avais fait quelques premières expérimentations qui s'étaient bien déroulées. Là j'ai l'ambition d'enfin intégrer ça correctement. Depuis le netcode a été amélioré et on a réussi à le faire tourner sur une vraie NES.

Avant d'intégrer ça à l'alpha de Super Tilt Bro. J'aurais besoin de savoir à quel point ça marche vraiment en conditions réelles. Donc voilà un prototype suffisamment stable pour être utilisé sans avoir l'impression d'être Anakin Skywalker dans une course de pods. Le prototype vient avec un émulateur, la ROM et des scripts pour lancer tout ça correctement sur l'un des deux serveurs : Amérique du nord ou Europe. Pour vous le bon choix sera généralement Europe beret

Voilà le lien magique : http://supertiltbro.wontfix.it/online.html

Merci de me faire des retours. Que l'expérience soit bonne, mauvaise ou toute pourrie. Tout retour m'aidera à savoir sur quoi me concentrer ensuite. Aussi, les serveurs ne seronts pas très fréquentés, pensez à prévenirs vos amis avant de lancer une partie, histoire d'avoir quelqu'un contre qui jouer.
avatar

63

Supeeeer !
Je vais m’organiser avec mon frère pour tester. Je vais peut-être faire de la capture video aussi pour les retours !!
avatar
@originalfei
Homebrews Connexion
In pixels we trust.
ORE WO DARE DA TO OMOTTE YAGARU !

64

Merciii ! heart

Hâte d'entendre vos retours. Vous faites partie du top mondial sur ce jeu (même s'il suffit d'avoir joué pour rentrer dans le top.) S'il y a un truc que j'ai appris en implémentant ce netcode c'est que les problèmes sont clairement plus évidents pour les joueurs qui savent ce que font les boutons. Enfin, j'ai aussi appris tout un tas d'autres choses, ça méritera un article smile
avatar

65

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.

tRQLdEO.png
Un prototype de cartouche WiFi, par @BrokeStudio

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.

3ppvZrS.png
Alice et Bob vivement maintenant dans des univers parallèles

Le netcode basé sur le rollback

Super 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.

Jq44al8.png
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'input

Une 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.

Wqo13gV.png
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 autres

Bon, 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 web

Sur 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/1259171335135211523

Un dernier mot

Implé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-bro

Il y a aussi un Discord pour trouver des joueurs : https://discord.gg/qkxHkfx

Voilà, 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.
avatar

66

Merci c'est extra eek
avatar
@originalfei
Homebrews Connexion
In pixels we trust.
ORE WO DARE DA TO OMOTTE YAGARU !

67

68

formidable! bravo! doom
avatar

69

love Merci, merci !
avatar

70

Petites news du projet

Bon, bah, j'ai ré-implémenté un moteur de musique à partir de la page blanche. Mais pourquoi ? Deux raisons :

1. C'est rigolo à faire
2. J'ai en tête des fonctionnalités qui n'existent dans aucun
3. J'ai calculé que ça ne serait pas si long... mais je ne sais pas compter.

Le moteur est encore en rôdage, mais il fait déjà quelques trucs cools. Je vais le comparrer à ggsound et famitone, qui sont eux aussi des moteurs fais pour être utilisés par des jeux. Aussi au driver de Famitracker, qui lui est hyper complet mais bouffe beaucoup de ressources et est rarement utilisé pour des jeux, par contre en chiptune, c'est l'idéal.

1. On peut lui faire jouer des morceaux de Famitrackers qui contiennent des effets. ggsound et famitone ne font pas ça, ce qui oblige les compositeurs à faire en fonction du moteur,
2. Il est rapide et léger, il faut que je pousse la comparaison, mais pour l'instant il semble battre ggsound/famitone (de pas grand-chose, c'est comparable),
3. La taille des musiques importées est plus grosse que ggsound/famitone, sans être aussi obèse qu'avec Famitracker.

Et donc a terme j'aimerais lui faire jouer des musiques dynamiques. Qui changent de rythme en fonction du déroulement de la partie... Mais on n'y est pas encore. Je pense que je vais me motiver à écrire une entrée de devlog sur ce qui est déjà là. Il y a déjà une énorme quantité de détails techniques croustillants smile

Sinon, on a aussi fait un nouveau trailer avec une amie qui se lance en freelance de motion design. L'ancien avait déjà trois ans et ne ressemble même plus au jeu grin



Instant promotion gratuite : du coup si vous avez des besoins de motion design, pensez à elle. Elle répond sur son instagram.
avatar

71

Ce trailer ! top
avatar
@originalfei
Homebrews Connexion
In pixels we trust.
ORE WO DARE DA TO OMOTTE YAGARU !

72

Fei (./71) :
Ce trailer ! top
Grave !!!
avatar
Matmook -- http://www.barreteau.org
Twitter : @matmookJagware

73

Ça donne vraiment envie...
avatar

74

Merci ! Ça fait plaisir a etendre. A force de le regarder en boucle, j'ai appris a n'en voir que les défauts et trucs qui auraient pu être autrement smile
avatar

75

Relayé sur flipboard

https://www.mirari.fr/qtAk
avatar

76

Héhé, apparemment l'idée du jeu NES en réseau ça plait. Je ne vais pas m'en plaindre king
avatar

77

Netcode rollback sur la NES : les détails inavouables.

Attention, celui-là il va être technique !

Super Tilt Bro. ALPHA 5 vient de sortir. Grosse nouveauté : on peut jouer en ligne avec n'importe quel personnage sur n'importe quel niveau. Le moment idéal pour faire un bilan sur le fonctionnement du netcode, et les plus gros obstacles surmontés.

Vous les savez puisque vous avez lu le dernier post (au moins les parties avec des images), Super Tilt Bro implémente un netcode basé sur le rollback. L'expérience reste toujours fluide, quand les messages de l'autre NES prennent du temps à arriver, le moteur les prédit et continue d'avancer. Bon, parfois il se trompe. Dans ce cas il efface ses prédictions et re-joue les frames manquantes pour revenir à l'état courant de la partie.

Le protocole

Les netcodes basés sur le rollback sont connus pour être difficiles à implémenter. Il n'y a qu'à chercher un peu pour trouver des postes très longs et très sérieux assurant que la Switch n'est pas assez puissante pour faire ça. Et donc, comment réalise-t-on l'impossible sur NES ? (Vous vous en doutez : en trichant !)

Un des gros problèmes quand on implémente ce genre de netcode, c'est la gestion de la mémoire. Le jeu doit littéralement garder une copie de chaque état qu'il a prédit pour pouvoir s'en servir comme checkpoint. Tournant sur NES, avec 2 KB de mémoire (et pas des plus rapides), Super Tilt Bro. doit éviter ça à tout prix. Quand on n'a pas de mémoire, la solution est d'utiliser celle des autres. Les routeurs entre le serveur et la NES sont bourrés de mémoire !

Le protocole en quelque lignes :
  • Quand le joueur appuie sur un bouton, un message est envoyé au serveur. Un tout petit message, juste l'état de la manette et un timestamp.
  • Le serveur calcul l'état du jeu au moment de l'action. Il envoie un message étendu à l'autre NES.
  • Le message étendu contient : l'état de la manette, le timestamp et l'état du jeu à cet instant.
  • La NES qui reçoit ce message à tout ce qu'il lui faut. Il n'y a plus qu'à remplacer l'état du jeu par celui dans le message et simuler quelques frames pour compenser le temps de transit du message.

Voilà, la NES n'à plus à s'occuper de la gestion difficile de la mémoire. Le serveur s'en occupe et transmet tout ce qu'il faut. Aucun problème pour les routeurs, car on est sur NES, l'état de notre jeu fait une grosse centaine d'octets. C'est encore un tout petit paquet sur l'internet moderne. Il y a tout de même des compris faits. Déjà le serveur est plus complexe, il intègre un émulateur pour déterminer l'état du jeu. Le pire étant que, du coup, le serveur est nécessaire, impossible de migrer vers un protocole peer-to-peer. Avec le temps, on verra bien si ce rollback en passant par un serveur rivalise avec les netcodes basés sur l'input-lag en peer-to-peer.

Ok, second vilain : le CPU

Donc la NES a reçu un ancien état depuis le serveur et doit simuler les frames manquantes pour coller au présent. Ça DOIT être rapide. On ne veut pas délayer ça sur plusieurs frames vidéo, accélérant la vitesse de jeu, ça ruinerai le timing des inputs et ferait rater leurs combos aux joueurs. On veut rollback instantanément, qu'a la réception du message le jeu soit immédiatement mis à jour. On dispose d'exactement 20 millisecondes pour faire ça sans rater une frame vidéo (en PAL.)

La première chose à faire est de pouvoir mettre la simulation en “mode rollback”. Dans ce mode, rien n'a besoin d'être affiché, le moteur peux simplement ignorer tout ce qui est placement de sprites, modification du background, et tout ce qui est visuel.

Si on ne fait que ça, simuler une frame en rollback prend 25% de nos 20 millisecondes. Ça pourrait suffire à rollback quatre frames, donc compenser 80 ms de ping, si le reste du jeu ne prenait pas 60% du temps disponible. Notamment, on veut toujours simuler une frame en mode normal, juste histoire de voir quelque chose à l'écran. Du coup, ça permet de jouer, mais le moindre rollback est vite problématique.

Après ça, il reste l'optimisation pure et dure de tout ce qui est fait plusieurs fois par frame. La détection des collisions est là où on a vu les plus gros gains. C'était un vieux bout de code, implémenté tout en apprenant les base de 6502, et ça tourne chaque frame, pour chaque joueur, pour chaque plateforme... plusieurs fois. Autant dire que le grand ménage a été fait là-dedans.

Un autre gain a été trouvé sur le code qui gère les animations. En mode rollback, on se fiche de placer des sprites à l'écran, malheureusement les hitboxes sont définies dans les animations. Le moteur se retrouvait donc à parser des définitions de sprites, au cas où il y aurait une hitboxe au milieux. Ici l'astuce a été de toujours placer les définitions de hitboxes au début, le moteur s'arrête tout de suite après en mode rollback. Et puis des optimisations bas niveau, ça ne fait jamais de mal.

KzIwRkm.png
Avec les optimisations, on peut rollback trois frames... Bien, mais pas top.

Avec ça, on n'arrive pas encore à compenser les grosses latences. Heureusement, on a un serveur à disposition. Après tout, le serveur peut lui aussi prédire des frames, et il le fait. Il prédit juste ce qu'il faut pour compenser la latence minimum observée. Disons que le ping oscille entre 100 et 140 ms, le serveur va prédire de quoi compenser 100 ms. Dans le pire des cas, la NES devra compenser 40 ms, ce qui fait deux frames.

C'est quoi la suite ?

Ce netcode va avoir besoin de gagner l'expérience du terrain. C'est sûr et certain il n'est pas parfait, mais en quoi précisément ? Le seul moyen de le découvrir est de l'essayer, de jouer avec... de l'éprouver en somme. Plus il est utilisé, plus on trouvera de trucs, donc n'hésitez pas à l'essayer par vous-même, forcez vos amis à jouer avec vous, et envoyer moi toute remarque (promis, je ne m'énerverais pas.) Tout commence ici : https://sgadrat.itch.io/super-tilt-bro

Bon, aussi le netcode c'est bien, mais ce n'est pas tout ce qui fait un jeu en ligne. Pour l'instant, une interface moche nous bombarde dans une partie et voilà, point. Je pense commencer par créer un classement officiel consultable en ligne. Comme ça, si on est le meilleur au monde on pourra enfin se la péter. C'est important !
avatar

78

Vivement le petit livret making of offert avec le jeu grin
avatar
@originalfei
Homebrews Connexion
In pixels we trust.
ORE WO DARE DA TO OMOTTE YAGARU !

79

Benchmark des différents compilateurs C pour le 6502

TXwTjkP.png

Choisir un compilateur C pour le 6502 n'est pas si facile. Chacun à son lot de forces et faiblesses. On peut chercher la vitesse, la facilité d'utilisation, le respect du standard, ou vraiment n'importe quoi.

Quand j'ai dû faire ce choix pour Super Tilt Bro., la vitesse était le dernier de mes soucis. Cependant, en discutant avec d'autres developpeurs, le sujet revenait toujours sur le tapis. Ça sera donc notre principal focus : quel compilateur produit le code le plus rapide ?

Quelques petits bouts de code seront comparés. À chaque fois on expliquera ce que fait le code et pourquoi il est pertinent de le bencher.

Ensuite, je parlerai de mon expérience à utiliser chacun de ces compilateurs. Leurs forces et faiblesses au-delà de la performance. Forcément, c'est subjectif, j'essaierais de rester factuel mais je ne promets rien.

Enfin, une petite section "comment choisir son compilateur" conclura cet article. En espérant que ça serve à quelqu'un.

C'est partit !

Les candidats au - très convoité - titre de meilleur compilo ?

cc65 est le plus utilisé. Il bénéficie d'une énorme suite d'outils, est activement maintenu, se bonifie avec le temps et à une grande communauté d'utilisateurs. Ceci dit, il a la réputation de produire un code particulièrement lent... on verra bien smile

vbcc c'est le compilateur tendance. Moins utilisé que le roi, il a la réputation de produire un code bien meilleur.

KickC est le petit jeune plein d'avenir. Est-ce qu'il a déjà de quoi jouer dans la court des grands ?

6502-gcc est un sombre inconnu. Personne ne le maitrise vraiment, le projet semble mort, les instructions d'installation sont impénétrables,... Nous allons tenter de faire la lumière sur ses inavouables secrets !

6502-gcc à deux options de compilation très différentes :
  • "-O3" optimise le code pour être rapide
  • "-Os" optimise le code pour être petit

Décompression

Ce benchmark est issu de la vraie vie. Il s'agit d'une fonction prototypée en C pour Super Tilt Bro. avant d'être convertie en assembleur à cause de mauvaise performances. Les données compressées sont aussi celles d'origine.

La fonction fouille dans un tas de données compressée pour en extraire quelques octets. C'est de la grosse logique qui boucle, passant son temps à lire la mémoire.

d4dGxeU.png
En rouge on a le temps d'exécution, en bleu la taille du code.


Ça saute aux yeux, cc65 est fidèle à sa réputation. Le code généré est extrêmement lent.

Le gagnant est 6502-gcc, avec une avance correcte sans être confortable. KickC à un très léger avantage sur vbcc.

Fun fact : plus un compilateur est populaire, pire est sa performance sur ce test.

Memcopy

Là, c'est la tâche la plus commune au monde : on copie des octets d'un endroit à l'autre de la mémoire. Il est toujours judicieux de comparer les compilateurs sur ce qu'on leur demandera de faire le plus souvent.

Il y a deux versions de ce test :
  • Le normal est une boucle qui copie les données
    • le compilateur peut déterminer les adresses et la taille des données, ainsi il peut optimiser pour ce cas particulier.
  • Le "no-inline" passe par une fonction indépendante
    • le compilateur est forcé de générer un code générique de copie d'octets

uiPSOfL.png
Les compilateurs sont triés : du plus lent au plus rapide

cc65 est fidèle à son poste de dernier.

KickC confirme être un poil plus rapide que vbcc. À noter l'absence de KickC dans la version "no-inline" : KickC fonctionne toujours en connaissant tous les sources du projet, impossible de l'empêcher d'inliner.

6502-gcc prend un coup dur ici. Pire, en menant d'autres tests on s'aperçoit que ses performances varient énormément en changeant quelques détails dans le code.

Aussi, avez-vous remarqué que "-O3" est terriblement mauvaise dans la version inline ? gcc (le "vrai" ainsi que 6502-gcc) fonctionne en deux étapes : le front-end optimise le code C, puis le back-end génère du code machine. Le front-end de 6502-gcc est le même que celui de gcc, il optimise comme un dieu, puis vient le back-end. Le back-end 6502 manque sévèrement d'amour et ça se voit ici. Dans notre cas, le front-end comprend ce qu'on fait et dis simplement au back-end "copie 200 octets de $400 vers $200". Le back-end devrait implémenter ça de la façon optimale, mais il se contente d'appeler "jsr memcpy", qui est la fonction standard de copie et n'est pas DU TOUT adaptée à un CPU 8 bits.


Le meilleur JDR au monde !

Disclaimer : ce bench a été pensé pour tirer avantage du front-end extraordinaire de 6502-gcc.

C'est un moteur de JDR avec tout plein d'abstractions. Il y a des structs, des fonctions pour que le héro frappe, des fonctions pour que le monstres reçoivent un coup, le héro porte une arme,...

La fonction benchée initialise l'état du jeu, joue un tour et retourne. Du coup, bien qu'il y ait plein d'appels de fonctions, de calculs de point de vies, et d'aller-retours, finalement la fonction doit simplement mettre la mémoire dans l'état du jeu après un tour.

USdTOGE.png

cc65... ça devient lassant. Bon, ce test n'est définitivement pas pour lui. cc65 ne fait strictement aucune optimisation de haut niveau alors que bench test la qualité des optimisations de haut niveau.

KickC est un meilleur vbcc côté performances. Rien de nouveau.

6502-gcc ne déçoit pas. Son front-end est l'un des meilleurs au monde (sans mentir ni exagérer) et là, il voit l'arnaque. Le code généré est une simple succession de "LDA <constante> : STA <mémoire>" pour écrire en mémoire l'état de la partie après un tour de jeu.

Du code pour cc65

Avez-vous lu le superbe article "Advanced optimizations in CC65" ? Vous le trouverez ici :
GitHub - ilmenit/CC65-Advanced-Optimizations: How to optimize C code for CC65 compilerGitHubHow to optimize C code for CC65 compiler. Contribute to ilmenit/CC65-Advanced-Optimizations development by creating an account on GitHub.


Opération sauvez le soldat cc65 ! On va bencher un code spécialement fait pour cc65, codé par un expert. Ça devrait le faire, hein ?

Le code est comparable au moteur de JDR : c'est un moteur de JDR plein d'abstractions inutiles. La différence est qu'il boucle 100 fois et affiche les personnages à l'écran à chaque fois, donc pas moyen de simplement réduire ça à une suite de LDA+STA, il va falloir calculer des trucs.

Sur cette base, Ilmenit applique différentes optimisations jusqu'à ce que le code aille vraiment vite.

On benchera deux versions : la première, sans optimisation, et la dernière, avec toutes les optimisations.

vSFSIVv.png

KickC a disparu et vbcc en piteux état !

KickC a simplement trop de limitations pour compiler ce code. Entre autres il refuse de générer du code pour un modulo ou une division.

vbcc a généré une boucle infinie sur la version non-optimisée. Il a aussi souffert de mon manque d'expérience pour l'intégrer à mon outil (d'où l'absence d'info sur la taille du code.)

6502-gcc a dévoilé un sale bug. Les variables globales sont visiblement mises dans un segment au hasard. L'assembleur généré a dû être fixé à la main pour le faire tourner.

EDIT : après publication de cet article le mainteneur de gcc est intervenu pour régler le bug en question.

Donc, même sans lire les graphiques : cc65 a gagné ! Il est le seul à réussir à compiler ce code !

Il est intéressant de voir que 6502-gcc n'est pas particulièrement bon sur la version non-optimisée. Après tout ce qu'on a dit sur la puissance de ses optimisations de haut niveau ! Le truc c'est que le code ne lui permet pas d'inliner. En C, quand on déclare une fonction sans la précéder de "static", elle doit être accessible aux autres unités de compilation et donc être complètement utilisable : avec passage de paramètres par la pile et tout le tintouin. Bien sûr ça ne change pas grand-chose pour cc65 qui de toute façon n'inline rien.

Sur la version optimisée, il n'y a pas de grande différence. vbcc est derrière mais souffre peut-être de mon inexpérience et de mesures grossières. Le code a été optimisé pour être facile à compiler, rien d'étonnant à ce que tous les compilateurs est à peu-prêt le même résultat.

Et ce "???", qu'est-ce que c'est ? Si vous n'avez pas encore lu "Advanced optimizations in CC65" (Ayez honte!), vous ne vous rendez pas compte à quel point le code optimisé est illisible. Toutes les astuces possibles et imaginable y sont passée, même l'auteur ne recommande pas ça dans la vraie vie.

"???" c'est "6502-gcc -Os", sur un code avec seulement deux des 12 optimisations et surtout le mot clé "static" utilisé à bon escient. Cette version ressemble beaucoup à la version non-optimisée et la pénalité de performance est quasi nulle.

C'est pourquoi je pense que les optimisations haut niveau sont importantes. Elles permettent d'écrire en C, sans trop se soucier de l'assembleur généré. On laisse les astuces bas niveau au compilateur et on se concentre sur la logique.

Les performances, ce n'est pas tout

Ok, côté performances il semblerait que cc65 est loin derrière, vbcc et KickC sont quasiment ex aequo et 6502-gcc oscille entre l'excellence et la catastrophe.

À vrai dire, les performances, ce n'est pas ce qui devrait compter le plus quand on choisit un compilateur C (pour le 6502.) Apprenez l'assembleur et vous aurez toujours moyen d'injecter des performances là où ça compte.

Voici un petit résumé des avantages et inconvénients de chaque compilateur :

cc65
Pros
  • Rock-solid et battle-tested, il ne vous claquera pas entre les doigts.
  • En développement actif
  • Plein de ressources disponibles pour apprendre
  • L'une des suites d'outils les plus complètes
Cons
  • Performances (sérieusement, c'est son seul défaut)

vbcc
Pros
  • Performances acceptables
  • Il a des utilisateurs actifs
  • La documentation est super complète
  • Suite d'outils bien fournie (assembleur, linkeur, fichiers de configurations,...)
Cons
  • Pire licence possible (vraiment je recommande de l'éviter à cause de ça)
  • Parfois buggué

KickC
Pros
  • Bonnes performances
  • Difficile à intégrer avec d'autres outils (il veut compiler tout un projet d'un coup)
  • Développement actif (et la base est bonne, le futur s'annonce prometteur)
Cons
  • Respect partiel du standard C (même "const" est mal supporté)
  • La compilation est lente
  • Les erreurs de compilation sont cryptiques

6502-gcc
Pros
  • Respecte le standard C à la lettre
  • Optimisations haut niveau qui profitent de la puissance de gcc
  • Génère de l'assembleur pour ca65 (et donc compatible avec tous les outils de cc65)
  • Erreurs et warnings lisibles, et qui peuvent même remonter de vrais bugs dans votre code
Cons
  • Développement inactif
  • Buggué
  • Qualité du code généré très variable

Ok, donc lequel choisir ?

Comme toujours, ça dépend ! Qui êtes-vous ?

Êtes-vous un développeur expérimenté, habitué à l'un de ces compilateurs ? Restez dessus, vous le maîtrisez déjà, les autre n'apportent que peu d'avantages.

C'est votre premier projet C pour le 6502 ? Il vous faut cc65, c'est le plus mature et vous trouverez de l'aide quand vous en aurez besoin.

Il vous faut des optimisations de haut niveau, et un truc qui marche sans se prendre trop la tête ? KickC est fait pour vous.

Il vous faut des optimisations de haut niveau, et avez tendance à exploiter les détails du standard C ? Vous être prêts à vous faire vos propres outils au besoin ? 6502-gcc est le compilateur idéal.

Non. Je ne recommanderais jamais vbcc. Sa licence est horrible. Les sources ne sont pas disponibles et vous ne pouvez pas l'utiliser pour "usage commercial" (sans définition spécifique, ni d'info si ça s'applique aussi aux programmes générés.) En prime, il intègre divers outils et bibliothèques qui ont chacun leur propre licence. Pour faire les choses bien, vous devrez au moins lire trois licences avant de savoir si ce que vous avez en tête est autorisé.

Un dernier mot

Les benchmarks c'est joli et montre un classement clair, mais ce n'est jamais parfait. Ces benchmarks en particulier, c'est ma première expérience avec la plupart de ces compilateurs, j'ai possiblement raté des choses. Si vous voulez jouer les apprentis sorciers vous-même, l'outil utilisé est disponible ici : https://github.com/sgadrat/6502-compilers-bench Ça prend un fichier C et sort les infos de rapidité ainsi que l'assembleur généré.

En espérant que cette petite recherche aide quelqu'un un jour. Mais n'oubliez pas, en développement rétro, l'important c'est de s'amuser (au sens geek du terme.)
avatar

80

Juste GENIAL !
Thanks!
avatar
Matmook -- http://www.barreteau.org
Twitter : @matmookJagware

81

Héhé, moi qui avait peur d'être trop technique sur ce coup. J'imagine que comparer les compilateurs C n'est pas le dada de tout le monde. Ça devait être fait, trop de questions sans réponses claires, mais je vais tenter de me calmer pour la suite et rester sur des choses que les joueurs peuvent visualiser.

Merci matmook, ton entousiasme va droit dans mon petit cœur
avatar

82

C'est beau du CC65 optimisé, hein ?
Il y a d'autres articles du même accabit (peut-être une partie des mêmes probablement) ici :
https://sourceforge.net/projects/cc65extra/files/
avatar
De nouveaux jeux pour vos vieilles consoles ? En 2024 ?
https://yastuna-games.com

83

Disons qu'à écrire en C en pensant à l'assembleur, on perd beaucoup de l'intérêt du C.

Je ne sais pas pourquoi cc65 refuse les optims haut niveau. J'imagine que c'est un nid à bugs incroyable pour le compilo qui, d'un coup, doit gérer correctement les barrières mémoire même dans la logique abstraite.

C'est ce que j'ai aimé avec 6502-gcc. On peut faire un block inline asm et savoir que les clobbers sont respectés à la lettre. On peut appeller une fonction externe et savoir que toutes les écritures mémoire son effectives au moment de l'appel. Je n'aurais confiance en aucun autre pour ce genre de choses (a part cc65, au prix de la perf)
avatar

84

Bon, ça fait un moment que je n'ai pas bossé sur un truc technique digne d'une entrée de devlog, mais j'ai une jolie news pour patienter smile



Nouveau perso jouable ! Issue de l'excellentissime BD en ligne Pepper & Carrot et pixelisée par nul autre que le grand Fei !

Je suis super fier de ce que j'ai fait. Partant de nul part en game-design, j'ai l'impression d'en comprendre un peu plus à chaque perso. Et celui-là me semble être le plus cohérant et sympa à jouer.
avatar

85

top

(c'est vrai que je fais 1m89 quand même ! xD)
avatar
@originalfei
Homebrews Connexion
In pixels we trust.
ORE WO DARE DA TO OMOTTE YAGARU !

86

Pas un petit joueur !
avatar
MK !
Collectionneur, retrogamer.
Enfin, un peu moins maintenant.

87

Je ne suis que la moitié de l'homme qu'il est !
avatar

88

RogerBidon (./87) :
Je ne suis que la moitié de l'homme qu'il est !

Ah oui mais tu comptes le poids aussi là xD
avatar
@originalfei
Homebrews Connexion
In pixels we trust.
ORE WO DARE DA TO OMOTTE YAGARU !

89

Moteur audio: quand on ne sait pas faire, on triche !

Bq21BmT.png

Il est grand temps de parler de musique ... Ou plutôt de comment jouer des musiques avec du code !

Super Tilt Bro. 2 a de meilleures musiques que la première version, mais ça ne s'est pas fait sans heurts. Le moteur audio de l'ancienne version est un moteur maison écrit aux tout débuts. S'il est particulièrement léger, il manque de fonctionnalités. Mais pas qu'un peu. Importer la moindre musique est le travail de plusieurs jours, et on est obligé de massacrer le morceau au passage. Quand Tuï est arrivé avec une musique formidable à la technique impeccable, il était hors de question de la massacrer.

Il aurait été trop facile d'intégrer un des moteurs audio de référence. GGSound et Famitone, ils sont super efficaces, hyper stables et ont prouvés leurs qualités à maintes reprises. Super Tilt Bro. a besoin de technologies expérimentales, instables et *hmm* *hmmm* AUDACIEUSES !

Et puis bon, avoir un moteur audio maison, ça ouvre des possibilités, mais nous y reviendrons. D'abord quelques généralités sur le système audio de la NES, des détails gores sur le moteur de Super Tilt Bro. et des comparaisons biaisées avec la concurrence.

Juste une petite mise en bouche à propos des fonctionalités de Super Tilt Bro.:
- Gestion des effets Famitracker: Axx, Bxx, Dxx, Fxx, Gxx, Qxx, Rxx, Sxx, 1xx, 2xx, 3xx, 4xx (Et à terme tous les autres)
- Gestion des instruments
- Gestion du tempo
- Plus rapide que Famitone

Un moteur audio, comment ça marche ?

Au plus bas niveau il y a la puce audio de la NES, que l'on nomme APU pour Audio Processing Unit. L'APU sait faire plein de choses, mais au final elle expose quelques registres et le son joué dépend simplement des valeurs qu'on écrit dans ces registres. Il s'agit donc de changer régulièrement ces valeurs pour jouer une musique.

Un moteur audio, est donc un bout de code qu'on appelle régulièrement et qui va lire les données de la musique pour les écrire dans les registres de l'APU. Le nerf de la guerre sera de faire ça le plus rapidement possible, pour laisser du CPU au reste du jeu, tout en gardant un format de musique correctement compressé. Ah ... Et puis aussi d'être capable de jouer ce que les musiciens ont composé.

Sur la NES, l'écrasante majorité des musiciens travaillent avec un tracker nommé Famitracker. Famitracker a une tétra-chiée de fonctionnalités et un format bien à lui. On va alors passer par une étape d'importation qui consiste à convertir le format Famitracker dans le format de notre moteur.

ne86qGP.png
Du tracker au format binaire du moteur.

Ce qui va faire la particularité de Super Tilt Bro. est d'utiliser un moteur extrêmement léger et compenser par un script d'importation très puissant. Les autres moteurs tendent à gérer beaucoup de fonctionnalités, mais imposent au musicien de se priver du reste. Super Tilt Bro. ne gère que le minimum, mais ce que le moteur ne sait pas gérer est converti à l'importation en un équivalent plus simple à jouer.

À partir de maintenant je vais partir du principe que vous êtes habitués aux trackers et avez des notions de chiptune. Désolé les autres, je ne peux que vous conseiller d'essayer Famitracker par vous-même.

Aussi je parlerai de "ticks." Un tick est simplement une invocation du moteur. Comme on l'invoque régulièrement, c'est l'unité de rythme la plus courte. Ici je partirai sur six ticks pour une noire.

Nous disions donc, le but du moteur est d'écrire des valeurs dans les registres de l'APU. Pour ce faire, il lit un format de données, l'interprète et agit en conséquence. Comme ces données décrivent le comportement que doit avoir le moteur, le plus simple est d'utiliser un bytecode, c'est-à-dire une suite d'instructions à respecter. Ainsi, pour jouer une gamme, on aura quelque chose comme :

joue la note do attend 6 ticks joue la note ré attend 6 ticks joue la note mi attend 6 ticks joue la note fa attend 6 ticks joue la note sol attend 6 ticks joue la note la attend 6 ticks joue la note si attend 6 ticks
Bien entendu, la NES a plusieurs canaux (pulse1, pulse2, triangle et noise.) Nous avons donc une suite d'instructions par canal. Pour pouvoir compresser ça, Super Tilt Bro. introduit le concept de sample. Un sample est une suite d'instructions, chaque canal a une suite de samples à lire. Ainsi nous pouvons par exemple mettre le refrain dans un sample qui sera rejoué plusieurs fois :

CANAL pulse1: sample1 sample_refrain sample2 sample_refrain CANAL pulse2: sample_silence CANAL triangle: sample_silence CANAL noise: sample_silence sample1: joue la note do attend 6 ticks joue la note ré attend 6 ticks ... fin du sample sample_refrain: joue la note do attend 5 ticks silence attend 1 tick joue la note do attend 5 ticks silence attend 1 tick joue la note sol attend 5 ticks ... fin du sample sample2: joue la note si attend 6 ticks joue la note la attend 6 ticks ... fin du sample sample_silence: silence attend 255 ticks fin du sample
Bien sûr, on pourra aller plus loin en faisant des samples plus petits, comme ici avoir un sample pour la succession "do pendant 5 ticks, puis silence pendant un tick."

C'est vite très verbeux, et particulièrement illisible pour nous autres humains. Il est pratique d'utiliser un format intermédiaire, s'approchant des formats de trackers, mais se limitant à ce que le moteur de Super Tilt Bro. sait faire :

CHANNEL 00 00, 01 SAMPLE 00 (2a03-pulse) # note frequency_adjust volume duty pitch_slide 00 : C-3 ... ... ... ... 01 : ... ... ... ... ... 02 : ... ... ... ... ... 03 : ... ... ... ... ... 04 : ... ... ... ... ... 05 : ... ... ... ... ... SAMPLE 01 (2a03-pulse) # note frequency_adjust volume duty pitch_slide 00 : E-3 ... ... ... ... 01 : ... ... ... ... ... 02 : ... ... ... ... ... 03 : ... ... ... ... ... 04 : ... ... ... ... ... 05 : ... ... ... ... ...
C'est déjà plus lisible si vous êtres habitués aux trackers. (Sinon, je vous avais prévenu !) "CHANNEL 00" étant le pulse1, les autres ne sont pas dans cet exemple par soucis de concision.

On peut y voir les fonctionnalités du moteur. Pas de colonne "effets" fourre-tout, chaque colonne à un rôle précis. Il n'y a notamment pas d'instrument. Note, volume et duty sont simples. Pitch slide, active un slide de pitch, comme les effets 1xx et 2xx de Famitracker. Frequency adjust change la fréquence actuellement jouée, même si elle a été impactée par le pitch slide depuis la dernière note. Ce n'est pas utilisé quand on importe depuis Famitracker, mais peut servir à gérer un arpège avec un slide comme sait le faire Famistudio.

Si ce format est lisible, il est également trivial à convertir en suites d'opérations pour le moteur de Super Tilt Bro. Mais comment fait-on pour convertir un morceau complexe dans ce format si limité ?

Voici un extrait d'un vrai fichier composé par Tuï:

ROW 00 : C#3 12 . ... ... : ROW 01 : ... .. . ... ... : ROW 02 : ... .. . ... ... : ROW 03 : ... .. . ... ... : ROW 04 : ... .. . 4AA 103 : ROW 05 : ... .. . ... ... : ROW 06 : ... .. . ... ... : ROW 07 : ... .. . ... ... : ROW 08 : ... .. . ... ... : ROW 09 : ... .. . ... ... : ROW 0A : ... .. . ... ... : ROW 0B : ... .. . ... ... : ROW 0C : ... .. . ... ... : ROW 0D : ... .. . ... ... : ROW 0E : ... .. . ... ... : ROW 0F : ... .. . ... ... : ROW 10 : ... .. . ... ... : ROW 11 : ... .. . ... ... : ROW 12 : ... .. . ... ... : ROW 13 : ... .. . ... ... : ROW 14 : ... .. . ... ... : ROW 15 : ... .. . ... ... : ROW 16 : ... .. . ... ... : ROW 17 : ... .. . ... ... : ROW 18 : ... .. . ... ... : ROW 19 : ... .. . ... ... : ROW 1A : ... .. . ... ... : ROW 1B : ... .. . ... ... : ROW 1C : ... .. . ... ... : ROW 1D : ... .. . ... ... : ROW 1E : ... .. . ... ... : ROW 1F : ... .. . ... ... : ROW 20 : --- 00 . 400 100 :
L'instrument 12 est simple, il force juste le duty à 2. L'effet 4xx est un vibrato et l'effet 1xx un pitch slide allant du grave à l'aigu. Le tout forme un effet bien sympatique.



On va simplement transformer le fichier pour s'approcher petit à petit de notre format. D'abord il va falloir intégrer l'instrument dans le pattern et s'arranger pour n'avoir que des effets 1xx ou 2xx. Voilà ce que ça donne :

ROW 00 : C#3 .. F ... ... ... V02 : ROW 01 : ... .. F ... ... ... V02 : ROW 02 : ... .. F ... ... ... V02 : ROW 03 : ... .. F ... ... ... V02 : ROW 04 : ... .. F 214 103 ... V02 : ROW 05 : ... .. F ... ... ... V02 : ROW 06 : ... .. F 114 ... ... V02 : ROW 07 : ... .. F ... ... ... V02 : ROW 08 : ... .. F ... ... ... V02 : ROW 09 : ... .. F 214 ... ... V02 : ROW 0A : ... .. F ... ... ... V02 : ROW 0B : ... .. F ... ... ... V02 : ROW 0C : ... .. F 114 ... ... V02 : ROW 0D : ... .. F ... ... ... V02 : ROW 0E : ... .. F ... ... ... V02 : ROW 0F : ... .. F 214 ... ... V02 : ROW 10 : ... .. F ... ... ... V02 : ROW 11 : ... .. F ... ... ... V02 : ROW 12 : ... .. F 114 ... ... V02 : ROW 13 : ... .. F ... ... ... V02 : ROW 14 : ... .. F ... ... ... V02 : ROW 15 : ... .. F 214 ... ... V02 : ROW 16 : ... .. F ... ... ... V02 : ROW 17 : ... .. F ... ... ... V02 : ROW 18 : ... .. F 114 ... ... V02 : ROW 19 : ... .. F ... ... ... V02 : ROW 1A : ... .. F ... ... ... V02 : ROW 1B : ... .. F 214 ... ... V02 : ROW 1C : ... .. F ... ... ... V02 : ROW 1D : ... .. F ... ... ... V02 : ROW 1E : ... .. F 114 ... ... V02 : ROW 1F : ... .. F ... ... ... V02 : ROW 20 : --- .. F 100 100 ... V00 :
Note: Tout ceci se passe dans la RAM du script d'importation, il peut se permettre d'ajouter des colonnes à la volée, ce qui est bien pratique pour éviter les conflits.

Comme le moteur de Super Tilt Bro. ne gère qu'un pitch slide à la foi, il faut fusionner les colonnes qui en contiennent. C'est une simple addition, et voilà le résultat :

ROW 00 : C#3 .. F ... ... ... V02 ... : ROW 01 : ... .. F ... ... ... V02 ... : ROW 02 : ... .. F ... ... ... V02 ... : ROW 03 : ... .. F ... ... ... V02 ... : ROW 04 : ... .. F ... ... ... V02 211 : ROW 05 : ... .. F ... ... ... V02 ... : ROW 06 : ... .. F ... ... ... V02 117 : ROW 07 : ... .. F ... ... ... V02 ... : ROW 08 : ... .. F ... ... ... V02 ... : ROW 09 : ... .. F ... ... ... V02 211 : ROW 0A : ... .. F ... ... ... V02 ... : ROW 0B : ... .. F ... ... ... V02 ... : ROW 0C : ... .. F ... ... ... V02 117 : ROW 0D : ... .. F ... ... ... V02 ... : ROW 0E : ... .. F ... ... ... V02 ... : ROW 0F : ... .. F ... ... ... V02 211 : ROW 10 : ... .. F ... ... ... V02 ... : ROW 11 : ... .. F ... ... ... V02 ... : ROW 12 : ... .. F ... ... ... V02 117 : ROW 13 : ... .. F ... ... ... V02 ... : ROW 14 : ... .. F ... ... ... V02 ... : ROW 15 : ... .. F ... ... ... V02 211 : ROW 16 : ... .. F ... ... ... V02 ... : ROW 17 : ... .. F ... ... ... V02 ... : ROW 18 : ... .. F ... ... ... V02 117 : ROW 19 : ... .. F ... ... ... V02 ... : ROW 1A : ... .. F ... ... ... V02 ... : ROW 1B : ... .. F ... ... ... V02 211 : ROW 1C : ... .. F ... ... ... V02 ... : ROW 1D : ... .. F ... ... ... V02 ... : ROW 1E : ... .. F ... ... ... V02 117 : ROW 1F : ... .. F ... ... ... V02 ... : ROW 20 : --- .. F ... ... ... V00 100 :
Mine de rien, en quelques étapes, on est arrivé à un fichier Famitracker qui produit le même résultat et respecte les contraintes de notre moteur. Voici le résultat dans notre format interne :

SAMPLE 00 (2a03-pulse) # note frequency_adjust volume duty pitch_slide 00 : C#3 ... 15 2 ... 01 : ... ... ... ... ... 02 : ... ... ... ... ... 03 : ... ... ... ... ... 04 : ... ... ... ... 17 05 : ... ... ... ... ... 06 : ... ... ... ... -23 07 : ... ... ... ... ... 08 : ... ... ... ... ... 09 : ... ... ... ... 17 0A : ... ... ... ... ... 0B : ... ... ... ... ... 0C : ... ... ... ... -23 0D : ... ... ... ... ... 0E : ... ... ... ... ... 0F : ... ... ... ... 17 10 : ... ... ... ... ... 11 : ... ... ... ... ... 12 : ... ... ... ... -23 13 : ... ... ... ... ... 14 : ... ... ... ... ... 15 : ... ... ... ... 17 16 : ... ... ... ... ... 17 : ... ... ... ... ... 18 : ... ... ... ... -23 19 : ... ... ... ... ... 1A : ... ... ... ... ... 1B : ... ... ... ... 17 1C : ... ... ... ... ... 1D : ... ... ... ... ... 1E : ... ... ... ... -23 1F : ... ... ... ... ... 20 : --- ... ... 0 0
Pour enfin devenir le bytecode suivant:

sample0: joue la note C#3 volume 15 duty 2 attend 4 ticks active le pitch slide à 17 unités par tick attend 2 ticks active le pitch slide à -23 unités par tick attend 3 ticks ...
Bien sûr, avant de générer le bytecode, le script d'importation applique une passe de compression qui consiste à trouver le découpage optimal en samples pour limiter la taille du morceau.

Qu'est-ce que ça donne face aux autres ?

Il y a plein de moteurs plus ou moins fonctionnels. Nous retiendrons les trois poids lourds que sont Famitracker, Famitone et GGSound.

Famitracker est le moteur livré avec le tracker du même nom. Il a absolument toutes les fonctionnalités requises pour jouer ses musiques. Malgré ça, il est peu utilisé dans le cadre de jeux vidéo car particulièrement gourmand en ressources CPU.

Famitone, par Shiru, est le plus léger des trois. Manquant de fonctionnalités, on préfère l'utiliser sur les petits jeux, quand l'espace dans la ROM est plus précieux que le confort du compositeur.

GGSound, par Gradual Games, est un excellent entre-deux qui allie un certain confort d'utilisation à des performances sous contrôle.

Un petit tour rapide de l'implémentation de chacun. (A part Famitracker, désolé je n'ai pas pris le temps de regarder en détail.)
- Famitone comme GGSound implémentent les instruments, mais pas d'effet. Super Tilt Bro. ne gère pas d'instrument, mais les effets 1xx et 2xx.
- Le format de musique change beaucoup
- Famitone utilise un bytecode minimal: jouer une note, attendre, changer d'instrument et boucler le morceau.
- GGSound ajoute au bytecode du controle de flux: appeler une sous partie (comme nos samples), GOTO, ...
- Super Tilt Bro. a des opérations pour changer chaque registre de l'APU individuellement ou en groupe (puisque pas d'instrument)

plrP2cl.png
After the rain, un morceau de démo de Famitone, et son impact sur les différents drivers.
Note: le coût CPU est le pire tick rencontré dans le morceau.

Deux choses sautent aux yeux. Super Tilt Bro. est plus léger que les autres. Normal, il ne gère pas les instruments et son format contient directement les valeurs à écrire dans l'APU. Les musiques sont plus couteuses en ROM. Grâce aux instruments, la plupart des données pour les autres moteurs est une simple suite de notes. Super Tilt Bro. compense sa faiblesse avec de la compression, mais ce n'est pas encore suffisant pour rivaliser.

Qu'est-ce qu'il faut en penser ?

Que vous avez lu jusqu'ici, ce qui est super impressionnant. Le sujet n'est pas simple smile

Sinon, le moteur de Super Tilt Bro. est donc un moteur extrêmement léger, malgré ça il peut jouer à peu près n'importe quel morceau. C'est grâce à un gros travail d'adaptation qui est fait une bonne foi pour toute avant même de lancer le jeu. D'un autre côté, les musiques prennent plus de place en ROM. Dans le cas de Super Tilt Bro. ce n'est pas trop grave, économiser quelques cycles CPU est très important pour le netcode, alors qu'il reste plein d'espace inutilisé en ROM. Ceci-dit ça ne conviendra pas à tout le monde. Il n'y a pas de magie, juste de la triche et là, ça se voit.

Dans le futur on pourra certainement gagner un peu de place en ROM en optimisant le bytecode et aussi sortir ce moteur dans son propre projet s'il intéresse du monde. Enfin, ce pourquoi j'ai tenu à implémenter mon propre moteur de jeu: tout est possible. Mis de côté à cause du manque de temps, j'aimerais supporter la musique dynamique, c'est-à-dire une musique qui devient plus rythmée quand le jeu s'accélère et se calme quand les joueurs sont passifs. C'est une raison d'être des samples, en ayant plusieurs variantes de chaque sample, on pourrait choisir celui qui convient le mieux à la situation.

Le travail sur l'importation pourrait aussi servir à d'autres. GGSound pourrait profiter d'une compression semblable à celle par samples puisqu'il est capable de se comporter de façon similaire. Aussi, pour peu d'intégrer le pitch slide à Famitone ou GGSound, on pourrait gérer énormément d'effets dans ces moteurs en les simplifiant comme le fait l'importation de Super Tilt Bro. Le script d'importation est déjà capable de produire un fichier Famitracker simplifié.

Je me rends aussi compte que j'ai énormément répété le nom du jeu et ce parce que le moteur n'a pas de nom à lui. Quelqu'un à une idée de nom ?
avatar

90

Super article, super boulot, merci !
Tu n'as pas parlé de la gestion des effets sonores par dessus la musique ? C'est géré ?
Si oui, avec un autre format ? Un autre bout de driver ?
Et aussi super des puces d'extensions audio type VRC6 ou autre ?

Je propose Bidon Audio Driver comme nom grin
(en plus ça fait BAD...)
avatar