Desire « ONE » : le coding-of d’une BBS-intro sur Amiga

Enfin, la dernière ! Au terme de quelques années de retour dans le passé pour revisiter la programmation du hardware de l'Amiga, voici Desire "ONE", la dernière des productions d'une série dont l'idée était de rendre hommages aux grandes figures de ce que fut la scène. Après avoir successivement salué le mérite des graphistes dans Scoopex "TWO", des coders dans Scoopex "ONE", des crackers dans Scoopex "THREE", c'est donc sur une salutation aux sysops que le rideau tombe définitivement.
Desire "ONE" : Une BBS-intro sur Amiga
Sans doute, il resterait encore bien assez d'aspects du hardware à explorer pour trouver l'occasion de produire des hommages à bien d'autres figures : musiciens, swappers, suppliers, spreaders, editors et qui sais-je encore. Toutefois, il faut rester au diapason du progrès technique, et sans qu'il s'agisse de prétendre en devenir un spécialiste, mais juste savoir un peu de quoi on parle, cela impose actuellement de se former à de sujets denses et divers comme jamais : Cloud, Machine Learning, Ethical Hacking, Ray-Tracing, etc. Le temps étant compté - le développement n'est pas mon métier -, cette mise à l'heure m'apparaît prioritaire.
La production dont il sera question ici est une BBS-intro. Autrement dit, c'est une intro consacrée à la promotion d'un Bulletin Board System (BBS). Elle est le fruit d'une collaboration avec le groupe Desire, qui reste très actif sur plusieurs scènes, notamment la scène Amiga.
Comme toujours : code, data et explications dans tout ce qui suit...
Mise à jour du 15/09/2019 : La BBS-intro a été distribuée lors de la Function 2019 qui s'est tenue à Budapest, et l'archive contenant l'ADF ainsi que le source et les données est donc maintenant proposée au téléchargement.
A écouter en lisant l'article...
Cliquez ici pour télécharger l'archive contenant l'ADF, le code et les données de la BBS-intro.

Le coding-of

Une fois n'est pas coutume, commençons par la petite histoire. Il est vrai qu'elle permet d'éclairer les raisons pour lesquelles les explications techniques qui vont suivre, partie par partie, sont parfois succinctes.
Début 2019, le programme que je m'étais donné - produire une intro par rôle de la scène Amiga auquel rendre hommage : coder, graphiste, etc. - n'est pas achevé. Toutefois, j'ai encore quelques FX en stock, codés durant le très productif été 2017, quand je m'étais remis à coder sur Amiga pour écrire des articles destinés à la rubrique Vintage de Programmez!. Par ailleurs, je trouve qu'il était temps de passer à autre chose, ayant commencé à travailler sur d'autres sujets assez conséquents, dont le Machine Learning en Python.
Tout cela me pousse à packager au plus vite ces FX sous la forme d'une ultime production.
Cette fois, c'est vers le groupe Desire que je me retourne, plus particulièrement Ramon B5, qui m'a bien aidé en me permettant de changer de graphiste en catastrophe pour sortir Scoopex "TWO" dans les délais. En retour, je m'étais engagé à produire une BBS-intro pour le groupe.
Après avoir fait le tour des FX que je pourrais utiliser, je propose début juin le scénario suivant à Ramon B5, qui l'adopte - enfin, ce n'est pas comme si je lui laissais le choix, car je n'entends pas du tout me lancer dans de nouveaux FX... :
Hi Ramon
As told before, the intro is a tribute to sysops and you mentionned Fastline, which is (or was?) one of your BBS. So this may be a BBS intro for Fastline.
The first part is the signal made of particles, the second part is the wall of scrolls and the last part is the wired mesh.
We may use those FX to tell a little story about the way data exchange on wire evolved in time. First, there was nothing (text screen). Then a carrier to connect people (parrticles). So people can chat (wall of scrolls) on the BBS. Then come the Web (wired mesh), if ever Fastline moved to the Web. This is a little bit far fetched, but we do with whatever FX is available.
So we would need to tell the story with text screens between the FX. The texts must be short. They are:
1/ Opening, to tell that the carrier is going to be heard
2/ After the carrier, tell that people from all over Europe connected to the Fastline BBS and start talking(texts will be in several language, provided they use the A...Z characters)
3/ After people talked, tell that the BBS era is over and now is the WWW era
4/ Closing, tell people that they can connect to whatever Desire web site to keep talking
Je dois alors dire que c'est une bonne surprise, car Ramon B5 se montre particulièrement efficace pour dénicher un graphiste et un musicien. Sans doute, l'affaire traîne durant l'été, du fait que ces artistes sont en vacances. Toutefois, c'est ma très grande faute, car je n'ai pas voulu leur demander de produire quoi que ce soit tant que le code n'était pas terminé.
Au total, le coding-of s'est donc révélé nettement moins chaotique que celui que j'ai narré dans le cas de Scoopex "TWO". Ce qu'il convient de retenir, c'est que pour ce qui me concerne, à savoir le code, je me suis surtout contenté de packager, ce qui m'a permis de soigner assez les transitions entre les différentes parties de la BBS-intro. Ainsi, les particules disparaissent, les scrolls s'arrêtent, le maillage se dissout, les carrés se stabilisent progressivement, et tout cela est conjugué à des fondus de palette qui viennent en rajouter à l'impression que tout s'enchaîne calmement.

