Scoopex « TWO » : Le coding-of d’une cracktro sur Amiga 500

Et voilà ! On promet que c'est le dernier pour la route, et on s'en remet un malgré tout. Difficile de décrocher de la programmation en assembleur du hardware vidéo de l'Amiga 500, tant que la connaissance de ce dernier semble à parfaire, qu'il se trouve des FX à créer ou, plus modestement, à reproduire.
Un quart de siècle après la "dernière", trinquons donc encore ensemble avec Scoopex "TWO", cracktro codée en 2018 en guise d'appetizer pour le portage du jeu Starquake par Galahad du glorieux groupe Scoopex.
Scoopex "TWO" : Une cracktro pour A500 en 2018
Au menu : BOBs qui bougent, printer qui affiche, et rotozoom qui tourne. Et comme toujours, quelques leçons de portée plus générale que le sujet initial ne semblerait offrir l'opportunité d'en dispenser.
Mise à jour du 22/11/2018 : Optimisation du code JavaScript du rotozoom pour le mettre à disposition de l'auteur du portage de la cractro en HTML5 sur Flashtro.
Mise à jour du 19/11/2018 : Ajout d'un encadré (un peu long, mais me semble-t-il intéressant) sur l'enjeu pour l'authenticité que soulève l'émulation d'un jeu.
Mise à jour du 18/11/2018 : Cliquez ici pour télécharger copperScreen.s, un programme qui permet de générer un écran au Copper de n'importe quelle hauteur à n'importe quelle position verticale.
Mise à jour du 17/11/2018 : En complément, une vidéo de la cracktro est disponible sur YouTube. Par ailleurs Galahad raconte l'intéressant making-of du portage du jeu dans un fil de l'English Amiga Bord.
Cliquez ici pour télécharger l'archive contenant le code et les données de la cracktro.
Cette archive contient les fichiers suivants :
scoopexTWOv4.adfL'image d'une disquette autoboot pour tester la cracktro
scoopexTWO.sle source de la cracktro
common/ptplayerLe source du player de module
common/advancedPrinter.sLe source du printer
common/bob.sLe source de l'affichage de BOBs
common/debug.sLe source de routines de débogage
common/fade.sLe source du fondu
common/interpolate.sLe source de l'interpolateur linéraire
common/registers.sLa définition des constantes des registres hardware
common/wait.sLe source de l'attente du raster
data/alienKingTut320x256x5.rawbL'image en RAWB
data/scoopexTWOLogo320x64x4.rawbLe logo en RAWB
data/fontBevelled8x8x1.rawLa police en RAW
data/salah's_fists.modLe module
tools/advancedPrinter.htmlUn outil pour tester l'algorithme du printer
tools/bitmapConverter.htmlUn outil pour convertir un image en RAW et RAWB
tools/scoopexTWO.xlsmUn outil pour convertir la description d'un puzzle en données binaires
tools/styles.cssUne feuille de styles CSS utilisés par les outils précédents
tools/alienKingTut320x256x5.gifL'image en GIF
tools/fontBevelled8x8x1.pngLa police en PNG
tools/scoopexTWOLogo320x64x4.gifL'image en GIF
NB : Cet article se lit mieux en écoutant l'excellent module Sound Traveller composé par Supernao / Virtual Dreams pour R.A.W. #8, mais c'est affaire de goût personnel...

Le puzzle

Le premier FX de la cracktro est donc l'assemblage progressif d'un image 320 x 256 en 5 bitplanes à partir d'éléments carrés. Un carré peut avoir un côté variable multiple de 16 - 16, 32 et 64 pixels. Il rentre dans l'écran par un des quatre bords depuis l'extérieur. Il se déplace horizontalement - s'il vient du bord gauche ou droit - ou verticalement - s'il vient du bord haut ou bas - à une vitesse propre. Il s'arrête quand il a atteint la position à laquelle il a été découpé dans l'image.
Ici, il fallait relever plusieurs "défis" :
  • déplacer le maximum de carrés simultanément en une trame (ie: 1/50ème de seconde) ;
  • faire en sorte que les carrés semblent toujours venir de tous bords.
La solution au premier problème passe définitivement par l'exploitation des BOBs. En effet :
  • Les sprites sont impropres à l'usage. Tout d'abord, comme expliqué ici, ils sont comme les hobbits, c'est-à-dire pratiques et colorés, mais petits et peu nombreux. Ensuite et surtout, l'image à composer comporte 5 bitplanes alors que les sprites peuvent au mieux être combinés pour composer un objet en 4 bitplanes.
  • Le CPU ne vaut pas mieux. Tout d'abord, sa puissance est trop limitée pour envisager de le préférer au Blitter dès lors qu'il sagit d'afficher sur 5 bitplanes. Par ailleurs, quand bien même il est possible de combiner CPU et Blitter, il serait horriblement compliqué de paralleliser l'affichage sachant qu'il ne peut l'être sans danger que si le carré affiché au CPU et celui affiché au Blitter ne se recouvrent pas.
