Z-machine Hacks

From IFWiki

Here a list a bunch of things that can be done with Z-machine, not specific to any programming language (although some may only be possible in some programming languages). Examples may sometimes be given in the Frolg assembler language, if necessary.

Note that the use of some of these hacks will not run in some Z machine interpreters. For example, the story file for Wry Humor crashes in Frotz.

Alphabet table

The alphabet table can be modified so that common characters are on row 0. If there are uncommon characters but they often go together in long strings (for output only, though), you might put them on row 2; a permanent shift into row 2 can more efficiently select a single character from row 1 than the other way around. (Note: The Z-Machine Standard only allows shift locks in V1 and V2; Infocom's interpreters allowed them for later versions, but this functionality was never used. Frolg is the only compiler that will encode them in later versions, and only if it is not in COMPLIANT mode.)

See also: Fun With ZCharacter

Dynamic alphabet table

The alphabet table could be modified at runtime. This could be used if in some areas all input and output is affected by a substitution cipher, or if you want to temporarily shift to display some text in all uppercase (even though it isn't encoded in that way), or if you want to search the vocabulary for the ROT13 form of a word.

Dynamic fwords

Reserve part of the fwords table for dynamic entries. This can be used to allow descriptions to be changed depending on circumstances. An example might be a user option to switch between British or American spelling, or if a lot of descriptions will include the player's name, or if there is an object whose name can change at some point during the game.

(Frolg has built-in commands to support dynamic fwords tables.)

Global variables overlapping the extension table

The global variable table can overlap the extension table, so that the mouse and joystick state can be accessed directly as variables.

Global variables overlapping the object table

If the object table is placed right after the global variables table, the first object header might fit in the global variables table. If the first object is the player, then you can read their location easily as a global variable.

Level data in high memory

If the level data doesn't fit in core memory, you can store it in high memory, as a text string loaded into output stream 3.

A two-stage compression can be used, one of which is the Z-machine text packing (which can use the fwords table and other things), and then use a secondary decompression implemented in your program, which might involve run-length encoding, Huffman coding, and whatever else is necessary.

Remember that you will want to represent the most common tiles, as well as other things like run-length, in row 0 (lowercase letters).

Memory layout in version 6 and 7 story files

Version 6 uses offsets in the header which are added to the routine number and string number to calculate their address. This can be used to make overlong story files.

An example of a memory layout might look like this:

  • $00000-$0FFFF: Core
  • $10000-$1FFFF: Strings for fwords table
  • $20000-$5FFFF: Routines
  • $60000-$9FFFF: Strings

The VERIFY command won't work if you do this, so if you want to verify it, the interpreter will need to have some other way of doing so.

Object classes

You may define many object classes (not the same as the classes in many programming languages). These classes can do a few things:

  • Assign ranges of object numbers, so that you know what class an object belongs to.
  • Some properties and flags may only be used with objects of certain classes. This can allow the property/flag numbers to be reused for other property/flags.
  • Some properties might be read-only for certain classes of objects. This can allow certain compiler optimizations to be used.
  • They can be used for different kinds of containment, such as a physical object containment, or an effect which has been placed on another object, etc.

Overlapping text strings

Text strings can overlap, if one of them doesn't have the high bit set when it ends when it will continue with the next one. Therefore you can have one string which is short and then another one which ends with the same text as that one.

You might also need padding. How this is done depends on the current state and on the Z-machine version. You will need to change it from its current state to row 0.

Here are the possible paddings (after any one, you can place any sequence of state 0 paddings, to fill up the extra padding if required):

  • Version 1, state 0: empty, 45, 444.
  • Version 1, state 1: 5, 44.
  • Version 1, state 2: 4, 55.
  • Version 3, state 0: empty, 45, 445.
  • Version 3, state 1: 5, 45.
  • Version 3, state 2: 4, 54.

Shared property tables

You can make multiple objects sharing property tables if it would help. Such objects will then share their name and property values, although they will still have their individual locations and attributes.

It might help if the properties are read-only for those class of objects. An example might be if many rooms contain an identical magical portal all leading to the same location.

However, it can also be used with writable properties, in case of something that exists in many places at once, where changing one automatically changes the others, although they still have separate flags.

Shared text

If you have a long string with only lowercase letters and spaces (no fwords references) and you want the uppercase variant, it can be the previous text string which encodes a permanent shift into uppercase letters. The coding is:

  • Version 1: 454.
  • Version 3: 444.
  • Version 5: 444444.

It can also be used to make two addresses point to the same text string, regardless of what they contain, in the same shift state too, in case for whatever reason you need two different numbers for the same string. The coding is:

  • Version 1: 444.
  • Version 3: 445.
  • Version 5: 444445.

Spare object heap

Some of the object numbers can be reserved for dynamically allocated objects, by using the LOC and FIRST fields to create a double-linked list. Therefore one can easily be allocated or freed by using the MOVE instruction.

One kind of spare object heap can be if the name and properties are static, although locations and flags can change (also see storing variables in flags). In this case, you will have a property table for each different kind of object it can be, and then repoint the property table to the one it is an instance of.

The other kind of spare object heap will be if the properties are dynamic. You probably cannot use the name (unless you allocate enough space to fit the name, or use a dynamic fwords table, or store all of the dynamic names in the (static) fwords table, or stream 3, or whatever), although you can change the properties. You will need to allocate enough space for the properties it uses at compile-time.

The first kind will use up much less RAM.

Storing object variables in the object table

If you are using shared property tables, you may want to store some variables independent of the property tables that are shared between many objects; you can use the flags in the object header as additional properties which do not share, if you need to (for example, how many of an object or how much it is worth, or time limits for identical effects on different objects).

Truncated default property table

If you don't need 63 default properties, you can truncate the default property table to only the default properties you need. Number the properties that need defaults with the highest numbers, 63 and down. The header to specify where the object table is, it is then deducted the unused default properties so that the address of the default properties will be correct. (Frolg automatically does this.)

Version 7 and 8 comparison

The extended Z-machine versions 7 and 8 are both allowing larger files than version 5, although they have differences and which one to use may depend on it.

Advantages of version 7:

  • If you have many small strings/routines.
  • If you have a lot of strings in the fwords table.
  • If the space taken up by strings and routines is nearly equal.
  • If you are using up most of the low 64K and want to add more text above that.
  • Also see the above section about version 7 memory layouts.

Advantages of version 8:

  • If you have many large strings/routines.
  • If you have a lot more strings than routines or vice versa.
  • If you are interleaving strings and routines a lot.
  • If you want to tell by a packed address if it is a string or routine.
  • If instructions are stored in the core memory.
  • The LMRG and RMRG header words are available (but this would be really uncommon).

Very long object names

Object names have to be in core (which is limited to 64K) and are limited to 765 Z-characters. If you have very long names, you can make them fit by storing them in the fwords table. (The Z-machine port of Wry Humor does this.)