Table des matières

AGI Specifications Resources : Logic resources

Introduction

Au cœur de l'AGI (Adventure Game Interpreter) de Sierra se trouve le fichier “logique”.

Ces fichiers contiennent le code qui compose le jeu. Chaque pièce est accompagnée d'un script logique. Ce script logique régit ce qui peut se passer dans cette salle.

Voici un exemple de ce que le programmeur écrit lors de la création d'un jeu.

Exemple: KQ4. Room 7.

if (said( open, door))       // must be close enough
{
   if (posn( ego, 86, 120, 106, 133))
   {
      if (!night)
      {
         if ( door.open)
         {
            print("The door is already open");
         }
         else
         {
            set( game.control);
            set.priority( ego, 11);
            start.update( door);
            end.of.loop( door, door.done);
         }
      }
      else
      {
         print("You can't -- it's locked");
      }
   }
   else
   {
      set( notCloseEnough);
   }
}

Command list and argument types

Les tableaux suivants présentent une liste de toutes les commandes AGI et de leurs types d'arguments.

Les noms des commandes sont tirés des messages de débogage contenus dans certains jeux AGI.

Opcode Command Args 1 2 3 4 5
01 equaln 2 var num
02 equalv 2 var var
03 lessn 2 var num
04 lessv 2 var var
05 greatern 2 var num
06 greaterv 2 var var
07 isset 1 flag
08 issetv 1 var
09 has 1 item
0A obj.in.room 2 item var
0B posn 5 obj num num num num
0C controller 1 ctr
0D have.key 0
0E said -
0F compare.strings 2 str str
10 obj.in.box 5 obj num num num num
11 center.posn 5 obj num num num num
12 right.posn 5 obj num num num num