Les carrés sont donc des BOBs, c'est-à-dire des blocs de données copiés au Blitter, longuement présentés ici. Les routines utilisées sont _bobDrawBOB et _bobClearBOBFast, factorisées dans bob.s. Ces routines sont commentées avec un luxe de détail, au point qu'il y a bientôt plus de lignes de commentaires que de code dans ce fichier. Si vous n'y comprenez rien après vous être reporté à l'article d'abord, à ce fichier ensuite, désolé, mais la programmation du hardware de l'Amiga n'est peut-être pas pour vous. Envisagez la poterie (Atari STF) ou le macramé (Atari STE) ?
Il n'y a pas plus à dire sur l'affichage des BOBs. Précisons seulement qu'un BOB n'est pas clippé, ce qui signifie que même qu'il est visible partiellement parce qu'il rentre de l'extérieur par un bord dans l'image, il est intégralement dessiné dans les bitplanes. Ces derniers sont donc beaucoup plus larges que l'image affichée : ils sont entourés d'un bord dont l'épaisseur est équivalente au côté maximal d'un BOB.
Certes, clipper un BOB permet d'en accélérer l'affichage - c'est l'intérêt même de tout clipping. Toutefois, il est toujours risqué dans une cracktro de recourir à un FX dont le temps de calcul est variable : on ne sait jamais quand le pic de charge va se présenter, ce qui multiplie les risques de louper une trame - ne pas faire tenir l'exécution de la boucle principale dans la trame. Par ailleurs, clipper impose de programmer le clipping, ce qui est une perte de temps dans le cas présent où le puzzle ne constitue pas le principal FX - on n'est pas en train de balader le spectateur dans un monde en 3D durant des heures.
A l'inverse, il faut bien constater que ne pas clipper consomme des ressources, en premier lieu de la mémoire. 5 bitplanes en 320 x 256 occupent 51 200 octets, ce qui est déjà conséquent. Si l'on souhaite afficher des BOBs dont le côté peut atteindre 64 pixels, ce sont des bitplanes de 448 x 384 qu'il faut prévoir, ce qui représente 107 520 octets. Cet espace doit être doublé, car il est indispensable de travailler en double buffer pour éviter tout flickering, comme expliqué ici. En fait, il faut même le tripler, car il faut encore rajouter un buffer correspondant à l'image finale en cours de composition, c'est-à-dire une image où un BOB est copié une fois pour toutes quand il parvient à destination - cette image est copiée dans le back buffer pour l'effacer à chaque itération. Enfin, il faut rajouter l'espace occupé par l'image initiale en 320 x 256, où les BOBs sont découpés. Au total : 373 760 octets. Mazette ! Comme on le verra, cela n'a pas été sans nécessiter un downsizing...
A l'inverse de leur affichage, l'apparition des BOBs appelle quelques commentaires. Pour éviter que les BOBs ne semblent trop provenir d'un certain côté, il faut s'assurer qu'ils seront initialement distribués assez équitablement entre toutes les positions de départ qu'ils peuvent occuper le long des différents bords de l'écran. Cette réflexion a d'abord conduit à élaborer un algorithme assurant une telle distribution, en cherchant par ailleurs à ce que les BOBs d'une colonne attribués à un bord horizontal soient bien répartis le long de cette colonne - de même pour les BOBs d'une ligne attribués à un bord vertical :
L'outil JavaScript de composition du puzzle
Il n'est pas utile de rentrer dans les détails de cet algorithme, car... il n'a pas servi. Lesson number one, comme dirait le Mandarin : dans la vie, il y a toujours moins bien, mais c'est plus cher. L'idée de générer une distribution des BOBs à chaque démarrage de la cracktro est sympathique, mais elle présente deux inconvénients. Tout d'abord elle contraint de programmer cette génération, ce qui est ennuyeux à mourir pour le codeur. Ensuite et surtout, le puzzle ne peut être composé que de BOBs d'un même côté, ce qui est ennuyeux à mourir pour le spectateur.
Pour cette raison, c'est une autre solution qui a été retenue. Elle consiste à perdre un peu de temps à composer manuellement le puzzle à base de BOBs de différents côtés, avant d'exporter la composition sous forme de données. Il n'y a qu'un puzzle, mais il est assurément composé de manière plus passionnante à contempler pour le spectateur.
Une macro dans Excel a été réalisée pour faciliter ce travail. Elle analyse un ensemble de formes rajoutées à la main en s'aidant la possibilité d'aligner les formes sur la grille d'une feuille transformée en quadrillage. De chaque forme, la macro déduit le côté du BOB correspondant à partir des dimensions, et le bord de l'image par lequel ce BOB doit faire son entrée à partir de la couleur (rouge foncé pour une entrée par le bord gauche, vert clair pour une entrée par le haut, etc.). Pour finir, elle associe au BOB un vecteur vitesse déduit du bord et lui affecte une norme aléatoire, comprise entre deux bornes. Que demander de plus ? C'est la leçon nummer zwei : ce n'est pas parce qu'on doit vider la mer à la petite cuillère qu'il faut ignorer la pelleteuse à côté.
L'outil Excel de composition du puzzle
Les données d'un BOB générées par la macro se présentent ainsi :
	DC.W 16, 16, 64, 0, 352, 32, 352, 32, 96, 32, -7, 0, 0
Il s'agit d'une structure qui se lit de la manière suivante :
0Largeur (multiple de 16)
2Hauteur
4Coordonnées dans l'image où le BOB est découpé (x, y)
8Coordonnées dans le front buffer (x, y)
12Coordonnées dans le back buffer (x, y)
16Coordonnées d'arrivée (x, y)
20Vitesse (x, y)
24Etat
Un BOB peut prendre les états suivants :
PZ_STATE_TODISPLAYLe BOB doit être simplement affiché dans le back buffer
PZ_STATE_TOMOVELe BOB doit être effacé du back buffer, déplacé, et de nouveau affiché dans le back buffer
PZ_STATE_TOREMOVELe BOB étant arrivé à destination, il doit être copié dans l'image finale
PZ_STATE_TOIGNORELe BOB ne doit pas être pris en compte durant cette boucle principale
L'existence d'un état PZ_STATE_TODISPLAY, qui est l'état initial de tout BOB, se justifie car il faut bien afficher une première fois un BOB avant d'entreprendre de le déplacer puis l'afficher en boucle.
La boucle principale parcourt la liste des BOBs et commande l'affichage des BOBs dans la limite du nombre maximum de BOBs PZ_NB_DISPLAYEDBOBS qui peuvent être déplacés simultanément. Sur Amiga 500, ce maximum est fixé à 12, le côté d'un BOB pouvant être 16 ou 32 pixels. C'est ce qui est ressorti d'un test de la composition retenue.
Il aurait été possible d'utiliser des BOBs plus gros. Toutefois, les BOBs de 64 pixels de côté ne produisent pas toujours le meilleur effet. En effet, pour le spectateur, ils semblent étrangement ne pas se déplacer dans la trame, alors qu'ils se déplacent pourtant dans la trame ! Vous pouvez le tester en assemblant la version prévue pour A1200 - la version A500 n'utilise pas des BOBs aussi gros. Pour cela, passez la constante A500 à 0, et la constante DEBUG à 1 :
Test du puzzle sur A1200 pour vérifier que tout tient dans la trame