Le signal : un système de particules

Dans cette partie, un générateur de particules circule le long d'une courbe. A intervalles réguliers, il génère une particule qui s'éloigne dans une direction corrélée à celle du générateur. Tandis que le temps passe, la particule ralentit et s'assombrit, jusqu'à disparaître. D'ici là, si elle atteint un bord de l'écran, la particule rebondit dessus.
Le signal : une utilisation du système de particules
Le FX s'appuie sur le même système de particules que celui de Scoopex "ONE", et a donc déjà été amplement décrit. La seule différence est dans le calcul des coordonnées du vecteur directeur de la trajectoire d'une particule à sa génération.
Dans le cas précédent, ce vecteur directeur était une approximation de la tangente à la trajectoire, des plus grossières parce que réduite à la variation (DX, DY) des coordonnées du générateur entre la frame précédente et la frame courante. Dans le cas présent, ce vecteur est légèrement perturbé de manière pseudo-aléatoire en rajoutant des quantités aux valeurs absolues de DX et DY :
	btst #0,VHPOSR+1(a5)				;Randomize (|DX| += n * bit #1 of raster X position)
	beq _sngNewParticleNoRandomnessX
	addq.w #6,d0						;n set to 6
_sngNewParticleNoRandomnessX:
	btst #0,VHPOSR(a5)					;Randomize (|DY| += n * bit #0 of raster Y position)
	beq _sngNewParticleNoRandomnessY
	addq.w #2,d1						;n set to 2
_sngNewParticleNoRandomnessY:
Comme il est possible de le constater, l'aléatoire réside dans la position du raster au moment où le calcul est effectué. Ca ne va pas chercher loin, mais cela suffit pour parvenir au résultat escompté, à savoir que les particules forment une traîne ni trop prévisible ni trop imprévisible du générateur, une queue de comète pour ainsi dire. Pour cela, il était notamment indispensable que les trajectoires de particules générées à haute fréquence adoptent des vecteurs directeurs distincts.
Quant à la trajectoire du générateur, nul besoin était de la calculer en temps réel. Elle a été sagement précalculée dans Excel, sous la forme d'une composition de sinus et cosinus qui pourrait servir d'introduction à un cours sur la décomposition en série de Fourier :
Précalcul de la trajectoire de la particule dans Excel

Le BBS : un mur de scrolls au Blitter

