Ho dato a un agente AI i dischetti dell'Amiga. Dopo una sera Pirates! girava in Unity
Ho dato a un agente di programmazione quattro immagini di dischetti con la versione Amiga di Sid Meier's Pirates! del 1990 e gli ho chiesto di portare il gioco in Unity. A lavorare è stato Claude Opus 5.5 in Claude Code. Dopo una sera il gioco si poteva percorrere dalla schermata del titolo, attraverso un duello, una città e la navigazione sulla mappa, fino a una battaglia navale, con la grafica e il sonoro originali. Più importante del ritmo è il modo in cui il risultato è stato verificato: lo stesso copione di decisioni è stato eseguito sul porting in C# e sul codice macchina originale fatto girare in un emulatore di processore. In 802 esecuzioni, cioè circa 6 milioni di righe confrontate, le due versioni hanno mostrato al giocatore la stessa cosa.
Descrivo qui le tappe di questo lavoro perché insieme formano un metodo che si può ripetere con un altro gioco e un altro modello. Alla fine mostro un secondo progetto realizzato con lo stesso schema, il porting di Centurion: Defender of Rome da DOS a JavaScript. Entrambi sono progetti privati di studio: i giochi originali, la loro grafica e il loro sonoro restano protetti dal diritto d'autore e non li distribuisco.

La schermata del titolo in Unity. L'immagine viene decodificata a runtime dai file originali del gioco.
Dall'immagine del dischetto al codice decompilato
L'agente ha cominciato identificando i file. L'immagine di un dischetto Amiga (ADF) è una copia grezza dei settori, quindi sono bastate poche decine di righe di Python per estrarne il contenuto. Il programma del gioco si è rivelato un eseguibile a overlay: un nucleo resta sempre in memoria, mentre altri tre segmenti di codice vengono caricati dal disco quando servono. Ghidra, un decompilatore gratuito, non supporta nativamente questo formato. L'agente ha scritto uno script che dispone tutti i segmenti a indirizzi fissi in un'unica immagine e trasforma 45 salti indiretti verso gli overlay in normali chiamate. In questo modo il decompilatore ha visto circa 630 funzioni come un solo programma.
I dati del gioco hanno riservato una sorpresa. I file con estensione .rez hanno la struttura delle risorse dei computer Macintosh, perché la versione Amiga era un porting da Mac. Una volta riconosciuto il formato, l'agente ha estratto 210 testi, 82 liste di stringhe, i dati delle città per sei epoche, immagini, animazioni, sprite e suoni. I testi si sono rivelati la migliore mappa del codice: il numero della stringa passata alla routine «mostra testo» dice quale schermata del gioco gestisce una data funzione.
Tredici agenti leggono il codice, nove lo riscrivono
Il codice decompilato contava quasi 29 mila righe, senza nomi di funzioni né di variabili. L'agente principale lo ha tagliato in pezzi da circa 2 mila righe e ha affidato ciascuno a un agente separato, con lo stesso brief scritto. Ognuno doveva salvare in un file la descrizione di tutte le funzioni del proprio frammento: formule esatte, costanti, intervalli dei valori casuali, numeri dei testi visualizzati e variabili globali lette e scritte. Il brief conteneva una frase che si è rivelata importante: una formula sbagliata è peggio di una lacuna segnalata. Gli agenti dovevano quindi indicare ciò che non erano riusciti a stabilire e verificare i punti poco chiari nell'assembly. Qui il decompilatore perdeva molti argomenti delle funzioni, perché il compilatore di 35 anni fa usava un tipo int a 16 bit.
La seconda fase ha avuto la stessa suddivisione. Nove agenti hanno riscritto le regole del gioco in C#, ciascuno per il proprio intervallo di indirizzi. Ne sono uscite circa 16 mila righe di codice, e lo stato del gioco è finito in un array di byte indirizzato esattamente come la memoria dell'originale. Non è una soluzione elegante, ma elimina un'intera classe di errori. Il vecchio codice sovrappone liberamente le strutture l'una all'altra e legge le tabelle a partire dal byte che ne precede l'inizio. Un porting costruito su un'immagine della memoria si può tradurre riga per riga e confrontare con l'assembly, e le tabelle di costanti restano dove il codice le cerca. Disegno, sonoro e controlli sono stati sostituiti dagli agenti con chiamate a un'interfaccia che il motore implementa a parte.
L'oracolo: il codice originale in un emulatore di processore
Leggere il codice decompilato non dimostra che il porting si comporti come l'originale. L'agente ha quindi costruito uno strumento di confronto. Il codice macchina originale per il processore Motorola 68000 gira nell'emulatore Unicorn. Le routine di sistema, cioè menu, finestre di testo, lettura dell'input e disegno, sono state sostituite con funzioni in Python. Queste funzioni rispondono a ogni domanda del gioco seguendo un semplice generatore di decisioni e registrano tutto ciò che il gioco ha mostrato. Il porting in C# riceve l'identica sequenza di decisioni tramite un «giocatore scriptato» che gira fuori da Unity. Alla fine le due registrazioni vengono confrontate riga per riga. I testi contengono l'oro, la consistenza dell'equipaggio, le date e i nomi delle città, quindi la corrispondenza delle registrazioni è un test solido delle regole.
La prima misurazione completa ha dato 800 esecuzioni concordi su 802. Le due discrepanze avevano un'unica causa: l'aritmetica in virgola mobile. Il gioco usa una libreria software che non arrotonda come i processori di oggi. La sua radice quadrata di 81 vale 8,999999999999998 e il gioco tronca i risultati a numeri interi, quindi nel porting il morale dell'equipaggio risultava più alto di un punto per certe proporzioni tra oro ed equipaggio. La correzione è consistita nel riscrivere le routine della libreria istruzione per istruzione: 1.572 istruzioni, 43 routine e 21.701 vettori di test calcolati dal codice originale, senza alcuna differenza. Dopo questa modifica tutte le 802 esecuzioni concordano, e in altre 120 lo stato del gioco è stato confrontato fotogramma per fotogramma nel duello, nella navigazione e nelle battaglie navale e terrestre.
Il confronto ha fatto emergere anche due bug del gioco stesso del 1990: una lettura della mappa attraverso un puntatore nullo quando si attacca una città in diagonale e una divisione per zero quando l'equipaggio scende a zero. In questi punti il porting deve scegliere un comportamento, e lo strumento di confronto ha un interruttore che obbliga l'originale a seguire la stessa strada. Il risultato ha anche dei limiti. Lo strumento non confronta né il disegno né il sonoro, e i percorsi rari, come il matrimonio, la mappa del tesoro completa o i gradi più alti, sono stati esercitati poco.
La presentazione e le aggiunte che nel 1990 non c'erano
Lo strato visibile al giocatore è stato costruito dagli agenti in Unity via codice, senza scene già pronte. Immagini e animazioni vengono decodificate dalle risorse originali, gli sprite prendono i colori dalla palette della schermata corrente e la musica è stata renderizzata in file audio emulando il sequencer a quattro canali del gioco. L'agente pilotava il gioco in esecuzione dalla riga di comando, catturava schermate e le guardava. Così sono venuti a galla alcuni difetti che nessun test delle regole intercetta, per esempio l'assenza del sonoro nella versione per browser, perché la conversione dell'audio richiedeva il programma ffmpeg, che non era installato nel sistema.