Le rotozoom

Le rotozoom présente la particularité d'exploiter intensivement le Copper. En effet, l'image en rotation est produite par une série de MOVE modifiant la couleur 0 tous les 8 pixels LowRes d'une ligne de l'écran. Répétée à chaque ligne sur 8 lignes, la manoeuvre permet de produire au total une image dont la résolution est de 40 x 32 "pixels" - des carrés de 8 x 8 pixels.
A la base, le rotozoom utilisait la couleur 1 et non la couleur 0 - le retour à la couleur 0 est narré dans la partie sur le coding-of. L'idée n'était pas tant d'éviter de saloper les bords de l'écran qui sont affichés en couleur 0, que de tramer le bitplane 1 pour limiter l'effet "gros pixel". Toutefois le résultat a semblé trop sombre. Par ailleurs, il aurait fallu se fatiguer à tenir compte de l'impact du tramage sur la définition de la palette afin que le logo et le texte n'en souffrent pas. A titre d'information, voici ce que cela aurait donné :
Tramage de l'image au Copper
Mais pourquoi ne pas avoir plutôt cherché à décaler une ligne sur deux de 4 pixels LowRes en jouant sur la position horizontale du WAIT en début de ligne, demandera le lecteur attentif ?
Cher lecteur, je note que tu as bien lu cet article, qui explique comment produire une telle image au Copper. Toutefois, tu oublies de noter que cette technique y est présentée avant d'évoquer celle qui exploite les boucles au Copper. Or une boucle au Copper sur 8 lignes ne permet que de répéter strictement la même séquence de 40 MOVE, interdisant donc la manoeuvre. Et l'écran que tu peux contempler dans la cracktro est intégralement produit à l'aide de boucles au Copper...
Avec astuce, le même lecteur fera remarquer que l'article cité à l'instant se terminait sur un cliffhanger suscitant une attente d'autant plus angoissante que la seconde partie de l'article n'a jamais été publiée. N'était-il pas question d'une surprise, qu'on pouvait augurer mauvaise ? De fait, cher lecteur, l'utilisation de boucles au Copper pour répéter les 40 MOVE produit cet effet là :
Glitches pour produire un écran de 40 x 32 avec des boucles au Copper
"Ce n'est pas sale", comme disait l'autre, mais ce n'est pas beau, rajouterons-nous. Sans spoiler la seconde partie de l'article qui sortira bien un jour - c'est un sujet encore plus pénible à expliquer que le zoom horizontal hardware traité ici... -, apprends donc, cher lecteur, qu'il existe une solution, et qu'elle est justement exploitée dans la présente cracktro pour afficher l'écran au Copper utilisé par ce rotozoom. On en dira pas plus, mais il est facile de deviner à la lecture des séries d'instructions WAIT en rzWAITs et SKIP en rzSKIPs qu'il faut découper les boucles qui comprendraient autrement des lignes litigieuses.
Réflexion faite, mieux vaut prévenir que guérir. Je ne vais pas prendre le risque de ne jamais écrire la seconde partie de l'article, et que j'enterre un jour mon disque dur - ou qu'on l'enterre avec moi - alors qu'il contient du code qui pourrait servir à d'autres pour faire vivre la scène Amiga. Cliquez ici pour télécharger copperScreen.s, un programme qui permet de générer un écran au Copper de n'importe quelle hauteur à n'importe quelle position verticale.
Utiliser des boucles au Copper permet de limiter le temps requis pour changer l'image en modifiant les valeurs que les MOVE écrivent dans le registre COLOR01. En effet, il suffit de modifier une série de 40 MOVE pour modifier l'apparence de 8 lignes à l'écran, soit une ligne de "pixels", au lieu d'en modifier 8. C'est tout simplement ce qui permet de réaliser un rotozoom en plein écran qui tient dans la trame.
Le petit bonus est que l'image au Copper est true color, au sens où elle n'a pas de palette, chaque "pixel" prenant sa propre couleur. Toutefois, cette possibilité n'est pas exploitée dans la cracktro, car l'idée était de montrer la belle image avant de la rotozoomer. Or pour montrer l'image, il fallait bien qu'elle puisse être affichée comme une image, donc en 32 couleurs seulement.
L'affichage au Copper mis à part, le rotozoom est en lui-même assez simple. Comme chaque fois qu'il s'agit de déformer une image, l'erreur à ne pas commettre est de partir de l'image pour aller vers l'écran - calculer les coordonnées du pixel à l'écran à partir de celles d'un pixel de l'image -, alors qu'il faut faire l'inverse. En l'espèce, pour que l'image tourne et zoome dans l'écran, il faut donc faire tourner et zoomer l'écran dans l'image.
Ainsi, les coordonnées des sommets d'une représentation de l'écran dans l'image sont calculées par rotation et zoom, puis chaque ligne de l'écran est balayée simultanément à l'écran - la série de 40 MOVE qui composent la ligne de "pixels" - et dans cette représentation. A chaque "pixel", un pixel de l'image se trouve ainsi associé, dont la couleur est reprise par le MOVE duquel résulte le "pixel".
Décomposition du rotozoom
Exception faite des multiplications requises pour calculer les images de trois points par rotation, les calculs n'impliquent que des additions et des soustractions pour gagner du temps. Cela est rendu possible par le recours généralisé à l'algorithme de Lucas, qui permet très simplement de faire évoluer une quantité en fonction d'une autre dans un certain rapport, comme c'est le cas de l'ordonnée en fonction de l'abscisse lors du tracé d'une droite d'une certaine pente.
Ainsi, pour calculer dxM1 et dyM1 en fonction de dxM0 et dyM0, l'algorithme de Lucas est utilisé pour faire évoluer des valeurs dans les rapports entre :
  • screenDX et top ;
  • topDX et topDY ;
  • screenDY et left ;
  • leftDX et leftDY.
