6. The game state: storage and routine calls
The “state of play” is defined as the following: the contents of dynamic memory; the contents of the stack; the value of the program counter (PC), and the “routine call state” (that is, the chain of routines which have called each other in sequence, and the values of their local variables). Note that the routine call state, the stack and the PC must be stored outside the Z-machine memory map, in the interpreter’s private memory.
The entire state of play must be stored when the game is saved.
An internal saved game for “undo” purposes (if there is one) is not part of the state of play. This is important: if a saved game file also contained the internal saved game at the time of saving, it would be impossible to undo the act of restoration. It also prevents internal saved games from growing larger and larger as they include their predecessors.
On a “restore” or “undo” (which restores a game saved into internal memory), the entire state of play is written back except that ’Flags 2′ in the header is preserved. (This information includes whether the game is being transcribed to printer and whether a fixed-pitch font is being used.)
Before a “restore”, an interpreter should check that the file to be used has been saved from the same game currently being played. (See remark below.)
A “restart” is similar: the entire state is restored from the original story file, and the stack is emptied; but ’Flags 2′ is preserved; and the interpreter should reset the Rst parts of the header.
Global variables (variable numbers
$ff) are stored in a table in the Z-machine’s dynamic memory, at a byte address given in word 6 of the header. The table consists of 240 2-byte words and the initial values of the global variables are the values initially contained in the table. (It is legal for a program to alter the table’s contents directly in play, though not for it to change the table’s address.)
Writing to the stack pointer (variable number
$00) pushes a value onto the stack; reading from it pulls a value off. Stack entries are 2-byte words as usual.
The stack is considered as empty at the start of each routine: it is illegal to pull values from it unless values have first been pushed on.
The stack is left empty at the end of each routine: when a return occurs, any values pushed during the routine are thrown away.
The absolute minimum standard for stack size is defined as:
Let the ‘usage’ of a routine call be 4 plus the number of local variables it has. During a game the total of the usages for each routine in the recursive chain of routines being called, plus the game’s own stack usage, can be assumed to never reach 1024.
However, more recent games have required a much larger stack size than this allows for. It is advised that interpreters allow for these games by having a larger stack size if at all possible.
Two examples of modern interpreters with increased stack size are Windows Frotz, with 32768, and nfrotz with 61440.
Routine calls occur in the following circumstances: when one of the
call… opcodes is executed; in Versions 4 and later, when timed keyboard input is being monitored; in Versions 5 and later, when a sound effect finishes; in Version 6, when the game begins (to call the “main” routine); in Version 6, when a “newline interrupt” occurs.
A routine call may have any number of arguments, from 0 to 3 (in Versions 1 to 3) or 0 to 7 (Versions 4 and later). All routines return a value (though sometimes this value is thrown away afterward: for example by opcodes in the form
Routine calls preserve local variables and the stack (except when the return value is stored in a local variable or onto the top of the stack).
A routine call to packed address 0 is legal: it does nothing and returns false (0). Otherwise it is illegal to call a packed address where no routine is present.
When a routine is called, its local variables are created with initial values taken from the routine header (Versions 1 to 4) or with initial value 0 (Versions 5 and later). Next, the arguments are written into the local variables (argument 1 into local 1 and so on).
A “stack frame” is an index to the routine call state (that is, the call-stack of return addresses from routines currently running, and values of local variables within them). This index is a Z-machine number. The interpreter must be able to produce the current value and to set a value further down the call-stack than the current one, effectively throwing away its recent history (see
In Version 6, the Z-machine understands a third kind of stack: a “user stack”, which is a table of words in dynamic memory. The first word in this table always holds the number of spare slots on the stack (so the initial value is the capacity of the stack). The Z-machine makes no check on stack under-flow (i.e., pulling more values than were pushed) which would over-run the length of the table if the program allowed it to happen.
Some interpreters store the whole of dynamic memory to disc as part of their saved game files, which can make them as much as 45K or so long. A player making a serious attack on a game may end up wasting a whole megabyte, more than convenient without a hard disc. A technique invented by Bryan Scattergood, taken up by most modern interpreters, greatly reduces file size by only saving bytes of dynamic memory which differ from the initial state of the game.
It is unspecified how an interpreter should decide whether a saved game file belongs to the game currently being played. It is normal to insist that the release numbers, serial codes and checksums all match. The Pinfocom interpreter deliberately checks only the release number, so that saved games can be exchanged between different editions of Seastalker (presumably compiled to handle the sonarscope differently).
These issues are taken up in great detail in Martin Frost’s Quetzal standard for saved game files, created to allow different interpreters to exchange saved games. This Standard doesn’t require compliance with Quetzal, but interpreter writers are urged to consider it: it can only help authors if players can send them saved games where bugs seem to have appeared.
The stack is stored in the interpreter’s own memory, not anywhere in the Z-machine. The game program has no direct access to the stack memory or stack pointer; on some implementations the game’s main stack is also used to store the routine call state (i.e. the game stack and the call-stack are the same) but this need not be true.
The stack size specification guarantees in particular that if the game itself never uses more than 32 stack entries at once then it can have a recursive depth of at least 90 routine calls. The author believes that old Infocom games will all run with a stack size of 512 words.
Note that the “state of play” does not include numerous input/output settings (the current window, cursor position, splitness or otherwise, which streams are selected, etc.): neither does it include the state of the random-number generator. (Games with elaborate status lines must redraw them after a restore has taken place.)
Zip provides “undo” but most versions of the ITF interpreter do not (and
save_undo returns 0, unfortunately). This is probably its greatest failing. Some Infocom-written interpreters will only provide “undo” to a game which has bit 4 of ’Flags 2′ set: but Inform 5.5 doesn’t set this bit, so modern interpreters should be more generous.
Given the existence of Quetzal, a portable saved file format, it is quite possible that after loading, the game may be running on a different interpreter to that on which the game started. As a result, it is strongly advisable for games to recheck any interpreter capabilities (eg Standard version, unicode support, etc) after loading.