Z-machine standard: unclear aspects/ambiguities

Yeah, I’ve seen that one already while searching for more info on print_form and print_table.

You are right. That’s why I had to start this thread in the first place. =)

Here’s one more minor issue I’ve stumbled upon recently:

16. The verify opcode

This opcode is listed as being available from version 3, and it says that the interpreter should use word $1A in the header to calculate the checksum and compare it against the known value in word $1C. The problem is that S.11 also says that some early Version 3 files do not contain length and checksum data. What does this mean, exactly? (e.g. that both words will be 0, or perhaps they will contain some unrelated data pertaining to e.g. the object table or some such).

It would also be logical to assume that any file that does not contain this information also does not contain the verify opcode, but does anyone know if that’s actually the case? If not, it’s unclear what the interpreter should do if it sees that the length and checksum values are not provided, assuming it is possible to find that out in the first place.

They are zero in every example I’ve looked at (that does not contain a verify instruction).

This is true in every example I’ve looked at.

The only way would be if they are zero. There is no other indicator.

Edit: I think Infocom was forward thinking enough to leave unused header bits zeroed until needed.

Additional Edit: Thinking further on this, it is (however unlikely) entirely possible for the checksum to legitimately be zero, so the only thing you can do when encountering the verify opcode is naively calculate and compare to the header value.

I see. Then there’s nothing to worry about here.

I don’t actually know this story. What was it?

2 Likes

See thread: Beyond Zork passing invalid values to @get_prop_addr

2 Likes

Something else I’ve been wondering about recently:

17. Address truncation

Apparently, according to this thread: Index to @loadw: signed or unsigned?, the final address of the word indicated by the opcode loadw is supposed to be truncated to the bottom 16 bits. But what about the other opcodes? (Logically, at least storew should be expected to do the same.)

S.1 says that the total of dynamic plus static memory must not exceed 64K, so my current assumption is that any opcode that does not accept a packed address as an argument should be handled this way. Is this correct?

1 Like

That’s correct, loadw/storew and loadb/storeb are limited to 64K. This is unfortunate, but too late to change I think. Packed addresses allow reading strings and executing code beyond that limit.

In my thread about undefined behavior I alluded to a way to circumvent even the packed address limit, but that definitely falls in the realm of undefined behavior that probably will not work on most interpreters.

What about opcodes like copy_table, scan_table, or print_addr (NB: not print_paddr) ? Should the address wrap around from $FFFF to zero while scanning/copying/decoding?

I doubt you’ll ever see it happen, but there’s probably no harm in letting them exceed the limit. The description for print_addr says “Print (Z-encoded) string at given byte address, in dynamic or static memory.” but it isn’t clear if it simply means the address, or the entire string. Since print_paddr already prints strings beyond this limit, I don’t see any problem with a string that overruns it. I doubt anyone is going to make a game with a string that runs over the limit and then be surprised by the behavior of your interpreter if it prints the whole thing vs printing the contents of the story header! The descriptions for scan_table and print_table don’t mention memory limits, so it’s anybody’s guess, but again I doubt you’ll ever see it.

1 Like

Bocfel wraps in copy_table and scan_table, and it looks like Frotz does the same. I’d probably consider this undefined behavior, and just do what you want. Games can’t rely on accessing memory above 64K since some existing interpreters won’t allow it, so I’d say that any game that tries to do it is wrong.

print_addr is a more interesting case. Most interpreters (Bocfel, ZVM, Fizmo, Zoom, Nitfol, Viola) support crossing the 64K boundary. Frotz (including Windows Frotz) does not, although the result is a segfault, so I don’t think it was a conscious decision in Frotz to disallow it, just a consequence of not expecting it to ever happen.

Infocom’s Beyond Zork interpreter (from Lost Treasures) also handles it properly.

I’m attaching a small program that calls print_addr on a string that crosses the 64K boundary. It prints the address of the string (using print_num, so it’s signed), which is -2/0xfffe, and then prints the string itself. This should allow easy testing of such behavior.

64k.zip (388 Bytes)

Edit: I’m adding a less-annoying file to demonstrate the same thing. The old one has thousands of @nop calls, which made it easier for me when using a disassembler to ensure I was at the 64K boundary, but here’s one that just fills the space with zeros so disassembly/running is cleaner.

64k-new.zip (398 Bytes)

2 Likes

The issue here is that memory limits are not mentioned for loadw either, and yet they do exist.