Attention, car l'algorithme ne s'utilise pas de la même manière pour tracer une droite entre (x0, y0) et (x1, y1) que pour interpoler une valeur entre [v0, v1] en n étapes. Cliquez ici pour tester et visualiser le source du tracé de ligne et de l'interpolation en JavaScript.
Plutôt que de remplir des pages dans le cadre de cet article, il est apparu plus simple de porter l'algorithme du rotozoom en JavaScript, aux précalculs près. Cliquez ici pour tester et visualiser le source pour comprendre. Encore une fois, il s'agit d'un portage de l'algorithme. Il est clair que s'il fallait réaliser le FX en JavaScript, c'est un algorithme bien différent qui serait mobilisé pour atteindre un certain niveau de performance - voire, se contenter d'appeler une fonction de l'API Canvas 2D ou de WebGL.
Portage de l'algorithme du rotozoom en JavaScript
Grosso modo, comme le schéma plus tôt le laisse entendre, l'écran au Copper est parcouru de haut en bas et de gauche à droite. Cette progression est effectuée simultanément dans le rectangle qui corespond à l'image de cet écran dans l'image à représenter. Ainsi, il est possible d'associer un "pixel" de l'écran au Copper à un pixel de l'image.

Le printer

Le printer n'a l'air de rien, mais il est capable. En fait, c'est le FX de la cracktro dont la programmation a pris le plus de temps. Comme quoi, leçon numero tre : le diable se loge toujours dans les détails.
Ici encore, pour ne pas encombrer cet article, cliquez ici pour accéder à un portage de l'algorithme en JavaScript, et ainsi tester et visualiser le source pour comprendre :
Elaboration de l'algorithme du printer en JavaScript
Contrairement au portage du rotozoom, ce portage n'en est pas vraiment un. La complexité de l'algorithme a imposé la démarche inverse, à savoir implémenter d'abord en JavaScript, puis porter en assembleur 68000 :
Portage de l'algorithme du printer en assembleur 68000
D'où la leçon numero cuatro : pour produire un pestacle, il faut bien autant trimer dans les coulisses que sur scène.
Ce qui fait la complexité de l'algorithme, c'est la multiplicité des caractères spéciaux (espace, fin de ligne, fin de page) et des délais (entre deux caractères, entre deux pages), ainsi que l'articulation du printer de pages avec un printer de caractères qui peut varier. En effet, le printer présente l'intérêt de pouvoir être facilement paramétré pour utiliser une routine particulière d'animation de l'affichage d'un caractère. Si vous jetez un oeil dans advancedPrinter.s, vous constaterez que pas moins de sept printers de caractères sont disponibles : Basic, Roller, Raiser, Animator, Shifter, Interwiner et Assembler.
La programation du printer a donné l'occasion de peaufiner la manière de factoriser du code pour le réutiliser facilement, non sans faire dériver la pratique vers l'utilisation de structures de données plutôt que des registres pour passer des paramètres et préserver l'état - bref, le code tire vers le C compilé. Ainsi, pour utiliser le printer comme c'est le cas dans le programme principal de la cracktro, il suffit de commencer par inclure le fichier source :
	INCLUDE "SOURCES:common/advancedPrinter.s"
Ensuite, il faut définir une constante qui va permettre de limiter l'assemblage au seul code du printer utilisé, c'est-à-dire au code du printer de pages dans tous les cas, et au printer de caractères spécfiquement désigné dans le cas particulier. Cette solution de l'assemblage partiel a été préférée à celle de l'assemblage total pour limiter l'espace mémoire occupé par le code du printer au seul code réellement utilisé :
PRT_PRINTER=5
Le printer peut alors être démarré en commençant par appeler une routine d'initialisation. Cette dernière prend un bon nombre de paramètres pour que le printer puisse fonctionner dans des configurations vidéos assez variées. La définition systématique de constantes OFFSET_PRINTERSETUP_* permet de mieux s'y repérer :
	lea prtSetupData,a0
	move.l rzFrontBuffer,d0
	addi.l #((RZ_TEXTBITPLANE-1)+RZ_TEXTY*RZ_DISPLAY_DEPTH)*(DISPLAY_DX>>3),d0
	move.l d0,OFFSET_PRINTERSETUP_BITPLANE(a0)
	move.w #DISPLAY_DX>>3,OFFSET_PRINTERSETUP_BITPLANEWIDTH(a0)
	move.w #(RZ_DISPLAY_DEPTH-1)*(DISPLAY_DX>>3),OFFSET_PRINTERSETUP_BITPLANEMODULO(a0)
	move.w #RZ_TEXTDY,OFFSET_PRINTERSETUP_BITPLANEHEIGHT(a0)
	move.b #RZ_TEXTCHARDELAY,OFFSET_PRINTERSETUP_CHARDELAY(a0)
	move.w #RZ_TEXTPAGEDELAY,OFFSET_PRINTERSETUP_PAGEDELAY(a0)
	move.l #font,OFFSET_PRINTERSETUP_FONT(a0)
	move.l #printerText,OFFSET_PRINTERSETUP_TEXT(a0)
	bsr _prtSetup
Dans la boucle principale, il suffit alors d'appeler une routine par itération :
	bsr _prtStep
De même à la fin de la boucle principale, pour libérer les ressources accaparées par le printer :
	bsr _prtEnd
Difficile de faire plus simple. Noter que plutôt qu'à la volée, la structure de données utilisée pour l'initialisation peut être peuplée en grande partie dans une déclaration de données pour limiter plus encore le nombre d'instructions relatives au printer dans le programme qui l'utilise.

Le programme principal