Dans cette partie, l'écran est découpé en scrolls horizontaux et verticaux. Du texte y défile à différentes vitesses, affiché à l'aide de polices à largeur variable de différentes tailles.
Un mur de scrolls en polices à espace variable
Formalisé sous Excel, le découpage est le suivant - les numéros indiquent l'ordre dans lequel les scrolls sont démarrés :
Composition du mur de scrolls
Il ne s'agissait certainement pas de rentrer dans le détail des formats de police à largeur variable du système pour afficher le texte. A la place, des polices de ce type ont été converties de leur format vectoriel au format bitmap sur PC, à l'aide d'un outil certainement apprécié par tous ceux qui codent en 3D : Codehead's Bitmap Font Generator :
Codehead's Bitmap Font Generator
Toutefois, cet outil ne produit qu'un bitmap. Il fallait le travailler pour en extraire des informations nécessaires, notamment la largeur de chaque caractère et l'abscisse de son premier pixel utilisé, et plus généralement ordonner les pixels sous une forme plus facilement exploitable pour afficher un scroll. La transformation du bitmap a été effectuée à l'aide d'un outil codé par l'occasion en JavaScript :
Le convertisseur de police à largeur variable
Au final, un bitmap est converti dans un fichier .vfnt, dont le format est sur mesure :
0Nombre de caractères
1Code ASCII du premier caractère
2Largeur d'un caractère (N pour 2N pixels)
3Hauteur d'un caractère (N pour 2N pixels)
4Padding à droite
5Largeur de l'espacement entre deux caractères
Le padding à droite est un espace vide ménagé sur la droite du caractère pour que le suivant n'y colle pas immédiatement, quand bien même l'espacement entre les caractères est nul. A vrai dire, je ne me souviens même plus pourquoi j'avais prévu cela...
Suit une table d'informations relatives aux caractères. Pour chacun, on trouve simplement :
0Abscisse où le caractère commence dans son bitplane
1Abscisse où le caractère se termine dans son bitplane, plus le paddding à droite
Suivent les données proprement dites des caractères, c'est-à-dire les unes après les autres, les lignes du bitplane de chacun.
L'affichage d'un scroll ne présente aucune subtilité. A chaque étape, la partie qui doit rester visible est recopiée avec le Blitter en étant décalée dans le sens du défilé, et une nouvelle partie du dernier caractère et/ou du caractère suivant est affichée dans la partie libérée. La seule subtilité est que cette nouvelle partie est affichée différemment selon que le scroll est horizontal ou vertical :
  • Si le scroll est horizontal, le Blitter est utilisé pour tracer des droites dont le motif correspond à une colonne de 16 pixels d'un caractère - ce qui peut requérir plusieurs tracé de droite si la hauteur du caractère est de plus de 16 pixels. Cette technique a déjà été décrite ici, où elle sert à tracer les colonnes de 16 pixels des caractères d'un sine scroll. Pourquoi utiliser le Blitter ? Car cela permet de se dispenser d'opérations compliquées de masquage et de décalage pour positionner ou effacer des bits dans colonnes de bits traversant le même mot de lignes successives de l'écran.
  • A l'inverse, si le scroll est vertical, c'est le CPU qui est utilisé pour faire exactement la même chose. Pourquoi le CPU ? Car il n'est jamais ici question que d'écrire un ou plusieurs mots composant une colonne de pixels d'un caractère. Pas besoin d'opérations compliquées de masquage et de décalage, donc pas besoin de Blitter pour se simplifier la vie.
Pour optimiser tout cela, on voit qu'il était nécessaire que la hauteur d'un caractère soit toujours multiple de 16. C'est pourquoi le mur n'est composé que de scrolls de 16, 32 ou 64 pixels de hauteur quand il sont horizontaux, de largeur quand ils sont verticaux.

Le Web : un maillage 2D déformé par un attracteur

Dans cette partie, un maillage semble s'avancer et reculer périodiquement, tandis qu'il subit une déformation localisée, certins de ses points semblant attirés par un attracteur invisible en mouvement. De plus, le maillage est coloré, comme s'il disposait d'un relatif relief.
Un maillage déformé par un attracteur invisible
La mécanique de base est reprise d'un bref programme JavaScript présenté ici, que j'ai écrit il y a bien longtemps, quand je m'initiais à la programmation de vertex shaders pour WebGL.
Le maillage n'est donc pas en 3D, mais en 2D. Il est composé de MESH_DX sur MESH_DY points. A chaque frame, un point est déplacé le long du vecteur joignant l'attracteur à ce point, selon une amplitude qui dépend du rapport entre la "distance" du point à l'attracteur et la force qu'exerce ce dernier. La distance est évoquée entre guillemets, car on s'en remet à une approximation :
d = max (|dx|, |dy|) + (min (|dx|, |dy|) >> 2) + (min (|dx|, |dy|) >> 3)
Cette approximation est d'ailleurs sans doute déjà d'une précision superflue, car comme toujours dans un FX, seul le résultat compte. Autrement dit, la rigueur des calculs importe peu, il suffit qu'à l'écran, "ça passe" bien.
L'attracteur est en rotation sur un cercle centré à l'écran, de rayon MSH_RADIUS. Sa force varie selon une interpolation linéaire entre un minimum MSH_STRENGTH_START et un maximum MSH_STRENGTH_END en MSH_STRENGTH_STEPS étapes, le minimum et le maximum étant inversés quand la force atteint une de ces bornes.
Ainsi, la force que l'attracteur exerce sur un point varie indépendamment de ce point - elle oscille entre entre deux bornes -, et elle varie aussi en fonction de ce dernier - elle décroît tandis que la distance du point à l'attracteur s'accroît. Comme toujours, c'est donc la conjugaison de transformations élémentaires qui permet de produire une transformation en apparence complexe, puisque le maillage semble comme en 3D.
Le seul point un tant soi peu délicat est le clipping. En effet, la déformation du maillage peut faire sortir de points de la surface de l'écran. Une solution aurait consisté à se donner de la marge à l'aide d'une surface de rendu plus grande que celle affichée. Il aurait ainsi été possible de tracer des droites débordant de l'écran, dans une certaine limite, sans se préoccuper d'avoir à les clipper. Pour le fun, c'est une solution moins confortable qui a été adoptée, qui consiste donc à clipper.
Le clipping d'une droite n'est pas une opération compliquée - ce n'est pas comme clipper un polygone. La première étape consiste à tirer les extrémités de la droite à clipper, pour commencer toujours par le point le plus haut. S'ensuit un clipping vertical, qui s'appuie tout simplement sur le théorème de Thalès. Les points sont alors triés pour commencer toujours par le plus à gauche, avant de procéder au clipping horizontal. Trier les points permet de limiter les opérations visant à tester le signe.
Enfin, la coloration du maillage est très simplement produite par rémanence. A chaque frame, le bitplane 2 devient le 3, le 3 devient le 4, et le 4 est retiré de la vue pour servir de backbuffer à la frame suivante. Quant au bitplane 2, il est remplacé par le backbuffer de la frame courante, c'est-à-dire le bitplane qui n'est pas affiché, où la nouvelle image de l'animation du maillage vient d'être rendue. Dans ces conditions, les bits utilisés dans les bitplanes se superposent d'autant plus que le maillage a peu bougé d'une image à une autre. Cela de mettre plus ou moins en relation l'amplitude du mouvement avec des couleurs via une palette dont les couleurs tirent sur le violet là où le maillage bouge peu, et sur le bleu là où il bouge beaucoup.
Au terme de la partie, le maillage disparaît progressivement. Toutes les droites étant tracées au Blitter - abusivement d'ailleurs, car il ne faudrait recourir au coprocesseur que si cela en vaut la peine -, l'effet est simple à produire. En effet, comme dans le cas de l'affichage de scrolls verticaux décrit plus tôt, il s'agit de jouer sur la valeur de BLTBDAT, qui correspond au motif que le Blitter répète lorsqu'il trace une droite. Partant donc d'une valeur de $FFFFcorrespondant à un trait plein, il s'agit d'effacer progressivement des bits de cette valeur pour effacer progressivement la droite. Quant au choix du nouveau bit à effacer à chaque étape, il doit être dicté par l'esthétique. Ici, l'idée est de répartir au mieux les bits effacés le long de la droite :
Animation du motif d'une ligne du maillage

