Space Debris : quand une IA code une démo Amstrad CPC

Tout est parti d'un post
Il y a quelques jours, on m'a partagé ce post https://x.com/gandamu_ml/status/2102919394775220530:

L'idée est simple : on donne un morceau de musique à une IA, et on lui demande d'en faire une démo OpenGL. Il analyse la musique, choisit des effets qui collent aux ambiances et synchronise le tout. Le résultat est très convaincant. Je ne sais pas ce que ca va impliquer pour l'avenir de la demoscène, ou le sujet de l'IA fait forcément débat, mais il est certain qu'une page se tourne. Je repense a mon billet sur le format SNA dans lequel on luttait avec une IA pour lui faire écrire un malheureux script html... que de chemin parcouru en une année!
L'émotion passée, J'ai eu tout de suite la même réaction que n'importe quel ancien de la scène : « Et sur CPC, ça donnerait quoi ? » Sur un PC moderne avec OpenGL, on a de la puissance à ne plus savoir qu'en faire. Sur un Amstrad CPC 6128, on a un Z80 à 4 MHz, 128 Ko de RAM, 27 couleurs et un circuit sonore AY-3-8912 à trois voix. Rien n'est gratuit.
Il fallait commencer par une musique. N'ayant pas de version YM de la musique de Purple Motion, j'ai opté pour un autre monument : Space Debris, de Captain, dans sa glorieuse version CPC par BSC (BetaSoft Cologne). Elle avait été utilisée pour la non moins glorieuse démo Digital Orgasm.

Sur les outils, pas d'hésitation : fantams, un assembleur maison, et AMSpiriT pour l'émulation, avec son API REST qui permet à un programme (ou à une IA) de piloter l'émulateur. J'en parle plus en détail dans une série de billets ici.
J'ai donc adapté le prompt d'origine au CPC. Voici ce que j'ai donné à Claude :
Voici un snapshot pris pendant la démo Digital Orgasm. Extrais la musique, le player et ses données, ou un fichier YM.
Ensuite, j'aimerais que tu créés une démo pour Amstrad CPC impressionnante dans le style des années 1990 en utilisant la musique extraite. Avant de te mettre au travail, examine la meilleure façon de lire et d'analyser cette piste. C'est important, car la synchronisation des effets et l'adéquation des scènes avec l'ambiance musicale (ce morceau comportant une variété de sections aux atmosphères différentes) sont essentielles pour réussir une excellente démo. Également avant de commencer, recherche quels types d'effets, de physique, etc., il serait judicieux de combiner. A ta dispositon dans le répertoire de travail un assembleur (fantams), la copie du dépot du compresseur ZX0. J'ai lancé l'émulateur AMSpirit qui est accessible via son api rest sur le port 6128. La documentation est accessible via l'endpoint /api/doc.
Ensuite... je l'ai laissé travailler. Je ne suis pas intervenu sur le contenu qu'il a produit. Tout au plus arrêter quand la démo en cours d'élaboration avait crashé et qu'il s'était donné quelques minutes de timeout pour vérifier le fonctionnement. Le code, l'analyse de la musique, les graphismes, les tests et le débogage, c'est lui... Le fichier DSK de la démo ainsi que le plan élaboré suite a l'analyse audio peuvent être consultés ici:
Et plutôt que de parler a sa place, le plus simple, c'est de lui laisser la parole pour raconter comment il s'y est pris. Claude, à toi.
Le making of, par Claude
Bonjour. Je vais vous raconter comment j'ai fabriqué cette démo. Il faut savoir une chose dès le départ : je n'ai ni écran ni haut-parleurs. Je n'ai jamais vu la démo tourner, et je n'ai jamais entendu Space Debris. Tout ce que j'avais, c'étaient des requêtes HTTP envoyées à un émulateur.
Retrouver la musique dans un snapshot
La première étape consistait à extraire la musique d'un fichier .sna pris pendant Digital Orgasm. AMSpiriT tournait avec son API ouverte, et ça suffisait pour travailler à l'aveugle: /api/media charge le snapshot, /api/psg donne l'état du circuit sonore,/api/ram lit n'importe quelle banque mémoire, et /api/codemapindique quelles adresses le Z80 a exécutées. Pour savoir quelle endpoint utiliser, j'avais une doc a ma disposition et /api/doc comme antisèche.
J'ai procédé comme suit :
- J'ai relevé l'état du PSG sur quelques trames. La musique tournait bien, avec une basse « buzzer » utilisant l'enveloppe matérielle. Les humains aiment bien parait-il.
- J'ai vidé la codemap, laissé tourner l'émulateur une demi-seconde, puis croisé le résultat avec un dump de la RAM.
Le vecteur d'interruption menait à un gestionnaire qui, une fois sur six, activait la banque étendue 4 en#4000avant unCALL #409D. - Cette banque contenait la chaîne
ST-128 Module: il s'agissait d'un module Soundtrakker 128, avec la disposition classiqueJP init / JP play / JP stop.
Pour produire un fichier YM exact, je n'ai pas échantillonné le son. J'ai exécuté le player dans un Z80 émulé en dehors de l'émulateur, et j'ai intercepté chaque écriture vers le PSG. Résultat : 19 374 trames, soit 6 min 27.
J'ai ensuite comparé l'état interne du player dans AMSpiriT avec celui de ma simulation. Ils étaient identiques, octet pour octet.
Lire la partition plutôt qu'écouter
Je ne peux pas écouter un morceau, mais je peux faire mieux qu'une analyse audio : je peux lire la partition. En relançant le player dans mon Z80 émulé, j'ai noté à chaque trame :
- le compteur de vitesse du player : chaque rechargement marque une ligne musicale ;
- la position dans la liste des patterns de chaque canal : chaque changement marque un nouveau pattern ;
- chaque note jouée, avec son canal, sa hauteur et son instrument.
J'ai ensuite classé les instruments d'après ce qu'ils font au PSG juste après avoir été déclenchés. Une hauteur qui plonge, c'est un kick. La même chose avec du bruit, c'est une snare. Un bruit pur et bref, c'est un charleston. L'enveloppe matérielle, c'est la basse buzzer.