Exception faite de l'initialisation - allocations en mémoire et prise contrôle du hardware - et de la finalisation - libération de la mémoire allouée et restauration du système -, le programme principal contient l'enchaînement des boucles correspondant aux FX successifs de la cracktro : puzzle, fondu, rotozoom.
La difficulté dans une production qui enchaîne les FX, c'est que tous les FX ne peuvent généralement pas être initialisés en une fois, et leurs exécutions enchaînées simplement. Il faut programmer des transitions pour finaliser le FX qui vient de s'achever - libérer de la mémoire - et initialiser celui qui va démarrer - allouer de la mémoire, modifier la configuration du hardware.
A la base, une transition est particulièrement pénible à programmer, car c'est une forme d'initialisation qui se déroule en live avec des ressources limitées. En effet, le principe d'un FX n'est-il pas d'aller aux limites, en mobilisant toutes les ressources disponibles ? Partant, pour passer d'un FX à un autre, le codeur a toutes les chances de se retrouver à sec : plus de mémoire, plus de cycles CPU. En conséquence, il lui faut prévoir une baisse de régime du premier FX et une montée en régime du suivant. Autrement dit, il ne suffit pas d'avoir programmé un FX qui tourne à plein régime ; il faut encore l'avoir paramétré pour tenir un régime quelconque.
Dans ces conditons, programmer une transition est encore plus pénible quand les FX n'ont pas été conçus à la base pour s'enchaîner. Non seulement il faut rentrer de nouveau dans le code des FX pour le rendre paramétrable, mais il faut aussi réécrire les initialisations et les finalisations en faisant la glue entre elles. C'est la leçon nummer vijf : toujours concevoir du puzzle à la pièce, ou alors prévoir que la pièce devra se loger dans le puzzle dès sa conception.
En l'espèce, une transition est nécessaire pour libérer de la mémoire, car il serait impossible d'allouer en un fois toute celle que le puzzle et le rotozoom requièrent, et enchaîner des Copper lists très différentes. En cette dernière matière, pour éviter que ce soit trop brutal, le mieux était de rajouter un fondu au blanc de la palette de l'image assemblée au terme du puzzle, avant d'afficher le rotozoom. Sans doute, un fondu inverse - du blanc à la palette du rotozoom - aurait été ensuite bienvenu, mais comme déjà expliqué, le rotozoom n'a pas de palette, chaque "pixel" étant affiché en true color ; c'eût donc été une plaie que d'élaborer un tel fondu.

Le coding-of

La genèse de cette cracktro remonte à loin. Après avoir renoué avec les joies de la programmation du hardware de l'Amiga en assembleur durant l'été 2017 dans le cadre de la publication d'articles portant sur mes cracktros pour Programmez! - en ligne sur ce site ici et -, je décide de me relancer dans la programmation de FX divers et variés, dont le rotozoom. C'était un des derniers FX auxquels je m'étais essayé avant de laisser tomber l'Amiga vers la fin des années 1990. Manière de renouer avec le fil, donc.
A la base, mon idée est de produire une démo. Toutefois, il m'apparaît rapidement que le travail que cela nécessiterait sur les transitions serait considérable - ces dernières auraient d'autant plus à être soignées que les FX n'iraient pas chercher bien loin. C'est donc finalement vers la production d'une cracktro que je m'oriente. L'occasion m'en est donnée au fil d'un échange par Galahad, qui me propose d'en réaliser une pour le portage sur Amiga du jeu Starquake qu'il était en train de finaliser. C'est parti.
Pour produire une cracktro, il faut au moins un codeur et un musicien, et un graphiste peut aider. Par le passé, je n'ai eu recours aux services d'un graphiste qu'une seule fois, pour produire cette cracktro pour Skid Row.
Pour le musicien, je n'ai pas à chercher bien loin. En effet, Monty n'est pas disponible, mais Galahad missionne Notorious / Scoopex qui livre rapidement un module.
Pour le graphiste, les choses s'avèrent nettement plus compliquées. En cherchant à recontacter Monty, j'ai trouvé qu'il a cofondé le remarquable site Web Amiga Music Preservation (AMP pour les intimes) avec Crown / Cryptoburners et Asle. Les deux derniers me permettent d'entrer en contact Alien / Paradox, qui me produit un magnifique logo.
Je me lance donc dans la programmation d'une cracktro qui exploite un petit système de particules. Très rapidement, il s'avère que les particules ne peuvent être démultipliées pour produire un FX intéressant que sur un A1200. C'est donc une cracktro AGA que je livre à Galahad. Elle n'est pas sortie à cette date - de toute manière, il faudra que je la retravaille du fait de la "découverte" qui sera évoquée plus loin -, mais voici un aperçu :
Preview de Scoopex "ONE" (version pas "cycle-exact")
Galahad accuse réception, mais précise qu'il n'a pas de jeu AGA qui doive sortir bientôt. Il la garde donc sous le coude et me précise que Starquake étant destiné à un A500, je peux toujours en réaliser une autre pour OCS. Qu'à cela ne tienne, c'est donc reparti.
Je repars en quête d'un musicien et d'un graphiste. Pour le premier, Crown et Asle me mettent en contact avec leur acolyte Curt Cool / Depth. Pour le graphiste, n'arrivant plus à joindre Alien, je me rabats sur la seule solution qui me reste, un appel au peuple sur le site Wanted de la scène. C'est un coup d'épée dans l'eau. C'est alors que Notorious me met en rapport avec Sim1 / Wanted Team.
Après avoir exposé à Sim1 l'idée que cette cracktro serait un hommage aux graphistes - la première se voulait un hommage aux coders -, ce dernier a l'idée de reprendre le graphisme de Deluxe Paint II, à savoir la fameuse bouille de Toutânkhamon dessinée par Avril Harrison. Excellente idée, lui dis-je, et il se met au dessin tandis que je me mets à la programmation.
Un facteur que j'ai eu du mal intégrer, c'est que désormais, ceux qui participent encore à la scène Amiga n'ont pas que cela à faire. Par ailleurs, ils peuvent être assez méticuleux, ce qui rallonge encore les délais pour produire une cracktro. Or Sim1 est fort affairé et méticuleux. Mi-septembre, quand Galahad m'informe qu'il est temps de boucler car il doit envoyer la disquette de Starquake à la duplication, j'ai bien terminé de programmer la cracktro, Curt Cool a bien terminé de composer le module, mais Sim1 n'a toujours pas terminé sa version de Toutânkhamon.
C'est un week-end. J'écris en catastrophe à Ramon B5 / Desire, qui s'est fait connaître après la publication de mon appel pour me proposer de produire pour Desire, assurant qu'il n'y avait chez lui aucun problème pour trouver un musicien et un graphiste qui produisent tout ce que l'on veut dans les temps. Ramon B5 passe la balle à Alien, avec qui je me retrouve donc de nouveau en contact. Quelques heures plus tard - oui, quelques heures seulement ! - Alien me livre une magnifique image qu'il retravaille le lendemain pour m'en livrer une version finale. L'ensemble produit un bel effet :
Première version de Scoopex "TWO" (pas cycle-exact)
Joie ! Tout est bouclé, et je peux envoyer une archive à Galahad dans les temps. Le seul point noir est que je dois donc informer Sim1 que son travail ne sera pas retenu, ce qui est toujours gênant, car même s'il n'est pas achevé, il n'en est pas moins assez avancé - la dernière fois, il ne manquait que sa barbichette au King Tut.
Quelques jours plus tard, Galahad accuse réception, mais fait valoir que l'intro ne fonctionne pas sur un "A500 512k". Comment cela, un "A500 512k" ? Tout A500 digne de ce nom ne doit-il pas être considéré comme doté de ses 512 Kb de Chip et d'une extension de 512 Kb de Fast ? Eh bien, non, pas dans le cas du remake de Starquake, qui décidemment se veut roots. Galahad s'excuse d'avoir oublié de le mentionner, et je me remets donc en catastrophe au travail.
Après différents calculs pour évaluer la mémoire requise, il s'avère qu'il n'y a pas d'autres solutions que le downsizing. De mémoire :
  • il faut réduire le côté maximal des BOBs de 64 à 32 pixels pour réduire d'autant la marge autour des trois buffers du puzzle ;
  • il faut réduire la taille de la partie de l'image coppérisée, qui passe de 320 x 256 à 200 x 200.
