Artículo · · 15 min de lectura
Le di a un agente de IA unos disquetes de Amiga. Tras una tarde, Pirates! funcionaba en Unity
Le di a un agente de programación cuatro imágenes de disquete de la versión de Amiga de Sid Meier's Pirates! de 1990 y le pedí que llevara el juego a Unity. Trabajó Claude Opus 5.5 en Claude Code. Tras una tarde el juego se podía recorrer desde la pantalla de título, pasando por un duelo, una ciudad y la navegación por el mapa, hasta una batalla naval, con los gráficos y el sonido originales. Más importante que el ritmo es la forma en que se comprobó el resultado: el mismo guion de decisiones se ejecutó en el port en C# y en el código máquina original funcionando en un emulador de procesador. En 802 ejecuciones, unos 6 millones de líneas comparadas, ambas versiones le mostraron al jugador lo mismo.
Describo aquí las etapas de este trabajo porque juntas forman un método que se puede repetir con otro juego y otro modelo. Al final muestro un segundo proyecto hecho con el mismo esquema, el port de Centurion: Defender of Rome de DOS a JavaScript. Ambos son proyectos privados para aprender: los juegos originales, sus gráficos y su sonido siguen protegidos por derechos de autor y no los distribuyo.

La pantalla de título en Unity. La imagen se decodifica en tiempo de ejecución a partir de los archivos originales del juego.
De la imagen de disquete al código descompilado
El agente empezó por identificar los archivos. Una imagen de disquete de Amiga (ADF) es una copia en bruto de los sectores, así que bastaron unas decenas de líneas de Python para extraer el contenido. El programa del juego resultó ser un ejecutable con superposiciones (overlay): un núcleo permanece en memoria y otros tres segmentos de código se leen del disco cuando hacen falta. Ghidra, un descompilador gratuito, no tiene soporte integrado para ese formato. El agente escribió un script que coloca todos los segmentos en direcciones fijas dentro de una sola imagen y convierte 45 saltos indirectos hacia las superposiciones en llamadas normales. Con eso el descompilador vio unas 630 funciones como un único programa.
Los datos del juego trajeron una sorpresa. Los archivos con extensión .rez tienen la estructura de los recursos de Macintosh, porque la versión de Amiga era un port desde Mac. Una vez reconocido el formato, el agente extrajo 210 textos, 82 listas de cadenas, los datos de las ciudades para seis épocas, imágenes, animaciones, sprites y sonidos. Los textos resultaron ser el mejor mapa del código: el número de una cadena que se pasa a la rutina «mostrar texto» indica qué pantalla del juego atiende cada función.
Trece agentes leen el código, nueve lo reescriben
El código descompilado tenía casi 29 mil líneas sin nombres de funciones ni de variables. El agente principal lo cortó en trozos de unas 2 mil líneas y entregó cada uno a un agente distinto con el mismo encargo escrito. Cada uno debía guardar en un archivo la descripción de todas las funciones de su fragmento: fórmulas exactas, constantes, rangos de azar, números de los textos mostrados y variables globales leídas y escritas. El encargo contenía una frase que resultó importante: una fórmula errónea es peor que una laguna señalada. Los agentes debían por tanto marcar lo que no lograban determinar y comprobar los puntos dudosos en el ensamblador. El descompilador perdía aquí muchos argumentos de función, porque el compilador de hace 35 años usaba un tipo int de 16 bits.
La segunda etapa tuvo la misma división. Nueve agentes reescribieron las reglas del juego en C#, cada uno su rango de direcciones. Salieron unas 16 mil líneas de código, y el estado del juego fue a parar a un array de bytes direccionado igual que la memoria del original. No resulta elegante, pero elimina toda una clase de errores. El código antiguo superpone estructuras libremente y lee tablas desde el byte anterior a su comienzo. Un port sobre una imagen de memoria se puede traducir línea a línea y comparar con el ensamblador, y las tablas de constantes quedan donde el código las busca. El dibujo, el sonido y el control se sustituyeron por llamadas a una interfaz que el motor implementa aparte.
El oráculo: el código original en un emulador de procesador
Leer código descompilado no demuestra que el port se comporte como el original. Por eso el agente construyó una herramienta de comparación. El código máquina original del procesador Motorola 68000 se ejecuta en el emulador Unicorn. Las rutinas del sistema, es decir, menús, cuadros de texto, lectura de controles y dibujo, se reemplazaron por funciones en Python. Esas funciones responden a cada pregunta del juego según un generador de decisiones sencillo y registran todo lo que el juego mostró. El port en C# recibe la misma secuencia de decisiones a través de un «jugador guionizado» que funciona fuera de Unity. Al final los dos registros se comparan línea a línea. Los textos contienen el oro, el tamaño de la tripulación, las fechas y los nombres de las ciudades, de modo que la coincidencia de los registros es una prueba sólida de las reglas.
La primera medición completa dio 800 ejecuciones coincidentes de 802. Las dos discrepancias tenían una sola causa: la aritmética de coma flotante. El juego usa una biblioteca por software que no redondea como los procesadores actuales. Su raíz cuadrada de 81 vale 8,999999999999998, y el juego trunca los resultados a enteros, así que la moral de la tripulación salía un punto más alta en el port para ciertas proporciones de oro y tripulación. La corrección consistió en reescribir las rutinas de la biblioteca instrucción por instrucción: 1572 instrucciones, 43 rutinas y 21 701 vectores de prueba calculados por el código original, sin ninguna diferencia. Tras ese cambio coinciden las 802 ejecuciones, y en otras 120 se comparó el estado del juego fotograma a fotograma en el duelo, en la navegación y en las batallas naval y terrestre.
La comparación reveló además dos errores del propio juego de 1990: una lectura del mapa a través de un puntero nulo al atacar una ciudad en diagonal, y una división por cero cuando la tripulación baja a cero. En esos puntos el port tiene que elegir un comportamiento, y la herramienta de comparación tiene un interruptor que hace que el original siga el mismo camino. El resultado también tiene límites. La herramienta no compara el dibujo ni el sonido, y las rutas poco frecuentes, como el matrimonio, el mapa del tesoro completo o los rangos más altos, se ejercitaron poco.
Presentación y añadidos que no existían en 1990
La capa que ve el jugador se construyó en Unity desde el código, sin escenas preparadas. Las imágenes y animaciones se decodifican de los recursos originales, los sprites toman sus colores de la paleta de la pantalla actual, y la música se renderizó a archivos de audio emulando el secuenciador de cuatro canales del juego. El agente controlaba el juego en ejecución desde la línea de comandos, hacía capturas de pantalla y las miraba. Así aparecieron varios defectos que ninguna prueba de reglas detecta, por ejemplo la falta de sonido en la versión para navegador, porque la conversión de audio necesitaba el programa ffmpeg, que no estaba instalado.