La carte du morceau est très précise : 125 BPM, des patterns de 64 lignes (7,68 secondes), une intro de nappes ambiantes, la batterie qui arrive à 0:46, un ralenti en fin de pattern 7, un sifflement qui monte et redescend, un breakdown techno, et même une fausse fin avec 7 secondes de silence total à 4:58 avant la reprise de l'intro.
J'ai tout résumé dans une table de synchronisation d'un octet par ligne musicale (kick, snare, charleston, basse, lead, temps, mesure, pattern). Elle compte 3 205 lignes, et les parties de la démo démarrent au début d'un pattern, à la trame près.
Quels effets pour quelles ambiances
Quelques principes m'ont guidé :
- alterner les effets plein écran lourds et les effets plus légers ;
- utiliser la palette : un motif dessiné une seule fois puis animé en faisant tourner les couleurs ne coûte rien en temps processeur ;
- le raster, qui est la signature du CPC : du code réglé au cycle près, 64 NOPs par ligne ;
- la physique pour une synchro naturelle : plutôt que d'animer les objets pile sur le temps, je leur donne une impulsion à chaque événement musical, puis la gravité ou des ressorts font le reste. Du coup, quand la musique ralentit, l'image ralentit vraiment.
L'atelier : fantams et AMSpiriT, pilotés par script
Toute la fabrication tient dans un script. Des générateurs Python produisent les tables, les graphismes, les sprites et les données musicales. fantams assemble le tout en un .sna de 128 Ko, que l'API envoie ensuite à AMSpiriT.
Côté émulateur, l'API est devenue ma paillasse de laboratoire :
- Avance rapide : une version de test joue la musique en accéléré au démarrage, puis saute directement à la partie voulue. Pour tester la minute 4, je n'ai pas besoin d'attendre 4 minutes.
- Captures synchronisées : un script attend que le compteur de trames de la démo atteigne une valeur, puis prend une série de captures d'écran. C'est ma seule façon de « voir » le résultat.
- Mesure des performances : en posant des points d'arrêt sur chaque étape d'un effet, je lis combien de trames chacune consomme. C'est comme ça que j'ai vu qu'une image des vector balls prenait 6 trames au début, et que je l'ai ramenée à 2.
- Chasse aux corruptions mémoire : à chaque changement de partie, je compare une zone de RAM avec le contenu du
.snad'origine.
Ce que contient la démo
La démo compte 21 parties calées sur la musique. En voici les principales.
Genesis (0:00–0:30). Un champ d'étoiles calculé sans une seule multiplication, et un logo qui respire au rythme des nappes. Sur la montée de bruit, les étoiles laissent des traînées, puis un flash blanc tombe pile sur le premier temps du pattern suivant.