Le premier downsizing ne gêne pas trop, car les BOBs de 64 pixels de côté n'étaient pas légion dans le puzzle. En fait, ce n'est même pas plus mal, car je trouvais qu'en se déplaçant même dans la trame, ils donnaient l'impression de ne pas se déplacer dans la trame. Le second downsizing est plus gênant, car il entraîne une limitation du zoom. Mais bon, ça passe. Je livre donc dans l'urgence à Galahad une nouvelle version qui assurément fonctionne sur un A500 doté de seulement 512 Ko de Chip.
Quelques semaines plus tard - après la précipitation des précédents événements, j'avoue que je trouve le temps un peu long -, Galahad me fait signe de nouveau. Il m'explique que la cracktro lui a donné du fil à retordre. Et comment ! Pour la faire tenir sur la disquette, il a dû tout simplement réécrire le trackloading afin d'adresser des unités de granularité plus fine : des secteurs au lieu de pistes. Par ailleurs, il a dû assurer la relocation du code une fois dépacké. Là-dessus, il envoie la disquette à Photon / Scoopex pour d'ultimes tests avant de lancer la duplication.
Quelques jours plus tard, courriel de Photon, qui s'étonne que parfois, elle "freeze" durant le rotozoom. "Freeze" ? Sans doute, j'ai pu noter que cette #@! de player fait parfois louper une trame, mais même si c'est lamentable, je n'ai pas trouvé cela gênant au point de chercher une solution - j'avais eu assez de problème avec mon code pour en chercher dans celui d'autrui, me dis-je en pensant, certainement à tort, que le problème ne vient pas de moi.
Je demande donc à Photon de me préciser ce qu'il entend par "freeze" et de me faire parvenir la version dont il dispose. Je teste : rien de plus que ce que j'avais déjà constaté. Après un échange, il me précise que si j'utilise WinUAE, je ferai bien d'activer l'option Cycle-exact (full). L'option quoi ? L'OPTION QUOI ? Sérieux, c'est un sketch ? Comme l'autre, je dois dire "Oh la boulette !" ?
J'avoue que sur l'instant, je me suis senti gagné par une vague de découragement. Pourquoi les par ailleurs géniaux créateurs de WinUAE ont-ils pensé que par défaut, il ne serait pas utile de proposer une émulation la plus fidèle possible, au point que notamment le CPU ne trouve pas les cycles dont il a besoin quand ils sont accaparés par le DMA, je ne le sais pas. Enfin, toujours est-il que depuis que je m'étais remis à programmer sur Amiga, je n'avais pas relevé l'option, et que j'avais donc programmé pour un Amiga bien plus rapide qu'en réalité. De là, leçon numer sześć : always read the fine prints.
Que faire ? Le rotozoom est visiblement très loin de tenir dans la trame. Tout de suite, je me doute que la solution passe par la réduction du nombre de bitplanes pour libérer des cycles accaparés par le DMA que le CPU pourrait utiliser. Après quelques heures le lendemain, je produis dans la nuit une nouvelle version du rotozoom qui tient dans la trame, aux sautes d'humeur près du player. Pour cela, il m'a fallu abandonner l'idée que le rotozoom serait plein écran. J'ai modifié la Copper list pour introduire un MOVE sur BPLCON0 qui réduit le nombre de bitplanes de 4 à 1 après la base du logo. Le résultat est le suivant :
Seconde version cycle-exact de Scoopex "TWO" (cycle-exact)
Ca passe, mais le lendemain matin, je ne trouve pas cela du tout glorieux. A la base, la cracktro devait permettre d'illuster la possibilité d'utiliser des boucles au Copper pour manipuler tout un écran à base de MOVE. En réduisant la surface de cet écran, il me semble que je passe donc à côté du sujet. Je profite de ce que Photon ne m'a pas encore répondu pour pour chercher une autre solution durant la matinée.
Après divers tests, il s'avère que la seule solution pour que le rotozoom tienne dans la trame est malheureusement de réduire le nombre de bitplanes à... un seul. Ce bitplane doit servir pour le logo et le texte ; le rotozoom ne doit plus modifier la couleur 1, mais la couleur 0.
Entre autes conséquences, la réduction drastique du nombre de bitplanes emporte l'éjection du magnifique logo d'Alien, qui servira donc pour une autre occasion. En catastrophe, je le remplace par un "Scoopex" le moins moche possible. Je le produis à la hâte avec le formidable Pro Motion NG qui permet fort heureusement de saisir du texte avec n'importe quelle police, dont la Ravie qui me semble produire un résultat moins catastrophique que les autres. Le résultat est alors le suivant :
Troisième version de Scoopex "TWO" (cycle-exact, mais scratches)
C'est la v7, et j'avoue que je ne peux plus voir cette cracktro, et encore moins son code, même en portrait. Je l'envoie à Photon et à Galahad, demandant au premier de tester et avertissant le second qu'il va avoir du travail ce week-end, car j'ai tout de même modifié le code en plusieurs endroits. Au passage, je lui demande s'il n'avait pas trouvé bizarre que le rotozoom ne tourne pas dans la trame. Il ne manquerait plus qu'il n'ait pas osé me le dire... Si en plus d'avoir vieillis, nous avons abaissé nos standards, c'est que tout est parti en quenouille.
Inutile de dire que dans la foulée, je re-teste tout le code pour Amiga mis à disposition sur ce site en configuration "cycle-exact". Fort heureusement, je ne détecte aucun problème, sauf ici, quand l'étoile est rajoutée à l'arrière du sine scroll. Pour ceux que cela intéresse, j'ai corrigé le problème en recourant à diverses optimisations - une parallélisation des opérations au Blitter et au CPU pour A1200 ; un précalcul d'animation pour A500 - et mis à jour l'article. Enfin, l'honneur est sauf !
Mais ce n'est pas fini. Sans nouvelles sur l'instant de Galahad et de Photon, je décide de donner la cracktro à tester à Stormtrooper, ancien camarade de l'époque Amiga. Il me rapporte que le rotozoom loupe parfois une trame et que cela affecte la musique qui "scratche". Bref, je ne peux plus ignorer le problème que j'avais l'intention de passer sous le tapis. Je me rappelle alors d'une suggestion de Photon mentionnant le fait que j'attends la fin de la trame en attendant que le raster atteigne une position verticale particulière. Flûte ! C'est effectivement un reliquat de tests. Je modifie le code pour attendre que attendre que le raster atteigne ou dépasse cette position verticale : plus de trames loupées, donc plus de "scratches" chez moi.
C'est la v8, que j'envoie à Galahad et Photon. Quand ces derniers vont ouvrir leurs boîtes aux lettres, ils vont me détester, c'est sûr. Mais c'est le moindre des problèmes : est-il bien entendu que le cauchemar est terminé ? Le fait que ni Galahad ni Photon ne m'ont rapporté que la première version du rotzoom ne tournait clairement pas dans la trame ne laisse pas de m'inquiéter. Testons-nous bien la même chose ? A vrai dire, qui a testé sur un véritable Amiga ? Je me trouve confronté au problème des qualias : demander à quelqu'un si la cracktro tourne dans la trame, c'est comme lui demander de décrire la couleur rouge. La seule solution est de procéder par comparaison. Je package plusieurs versions de Scoopex "ONE" et de Scoopex "TWO" sur un ADF, la première version de chaque cracktro tenant obligatoirement dans la trame - 50 particules seulement dans Scoopex "ONE", rotozoom ne débutant qu'après le logo et sur un bitplane dans Scoopex "TWO".
J'envoie l'ADF à Crown et Asle, en leur demandant de tester sur de vrais Amiga. Manque de bol : ils n'en ont pas à disposition. Crown me renvoie sur Curt Cool, auquel je fais donc suivre l'ADF. Parallèlement, je commande sur eBay un kit permettant de transférer des fichiers entre PC et A1200 via une carte SD glissée dans un adaptateur CF, lui-même glissé dans une carte PCMCIA - c'est simple la vie ! Car à cet instant, je commence à me demander si je ne suis plus le seul sur Terre à encore disposer d'un A500 et d'un A1200 en parfait état de marche. Il est vrai que dans cette aventure, c'est une peu tout le temps le monde à l'envers.
De l'authenticité des productions sur émulateur
Dans ce message de ce fil de l'English Amiga Bord, Galahad rend compte de la manière dont il a porté Starquake. En particulier, il rapporte sa déception :
On A1200, it plays exactly as it should, but its too sluggish on A500, its due to the nature of how the conversion is done, and I wrongly thought the action onscreen wouldn't be much of an issue, but it turns out I was wrong.
Pour autant, à en juger par les réactions dans le fil, l'accueil de Starquake est très favorable : cela ne gêne visiblement personne d'avoir à faire tourner le jeu sur une configuration qui n'est pas celle à laquelle il était initialement destinée - un A500 de base, doté de 512 Ko de Chip. Cela a de quoi surprendre. Starquake n'était donc pas attendu par un public cherchant à renouer avec le passé dans ses moindres détails, ou pour le dire autrement, en quête d'authenticité véritable ?
Pour ma part, j'aurais tendance à évoquer deux explications :
  • Ceux qui utilisent encore de véritables Amiga disposent de configurations dont les performances dépassent celles l'A500 de base, et de loin. Pourvu que le jeu tourne sur leur machine, il y tourne donc à la bonne vitesse, si bien qu'ils n'y voient que du feu. En fait, ils sont même d'autant moins enclins à y voir autre chose que pour eux, un Amiga est l'Amiga qu'ils utilisent actuellement - c'est un peu comme s'ils n'avaient jamais vu leur Amiga vieillir, car il a imperceptiblement vieilli chaque jour. Il ne leur viendrait pas à l'idée de faire tourner le jeu sur un A500 de base.
  • Ceux qui n'utilisent pas de tels Amiga ont recours à un émulateur, lequel permet très facilement de modifier la configuration faire tourner le jeu à la bonne vitesse. Or cette manipulation leur paraît d'autant moins artificielle que c'est une chose à laquelle ils ont été habitués par l'émulateur. En effet, il est notoire que l'émulation de certains jeux requiert une configuration très particulière. De plus, nombre de jeux ont été corrigés au fil du temps pour tourner sur toutes les configurations, si bien qu'il est devenu possible d'émuler un Amiga plus puissant qu'un A500 de base et de s'y tenir. Bref, ici encore, la tendance est de n'y voir que du feu.