La navegación por el mapa. El fondo se construye a partir de una cuadrícula de terreno de 160 por 200 celdas y las baldosas originales.
Después de las primeras partidas pedí tres cambios. La pregunta sobre la fecha de llegada de la flota del tesoro era en el original la protección anticopia y exigía el manual impreso; ahora el cuestionario se mantiene, pero la respuesta correcta aparece en las «notas del capitán». La lista de ciudades mostraba solo los datos de una ciudad, porque el juego daba por hecho que el jugador tenía un mapa impreso; ahora la ciudad elegida aparece marcada en una carta del Caribe. La pantalla del duelo recibió una tabla con la fuerza, la moral y el arma de ambos bandos, dispuesta como en el original. Los añadidos se pueden desactivar y las pruebas de comparación funcionan sin ellos, para que los registros sigan coincidiendo con el código de 1990.

El duelo en el original. Fuente de la captura: OpenRetro Game Database.

La misma pantalla en el port. Las cifras de la tabla se leen en vivo del estado del duelo.

Un añadido respecto al original: la ciudad elegida en una carta del Caribe.
Centurion: el mismo esquema en JavaScript y con jugabilidad ampliada
El segundo proyecto es Centurion: Defender of Rome, un juego de DOS de 1990, llevado a JavaScript y al elemento Canvas, sin emulador. Los pasos fueron los mismos con otros nombres. Primero hubo que desempaquetar el ejecutable, porque estaba comprimido con la herramienta EXEPACK. Cinco módulos del juego (batalla terrestre, arena de gladiadores, carrera de cuadrigas, batalla naval e introducción) estaban en un archivo de superposiciones y compartían un único segmento de datos. El estado de la campaña funciona sobre una copia viva de los 64 kilobytes de ese segmento, porque el motor del juego depende del desbordamiento de 16 bits, la división entera y los bytes con signo, que los números de coma flotante de JavaScript no reproducen por sí solos. La partida guardada coincide con el formato original byte a byte, cosa que verifican las pruebas.