Consummatum est...

Et voilà ! Comme d'habitude, il convient de conclure par les inévitables greetings. Je remercie donc tout particulièrement :
Et je salue ceux qui sont allés jusqu'à présenter la BBS-intro à la Function 2019, sur du vrai hardware :
Desire "ONE" à la Function 2019
Comme indiqué au début de cet article, cette fois, c'est vraiment la dernière. WinUAE ne demeurera sur mon PC que pour me permettre de voyager dans le passé l'espace d'un instant, et non plus pour coder quoi que ce soit.
Rétrospectivement, je trouve assez amusant d'avoir sans doute plus produit sur celle qui restera ma machine préférée l'espace de ces quelques années - de l'été 2017 au printemps 2019, mais surtout en 2018 - que durant la décennie 90. Mais il faut dire qu'à l'époque, je découvrais la programmation, sans bénéficier par ailleurs de la possibilité de pouvoir créer aussi facilement des outils pour m'aider que celle qu'un navigateur Web peut offrir. Et finalement, je n'ai passé guère plus de temps à sortir des cracktros, si j'en juge d'après les dates rapportées sur Janeway. Toute de même, je ne faisais alors presque que cela, ce qui n'a certainement pas été le cas en cette fin de décennie, comme chacun peut en juger par la variété des sujets abordés sur ce blog...
Il faut mesurer la chance que le Web peut offrir de remonter ainsi dans le temps pour se replacer dans le contexte d'une époque dont des passionnés s'échinent à préserver ce qui en fit pour eux le principal attrait. Ils ne luttent pas contre le flot général du temps qui s'écoule inexorablement, emportant tout vers l'oubli - qui le pourrait ? Au prix d'efforts considérables, ils ont creusé un canal pour en divertir ce qu'il pouvait charrier à leurs yeux d'important, qui débouche sur un bassin de rétention où tout est préservé, véritable fontaine de Jouvence pour celui qui, comme eux, a connu l'époque, s'il se décide à y tremper le pied.
Le défi n'est plus que de passer la main. Mais qui, aujourd'hui, parmi ceux qui ne l'ont pas connu alors, s'intéresse à l'Amiga ? Je doute qu'ils soient bien nombreux. Tout de même, puisqu'après tout, il ne suffit jamais que d'un seul pour sauver une ville, la tâche doit être de susciter des vocations en répandant la bonne parole. Si Scoopex "ONE", Scoopex "TWO", Scoopex "THREE" et Desire "ONE", ainsi que tous les articles publiés sur ce blog et dans Programmez! peuvent un jour y aider, mon heure de gloire sera celle-là 🙂
Desire « ONE » : le coding-of d’une BBS-intro sur Amiga