Pour ces raisons, la notion d'authenticité ne semble pas renvoyer, chez les afficionados, à l'authenticité au sens auquel le profane pourrait l'entendre. De toute manière, l'enjeu était peut-être pour eux ailleurs : le projet leur est apparu comme celui de porter Starquake sur Amiga, pour permettre à ceux qui y ont joué sur d'autres plates-formes de mordre de nouveau dans cette madeleine de Proust, mais sur leur bécane chérie de Commodore. Dans ces conditions, le fait que le jeu ne tourne pas à la bonne vitesse sur un A500 de base leur sera paru d'autant plus accessoire qu'il leur aura probablement échappé.
Reste que le contraste entre la déception éprouvée par Galahad et l'accueil réservé à son portage jeu témoigne de la variabilité de la notion d'authenticité quand il s'git de renouer avec le passé informatique. Dans un contexte où personne n'utilise plus guère la machine originale, chacun a donc tendance à juger d'après ce que lui permet de voir l'émulation logicielle ou matérielle - faire tourner un jeu pour A500 sur A1200, c'est une forme d'émulation - à laquelle il a recours.
Ce qui soulève un enjeu : qu'est-ce qu'une émulation "authentique" (*) ? Si celle qui prévaut est celle de la majorité, alors il faut avoir conscience qu'elle peut être très éloignée de l'émulation au sens strict, c'est-à-dire au sens où ce qui apparaît à l'écran de la machine qui émule serait exactement ce qui apparaîtrait sur celui de la machine émulée. En fait, c'est toujours plus vrai tandis que ceux qui émulent ont de moins en moins le souci de ressortir la machine émulée de leurs cartons, voire qu'ils n'en ont plus la possibilité, voire même qu'ils ne l'ont jamais possédée !
A vrai dire, ces derniers seraient même peut-être déçus s'ils devaient faire tourner un jeu original sur la machine orginale. C'est que dans certains cas, l'émulation dépasse l'original. C'est tout particulièrement vrai des jeux lents à l'époque, qui se trouvent accélérés, et/ou de ceux dont la qualité de l'image se trouve améliorée - on pense aux jeux qui exploitent graphismes procéduraux, soit avant toute chose des jeux à base de graphismes en 3D, les jeux à base de graphismes vectoriels étant des plus rares. Un exemple caricatural : jouer à The Legend of Zelda: Ocarina of Time sur Nintendo 64 ou sur émulateur. La différence est flagrante, tant dans la vitesse d'exécution que dans la finesse de l'image, comme il est possible de le constater ici.
A l'inverse, pour éclairer l'enjeu sous toutes ses facettes, il faut noter que l'émulation peut faire moins bien que l'original. Dans cet article des plus intéressants, l'auteur rapporte que les graphistes des éditeurs de jeux vidéo japonais avaient le souci d'exploiter les imperfections des écrans à tube cathodique, tout particulièrement l'espace entre les lignes - la technique du "0.5 pixels" dont il est question. Et comme l'auteur le rapporte en produisant des captures d'écran saisissantes, une émulation est loin de pouvoir restituer le charme de l'image produite en s'appuyant sur cette exploitation de ce qui peut rétrospectivement apparaître comme une imperfection du matériel. Ce qui soulève au passage un autre enjeu : pour parler d'émulation d'une machine, par quoi faut-il entendre "machine" ? Ne faut-il pas notamment pas manquer d'inclure l'écran ?
De tout cela, on conçoit une évolution - d'aucuns pourraient y voir dérive, mais tout l'enjeu est justement de savoir si c'en est vraiment une -, que la manière dont le portage de Starquake a été réalisé illustre finalement assez bien. Qui développe sur un émulateur peut-il vraiment prétendre avoir développé pour la machine émulée ? Avant même qu'il ne soit question de juger de la recevabilité d'une prétention à un statut - être celui qui s'inscrit véritablement dans la tradition -, c'est de la possibilité même de prétendre dont il doit être question. Une machine peut-elle être proprement émulée ? C'est-à-dire dans toutes ses qualités, mais aussi dans tous ses défauts ? La perception de celui qui s'en remet à l'émulation n'est-elle pas irréductiblement biaisée, quand bien même il cherche à faire au mieux ?
Cette réflexion sur l'authenticité n'est pas nouvelle. On peut la raccrocher à celle que nous fait partager Philip K. Dick dans The Man in the High Castle, ou à celle, somme toute plus fondamentale, que Plutarque rapporte en évoquant le bateau de Thésée, et elle doit animer tout conservateur. Toutefois, elle est indubitablement renouvelée par la technique de l'émulation.
(*) Noter qu'ainsi formulée, on voit tout ce que la problématique peut avoir de paradoxal, donc de relatif au point de vue de celui qui la formule...
Curt Cool me répond que tout fonctionne à la même vitesse sur A500. Je n'y comprends plus rien, mais comme Galahad m'envoie une version finale de la disquette du jeu où la V8 fonctionne, je décide d'en rester là. Non sans constater que finalement, Galahad a relevé la configuration de base requise pour le jeu de 512 Ko à 1 Mo. J'aurais pu m'éviter le premier downsizing. Enfin, bref. Pourvu que cette cracktro soit distribuée, c'est ce qui compte... On fera mieux s'il y a un Starquake II !