La pantalla de título de Centurion en el navegador, con los gráficos originales.
Aquí fui más allá de un port fiel. Añadí un interruptor de reglas ampliadas, desactivado por defecto: fusión de legiones y varias legiones en una provincia, vélites ligeros, elefantes de guerra y desembarcos desde el mar. El código de este port lo escribieron modelos distintos de los de Pirates: sobre todo Gemini 3.8 Flash, y en parte gpt-6.1-sol de OpenAI ejecutado mediante Codex. La elección se debió a las herramientas que tenía a mano, no a una comparación de modelos.

El menú de opciones. La primera entrada activa las reglas ampliadas que no existían en el original.

La pantalla de provincia con el menú de juegos.
Los problemas con los que se topó Gemini en Centurion
La mayor parte del tiempo se fue en lugares donde el código descompilado o los nombres de los archivos sugerían algo distinto de lo que el juego hace en realidad. Cinco ejemplos de las notas del proyecto:
- Superposiciones sin datos. Los cinco módulos del juego están en un solo archivo,
OVL0.OVL, y se cargan en la misma dirección, pero todos usan un segmento de datos compartido. Por eso el código de una superposición abierto por sí solo en Ghidra no mostraba cadenas ni tablas. Un script unió cada superposición con el segmento de datos en cinco ejecutables sintéticos, y solo entonces el descompilador relacionó el código de los minijuegos con las estructuras de la campaña. - Fronteras de las provincias conquistadas. Contornear una provincia a partir del mapa de zonas en las que se puede hacer clic daba líneas angulosas que no encajaban con el mapa dibujado a mano. El código mostró otro mecanismo: en la imagen del mapa cada provincia tiene una ruta oculta de un píxel de ancho en un color oscuro, y una tabla en los datos da un punto de partida para cada una de las 27 provincias. El juego repinta esa ruta con el color del imperio, empezando por el punto de partida, y el port hace lo mismo.
- Fotogramas que retroceden. El bucle original del juego funcionaba a 9–12 Hz, y un navegador dibuja 60 veces por segundo. En tres lugares un sprite retrocedía un fotograma por un instante. El globo de la introducción gira desplazando 14 colores de la paleta en 10 pasos, pero el primer paso de la tabla vale cero; desplazar la paleta en cada paso producía un tirón de los continentes en cada vuelta. En la marcha de las cohortes y las estocadas de los gladiadores hubo que reproducir el orden con exactitud: primero el fotograma intermedio, después el cambio de posición.
- Nombres de archivo engañosos. Los mapas de bits
BB1.CBMyBB2.CBMparecían estandartes de batalla, y dibujados en la parte superior de la pantalla taparon todo el campo de batalla. El código los carga solo cuando el ordenador no tiene tarjeta VGA: son fondos a pantalla completa para las tarjetas más antiguas EGA y CGA. La misma rutina reveló que el despliegue previo a la batalla tiene dos etapas. Elegir una formación sustituye la lista de tácticas disponibles, y la maniobra de Cannas aparece solo después de elegir la cuña. - Música apenas audible. Tras decodificar las 17 piezas y los 24 instrumentos del controlador de la tarjeta de sonido AdLib, las melodías eran correctas pero sonaban a unos −55 dB. Desensamblar el controlador mostró que en cada nota resta una cuarta parte de su intensidad a la atenuación guardada en el instrumento. Una línea de código devolvió unos 32 dB. Un segundo defecto estaba del lado del navegador: en notas muy cortas, cancelar los cambios de volumen programados en la Web Audio API borraba el ataque que aún estaba en curso y silenciaba la nota.