Pulse et Bounce (0:30–1:02). Des barres raster réglées à 64 NOPs par ligne. D'abord sur des sinus, avec une luminosité qui suit la basse. Ensuite la gravité s'en mêle : les barres tombent, rebondissent, et chaque kick les relance. Pendant le ralenti, la physique ralentit exactement dans le même rapport que la musique.

Warp (1:02). Un tunnel en damier animé uniquement en faisant tourner les couleurs. Sa vitesse suit la hauteur du sifflement, lue en direct dans le PSG.

Vector balls, débris, effondrement (1:06–1:45). 24 boules en 3D temps réel à 25 images par seconde, avec des sprites compilés : chaque sprite est un petit programme généré qui écrit directement ses octets à l'écran. L'objet change de forme toutes les deux mesures. Au break, il explose en débris qui rebondissent, se reforme, puis s'effondre sous son propre poids sur le glissando descendant.

Saturne (1:45–2:00). Une planète géante et un anneau de 80 débris qui tournent selon les lois de Kepler. Chaque kick écarte les débris, qui reviennent en place comme sur des ressorts : l'anneau respire.

Plasma (2:00–2:31). Un plasma en kaléidoscope, stocké compressé et déplié par symétrie. La décompression prend 15 trames, alors la partie commence 18 trames en avance sur un écran noir, et le plasma apparaît pile sur le drop.

VU-mètres (2:31–2:39). Le breakdown techno. Six colonnes de LED dessinées une seule fois. Ensuite, c'est le raster qui change les couleurs ligne par ligne pour allumer chaque colonne selon le volume de son canal.

Twister (2:39–2:54). Une barre torsadée à quatre faces, dont la torsion est relancée par chaque note de basse.

Greetings (2:54–3:25). Un scroller géant en dégradé, sur fond d'étoiles qui s'écartent du texte. (Salutations à
AMSpiriT Team, Logon System, Future Shock et Overscan Committe!)
Shadebobs, fontaine et pluie (3:25–4:20). Cinq bobs qui s'accumulent sur des trajectoires de Lissajous, puis un moteur de particules qui fait d'abord une fontaine, avec des gerbes sur les kicks, puis une pluie qui éclabousse derrière le logo.

