Corrigiendo el bug de HABLA en DAAD para Spectrum.

El mundo de las aventuras conversacionales se halla dividido desde los felices 80 en dos hemisferios que se dan la espalda el uno al otro: en uno lo común es manejar la comunicación con los «personajes seudo-inteligentes» mediante el comando DECIR seguido de un entrecomillado con la frase exacta que le dices al personaje (DECIR a PERSONAJE «bla-bla-bla»), en el otro el protocolo consiste en usar la fórmula HABLAR con PERSONAJE, pudiendo refinarse con la variante HABLAR con PERSONAJE sobre TEMA para afinar más en el asunto de la conversación en sí. El primero es común en los sistemas basados en condactos, como en la saga de herramientas de Gilsoft (Quill, PAWS, DAAD), especialmente optimizadas para funcionar así, mientras que el segundo se hizo habitual en las obras de Infocom, dada su mejor adaptación a los sistemas de programación orientada a objetos y es por tanto el que se usa comúnmente hoy en día en lenguajes como TADS, Inform, etc…
Sobre las posibilidades de evolución y expansión del segundo se habla largo y tendido en el capítulo dedicado a los personajes del libro Creating Interactive Fiction with Inform 7 de Aaron A. Reed. A su vez se puede leer una comparativa con los pros y contras de uno y otro en este reciente post del blog de Ricardo Oyon.

Sistemas de conversación en el libro de Aaron A. Reed.

Lo cierto es que no hay ninguna razóin técnica para circunscribir exclusivamente ambas tradiciones a un tipo de sistema de desarrollo u otro. Es perfectamente posible «cambiarlas de entorno» a gusto del autor. Así, han sido muchos los casos de aventuras hechas con sistemas de condactos que se decantan por la fórmula de HABLAR con PERSONAJE, pero en el caso de las plataformas de 8 bits se han encontrado sistemáticamente con un pequeño escollo: la incompatibilidad del uso de la forma imperativa de verbo HABLAR («HABLA») con los sistemas existentes de asociar los verbos acabados en LO, LA, LOS, LAS con el objeto de la orden inmediatamente anterior.

Estos sitemas, que permiten el uso de secuencias de órdenes como EXAMINA LIBRO y COGELO no generan ningún conflicto en sus equivalentes ingleses basados en los pronombred IT o THEM (EXAMINE BOOK and TAKE IT) pero en los sistemas españoles de la época clásica que lo implementaban (salvo error u omisión: PAWS y DAAD) tenían un efecto secundario inesperado al entrar en juego la orden HABLA. Si la secuencia de comandos del jugador era algo parecido a EXAMINA LIBRO y HABLA con PERSONAJE, la terminación en LA del imperativo HABLA activaría automáticamente el sistema de tal modo que el parser entendería que en la segunda órden el jugador está intentando hablar con el libro. Este «bug» es ya un «viejo conocido» entre los usuarios de PAWS, pudiendo rastrearse menciones al mismo en los manuales de las primeras aventuras del Doctor Van Halen de Josep Coletas allá por 2004, aunque no hay que descartar que las haya anteriores.

El bug de HABLA en el manual del primer Dr. VanHalen.

Es un hecho inapelable que el sistema funciona codificado «a fuego» en las entrañas del parser de PAWS e igualmente se halla en el interior del código de DAAD que maneja el funcionamiento del condacto PARSE. En principio eso significa que no hay manera de controlar este comportamiento por el autor de la aventura salvo acciones drásticas como usar condactos para eliminar todo el sistema de raíz, una solución claramente insatisfactoria porque supone prescindir totalmente de una característica del sistema simplemente porque falla en un caso concreto. Lo cierto es que el único modo de soslayar el problema pasa inequívocamente por hacer un hack al intérprete.