La navigazione sulla mappa. Lo sfondo nasce da una griglia del terreno di 160 per 200 caselle e dalle tile originali.
Dopo le prime partite di prova ho chiesto tre modifiche. La domanda sulla data di arrivo della flotta del tesoro era, nell'originale, la protezione contro la copia e richiedeva il manuale cartaceo; ora il quiz resta, ma negli «appunti del capitano» compare la risposta corretta. L'elenco delle città mostrava soltanto i dati della città, perché il gioco dava per scontato che il giocatore avesse una mappa stampata; ora la città scelta è segnata su una mappa dei Caraibi. La schermata del duello ha ricevuto una tabella con la forza, il morale e l'arma di entrambe le parti, disposta come nell'originale. Le aggiunte si possono disattivare e i test di confronto girano senza di esse, in modo che le registrazioni continuino a coincidere con il codice del 1990.

Il duello nell'originale. Fonte della schermata: OpenRetro Game Database.

La stessa schermata nel porting. I numeri della tabella vengono letti in tempo reale dallo stato del duello.

Un'aggiunta rispetto all'originale: la città scelta su una mappa dei Caraibi.
Centurion: lo stesso schema in JavaScript e con un gameplay esteso
Il secondo progetto è Centurion: Defender of Rome, un gioco per DOS del 1990, portato in JavaScript e sull'elemento Canvas, senza emulatore. I passi sono stati gli stessi, con nomi diversi. L'eseguibile andava prima decompresso, perché era stato compresso con lo strumento EXEPACK. Cinque moduli del gioco (battaglia terrestre, arena dei gladiatori, corsa delle bighe, battaglia navale e introduzione) stavano in un file di overlay e usavano un unico segmento dati condiviso. Lo stato della campagna lavora su una copia viva dei 64 kilobyte di quel segmento, perché il motore del gioco fa affidamento sull'overflow a 16 bit, sulla divisione intera e sui byte con segno, che i numeri in virgola mobile di JavaScript da soli non riproducono. Il salvataggio coincide con il formato originale byte per byte, cosa che i test verificano.