Et voilà !

On ne va pas se le cacher, cette cracktro est loin d'être ce qui se fait de mieux sur Amiga 500. Toutefois, elle tient la route. Le FX passe bien, avec l'aide des beaux graphismes et de la bonne musique réalisés pour l'occasion. Le dernier que j'évoquais au début de cet article n'est donc pas un verre de picrate. Si ce n'est pas du Champagne, c'est au moins un bon Sauternes, la robe se mariant bien avec les tonalités à l'écran.
Pas de cracktro digne de ce nom sans credits et greetings. Pour les premiers :
Pour les seconds, je renvoie aux pages de la cracktro. Néanmoins, je tiens à remercier tout particulièrement... :
  • Galahad / Scoopex, qui m'a offert l'opportunité de réaliser cette cracktro, et qui est allé jusqu'à réécrire le trackloading de son portage de Starquake pour qu'elle tienne sur la disquette ;
  • Sim1 / Wanted Team, avec qui j'avais commencé à travailler, mais dont je n'ai pas pu utiliser la production car les échéances se sont bousculées ;
  • Ramon B5 / Desire, qui m'a sauvé la mise en appelant en catastrophe Alien au secours pour tenir les délais en plein week-end.
Scoopex « TWO » : Le coding-of d’une cracktro sur Amiga 500