Vamos a proponer un modo de hacerlo en el caso del intérprete de DAAD para Spectrum. En otros intérpretes de DAAD o en el del PAWS podría aplicarse seguramente un metodo parecido salvando las diferencias de direcciones concretas de memoria en cada caso, pero no tengo tiempo material de ponerme a investigarlo 😦
Para «destripar» el interior del intérprete de Speccy vale cualquier desensamblador, pero por sus facilidades para hacer maniobras de ingeniería inversa he usado SkoolKit, un conjunto de scripts de Python especializado en «meter mano» a software de Spectrum y ponérselo facilito a quienes quieran analizar códigos fuente. A su vez aprovechamos que en DAAD la parte de código que queremos analizar se halla «aislada» en las rutinas que se ejecuten al invocarse el condacto PARSE (en el proceso 1 siguiendo la plantilla por defecto de DAAD) para añadir un pequeño proceso de «debug» que permita echar un vistazo al estado de las banderas justo antes y después de éste.
En las imágenes se puede ver que llamamos a un proceso 12 que nos imprime el estado de unos cuantos flags estratégicos (el grupo 33-36 y 43-47, ver el manual para sus usos concretos). Mediante este truco pudimos determinar que PARSE, al encontrarse con un verbo acabado en LO, LA, LOS, LAS adjudica al flag 34 (nombre) el valor del nobre de la acción anterior y a su vez coloca en 44 (NOUN2) el valor que hubiera correspondido al nombre de la entrada del jugador si no se hubiera detectado la terminación. Esto último es útil para secuencias de órdenes del tipo: EXAMINA LIBRO y DASELO a PERSONAJE, donde en la segunda orden el nombre es el nombre de la primera y el que hubiera sido el nombre original pasa a ser el segundo nombre o NOUN2. Si hubiera habido un NOUN2 en el input original este pasa a ser ignorado (tomen nota del detallito los autores de intérpretes alternativos 🙂 ).

A su vez con la opción de monitorear la memoria del programa durante la ejecución disponible en la práctica totalidad de emuladores de Spectrum es fácil determinar las posiciones exactas de memoria que ocupan las banderas o flags del DAAD. En el caso del Spectrum los 256 flags están entre 32540 y 32795. Sabiendo ésto es fácil deducir que los flags de verbo (33), NOUN (34) y NOUN2 (44) están respectivamente en 32573, 32574 y 32584.

Entra en acción SkoolKit. Podemos rebuscar entre las rutinas del código desensamblado instrucciones que afecten a esas direcciones de memoria o, en su defecto, al valor indicado por el registro IX del procesador más el número de flag, ya que en DAAD de Spectrum, IX almacena la dirección de inicio de estos. Ahora ya es cuestión de ensayo y error, paciencia, y algo de suerte para encontrar algo que resulte significativo para nuestro propósito. Tirando del hilo pronto aparece esta rútina que vemos en la imagen, con comentarios añadidos por mí aprovechando las facilidades de SkoolKit:

SkoolKit destripando a DAAD.

En ella podemos ver que en cierto momento de la ejecución, el parser mira en la zona de memoria donde ha almacenado el verbo del input del jugador y examina desde su final hacia atras. Si los caracteres que encuentra allí se corresponden con LO, LA LOS o LAS, pone una marca en el registro A, iza el flag Z del procesador, y el programa se bifurca hacia otra parte.

Monitoreando, es posible determinar que en ese preciso momento el flag 33 (verbo) ya está establecido, no así los referentes a NOUN, NOUN2, adjetivos y adverbio que continúan en 255 (255 es el valor al que se resetean en cada turno o sentencia lógica). Es fácil deducir que la asignación de estos se hará de una manera si no se ha detectado terminación y de otra en caso contrario. Es… ¡un buen sitio donde intervenir!

Podemos sustituir las instrucciones de salto marcadas en la imagen por una llamada a una pequeña rutina creada expresamente para la ocasión que colocaríamos en algún lugar libre de la memoria. Normalmente éste sería alguna posición indeterminada entre el final de la base de datos del juego y el comienzo de la de los gráficos, que es la zona de memoria que DAAD deja libre en Spectrum, pero eso significaría que tendría que ser una ubicación distinta en cada juego, ya que nunca sabremos a priori donde empezará y terminará ese espacio. Como la rutina va a ser en realidad pequeñita podemos buscarle un sitio estable razonablemente seguro que puede ser un puñado de bytes por debajo del comienzo del intérprete, concretamente la dirección 24560. La única precaución a tener en cuenta en este caso es que el cargador de BASIC del juego ponga la habitual instrucción CLEAR un byte más abajo, es decir 24559 en lugar del 24575 original.
Así que, como decía, sustituimos esas dos instrucciones del código DAAD original por un JP 24560. Las dos instrucciones juntas ocupaban 5 bytes y nuestro JUMP sólo 3 así que los 2 bytes restantes los ponemos a 0. La rutina a la que llamamos hará algo como esto:

ORG 24560
      JP NZ, 27449
      LD A, (32573)
      CP 31
      JP Z, 27449
      LD A, 6
      JP 27677

Veámoslo detenidamente. Hemos tomado nota como si entráramos en el molino del Quijote, o sea, cuidadosamente :-p, del estado de los flags del procesador en el momento de llegar al punto en que nos desviamos del código original. En este se levanta el flag Z si se ha encontrado la terminación verbal (y se carga el registro A con 6). Si ése es el caso, el código se desvía a 27677 y si no (no hay terminación) el desarrollo normal del programa sigue por 27449.

En nuestro hack primero mandamos la ejecución a 27449 si no está puesto el flag Z, o sea, le decimos que continue con normalidad porque no hay nada de LAS ni LOS.

A continuación cargamos en A el valor de 32573, donde sabemos que está el valor del verbo.

Comparamos con 31, que es el valor por defecto en la plantilla de DAAD para el verbo HABLAR. Ésto, por supuesto, será cierto en una mayoría de los casos, pero también podría darse perfectamente el caso de que en un juego concreto el verbo HABLAR tenga cualquier otro valor en la sección de vocabulario (/VOC) de su código fuente. Para esta situación bastará con que el cargador BASIC de la aventura haga, tras cargar el intérprete, un POKE 24567, valor (siendo valor el número que HABLAR tenga en VOC).

Si el verbo era, efectivamente HABLAR (31 o el valor que fuera en su caso) podemos afirmar por eliminación y con un 100% de seguridad que se ha detectado un LA y que el verbo era HABLAR, por lo que sabemos a ciencia cierta que, de seguir el flujo normal del programa, se produciría el cambio de NOUNs. Por eso le decimos que de estar izado el flag Z se vaya a 27449, donde procesará la entrada del jugador como si la terminación NO se hubiera detectado.

Y en caso contrario podemos asumir también con seguridad que se ha encontrado la terminación, pero el verbo NO era HABLAR, así que, tras cargar A con 6 como hubiera hecho en la rutina original, le decimos que vaya a 27677, donde procederá a hacer los cambios de NOUNs igual que hubiera hecho normalmente.

Tenéis el intérprete de DAAD de Spectrum con todas estas modificaciones disponible en esta DESCARGA.

En el archivo ZIP enlazado hay:

-Un binario con el intérprete modificado tal cual, sin ningún tipo de cabecera.
-Una versión alternativa del disco nº 33 de la descarga original del DAAD, el que contenía el intérprete de Spectrum en un disco de Spectrum +3, en la que el intérprete original y el cargdor BASIC han sido sustituidos por la versión hackeada.
-Una imagen de cinta de Spectrum TAP con un pequeño ejemplo.
-Un fichero de texto con las indicaciones básicas para su uso.

El primer juego de DAAD español para Spectrum en incluir la modificación es Torreoscura, de Bieno Marti, cuya versión de Spectrum actualizada ya está disponible en su web habitual: AQUÍ. Podéis probar nada más empezar con PULSA TIMBRE y HABLA con RECEPCIONISTA, algo que no funcionaba en la primera versión salvo que utilizaras el infinitivo HABLAR.

Torreoscura ya entiende HABLA en imperativo.

Y por lo que he podido probar hasta el momento, el hack es efectivo para órdenes tanto del tipo HABLA con PERSONAJE como de la modalidad HABLA con PERSONAJE sobre TEMA. Sabiendo cómo funciona el proceso en Spectrum sería posible buscar el modo de hacer lo propio en PAWS y en el resto de intérpretes de DAAD (pero, al contrario que en el clásico de los Rolling, el tiempo, eer… no está de mi lado 🙂 ).

Una respuesta to “Corrigiendo el bug de HABLA en DAAD para Spectrum.”

  1. baltasarq Says:

    Excelente trabajo, Rockersucke tienes mucha paciencia y unas extraordinarias dotes para explorar el ensamblador de la tripas del DAAD.

Deja un comentario