Undefined behavior per se does not concern me much, but I suppose there might be known cases of a story file (ab)using it in order to achieve the desired result, like in the Praxix case - that’s why I had to ask. Although Praxix is apparently not a real game but a test file, that particular scenario is obviously constructed on purpose. I wonder if there any actual cases of real games leveraging this wrapping behavior…

I see. I think I’m going to implement wrapping just for loadw, storew, loadb, and storeb, and leave the other cases as-is then.

Many thanks! I’m going to try it out whenever I’m actually able to run it. =)

I havn’t developed any interpreters but if I did I would assume that everything is limited to 64kb, unless explicitly defined otherwise (as with packed addresses). Infocom designed the z-machine for 8-bit computers and 64kb-address space was a given. As a game developer i would treat the behavoiur of every instruction that don’t deal with packed addresses as undefined and that I couldn’t count on a certain consistent behaviour over every interpreter out there. It could wrap around or it could extend beyond, either way I can’t count on it being consistent.

The limits are mentioned:

Stores array–>word-index (i.e., the word at address array+2*word-index, which must lie in static or dynamic memory).

1 Like

As an update, Frotz doesn’t segfault as a rule, but just as a consequence of this particular file and what address it wraps around to. Ultimately it’s undefined behavior in Frotz which might segfault, but might not.

Same for print_addr:

Print (Z-encoded) string at given byte address, in dynamic or static memory.

and yet:

In the end, it feels as if that remark is nothing more than a reminder that the behavior outside of the 64K boundary is not clearly defined.

I do think there is a slight difference between those cases, as for loadw it clearly means the address must lie below 64K, while with print_addr it isn’t clear if it means the start of the string (which must) or the entire string (which is undefined).

I think Chris put it best above:

Games can’t rely on accessing memory above 64K since some existing interpreters won’t allow it, so I’d say that any game that tries to do it is wrong.

With the obvious exceptions allowed by packed addresses of course.

Edit: I am kind of surprised by Frotz’s behavior with a string crossing the boundary, since printing strings beyond 64K is something all terps must do. In most implementations I would assume text decoding wouldn’t know or care whether it was initiated by print_addr, print_paddr, or something else. Still, it shows the dangers of assuming.

2 Likes

Since the consensus seems that a string straddling the 64K mark is illegal, I’ll modify the Frotz core to abort with a fatal error if encountered.

Without referring to any code, I think that Frotz barfs because an 8-bit variable is used internally and the others don’t.

I’m curious what Infocom’s terps would do when encountering this condition. I’ll explore that over the next couple days while addressing the Issue filed at https://gitlab.com/DavidGriffith/frotz/-/issues/276

1 Like

It was mentioned above that the Beyond Zork interpreter from Lost Treasures handles it ok.

1 Like

My personal feeling is printing strings across the boundary is a special case that should be allowed. Strings are valid on both sides already.

A similar case would be whether or not executable code can flow across the boundary. The program counter has to be able to handle addresses above 64K anyway. You can even have execution flow go from static to high memory without having code right at the boundary as well, via jumps, so checking the address of the program counter isn’t sufficient to warn or error.

2 Likes

Yes, you are technically correct. In the end, like I said before, I probably should do explicit wrapping for loadw et al., and leave the rest as-is because that’s undefined behavior.

I think I’ve found one more aspect of the screen model which is not properly explained. It is not V6-exclusive, so I hope it should be more or less known what to do here.

18. Screen height and width in lines and characters

S8.1 states:

Text may be printed in any font of the interpreter’s choice, variable- or fixed-pitch: except that <…exceptions omitted…>, then a fixed-pitch font must be used.

And S8.4 states:

The screen should ideally be at least 60 characters wide by 14 lines deep. <…> The interpreter may change the exact dimensions whenever it likes but must write the current height (in lines) and width (in characters) into bytes $20 and $21 in the header.

The question: if the current font is variable-pitch, how should the screen width be calculated? Also, should it be updated if the font changes from variable-pitch to fixed-pitch? It does say current screen size, so I presume it should be updated whenever the actual size changes, e.g. when the user resizes the game window - but it doesn’t say anything about the font.

S8.1.1 does say that the width of a font is defined as the width of ‘0’, but the context there is slightly different (V5+ font size information to put into header bytes $25 and $26), and it’s still unclear what to do when the font has changed.

The header bytes $20 and $21 are listed in S.11 as V4+. For V5+, S.8.4.3 also states:

In Version 5 and later, the screen’s width and height in units should be written to the words at $22 and $24.

Are these values used instead of the ones written to $20 and $21, or in addition to them?

1 Like