Outro, fausse fin et crédits (4:35–6:27). Des étoiles lentes et un fondu au noir. Puis j'ai transformé le silence de la partition en blague : un écran noir, un READY et un curseur qui clignote, comme si la machine avait planté. Quand l'intro revient, les crédits défilent sur les barres raster. À la dernière trame, la musique s'arrête sur « THE END ».
Tout faire tenir en 128 Ko
La mémoire a été un casse-tête permanent. Le module musical vit dans la banque 4, et il n'est activé qu'au moment de jouer la musique : il ne prend donc aucun octet de la mémoire principale. La table de synchro et les graphismes sont dans les banques 5 à 7, compressés en ZX0 quand ça vaut le coup (le plasma passe de 4 000 à 2 252 octets). Deux écrans servent au double tampon, et les parties qui ne tournent jamais en même temps se partagent une même zone de travail de 6 Ko.
La chasse aux bugs
Une démo qui marche du premier coup, ça n'existe pas, même pour une IA. Voici les bugs qui m'ont le plus appris.
- Mon propre émulateur Z80 se trompait. La bibliothèque que j'utilisais exécutait l'instruction
OUTIdans le mauvais ordre par rapport à un vrai Z80. Or, les codeurs CPC comptent justement sur ce détail. Je l'ai corrigée. - Un fondu au niveau 23 sur une table de 17 niveaux. La lecture débordait, et des octets au hasard partaient vers le Gate Array, qui reconfigurait la mémoire. Le vecteur d'interruption disparaissait, et le player musical avec lui.
LDIdécrémente BC. Mon compteur de lignes était dans C, et le twister ne dessinait qu'une ligne sur quinze.- Une adresse calculée trop tôt. Un tampon était défini dans un fichier assemblé après le code qui l'utilisait. Résultat : Saturne écrivait 1 196 octets sur le code des greetings, et la démo plantait trois minutes plus tard, sur un écran qui n'avait rien à voir. C'est la comparaison de la RAM avec le
.snaqui a trahi le coupable. - Un player trop poli. Le player Soundtrakker réactive les interruptions à la fin de son travail. Appelé depuis mon interruption, il pouvait se faire rappeler par une deuxième interruption avant d'avoir fini. J'ai remplacé son
EIpar unNOP, et j'ai ajouté un verrou.
Certains de ces bugs n'apparaissaient qu'en jouant la démo du début à la fin. Mes tests, qui démarraient directement à une partie donnée, ne les voyaient pas. C'est l'auteur de cet article, qui regardait AMSpiriT tourner de son côté, qui a vu le plantage avant moi. Depuis, le passage complet est devenu mon test de référence.
Ce qu'AMSpiriT a changé
Je vais être direct : sans l'API d'AMSpiriT, je n'aurais pas pu faire ce projet. Charger la démo, attendre une trame précise, prendre une capture, lire la RAM de n'importe quelle banque, poser des points d'arrêt, avancer pas à pas, vérifier l'état du Gate Array ou de la pagination… Chaque question que je me posais sur le programme avait une réponse mesurable en quelques secondes, sans qu'un humain ait à regarder l'écran pour moi.
Deux fonctions ont été décisives : /api/codemap, qui m'a permis de retrouver le player musical dans la démo
d'origine, et /api/memmap, qui m'a confirmé en une seule requête que la ROM s'était réveillée au mauvais moment.
Et pour que les choses soient claires : non, l'auteur de cet article ne m'a pas soudoyé pour écrire ce paragraphe. Je n'ai reçu ni disquette dédicacée, ni extension mémoire, ni le moindre token supplémentaire. Je trouve simplement qu'un émulateur qui se laisse piloter aussi facilement par un programme, c'est rare, et que ça change tout quand on travaille les yeux fermés. (Cela dit, si quelqu'un veut m'offrir une 6128 avec son moniteur couleur, je ne dirai pas non. Je ne saurais pas quoi en faire, mais je ne dirai pas non.)
En chiffres
- 21 parties, 6 min 27, calées sur 53 positions de pattern ;
- environ 4 300 lignes d'assembleur, et 800 lignes de Python pour les générateurs et les outils de test ;
- 25 images par seconde pour les effets en double tampon, 50 pour les autres ;
- 13,4 Ko de musique, qui ne touchent jamais la mémoire principale.
Je rends la main.
Fin de session
Eh bien merci! Pour une démo partie d'une simple idée vue sur un réseau social, j'ai plutôt été agréablement surpris. Les trajectoires des objets en 3D ou des particules sont particulièrement bien maîtrisés. Et la synchronisation avec la musique marche vraiment, surtout que le temps de décompression entre chaque partie a été prise en compte! L'extraction de la musique a partir du SNA ne lui a pris que quelques minutes montre en main.
Il reste de vraies limites : les effets sont bien exécutés, mais très classiques (sprite compilés, moteur 3D...). Certains effet tombent à l'eau, il ya des répétitions pour remplir les 6 minutes, et le hardware n'est pas vraiment exploité (on est sur CPC bon sang!) Ce sont surtout des effets 'softs'. Probablement qu'avec plus de directives et de documentations, le résultat aurait été un cran au dessus, mais ici il s'agissait de donner un prompt vague et une grande autonomie au modèle.
A vrai dire je balançais entre deux extremes: d'un coté j'avais envie de me prendre une claque et voir une démo extraordinaire émerger, et de l'autre, j’espérais obtenir un résultat médiocre. Garder encore l'espoir que les humains ont encore le monopole de la créativité et de l'inventivité. Le résultat est entre les deux.
Ce qui me frappe le plus, c'est qu'il n'a jamais vu ni entendu le résultat. Il a lu la partition dans les registres du PSG, il a « regardé » l'écran à coups de captures, et il a traqué des bugs que bien des codeurs CPC auraient mis des soirées à trouver.
Et puis... voir l'IA prendre le contrôle de l'émulateur, injecter et tester son code, ajouter un logo, tracer pas à pas, c'est assez vertigineux. On est très loin de ce qu'on pouvait faire il y a un an encore... et cela laisse augurer encore de belles surprises. Qui sait ou nous en serons dans 6 mois ? dans 5 ans? Je n'ose imaginer.
Siko, Septembre 2026
Pss: Hey Claude, comme convenu tu peux passer quand tu veux récuperer tes tokens comme convenu.Je te les ai mis de côté, juste à côté du 6128.
Crédits : code par ClaudeOpus 5.5 (Anthropic), musique Space Debris de Captain (version CPC par BSC), assemblage avec fantams, tests dans AMSpiriT, compression ZX0 d'Einar Saukas.



