How wide the limits stand
Between a splendid and an happy land.
— Oliver Goldsmith (1728–1774), The Deserted Village
The Z-machine is well-designed, and has three major advantages: it is compact, widely portable and can be quickly executed. Nevertheless, like any rigidly defined format it imposes limitations. This section is intended to help those few designers who encounter the current limits. Some of the economy-measures below may sound like increasingly desperate manoeuvres in a lost battle, but if so then the cavalry is on its way: Andrew Plotkin has written a hybrid version of Inform which removes almost every restriction. Although it doesn't quite have all the nooks and crannies of Inform yet working, it does allow most games to compile without difficulty to a very much larger virtual machine than the Z-machine called “glulx”.
1. Story file size. The maximum size of a story file (in K) is given by:
V3 | V4 | V5 | V6 | V7 | V8 |
128 | 256 | 256 | 512 | 320 | 512 |
Because the centralised library of Inform is efficient in terms of not duplicating code, even 128K allows for a game at least half as large again as a typical old-style Infocom game. Inform is normally used only to produce story files of Versions 5, 6 and 8. Version 5 is the default; Version 6 should be used where pictures or other graphical features are essential to the game; Version 8 is a size extension for Version 5, allowing games of fairly gargantuan proportions.
▲ If story file memory does become short, a standard mechanism can save about 810% of the total memory, though it will not greatly affect readable memory extent. Inform does not usually trouble with this economy measure, since there's very seldom any need, and it makes the compiler run about 10% slower. What you need to do is define abbreviations and then run the compiler in its “economy” mode (using the switch -e). For instance, the directive
Abbreviate " the ";
(placed before any text appears) will cause the string “ the ” to be internally stored as a single ‘letter’, saving memory every time it occurs (about 2,500 times in ‘Curses’, for instance). You can have up to 64 abbreviations. When choosing abbreviations, avoid proper nouns and instead pick on short combinations of a space and common two- or three-letter blocks. Good choices include " the ", "The ", ", ", " and ", "you", " a ", "ing ", " to". You can even get Inform to work out by itself what a good stock of abbreviations would be, by setting the -u switch: but be warned, this makes the compiler run about 29,000% slower.
2. Readable memory size. In a very large game, or even a small one if it uses unusually large or very many arrays, the designer may run up against the following Inform fatal error message:
This program has overflowed the maximum readable-memory size of the Z-machine format. See the memory map below: the start of the area marked “above readable memory” must be brought down to $10000 or less.
In other words, the readable-memory area is absolutely limited to 65,536 bytes in all Versions. Using the -D debugging option increases the amount of readable-memory consumed, and the Infix -X switch increases this further yet. (For instance ‘Advent’ normally uses 24,820 bytes, but 25,276 with -D and 28,908 with -X.) The following table suggests what is, and what is not, worth economising on.
Each … | Costs … |
Routine | 0 |
Text in double-quotes | 0 |
Object or class | 26 |
Common property value | 3 |
Non-common property value | 5 |
If a property holds an array | add 2 for each entry after the first |
Dictionary word | 9 |
Verb | 3 |
Different action | 4 |
Grammar token | 3 |
--> or table array entry | 2 |
-> or string array entry | 1 |
To draw some morals: verbs, actions, grammar and the dictionary consume little readable memory and are too useful to economise on. Objects and arrays are where savings can be made. Here is one strategy for doing so.
2a. Economise on arrays.
Many programmers create arrays with more entries than needed, saying
in effect “I'm not sure how many this will take, but it's bound
to be less than 1,000, so I'll say 1,000 entries to be on the safe
side.” More thought will often reduce the number. If not, look at
the typical contents. Are the possible values always between 0 and 255?
If so, make it a ->
or string
array and
the consumption of readable memory is halved. Are the possible values
always true
or false
? If so, Adam Cadre's
"flags.h" library extension offers a slower-access
form of array but which consumes only about 1/8th of a byte of readable
memory per array entry.
2b. Turn arrays of constants into routines. Routines cost nothing in readable memory terms, but they can still store information as long as it doesn't need to vary during play. For instance, ‘Curses’ contains an array beginning:
Array RadioSongs table "Queen's ~I Want To Break Free~." "Bach's ~Air on a G-string~." "Mozart's ~Musical Joke~."
and so on for dozens more easy-listening songs which sometimes play on Aunt Jemima's radio. It might equally be a routine:
[ RadioSongs n; switch (n) { 0: return 100; ! Number of songs 1: return "Queen's ~I Want To Break Free~."; 2: return "Bach's ~Air on a G-string~."; 3: return "Mozart's ~Musical Joke~.";
and so on. Instead of reading RadioSongs-->x
,
one now reads RadioSongs(x)
. Not an elegant trick, but it
saves 200 bytes of readable memory.
2c. Economise on object properties. Each time an object provides a property, readable memory is used. This is sometimes worth bearing in mind when writing definitions of classes which will have many members. For instance:
Class Doubloon(100) with name 'gold' 'golden' 'spanish' 'doubloon' 'coin' 'money' 'coins//p' 'doubloons//p', ...
Each of the hundred doubloons has a name
array with eight entries, so 1700 bytes of readable memory are consumed.
This could be reduced to 300 like so:
Class Doubloon(100) with parse_name [; ! A routine detecting the same name-words ... ],
2d. Make commonly occurring properties common.
Recall that properties declared with the Property
directive
are called “common properties”: these are faster to access
and consume less memory. If, for instance, each of 100 rooms in your
game provides a property called time_zone
, then the
declaration
Property time_zone;
at the start of your code will save 2 bytes each
time time_zone
is provided, saving 200 bytes in all.
(The library's properties are all common already.)
2e. Economise on objects.
In a room with four scenery objects irrelevant to the action, say
a window, a chest of drawers, a bed and a carpet, is it strictly necessary
for each to have its own object? Kathleen Fischer: “parse_name
is your friend… a single object with an elaborate parse_name
can be used to cover a whole lot of scenery.” In Kathleen's technique,
it would use parse_name
to record which of the words
“window”, “chest”, “bed” or
“carpet” was used, storing that information in a property:
other properties, like description
, would be routines which
produced text depending on what the object is representing this turn.
2f. Reuse objects. This is a last resort but L. Ross Raszewski's "imem.h" has helped several designers through it. Briefly, just as an array was converted to a routine in (1) above, "imem.h" converts object definitions to routines, with a minimal number of them “swapped in” as real objects at any given time and the rest – items of scenery in distant locations, for instance – “swapped out”.
3. Grammar.
There can be up to 256 essentially different verbs, each with up to 32
grammar lines. Using the UnknownVerb
entry point will get
around the former limit, and general parsing routines can make even
a single grammar line match almost any range of syntax.
4. Vocabulary. There is no theoretical limit except that the dictionary words each take up 9 bytes of readable memory, which means that 4,000 words is probably the practical limit. In practice games generally have vocabularies of between 500 and 2,000 words.
5. Dictionary resolution.
Dictionary words are truncated to their first 9 letters (except
that non-alphabetic characters, such as hyphens, count as 2 “letters”
for this purpose: look up Zcharacter
in the index for references
to more on this). Upper and lower case letters are considered equal.
Since general parsing routines, or parse_name
routines, can
look at the exact text typed by the player, finer resolution is easy
enough if needed.
6. Attributes, properties, names.
There can be up to 48 attributes and an unlimited number of properties,
at most 63 of these can be made common by
being declared with
Property
. A property entry can hold up to 64 bytes of data.
Hence, for example, an object can have up to 32 name
s. If an
object must respond to more, give it a suitable parse_name
routine.
7. Objects and classes. The number of objects is unlimited so long as there is readable memory to hold their definitions. The number of classes is presently limited to 256, of which the library uses only 1.
8. Global variables. There can only be 240 of these, and the Inform compiler uses 5 as scratch space, while the library uses slightly over 100; but since a typical game uses only a dozen of its own, code being almost always object-oriented, the restriction is never felt.
9. Arrays.
An unlimited number of Array
statements is permitted,
although the entries in arrays consume readable memory (see above).
10. Function calls and messages. A function can be called with at most seven arguments. A message can be called with at most five.
11. Recursion and stack usage. The limit on this is rather technical (see The Z-Machine Standards Document). Roughly speaking, recursion is permitted to a depth of 90 routines in almost all circumstances, and often much deeper. Direct usage of the stack via assembly language must be modest.