Ways to represent room connections in Z-machine

The simplest way is to burn a property index for each direction, but that’s 10-12 properties consumed right there.

On the other hand, it can be fairly efficient because on v3 properties can be either 1 byte or 2 bytes. A 1 byte property will always be a room index, and a 2 byte property will always refer to a string or a routine.

Property 4 on a room could mean “connection to the north” and property 4 on an object could probably mean something completely different as well.

Of course there’s nothing preventing you from having a property point at a table of (direction, destination) pairs either? Or just use v4+ and have properties of up to 64 bytes store (direction, destination) pairs directly. Directions only need four bits, and rooms can probably fit in twelve bits, but you’d need a different encoding entirely to represent string/routine addresses.

Inform requires that you can tell an object number, a routine address, and a string address apart. In the veneer code I see there’s some support for using the LSB to disambiguate a string and a routine. In some ways that cuts the maximum number of either in half, but also it’s less code and you could conceivably interleave routines and packed strings together to minimize wasted space. Assuming v5, if your routine is 4N+1 through 4N+4 bytes long, find a string to put after it. Otherwise, put another routine after it.

But the more common way seems to be to store all routines and strings separately, at which point you can remember the cutoff point. What bugs me about that though is unsigned compares don’t natively exist, and the veneer code expands to several instructions every time.

Having said all that, using the LSB does seem really simple and has minimal overhead.

Another option is instead of needing to tell routines and strings apart – just always use routines. A routine that only calls @print_ret only needs two bytes of overhead (the zero parameter count, and the opcode). (In that case, a return value of zero might produce “You can’t go that way” and object 1 cannot be a valid room destination)

I didn’t dig far enough into the ZIL code to see how it does it, although I assume the versions that test a bit or flag implicitly generate a routine call on your behalf.

1 Like

This was set up for v6/7, where the “separate ranges” strategy may not work at all. (Routines and strings have different starting offsets in v6/7, which means that packed addresses for both can start at 0.)

This is the -B switch for the I6 compiler:

  B   use big memory model (for large V6/V7 files)

The flag is called oddeven_packing_switch internally, which is easier to understand.

This flag can be used with v3/4/5/8. But I don’t know that anybody’s ever tested that configuration. I certainly haven’t. If you go down that road, be prepared for compiler and veneer bugs.

1 Like

In ZIL, all the exit types are distinguished by property length, padding with zeros to make the lengths unique. For example, in V3, 1 byte (a room number) is an unconditional exit, 2 bytes (a string address) is a blocked exit, 3 bytes (a routine address + a dummy byte) is an exit controlled by a routine, 4 bytes (room number + variable number + string address) is an exit controlled by a variable, and 5 bytes (room number + door number + string address + dummy byte) is an exit controlled by a door.

4 Likes

Interesting! That’s pretty cool and something I hadn’t considered. I went with “treat it as a number” (so either 1 or 2 bytes) and if the number is less than or equal to the number of objects, it’s a direct connection (and will often only take 1 byte, particularly in v3) and anything else is considered a routine address. If the routine returns 1 it’s blocked, otherwise it’s the other object (and 0 could mean a default failure response).

I chose 1 for blocked because print_ret prints a string and returns 1. A routine whose entire body is a single print_ret call is only two extra bytes of overhead.

Not that it’s likely to be a problem in practice these days, but this would’ve been tricky in V6/V7, where routine addresses are packed relative to a base address. You’d need to set the base address before the actual address of the first routine by at least 4 * (number of objects) bytes to make sure no routine address is a valid object number.

1 Like

Yup, I had another thread here somewhere on that topic. It’s partially why v8 was invented in the first place.

Inform also supports using the LSB to disambiguate strings from routines but apparently that path isn’t particularly well exercised.