GOOL Interpreter
In the Crash games, the GOOL Interpreter is responsible for interpreting an object's executable bytecode. This bytecode instructs the interpreter to perform a specific sequence of operations that can conditionally or unconditionally alter specific characteristics of the object (ex. appearance, location, status, etc.), and should ultimately render the object according to its changing characteristics. As a result, the player observes a lively, animated, and interactive enemy, box, collectible item, or whatever other kind of in-game object it may be.
Interpretation
A single interpretation of an object's bytecode consists of the following steps:
- Fetch the instruction at the object's program counter location
- Read the instruction at the object's program counter location
- Point the object's program counter to the location of the following instruction
- Perform the operation specified by the fetched instruction
- Continue repeating steps 1 and 2 until a suspending instruction is fetched
(A suspending instruction is typically either a RET, which marks the end of a block, or ANIF/ANIS, which change the object's current frame of animation.)
A single call to the GOOL interpreter routine with some desired object as its first argument results in a single interpretation of that object's bytecode. The GOOL bytecode interpreter routine expects an object that provides the following information:
- The location in its bytecode at which the interpreter shall begin its interpretation = object's program counter
- The location in its memory of a stack frame created for the interpretation = object's frame pointer
- The location in its memory at which the interpreter shall begin pushing the results of/popping the operands for interpreted instructions = object's stack pointer
Prior to an interpretation, each of these fields (program counter, frame pointer, stack pointer) is modified according to the object's current state [descriptor] and the type of interpretation.
Types of interpretation
Many separate interpretations of an object's bytecode can occur within a single frame. The GOOL interpreter routine can be called with the same object from up to 7 distinct locations in a single iteration of the game loop. Each of the 7 calls to the interpreter routine with that object, and thus the ensuing interpretations of its bytecode, occurs under a separate condition. The 7 interpretations are identified based on the conditions under which they occur, and are as follows:
- One that occurs at a specified rate, i.e. after a specific amount of time has elapsed since its previous occurrence. (Code Block interpretation)
- One that [always] occurs [for each frame]. (Trans Block interpretation)
- One that occurs whenever the object is sent an event and its current state specifies the location of an event service routine. (Event Block interpretation)
- One that occurs whenever the object is sent an event and: its current state does not specify an event service routine or its event service routine failed to return an event, and the event sent maps to an event handler. (Handles Block interpretation)
- One that occurs whenever the object changes state. (Head Block interpretation)
- One that occurs whenever the object changes state and its recent Trans Block interpretation was unsuccessful (*Trans Block interpretation)
- One that occurs when the object minimizes the distance during another object's query to 'find the nearest object', and the event sent for asynchronous handling (from the querying object) maps to an event handler. (*Handles Block interpretation)
Ignoring the details for now, the latter 2 types are just a Trans Block and Handles Block interpretation, respectively, although their calls cross-reference the interpreter routine from different locations. Thus, there are a total of 5 types. These 5 types are more simply viewed as 5 separate threads of execution of the object's bytecode. To demonstrate, here is the expanded game loop routine with only the calls to routines that have a descendant call to the interpreter routine:
sub_80011FC4(levelid) { ... C) sub_80011DD0 - Load entries and/or create universal game objects (HUD/display, Crash, Aku Aku, Shadows, Boxes, Fruit) 1) sub_8001C6C8 - CreateObject() [called once for each object created] ... GAME LOOP: { 1) Code for handling game pause/start button press A) If start pressed... i) If pause menu object does not exist.... 1) sub_8001C6C8 - CreateObject() [create pause menu object] ... else 1) sub_80024040 - SendEvent() [destroy pause menu object] ... 2) If Crash object does not exist: A) sub_8002E98C - Create HUD and initialize level i) sub_8001C6C8 - CreateObject() [create HUD object] ii)sub_80026650 - Reinitialize Level 1) sub_8001C6C8 - CreateObject() [create Crash object] ... 3) Code for loading a new level if necessary A) If new level to load ... ii)sub_80011DD0 - Load entries and/or create universal game objects 1) sub_8001C6C8 - CreateObject() [called once for each object created] ... ... 5) Spawn all objects in current zone [for those that have their respawn bit set] A) sub_80025928 - Spawn objects 1) sub_8001BCC8 - SpawnObject() [called for each object spawned] ... ... 9) Update all objects and create transformed primitives for them A) sub_8001D5EC - UpdateObjects() 1) sub_8001DA0C - UpdateObject() [called for each existing object] ... } }
Based on the above, consider an arbitrary frame of execution in terms of a single object. CreateObject() or SpawnObject() will not be called more than once [to create that object] in that frame; that is-the object is not created or spawned more than once, or even spawned after it is created and vice-versa. The object may not even be created/spawned in that frame because it either already exists or does not exist since the game has not requested it be created/spawned. If it is an existing object, it will also be updated only once in that frame. For a single object, the above can be reduced the following series of potential calls:
sub_80011FC4(levelid) { ... GAME LOOP: { ... CreateObject(obj,...) or SpawnObject(obj,...) ... SendEvent(src,obj,...) ... UpdateObject(obj) } }
What the above leaves out, however, are the number of other locations at which a call to the SendEvent() routine reside. In fact, there is nothing preventing the object from being the recipient of more than one event sent (from potentially multiple sources) in a single frame. There may be an interpretation for each event sent to the object during that frame, and there is no limit on the number of events that can be sent. Thus, there may be many calls to SendEvent() [with the object as the recipient] in a single frame, but still only at most one call to CreateObject/SpawnObject() and UpdateObject(). Without getting too far off track, it is simply necessary to understand that each of these routines contains (or calls a routine that calls a routine that contains) a call to the interpreter routine:
sub_80011FC4(levelid) { ... GAME LOOP: { ... CreateObject(obj,...) or SpawnObject(obj,...) InitObject(obj, ...) ChangeObjectState(obj, ...) CreateObjectStackFrame(obj); obj->pc = obj->pchead; InterpretObject(obj, 3, &stateref); // # 1 SendEvent(src,obj,...) -if there is an event service routine for the object (in its current state) CreateObjectStackFrame(obj); obj->pc = obj->pcevent; InterpretObject(obj, 8, &stateref); // # 2 -if there is not an event service routine for the object (in its current state) or the event service routine failed to return an event -if the event maps to an event handler in the event->state/handler map CreateObjectStackFrame(obj); obj->pc = handlerlocation; InterpretObject(obj, 3, &stateref); // # 3 -else ChangeObjectState(obj,state,...); * UpdateObject(obj) -if the object is able to animate -if there is a trans block to interpret for the object (in its current state) CreateObjectStackFrame(obj); obj->pc = obj->pctrans; InterpretObject(obj, 3, &stateref); // # 4 -if at least n ticks have elapsed, where n is given by the 'wait' operand of the most recently suspending animation type instruction of the object's code block InterpretObject(obj, 4, &stateref); // # 5 } }
UNFINISHED Thus, a potential sequence of instruction reads as a result of multiple calls to the interpret
routine for the same object in one iteration of the main game loop could be:
Offset/address | Instruction
---------------|-------------
headBlock: 0x0 | N
0x4 | N
0x8 | N
0xC | S
..... |
eventBlock:0x34| N
0x38| N
0x3C| S
..... |
transBlock:0x50| N
0x54| N
0x58| N
0x5C| N
0x60| N
0x64| N
0x68| S
..... |
codeBlock: 0x70| N
0x74| N
0x78| N
0x7C| N
0x80| S
Where the offsets/addresses of the interpreted instructions from the object's instantiating
executable entries' bytecode item are indicated in the left column along with labels at the offsets
that are pointed to in their respective object fields of the same name, and whether the instructions
either did not suspend execution-causing the next instruction in sequence to be read/interpreted
(indicated by an N)-or did suspend execution-causing the interpreter routine to return to the callee
(indicated by an S).
These object fields/pointers (headBlock, eventBlock, transBlock, codeBlock) define, for some object,
the absolute locations of 'blocks' in the object's code that the object's program counter will be
changed to point at, prior to each respective type of interpretation [during the iteration of the
game loop in which the object is created]. Equivalently, they define the respective entry points for
4 separate -threads- of execution for an object-a head thread, an event thread, a trans thread, and
a code thread. 3 of these object fields/pointers change whenever the object 'changes state', which
is accomplished with the 'changeState()' routine.
Opcode | Name | Encoding/Format | Explicit GOOL ops
in |
Explicit IMM. ops
in |
Implicit STACK ops
in |
Operation | Implicit STACK out | Explicit
GOOL ops out |
Description |
---|---|---|---|---|---|---|---|---|---|
0/0x00 | ADD | 00000000RRRRRRRRRRRRLLLLLLLLLLLL | L,R | O = L + R | O | add | |||
1/0x01 | SUB | 00000001RRRRRRRRRRRRLLLLLLLLLLLL | L,R | O = L - R | O | subtract | |||
2/0x02 | MUL | 00000010RRRRRRRRRRRRLLLLLLLLLLLL | L,R | O = L * R | O | multiply | |||
3/0x03 | DIV | 00000011RRRRRRRRRRRRLLLLLLLLLLLL | L,R | O = L / R | O | divide | |||
4/0X04 | CEQ | 00000100RRRRRRRRRRRRLLLLLLLLLLLL | L,R | O = (L == R),
O = ((L ^ R) == 0) |
O | check if equal | |||
5/0x05 | ANDL | 00000101RRRRRRRRRRRRLLLLLLLLLLLL | L,R | O = (L && R),
O = (L ? (R > 0) : 0) |
O | logical and | |||
6/0x06 | ORL | 00000110RRRRRRRRRRRRLLLLLLLLLLLL | L,R | O = (L || R) | O | logical or | |||
7/0x07 | ANDB | 00000111RRRRRRRRRRRRLLLLLLLLLLLL | L,R | O = L & R | O | bitwise and | |||
8/0x08 | ORB | 00001000RRRRRRRRRRRRLLLLLLLLLLLL | L,R | O = L | R | O | bitwise or | |||
9/0x09 | SLT | 00001001RRRRRRRRRRRRLLLLLLLLLLLL | L,R | O = L < R | O | set less than | |||
10/0x0A | SLE | 00001010RRRRRRRRRRRRLLLLLLLLLLLL | L,R | O = (L <= R) | O | set less than or equal | |||
11/0x0B | SGT | 00001011RRRRRRRRRRRRLLLLLLLLLLLL | L,R | O = L > R | O | set greater than | |||
12/0x0C | SGE | 00001100RRRRRRRRRRRRLLLLLLLLLLLL | L,R | O = (L >= R) | O | set greater than or equal | |||
13/0x0D | MOD | 00001101RRRRRRRRRRRRLLLLLLLLLLLL | L,R | O = L % R | O | modulo | |||
14/0x0E | XOR | 00001110RRRRRRRRRRRRLLLLLLLLLLLL | L,R | O = L ^ R | O | exclusive or | |||
15/0x0F | TST | 00001111RRRRRRRRRRRRLLLLLLLLLLLL | L,R | O = (((L & R) ^ R) == 0) | O | test bit | |||
16/0x10 | RND | 00010000AAAAAAAAAAAABBBBBBBBBBBB | A,B | O = B+(rand() % (A - B)) | O | random | |||
17/0x11 | MOVE | 00010001SSSSSSSSSSSSDDDDDDDDDDDD | S | D = S | [D] | move data | |||
18/0x12 | NOTL | 00010010SSSSSSSSSSSSDDDDDDDDDDDD | S | D = (S == 0) | D | logical not | |||
19/0x13 | PATH | 00010011AAAAAAAAAAAABBBBBBBBBBBB | (A),B | R = 0x100 | [R,A] | varies | P | B | path progress |
20/0x14 | LEA | 00010100SSSSSSSSSSSSDDDDDDDDDDDD | S | D = &S | D | load effective address | |||
21/0x15 | SHA | 00010101RRRRRRRRRRRRLLLLLLLLLLLL | L,R | O = ((R < 0) ? (L >> -R)
: (L << R)) |
O | arithmetic shift | |||
22/0x16 | PSHV | 00010110AAAAAAAAAAAABBBBBBBBBBBB | [A,[B]] | [arg_buf = A]
I = A; J = B; |
[I,[J]] | push value to stack | |||
23/0x17 | NOTB | 00010111SSSSSSSSSSSSDDDDDDDDDDDD | D = ~S | D | bitwise not | ||||
24/0x18 | MOVC | 000110000000RRRRRRIIIIIIIIIIIIII | R,C | see docs | O | move code pointer | |||
25/0x19 | ABS | 00011001SSSSSSSSSSSSDDDDDDDDDDDD | S | D = (S < 0) ? -S: S | D | absolute value | |||
26/0x1A | PAD | 00011010000TDDDDSSPPBBBBBBBBBBBB | B,P,S,D,T | O = testctrls(instr,0) | O | test controller buttons | |||
27/0x1B | SPD | 00011011VVVVVVVVVVVVBBBBBBBBBBBB | V,B | S = B + ((V*gvel) >> 10) | S | calculate speed | |||
28/0x1C | MSC | 00011100PPPPSSSSSLLLXXXXXXXXXXXX | X | P,S,L | various; see docs | *** | *** | multi-purpose | |
29/0x1D | PRS | 00011101PPPPPPPPPPPPDDDDDDDDDDDD | P,D | large calc; see docs | O | driven sine wave | |||
30/0x1E | SSAW | 00011110DDDDDDDDDDDDPPPPPPPPPPPP | M,P | O = (M + frameCount) % P | O | synchronized saw wave | |||
31/0x1F | RGL | 00011111000000000000IIIIIIIIIIII | I | O = globals[I >> 8] | O | read global variable | |||
32/0x20 | WGL | 00100000SSSSSSSSSSSSIIIIIIIIIIII | I,S | globals[I << 8] = *S | write global variable | ||||
33/0x21 | ANGD | 00100001RRRRRRRRRRRRLLLLLLLLLLLL | L,R | O = angdist(L,R) | angle between | ||||
34/0x22 | APCH | 00100010RRRRRRRRRRRRLLLLLLLLLLLL | L,(R) | S = 0x100 | [R,S] | O = approach(L,R,S) | O | approach a value | |
35/0x23 | CVMR | 00100011000IIIIIILLL000000000000 | I,L | O = obj.link[L].colors[I] | color vector or matrix read | ||||
36/0x24 | CVMW | 00100011000IIIIIILLLCCCCCCCCCCCC | C | I,L | obj.link[L].colors[I] = C | color vector or matrix write | |||
37/0x25 | ROT | 00100101RRRRRRRRRRRRLLLLLLLLLLLL | L,(R) | [R,S] | O = rotate(L,R,S,0) | O | approach an angle | ||
38/0x26 | PSHP | 001001100AAAAAAAAAAABBBBBBBBBBBB | [A,[B]] | I = &A; J = &B; | [I,[J]] | push pointer to stack | |||
39/0X27 | ANID | 00100111FFFFFFFFFFFFDDDDDDDDDDDD | F | D=&obj.global.anim[F>>5] | D | set animation | |||
128/0x80 | DBG | 10000000RRRRRRRRRRRRLLLLLLLLLLLL | L,R | debug print ops (beta only) | |||||
129/0x81 | NOP | 10000001000000000000000000000000 | no operation | ||||||
130/0x82 | CFL | 10000010TTCCRRRRRRIIIIIIIIIIIIII | T,C,R,I | general control flow | |||||
BRA | 100000100000RRRRRRVVVVIIIIIIIIII | R,V,I | branch | ||||||
BNEZ | 100000100001RRRRRRVVVVIIIIIIIIII | R,V,I | branch not equal zero | ||||||
BEQZ | 100000100010RRRRRRVVVVIIIIIIIIII | R,V,I | branch equal zero | ||||||
CST | 100000100100RRRRRRSSSSSSSSSSSSSS | R,S | change state | ||||||
CSNZ | 100000100101RRRRRRSSSSSSSSSSSSSS | R,S | change state not zero | ||||||
CSEZ | 100000100110RRRRRRSSSSSSSSSSSSSS | R,S | change state equal zero | ||||||
RET | 100000101000RRRRRRIIIIIIIIIIIIII | R,I | return | ||||||
131/0x83 | ANIS | 10000011HHTTTTTTSSSSSSSSSFFFFFFF | F,S,T,H | W | change animation sequence | ||||
132/0x84 | ANIF | 10000100HHTTTTTTFFFFFFFFFFFFFFFF | F | T,H | W | change animation frame | |||
133/0x85 | VECA | 10000101CCCTTTBBBAAAVVVVVVVVVVVV | V | T,A,B,C | * | ** | multi-purpose vector calcs | ||
134/0x86 | JAL | 10000110VVVV000000IIIIIIIIIIIIII | I,V | jump and link | |||||
135/0x87 | EVNT | 10000111LLLAAARRRRRREEEEEEEEEEEE | E | L,A,R | send an event | ||||
136/0x88 | RSTT | 10001000TTCCRRRRRR************** | R,C,T,* | ** | state return guard = true variant | ||||
137/0x89 | RSTF | 10001001TTCCRRRRRR************** | R,C,T,* | ** | state return guard = false variant | ||||
138/0x8A | CHLD | 10001010AAAATTTTTTTTSSSSSSCCCCCC | C,T,S,A | [C],
arg[0 to A] |
spawn children objects | ||||
139/0x8B | NTRY | 10001011TTTTTTTTTTTTEEEEEEEEEEEE | T,E | multi-purpose page operation | |||||
140/0x8C | SNDA | 10001100AAAAAAAAAAAABBBBBBBBBBBB | A,B | adjust audio levels | |||||
141/0x8D | SNDB | 10001101VVVVRRRRRRSSSSSSSSSSSSS | play sound effect | ||||||
142/0x8E | VECB | 10001110CCCTTTBBBAAAVVVVVVVVVVVV | V | T,A,B,C | multi-purpose vector calcs | ||||
143/0x8F | EVNB | 10001111LLLAAARRRRRREEEEEEEEEEEE | E | L,A,R | broadcast an event | ||||
144/0x90 | EVNU | 10010000LLLAAARRRRRREEEEEEEEEEEE | E | L,A,R | send event unknown variant | ||||
145/0x91 | CHLF | 10100000AAAATTTTTTTTSSSSSSCCCCCC | C,T,S,A | [C],
arg[0 to A] |
spawn children objects;
no replacement if obj pool full |
Specification Format |
A | AAAAAAAAAAAA | AAAA | AAA (EVNT/EVNU/EVNB) | AAA (VECA/VECB) |
---|---|---|---|---|---|
Name | Value A | Argument Count | Argument Count | Vector A Index | |
Specification Format | B | BBBBBBBBBBBB | BBBBBBBBBBBB (PAD) | BBBBBBBBBBBB (SPD) | BBB |
Name | Value B | Controller Buttons | Base Speed | Vector B Index | |
Specification Format | C | CCCCCCCCCCCC | CCCCCC | CCC | CC |
Name | Color Value | Spawn Count | Vector C Index | Conditional Check Type | |
Specification Format | D | DDDDDDDDDDDD | DDDDDDDDDDDD (PRS) | DDDD | |
Name | Destination | Wave Phase | Directional Buttons | ||
Specification Format | E | EEEEEEEEEEEE (NTRY) | EEEEEEEEEEEE (EVNT/EVNU/EVNB) | ||
Name | Entry | Event | |||
Specification Format | F | FFFFFFFFFFFFFFFF | FFFFFFFFFFFF | FFFFFFF | |
Name | Animation Frame | Animation Descriptor Offset | Animation Frame | ||
Specification Format |
H | HH | |||
Name | Horizontal Flip | ||||
Specification Format |
I | IIIIIIIIIIIIII | IIIIIIIIIIII | IIIIIIIIII | IIIIII |
Name | Immediate Code Location | Global Variable Index | Immediate Branch Offset | Color Index | |
Specification Format |
L | LLLLLLLLLLLL | LLL | ||
Name | Left Operand | Object Link Index | |||
Specification Format |
P | PPPPPPPPPPPP | PPPP | PP | |
Name | Wave Period | Primary Operation Subtype | Primary Check Type | ||
Specification Format |
R | RRRRRRRRRRRR | RRRRRR | ||
Name | Right Operand | Object Register Index | |||
Specification Format |
S | SSSSSSSSSSSSSS | SSSSSSSSSSSS | SSSSSSSSS | SSSSSS |
Name | State | Source | Animation Sequence Index | Subtype | |
Specification Format |
SSSSS | SS | |||
Name | Secondary Operation Subtype | Secondary Check Type | |||
Specification Format |
T | TTTTTTTTTTTT | TTTTTTTT | TTTTTT | TTT |
Name | Operation Subtype | (Object) Type | Time | Operation Subtype | |
Specification Format |
TT | T | |||
Name | Operation Subtype | Truth Invert Toggle | |||
Specification Format |
V | VVVVVVVVVVVV | VVVV | VVVV (SNDB) | |
Name | Velocity | Variable Count | Volume |
State Return Instruction Table
Opcode | Name | Encoding/Format | Explicit
IMM. ops in |
Description |
---|---|---|---|---|
136/0x88 | RSTT | 10001000TTCCRRRRRR************** | R,C,T,* | state return guard = true variant |
RST | 100010000100RRRRRRSSSSSSSSSSSSSS | R,S | state return guard = true | |
RSNT | 100010000101RRRRRRSSSSSSSSSSSSSS | R,S | state return if nonzero guard = true | |
RSZT | 100010000110RRRRRRSSSSSSSSSSSSSS | R,S | state return if equal zero guard = true | |
RSCT | 100010000111RRRRRRSSSSSSSSSSSSSS | R,S | state return eval prev cond guard = true | |
RNT | 100010001000RRRRRRxxxxxxxxxxxxxx | R | null return guard = true | |
RNNT | 100010001001RRRRRRxxxxxxxxxxxxxx | R | null return if nonzero guard = true | |
RNZT | 100010001010RRRRRRxxxxxxxxxxxxxx | R | null return if equal zero guard = true | |
RNCT | 100010001011RRRRRRxxxxxxxxxxxxxx | R | null return eval prev cond guard = true | |
GDT | 100010001100RRRRRRxxxxxxxxxxxxxx | R | guard = true | |
GNT | 100010001101RRRRRRxxxxxxxxxxxxxx | R | if nonzero guard = true | |
GZT | 100010001110RRRRRRxxxxxxxxxxxxxx | R | if equal zero guard = true | |
GCT | 100010001111RRRRRRxxxxxxxxxxxxxx | R | eval prev cond guard = true | |
GBNT | 100010000001RRRRRRVVVVIIIIIIIIII | R,V,I | if nonzero guard = true else branch | |
GBZT | 100010000010RRRRRRVVVVIIIIIIIIII | R,V,I | if equal zero guard = true else branch | |
137/0x89 | RSTF | 10001001TTCCRRRRRR************** | R,C,T,* | state return guard = false variant |
RSF | 100010000100RRRRRRSSSSSSSSSSSSSS | R,S | state return guard = false | |
RSNF | 100010000101RRRRRRSSSSSSSSSSSSSS | R,S | state return if nonzero guard = false | |
RSZF | 100010000110RRRRRRSSSSSSSSSSSSSS | R,S | state return if equal zero guard = false | |
RSCF | 100010000111RRRRRRSSSSSSSSSSSSSS | R,S | state return eval prev cond guard = false | |
RNF | 100010001000RRRRRRxxxxxxxxxxxxxx | R | null return guard = false | |
RNNF | 100010001001RRRRRRxxxxxxxxxxxxxx | R | null return if nonzero guard = false | |
RNZF | 100010001010RRRRRRxxxxxxxxxxxxxx | R | null return if equal zero guard = false | |
RNCF | 100010001011RRRRRRxxxxxxxxxxxxxx | R | null return eval prev cond guard = false | |
GDF | 100010001100RRRRRRxxxxxxxxxxxxxx | R | guard = false | |
GNF | 100010001101RRRRRRxxxxxxxxxxxxxx | R | if nonzero guard = false | |
GZF | 100010001110RRRRRRxxxxxxxxxxxxxx | R | if equal zero guard = false | |
GCF | 100010001111RRRRRRxxxxxxxxxxxxxx | R | eval prev cond guard = false | |
GBNF | 100010000001RRRRRRVVVVIIIIIIIIII | R,V,I | if nonzero guard = false else branch | |
GBZF | 100010000010RRRRRRVVVVIIIIIIIIII | R,V,I | if equal zero guard = false else branch |