Opcode Command Args 1 2 4 4 5 6 7
00 return 0
01 increment 1 var
02 decrement 1 var
03 assignn 2 var num
04 assignv 2 var var
05 addn 2 var num
06 addv 2 var var
07 subn 2 var num
08 subv 2 var var
09 lindirectv 2 var var
0A rindirect 2 var var
0B lindirectn 2 var num
0C set 1 flag
0D reset 1 flag
0E toggle 1 flag
0F set.v 1 var
10 reset.v 1 var
11 toggle.v 1 var
12 new.room 1 num
13 new.room.v 1 var
14 load.logics 1 num
15 load.logics.v 1 var
16 call 1 num
17 call.v 1 var
18 load.pic 1 var
19 draw.pic 1 var
1A show.pic 0
1B discard.pic 1 var
1C overlay.pic 1 var
1D show.pri.screen 0
1E load.view 1 num
1F load.view.v 1 var
20 discard.view 1 num
21 animate.obj 1 obj
22 unanimate.all 0
23 draw 1 obj
24 erase 1 obj
25 position 3 obj num num
26 position.v 3 obj var var
27 get.posn 3 obj var var
28 reposition 3 obj var var
29 set.view 2 obj num
2A set.view.v 2 obj var
2B set.loop 2 obj num
2C set.loop.v 2 obj var
2D fix.loop 1 obj
2E release.loop 1 obj
2F set.cel 2 obj num
30 set.cel.v 2 obj var
31 last.cel 2 obj var
32 current.cel 2 obj var
33 current.loop 2 obj var
34 current.view 2 obj var
35 number.of.loops 2 obj var
36 set.priority 2 obj num
37 set.priority.v 2 obj var
38 release.priority 1 obj
39 get.priority 2 obj var
3A stop.update 1 obj
3B start.update 1 obj
3C force.update 1 obj
3D ignore.horizon 1 obj
3E observe.horizon 1 obj
3F set.horizon 1 num
40 object.on.water 1 obj
41 object.on.land 1 obj
42 object.on.anything 1 obj
43 ignore.objs 1 obj
44 observe.objs 1 obj
45 distance 3 obj obj var
46 stop.cycling 1 obj
47 start.cycling 1 obj
48 normal.cycle 1 obj
49 end.of.loop 2 obj flag
4A reverse.cycle 1 obj
4B reverse.loop 2 obj flag
4C cycle.time 2 obj var
4D stop.motion 1 obj
4E start.motion 1 obj
4F step.size 2 obj var
50 step.time 2 obj var
51 move.obj 5 obj num
52 move.obj.v 5 obj var
53 follow.ego 3 obj num
54 wander 1 obj
55 normal.motion 1 obj
56 set.dir 2 obj var
57 get.dir 2 obj var
58 ignore.blocks 1 obj
59 observe.blocks 1 obj
5A block 4 num num
5B unblock 0
5C get 1 item
5D get.v 1 var
5E drop 1 item
5F put 2 item
60 put.v 2 var var
61 get.room.v 2 var var
62 load.sound 1 num
63 sound 2 num flag
64 stop.sound 0
65 print 1 msg
66 print.v 1 var
67 display 3 num num msg
68 display.v 3 var var var
69 clear.lines 3 num num msg
6A text.screen 0
6B graphics 0
6C set.cursor.char 1 msg
6D set.text.attribute 2 num num
6E shake.screen 1 num
6F configure.screen 3 num num num
70 status.line.on 0
71 status.line.off 0
72 set.string 2 str msg
73 get.string 2 str msg
74 word.to.string 2 word str
75 parse 1 str
76 get.num 2 str var
77 prevent.input 0
78 accept.input 0
79 set.key 3 num num num
7A add.to.pic 7 num num num num num num num
7B add.to.pic.v 7 var var var var var var var
7C status 0
7D save.game 0
7E restore.game 0
7F init.disk 0
80 restart.game 0
81 show.obj 1 num
82 random 3 num num var
83 program.control 0
84 player.control 0
85 obj.status.v 1 var
86 quit 1 num
87 show.mem 0
88 pause 0
89 echo.line 0
8A cancel.line 0
8B init.joy 0
8C toggle.monitor 0
8D version 0
8E script.size 1 num
8F set.game.id 1 msg
90 log 1 msg
91 set.scan.start 0
92 reset.scan.start 0
93 reposition.to 3 obj num num
94 reposition.to.v 3 obj var var
95 trace.on 0
96 trace.info 3 num num num
97 print.at 4 msg num num num
98 print.at.v 4 var num num num
99 discard.view.v 1 var
9A clear.text.rect 5 num num num num num
9B set.upper.left 2 num num
9C set.menu 1 msg
9D set.menu.member 2 msg ctr
9E submit.menu 0
9F enable.member 1 ctr
A0 disable.member 1 ctr
A1 menu.input 0
A2 show.obj.v 1 var
A3 open.dialogue 0
A4 close.dialogue 0
A5 mul.n 2 var num
A6 mul.v 2 var var
A7 div.n 2 var num
A8 div.v 2 var var
A9 close.window 0
AA set.simple 1 ???
AB push.script 0
AC pop.script 0
AD hold.key 0
AE set.pri.base 1 num
AF discard.sound 1 num
B0 hide.mouse 0 1
B1 allow.menu 1 ???
B2 show.mouse 0
B3 fence.mouse 4 num num num num
B4 mouse.posn 2 var var
B5 release.key 0
B6 adj.ego.move.to.xy 0

Logic resource format

le header

L'entête (Header) de chaque script logique a une longueur de sept octets pour les jeux antérieurs à 1988.

Après cette date, une compression semble avoir été introduite et l'en-tête a été modifié par la suite.

Cette compression sera discutée ultérieurement.

Offset Command
0-1 Signature (0x12–0x34)
2 Numéro du volume dans lequel la ressource est contenue
3-4 Longueur de la ressource sans l'entête
5-6 Décalage de la section des messages du code logique

Tout le texte qui peut être imprimé à l'écran à partir d'un script logique est stocké sous une forme cryptée à la fin du script logique.

Exemple: KQ1. Room 2.

 12 34    Signature
 01       vol.1
 5F 06    Length = 0x065F
 BA 02    Text start = 0x02BA

Opcodes

La section du code logique commence immédiatement après l'en-tête et se poursuit jusqu'au début de la section du message.