La segunda etapa del despliegue previo a la batalla: las tácticas disponibles para la formación en cuña.
Un trabajo aparte fueron las carreras de cuadrigas en el Circo Máximo. Allí una cuadriga no es una sola imagen. Mapas de bits separados para cada combinación de tres clases de carro, cuatro colores de auriga, cuatro acciones, dieciséis inclinaciones en la curva y tres fotogramas de galope habrían supuesto más de 2300 fotogramas, así que los autores del juego construyeron en memoria un árbol de nodos de 16 bytes. Cada nodo apunta al siguiente elemento del mismo nivel y a su primer hijo, y guarda un índice de fotograma, una tabla de desplazamientos para los sucesivos fotogramas de la animación y un indicador de visibilidad. La raíz son los cuatro caballos. La caja del carro tiene un desplazamiento horizontal adicional en el que la física de la curva escribe una inclinación de 0 a 15 píxeles, y el auriga es su hijo y hereda esa posición. Por eso en la curva el auriga se inclina junto con la caja mientras los caballos siguen corriendo por el eje de la pista. Las ramas del eje que se rompe y del carro que da vueltas existen todo el tiempo; solo tienen desactivado el indicador de visibilidad.
![]()
El árbol de sprites de la cuadriga reconstruido a partir de los datos del juego. El esquema se hizo en el proyecto con los recursos originales decodificados.
La pista tampoco está hecha de baldosas. El juego expande 72 pares de coordenadas guardados en 368 puntos de control y divide la posición a lo ancho de la pista en 16 carriles. Del carril depende la velocidad segura en la curva, calculada como (número de carril + 15) × 4: junto al muro interior es 60, junto a la barrera exterior 116–120. El carril interior es el camino más corto, pero entrar en la horquilla por encima de ese umbral inclina la caja, empuja el carro hacia fuera y activa un sorteo de accidente, tanto más probable cuanto mayor es el exceso. El marcador rojo de la barra de velocidad le muestra al jugador ese umbral.

Los 368 puntos de la pista y la velocidad segura en la curva según el carril.
Lo que queda de este trabajo además de los juegos
Dejé el método por escrito como un skill, un conjunto de instrucciones que un agente carga cuando recibe una tarea parecida. Contiene tres principios de ambos proyectos. Las reglas del juego se portan con fidelidad y la presentación se sustituye por una interfaz. El port conserva la disposición de memoria del original. No se confía en el resultado hasta que supera una comparación con el código original. El skill incluye también los encargos para los agentes de análisis y de port, y los scripts del proyecto Pirates.
La conclusión más interesante tiene que ver con los aficionados, no con los agentes. Quien tiene un juego que le gusta y lleva años echando algo en falta puede hoy rehacerlo a su manera, de forma rápida y barata, sin el código fuente. En Pirates, el cuestionario que exigía el manual impreso ahora da la respuesta, y apareció un mapa que el juego nunca tuvo. En Centurion añadí reglas que el original no tenía: fusión de legiones, elefantes de guerra y desembarcos desde el mar. Una vez que las reglas del juego son código normal en C# o JavaScript, el siguiente cambio es un cambio de código corriente y no un parche del binario. Del mismo modo se puede llevar un juego a un motor más rápido o a otra plataforma: Centurion funciona en el navegador, y Pirates se compila para Linux y para el navegador. El método tampoco está atado a los juegos antiguos. Un descompilador, un emulador de procesador y la comparación de registros funcionan igual con un programa más reciente; más código significa más trabajo, no otro método.
Fuentes
- Materiales propios de ambos proyectos: notas del análisis del código, encargos para los agentes, informe de la herramienta de comparación (802 ejecuciones, lista de diferencias halladas) y la descripción del port de Centurion con sus especificaciones. No están publicados.
- National Security Agency, Ghidra, el paquete de ingeniería inversa usado para descompilar ambos juegos.
- Unicorn Engine, el emulador de procesadores usado para ejecutar el código 68000 original.
- OpenRetro Game Database, la página de Pirates! para Amiga, fuente de la captura del juego original.