La schermata del titolo di Centurion nel browser, con la grafica originale.
Qui sono andato oltre il porting fedele. Ho aggiunto un interruttore per le regole estese, disattivato per impostazione predefinita: la fusione delle legioni e più legioni in una stessa provincia, i veliti armati alla leggera, gli elefanti da guerra e lo sbarco dal mare. Il codice di questo porting è stato scritto da modelli diversi rispetto a Pirates: per la maggior parte da Gemini 3.8 Flash e in parte da gpt-6.1-sol di OpenAI eseguito tramite Codex. La scelta è dipesa dagli strumenti che avevo sottomano, non da un confronto tra modelli.

Il menu delle opzioni. La prima voce attiva le regole estese che nell'originale non c'erano.

La schermata della provincia con il menu dei giochi.
I problemi in cui Gemini si è imbattuto con Centurion
La maggior parte del tempo è andata nei punti in cui il codice decompilato o i nomi dei file suggerivano qualcosa di diverso da ciò che il gioco fa davvero. Cinque esempi dagli appunti del progetto:
- Overlay senza dati. I cinque moduli del gioco stanno in un solo file,
OVL0.OVL, e vengono caricati allo stesso indirizzo, ma tutti usano un segmento dati condiviso. Il solo codice di un overlay aperto in Ghidra non mostrava quindi né stringhe né tabelle. Uno script ha unito ciascun overlay al segmento dati in cinque eseguibili sintetici, e solo allora il decompilatore ha collegato il codice dei minigiochi alle strutture della campagna. - I confini delle province conquistate. Tracciare il contorno di una provincia a partire dalla mappa delle aree cliccabili con il mouse dava linee spigolose, che non combaciavano con la mappa disegnata a mano. Il codice ha rivelato un altro meccanismo: nell'immagine della mappa ogni provincia ha un percorso nascosto, largo un pixel e di colore scuro, e una tabella nei dati indica un punto di partenza per ciascuna delle 27 province. Il gioco ridipinge quel percorso con il colore dell'impero, cominciando dal punto di partenza, e il porting fa lo stesso.
- Fotogrammi che tornano indietro. Il ciclo di gioco originale girava a 9–12 Hz, mentre un browser disegna 60 volte al secondo. In tre punti uno sprite arretrava per un attimo di un fotogramma. Il globo dell'introduzione ruota facendo scorrere 14 colori della palette in 10 passi, ma il primo passo nella tabella vale zero; far scorrere la palette a ogni passo provocava uno scatto dei continenti a ogni rotazione. Nella marcia delle coorti e negli affondi dei gladiatori è stato necessario riprodurre esattamente l'ordine: prima il fotogramma intermedio, poi il cambio di posizione.
- Nomi di file fuorvianti. Le bitmap
BB1.CBMeBB2.CBMsembravano stendardi di battaglia e, disegnate nella parte alta dello schermo, coprivano l'intero campo di battaglia. Il codice le carica soltanto quando il computer non ha una scheda VGA: sono sfondi a tutto schermo per le più vecchie schede EGA e CGA. Dalla stessa routine è emerso che lo schieramento prima della battaglia ha due fasi. La scelta della formazione sostituisce l'elenco delle tattiche disponibili, e la manovra di Canne compare solo dopo aver scelto il cuneo. - Musica appena udibile. Dopo aver decodificato 17 brani e 24 strumenti del driver della scheda audio AdLib, le melodie erano corrette ma suonavano a circa −55 dB. Il disassemblaggio del driver ha mostrato che a ogni nota esso sottrae un quarto della velocity della nota dall'attenuazione memorizzata nello strumento. Una sola riga di codice ha restituito circa 32 dB. Un secondo difetto stava dal lato del browser: con note molto brevi, annullare le variazioni di volume programmate nella Web Audio API cancellava l'attacco del suono ancora in corso e silenziava la nota.