Trois séries de codes sont utilisées dans un script logique.

La plupart des codes ont entre un et sept arguments inclus. Nous y reviendrons plus loin.

La première série de codes correspond aux commandes AGI elles-mêmes énumérées dans les tableaux précédents et se situe dans la plage 0x00–0xB6.

La deuxième série de codes est la suivante :

Code Command
FF if
FE else or goto
FD not
FC or

À l'heure actuelle, ce sont les seuls codes de grande valeur que l'on rencontre. Les codes if et or sont comme des parenthèses, c'est-à-dire que le code sera au début et à la fin de la section de codes à laquelle il se réfère. L'exemple suivant l'illustre :

Exemple: KQ1, Room 2.

   FF      'if' conditions start.
   07      07 = isset
   05      05 = flag 5
   FF      'if' conditions close.

que l'on peut traduire comme ci-dessous:

if (isset(5))

ce code test si le “flag” numero 5 est actif. Le 0xFF fait passer l'interpréteur en mode de vérification des conditions, ce qui nous amène à la série de codes énumérés dans les tableaux suivants

 0x00 - 0x12    Condition codes.

Lorsque l'interprète rencontre un 0xFF, il interprète les valeurs de code suivantes comme étant dans la plage de codes de condition jusqu'à ce qu'il rencontre le 0xFF suivant, ce qui le ramène en mode de commande AGI normal. normal de l'AGI.

Les deux octets qui suivent immédiatement le deuxième 0xFF déterminent le nombre d'octets que dure cette instruction if avant que le if ne se termine.

Lorsque le deuxième 0xFF est rencontré, l'interprète, que ce soit nous ou la machine, fait trois choses. l'interprète, que ce soit nous ou la machine, fait trois choses :

  1. Il lit les deux octets suivants.
  2. Il ouvre une parenthèse.
  3. Il passe en mode de commande AGI.

Exemple: KQ1, Room 2.

FF 07 05 FF    if (isset(5))
84 00          {			// For 0x0084 bytes.
18 00              load.pic(0);
19 00              draw.pic(0);
1B 00              discard.pic(0);
...		   ...
               }			// Closed. 0x0084 bytes counted.

la commande **else** et plus sur les parenthèses

L'instruction else continue toujours après un bloc de crochets if.

Cette caractéristique est importante et a causé un certain nombre de problèmes dans le passé.