La seconda fase dello schieramento prima della battaglia: le tattiche disponibili per la formazione a cuneo.
Un lavoro a parte sono state le corse delle bighe nel Circo Massimo. Lì una biga non è un'unica immagine. Bitmap separate per ogni combinazione di tre classi di bighe, quattro colori degli aurighi, quattro azioni, sedici inclinazioni in curva e tre fotogrammi di galoppo avrebbero significato oltre 2.300 fotogrammi, perciò gli autori del gioco hanno costruito in memoria un albero di nodi da 16 byte. Ogni nodo punta all'elemento successivo sullo stesso livello e al proprio primo figlio, e contiene un indice di fotogramma, una tabella di offset per i fotogrammi successivi dell'animazione e un flag di visibilità. La radice sono i quattro cavalli. La cassa della biga ha un offset orizzontale aggiuntivo in cui la fisica della curva scrive un'inclinazione da 0 a 15 pixel, e l'auriga è suo figlio e ne eredita la posizione. Per questo in curva l'auriga si inclina insieme alla cassa, mentre i cavalli continuano a correre lungo l'asse della pista. I rami con l'asse che si spezza e con il relitto che si ribalta esistono sempre; hanno soltanto il flag di visibilità disattivato.
![]()
L'albero degli sprite della biga ricostruito dai dati del gioco. Lo schema è stato realizzato nel progetto a partire dalle risorse originali decodificate.
Nemmeno la pista è costruita con tile. Il gioco espande 72 coppie di coordinate memorizzate in 368 punti di controllo e divide la posizione trasversale sulla pista in 16 corsie. Dalla corsia dipende la velocità di sicurezza in curva, calcolata come (numero della corsia + 15) × 4: lungo il muro interno vale 60, lungo la barriera esterna 116–120. La corsia interna offre il percorso più breve, ma entrare nel tornante sopra quella soglia inclina la cassa, spinge la biga verso l'esterno e fa scattare un'estrazione casuale per il ribaltamento, tanto più probabile quanto maggiore è l'eccesso. Il segno rosso sulla barra della velocità mostra al giocatore quella soglia.

I 368 punti della pista e la velocità di sicurezza in curva in funzione della corsia.
Che cosa resta di questo lavoro oltre ai giochi
Ho messo per iscritto il metodo sotto forma di skill, cioè un insieme di istruzioni che un agente carica quando riceve un compito simile. Contiene tre principi ricavati da entrambi i progetti. Le regole del gioco si portano fedelmente, mentre la presentazione si sostituisce con un'interfaccia. Il porting conserva la disposizione della memoria dell'originale. Del risultato non ci si fida finché non supera il confronto con il codice originale. Nella skill sono finiti anche i brief per gli agenti di analisi e di porting e gli script del progetto Pirates.
La conclusione più interessante riguarda i fan, non gli agenti. Chi ha un gioco che ama e a cui da anni manca qualcosa oggi può rifarlo a modo suo, in fretta e con poca spesa, senza il codice sorgente. In Pirates il quiz che richiedeva il manuale stampato ora dà la risposta, ed è comparsa una mappa che il gioco non aveva mai avuto. In Centurion ho aggiunto regole che l'originale non aveva: fusione delle legioni, elefanti da guerra e sbarchi dal mare. Una volta che le regole del gioco sono normale codice in C# o JavaScript, la modifica successiva è una normale modifica al codice, non una patch del binario. Allo stesso modo si può portare un gioco su un motore più veloce o su un'altra piattaforma: Centurion gira nel browser, e Pirates si compila per Linux e per il browser. Il metodo non è nemmeno legato ai vecchi giochi. Un decompilatore, un emulatore di processore e il confronto dei registri funzionano allo stesso modo per un programma più recente; più codice significa più lavoro, non un approccio diverso.
Fonti
- Materiali miei, da entrambi i progetti: appunti dell'analisi del codice, brief per gli agenti, il report dello strumento di confronto (802 esecuzioni, elenco delle differenze trovate), la descrizione del porting di Centurion e le sue specifiche. Non sono pubblicati.
- National Security Agency, Ghidra, la suite di reverse engineering usata per decompilare entrambi i giochi.
- Unicorn Engine, l'emulatore di processori usato per eseguire il codice 68000 originale.
- OpenRetro Game Database, la pagina di Pirates! per Amiga, fonte della schermata del gioco originale.
Uso contenuti generati dall'AI come parte del mio processo quotidiano di apprendimento.