Lorsqu'une instruction else suit une instruction if, la distance entre les crochets après l'instruction if sera plus longue de trois octets (ceci est une conséquence de la manière dont l'interpréteur gère les codes if et else, ce qui est discuté plus loin).

voici un exemple:

if (isset(231)) {                          FF 07 E7 FF 05 00
    printf("The door is already open.");   65 0F
}
else {                                     FE 11 00
    set(36);                               0C 24
    prevent.input();                       77
    start.update(5);                       3B 05
    assignn(152, 3);                       03 98 03
    cycle.time(5, 152);                    4C 05 98
    end.of.loop(5, 232);                   49 05 E8
    sound(70, 154);                        63 46 9A
}

En général, on s'attend à ce que la distance entre les crochets soit de 0x0002, mais dans le cas ci-dessus, elle est clairement de 0x0005, ce qui illustre la différence entre une instruction if directe et une structure if..else.

La situation est la même pour les structures if..else imbriquées.

Les instructions else elles-mêmes ressemblent beaucoup aux instructions if, sauf que leur condition de test est donnée après le code 0xFE, mais est l'inverse de la condition donnée par l'instruction if ci-dessus.

Seule la distance entre les crochets est donnée après le code 0xFE et ensuite l'horloge de commande AGI que l'instruction else englobe.

Test de conditions

Les conditions peuvent être de l'un des types suivants:

 FF 07 05 FF                         Une condition testée, c'est-à-dire isset(5)
 FF FD 07 05 FF                      Une condition Inversée (NOT), c'est-à-dire !isset(5)
 FF 07 05 07 06 FF                   Plusieurs conditions, liée par un ET logique (AND).
 FF FC 07 05 07 06 FC FF             Plusieurs conditions, liée par un OU logique (OR).
 FF FC 07 06 07 06 FC FD 07 08 FF    Combination.

Ces conditions se traduisent comme ceci:

if (isset(5))
if (!isset(5))
if (isset(5) && isset(6))
if (isset(5) || isset(6))
if ((isset(5) || isset(6)) && !isset(7))

Si plusieurs expressions booléennes sont regroupées, leurs valeurs respectives sont combinées ensemble par un ET logique (AND).

Si plusieurs expressions booléennes sont regroupées et entourées d'une paire de codes 0xFC, leurs valeurs sont combinées. leurs valeurs sont combinées par un OU logique (OR).

Le code 0xFD ne s'applique qu'au code de condition suivant dont il inverse la valeur booléenne.

Arguments

Vous vous demandez peut-être comment l'interpréteur sait combien d'arguments possède chaque code (ou fonction) et quel type d'argument est associé à chaque argument.

Cette information est stockée dans le fichier agidata.ovl de la version MS-DOS.

Dans ce fichier, il y a un tableau qui contient quatre octets pour chaque commande AGI et chaque code de condition.

Ces quatre octets sont interprétés comme suit :

Offset Description
0-1 Pointeur vers le code de mise en œuvre
2 Numbre d'arguments
3 Type d'arguments

Le type de valeur des arguments est interprété comme suit :

 Bit        7     6     5     4     3     2     1       0
 command( arg1, arg2, arg3, arg4, arg5, arg6, arg7); (unknown)

Si le bit est activé, l'argument est interprété comme une variable ; sinon, l'argument est interprété comme un nombre.

La fonction du bit 0 n'est pas connue, car aucune commande AGI ni aucun code de condition AGI n'a plus de sept arguments.

Examples:

la section messages

La section des messages d'un script logique contient toutes les chaînes qui peuvent être affichées par ce script logique.

Ces chaînes sont cryptées par XOR tous les onze octets avec la chaîne “Avis Durgan”.

Exemple: KQ1, Room 2.

if (said(look, alligators))
{
    print("The alligators are swimming in the moat.");
}

Dans l'exemple ci-dessus, l'instruction d'impression est représentée comme suit :

 65 08

Le 0x08 est le numéro attribué à la chaîne et correspond à sa position dans la liste des chaînes à la fin du script logique.

Le format de la section message est le suivant :

Offset Description
0 Number of messages
1-2 Pointer to the end of the messages
3-4 Array of message pointers
Array of message pointers
? Start of the text data. From this point the messages are encrypted with Avis Durgan (in their unencrypted form, each message is separated by a 0x00 value)

Implementation

The implementation for each AGI statement is found in the agi/ file. This is the AGI interpreter itself. The data in the agidata.ovl file is used to find the start of the implementation for an AGI statement. Below are a couple of examples:

Example: MH2, equaln.

;equaln   (eg.   if (work = 3)   )
0D71 AC            LODSB                       ;get variable number
0D72 32FF          XOR     BH,BH
0D74 8AD8          MOV     BL,AL
0D76 AC            LODSB                       ;get test number
0D77 3A870900      CMP     AL,[BX+0009]        ;test if var = number
0D7B B000          MOV     AL,00               ;return 0 if not equal
0D7D 7502          JNZ     0D81
0D7F FEC0          INC     AL                  ;return 1 if equal
0D81 C3            RET

Example: MH2, equalv.

;equalv  (eg.   if (work = maxwork)   )
0D82 AC            LODSB                       ;get first var number
0D83 32FF          XOR     BH,BH               ;clear bh
0D85 8AD8          MOV     BL,AL               ;BX = variable number
0D87 8AA70900      MOV     AH,[BX+0009]        ;get first var value
0D8B AC            LODSB                       ;get second var number
0D8C 8AD8          MOV     BL,AL
0D8E 32C0          XOR     AL,AL               ;return 0 if not equal
0D90 3AA70900      CMP     AH,[BX+0009]        ;compare variables
0D94 7502          JNZ     0D98
0D96 FEC0          INC     AL                  ;return 1 if equal
0D98 C3            RET

These two examples show the difference between how numbers and variables are dealt with. In the case of a variable, the variables number is used as an index into the table of variable values to get the value which is being tested. It appears that the variable table is at offset 0x0009 in the data segment.

How the interpreter handles the code

The following 8086 assembly language code is the actual code from the MS-DOS version of Manhunter: San Francisco. There are some calls to routines which aren't displayed. Take my word for it that they do what the comment says. For those of you who can't follow whats going on, I'll explain the interpretation in steps after the code block.

;Decoding a LOGIC file.
1E6C:2EF2 56            PUSH  SI
1E6C:2EF3 57            PUSH  DI
1E6C:2EF4 55            PUSH  BP
1E6C:2EF5 8BEC          MOV   BP,SP
1E6C:2EF7 83EC02        SUB   SP,+02
1E6C:2EFA 8B7608        MOV   SI,[BP+08]    ;SI -> start of LOGIC script.
1E6C:2EFD 8B7406        MOV   SI,[SI+06]    ;Skip first 6 bytes (header).
1E6C:2F00 AC            LODSB               ;Get next byte in LOGIC file.
1E6C:2F01 84C0          TEST  AL,AL         ;Is code a zero?
1E6C:2F03 7414          JZ    2F19          ;If so, jump to exit.
1E6C:2F05 3CFF          CMP   AL,FF         ;If an opening 'if' code is found
1E6C:2F07 7419          JZ    2F22          ;jump to 'if' handler.
1E6C:2F09 3CFE          CMP   AL,FE         ;If an 'else' has not been found
1E6C:2F0B 7505          JNZ   2F12          ;jump over else/branch.
1E6C:2F0D AD            LODSW               ;Get word (bracket distance)
1E6C:2F0E 03F0          ADD   SI,AX         ;Add to SI. Skip else code.
1E6C:2F10 EBEE          JMP   2F00          ;Go back to get next byte.
1E6C:2F12 E8A8D6        CALL  05BD          ;Execute AGI command.
1E6C:2F15 85F6          TEST  SI,SI         ;
1E6C:2F17 75E8          JNZ   2F01          ;Jump back to top.
1E6C:2F19 8BC6          MOV   AX,SI
1E6C:2F1B 83C402        ADD   SP,+02
1E6C:2F1E 5D            POP   BP
1E6C:2F1F 5F            POP   DI
1E6C:2F20 5E            POP   SI
1E6C:2F21 C3            RET
 
;Handler for 'if' statement.
;BH determines if its in an OR bracket (BH=1 means OR).
;BL determines the nature of the evalutation (BL=1 means NOT)
1E6C:2F22 33DB          XOR   BX,BX
1E6C:2F24 AC            LODSB               ;Get next byte
1E6C:2F25 3CFC          CMP   AL,FC         ;If less than 0xFC, then
1E6C:2F27 721C          JB    2F45          ;jump to normal processing.
1E6C:2F29 7508          JNZ   2F33          ;If greater, jump to 'if' close.
1E6C:2F2B 84FF          TEST  BH,BH         ;(Could BH be the evaluation reg?
1E6C:2F2D 7551          JNZ   2F80          ;or whether its the second FC?
1E6C:2F2F FEC7          INC   BH            ;
1E6C:2F31 EBF1          JMP   2F24          ;Go back to get next byte.
 
1E6C:2F33 3CFF          CMP   AL,FF         ;Is the code for an 'if' close?
1E6C:2F35 7505          JNZ   2F3C          ;If not, jump to 'not' test.
1E6C:2F37 83C602        ADD   SI,+02        ;
1E6C:2F3A EBC4          JMP   2F00          ;
1E6C:2F3C 3CFD          CMP   AL,FD         ;Is the code for a 'not'?
1E6C:2F3E 7505          JNZ   2F45          ;If not, jump to test command.
1E6C:2F40 80F301        XOR   BL,01         ;
1E6C:2F43 EBDF          JMP   2F24          ;Go back to get next byte.
1E6C:2F45 53            PUSH  BX            ;BX = test conditions??
1E6C:2F46 E8E8DD        CALL  0D31          ;Evaluate separate test command.
1E6C:2F49 5B            POP   BX            ;
1E6C:2F4A 32C3          XOR   AL,BL         ;Toggle the result for NOT.
1E6C:2F4C B300          MOV   BL,00         ;
1E6C:2F4E 7506          JNZ   2F56          ;If true jump to 2F56.
1E6C:2F50 84FF          TEST  BH,BH         ;If BH=0 then not in OR and
1E6C:2F52 742C          JZ    2F80          ;test is truely false.
1E6C:2F54 EBCE          JMP   2F24          ;Otherwise evaluate next OR.
1E6C:2F56 84FF          TEST  BH,BH         ;Are we in OR mode?
1E6C:2F58 7424          JZ    2F7E          ;If not, continue with testing.
1E6C:2F5A 32FF          XOR   BH,BH         ;If so, then we will skip the
1E6C:2F5C 32E4          XOR   AH,AH         ;rest of the tests in the OR
1E6C:2F5E AC            LODSB               ;bracket since the first is true.
1E6C:2F5F 3CFC          CMP   AL,FC         ;OR: Waiting for closing OR.
1E6C:2F61 741B          JZ    2F7E          ;If OR found, then continue testing.
1E6C:2F63 77F9          JA    2F5E          ;
1E6C:2F65 3C0E          CMP   AL,0E         ;If 'said' then goto said handler
1E6C:2F67 7507          JNZ   2F70          ;else goto normal handler
1E6C:2F69 AC            LODSB               ;Work out number of words in said
1E6C:2F6A D1E0          SHL   AX,1          ;and jump over them.
1E6C:2F6C 03F0          ADD   SI,AX         ;
1E6C:2F6E EBEE          JMP   2F5E          ;
1E6C:2F70 8BF8          MOV   DI,AX         ;Jumps over arguments.
1E6C:2F72 D1E7          SHL   DI,1          ;
1E6C:2F74 D1E7          SHL   DI,1          ;
1E6C:2F76 8A856407      MOV   AL,[DI+0764]  ;Load up the number of arguments
1E6C:2F7A 03F0          ADD   SI,AX         ;Add to the execution pointer
1E6C:2F7C EBE0          JMP   2F5E          ;
1E6C:2F7E EBA4          JMP   2F24
 
;Test is false.
;This routine basically skips over the rest of the codes until it finds the
;closing 0xFF at which point it will load the following two bytes and add
;them to the execution pointer SI.
1E6C:2F80 32FF          XOR   BH,BH
1E6C:2F82 32E4          XOR   AH,AH
1E6C:2F84 AC            LODSB               ;
1E6C:2F85 3CFF          CMP   AL,FF         ;If the closing 0XFF is found,
1E6C:2F87 741D          JZ    2FA6          ;jump 2FA6.
1E6C:2F89 3CFC          CMP   AL,FC         ;If greater than FC,
1E6C:2F8B 73F7          JNB   2F84          ;get next byte.
1E6C:2F8D 3C0E          CMP   AL,0E         ;If 'said' then goto said handler
1E6C:2F8F 7507          JNZ   2F98          ;else goto normal handler.
1E6C:2F91 AC            LODSB               ;Work out number of words in said
1E6C:2F92 D1E0          SHL   AX,1          ;and jump over them.
1E6C:2F94 03F0          ADD   SI,AX
1E6C:2F96 EBEC          JMP   2F84
1E6C:2F98 8AD8          MOV   BL,AL         ;Jump over arguments.
1E6C:2F9A D1E3          SHL   BX,1
1E6C:2F9C D1E3          SHL   BX,1
1E6C:2F9E 8A876407      MOV   AL,[BX+0764]  ;Load up the number of arguments.
1E6C:2FA2 03F0          ADD   SI,AX         ;Add to the execution pointer.
1E6C:2FA4 EBDE          JMP   2F84
1E6C:2FA6 AD            LODSW
1E6C:2FA7 03F0          ADD   SI,AX         ;Skip over if (includes 3 else bytes)
1E6C:2FA9 E954FF        JMP   2F00

Situation 1. Every logic script starts in normal AGI command execution mode. In this routine, if the code is below 0xFC, then it is presumed to be an AGI command. It will then call the main command execution routine which will jump to the relevant routine for the specific command using the jump table stored in agidata.ovl. The command is performed and it returns to the main execution routine where it loops back to the top and deals with the next code in the logic file.

Situation 2. If the code is an 0xFF code, then if jumps to the if statement handler. In this routine is basically assesses whether the whole test condition evaluates to true or to false. It does this by treating each test separately and calling the relevant test command routines using the jump table in the agidata.ovl file. Each test command routine will return a value in AL which says whether it is true or not. Depending on the NOTs and ORs, the whole expression is evaluated. If at any stage during the evaluation the routine decides that the expression will be false, it exits to another routine which skips the rest of the if statement and then adds the two byte word following the closing 0xFF code to the execution pointer. This usually has the affect of jumping over the if block of code. If the if handler gets to the ending 0xFF then it knows the expression is true simply because it hasn't exited out of the routine yet. At this stage it jumps over the two bytes following the closing 0xFF and then goes back to executing straight AGI commands.

Situation 3. If in the normal execution of AGI commands, the code 0xFE is encountered, a very simple action takes place. The two bytes which follow form a 16-bit twos complement value which is added to execution pointer. This is all it does. Previously we said that the 0xFE code stood for the else statement which is in actual fact correct for over 90% of the time, but the small number of other occurrences are best described as goto statements. If you're confused by this, the following example will probably explain things.

Example:

if (said( open, door)) {
    // first block of AGI statements
}
else {
    // second block of AGI statements
}

The above example is how the original coder would have written the AGI code. If we now look at the following example, it is not hard to see that it would achieve the same thing.

if (!said( open, door)) goto label1;
    // first block of AGI statements
    goto label2:
 
label1:
    // second block of AGI statements
 
label2:

This is exactly how all ifs and elses are implemented in the logic code. The if statement is a conditional branch where the branch is taken if the condition is not met, while the else statement is a nonconditional jump. If a 0xFE code appears in the middle of some AGI code and wasn't actually originally coded as an else, then it was most likely a goto statement.

The **said** test command

The above assembly language code does raise a very important point. The said command can have a variable number of arguments. Its code is 0x0E, and the byte following this byte gives the number of two byte words that follow as parameters.

Examples:

if (said(marble))                          FF 0E 01 1E 01 FF
if (said( open, door))                     FF 0E 02 37 02 73 00 FF

In the above examples, the values 0x011E, 0x0237, and 0x0073 are just random word numbers that could stand for the words given.

Inner loops

At first I almost totally discarded the existence of loops in the AGI code because it seemed to me that execution of the logic script continually looped. Loop code like “while”, “do..while”, and “for” statements wouldn't be needed because you could just use a variable to increment with each pass and an if statement to test the value of the variable and take action if it was withing the desired range.

Example:

if (greatern(30, 45) && lessn(30, 55)) {
    print("You're in the hot zone!");
    increment(30);
}

I have found evidence of this sort of thing taking place which means that they must loop over continuously. I don't know whether this is something that the interpreter does itself or whether it is part of the AGI code, e.g. at the end of one logic script it calls another which then calls the first one again. With the existence of the conditional branching and unconditional branching nature of the if and else statement, it is easy to see that some of the structures such as “do..while” can infact be coded into logic scripts.

Example:

FF FD 0D FF 03 00 FE F7 FF
 
do {
} while (!havekey);

The above translation is a simple one which is taken from SQ2. The value 0xFFF7 is the twos complement notation for -9 which is the exact branching value to take the execution back to the start of the if statement. If the above example had AGI code between the 0x00 and the 0xFE, then there would be code within the brackets of the “do..while” structure. I don't know whether the original AGI coders used these statements or used goto statements to achieve the same result.