Updating the Z-Machine Standard Documents

Clarification request: The opcode definition for @get_cursor does not specify whether the cursor position is retrieved in characters or units. It should be units, as indicated by Infocom’s YZIP documentation.

This matters in theory because Frotz and Bocfel implement this differently, with Bocfel returning the character position even in Version 6 games. This matters less in practice because the Infocom documentation recommends using @get_wind_prop rather than @get_cursor to retrieve the cursor position, which is unambiguous. (Also because nobody really expects Version 6 games to behave correctly on Bocfel anyway.)

Suggestion: add “(in units)” to the definition of @get_cursor to mirror the phrasing of @set_cursor, whose behavior it is also supposed to mirror.

It appears that this has already been updated in the files on github, where the description for get_cursor reads:

Puts the current cursor position (in units) into the given array, such that the y position is put into word 0 of the array, and the x position into word 1. (The array is not a table and has no size information in its initial entry.)

In Version 6, returns the position of the cursor in the current window. In other Versions, returns the position of the cursor in the upper window, even if the lower window is currently selected.

2 Likes

I found the report that led me to not cache abbreviations in ZVM: https://groups.google.com/g/parchment/c/CeUK2Ry6Yvk/m/oDl9tOSL7IEJ

This functionality is apparently used in Suveh Nux and Large Machine. Tom said it’s mentioned in the DM4 but I couldn’t find where.

Ah, that sort of sequencing wouldn’t be supported by what Parchment does. While it doesn’t cache functions in RAM, it does JIT the whole function (or until a branch) at once. So the new address would be used the next time that block of code was run, but not the current time.

If there’s a production-ready game that needs this kind of dynamic compilation I’d definitely look at how to fix ZVM to run it, but I probably wouldn’t just for a proof of concept. Instead I’d say “please no!” :stuck_out_tongue:

1 Like

Well, there might have been, but I rewrote it in a less insane way because it would have made the game incompatible with Parchment :stuck_out_tongue_winking_eye:

An error: The definition for the @make_menu opcode currently states:

This is technically true (barely), but so incomplete and misleading as to be functionally incorrect. The term “ZSCII string” is not formally defined by the Standard, but the usage of “ZSCII” and “string” in §3 would imply that it means “a text string encoded into ZSCII in the usual way”. This would imply something like (in ZAP syntax)

MENUTABLE::
    .WORD 3  ; length header
    .WORD STR?1
    .WORD STR?2
    .WORD STR?3

.GSTR STR?1,"Menu"
.GSTR STR?2,"Option 1"
.GSTR STR?3,"Option 2"

Actually, @make_menu takes a word table of character tables, where the entries in the character tables are single bytes containing ZSCII character codes:

MENUTABLE::
    .WORD 3   ; length header
    .WORD STR?1
    .WORD STR?2
    .WORD STR?3

STR?1::
    .BYTE 4   ; length header
    .BYTE 77  ; M
    .BYTE 101 ; e
    .BYTE 110 ; n
    .BYTE 117 ; u

...and so on

This behavior is documented in the Infocom YZIP documentation and is in fact how the opcode behaves in (e.g.) Frotz.

I might suggest spelling it out explicitly (since the Standard is already a bit vague on what “table” means in any given context):

4 Likes

I remember struggling with this when I first implemented it. I eventually got it working, but yeah the description is…not great.

Another error: §8.8.3.1.2.2 states:

But §8.8.3.3 states:

which contradicts the former’s statement that Window 0 initially has attribute 1 (wrapping) on.

The former is obviously correct, as confirmed by the Infocom YZIP documentation (section “Windows”), the behavior of Frotz, and the behavior of the Infocom Macintosh YZIP interpreter (see function INITWB in mx1.a). §8.8.3.3 should be changed to read “Window 0 initially has all attributes on.”

2 Likes

A “sort-of error but maybe something that’s so marginal or baked into existing interpreters that it’s not worth changing”:

§1.1.4 states that the maximum size of version 6-7 games is 512kb. However, the addressing scheme of these games technically supports up to 576kb if the routine pointer R_O does not overlap the 64kb of low memory at all (for a total of 64kb low memory + 256kb routines + 256kb packed strings). This is confirmed to be the intended design in Infocom’s YZIP documentation, which states under “Program Structure” that:

(I’m not aware if any existing interpreters actually impose a 512kb limit, but I wouldn’t be surprised to find one that declares a static 512kb buffer to hold the game file.)

I believe the problem here is that the maximum size of addressable memory in v6/v7 is larger than the maximum file size which can be stored in the header.

2 Likes

…also the behavior of the Apple II YZIP (see yzip.lst for any of rel 9, 13, or 15)

Yep, the header field at 1A limits us to 512KB (actually only 524280 bytes with a maximum address of one less than that). V6-7 would be able to address more than that otherwise.

Edit: My interpeter does enforce a hard limit at the maximum legal address determined by the file length header field and will not read past it.

It’s definitely worth explaining this in the spec, since it’s not obvious and has caused confusion a few times.

2 Likes

Ah, good point. I checked Macintosh YZIP and it does seem to care about the length value when loading the file. (Symbol PLENTH - which by the way would make a great Zork spell.) So it’s the Infocom documentation that is wrong, not the Standard.

EDIT: Apparently v0.2 of the standard originally said 576kb as well, but this was subsequently changed to 512kb before v1.0. Interesting.

2 Likes

Speaking of the file length header field. Here’s something I found interesting and not immediately obvious regarding the checksum word in the header. The standard mentions that some early V3 games lack both the file length and checksum fields (they are zero) used by the verify opcode. What I realized is that the verify opcode should rely on a non-zero file length field to indicate the presence or absence of the checksum, and not the checksum field. This is because zero is a valid (if unlikely) checksum. If V3 games continue to be made, eventually one will have a valid zero checksum.

2 Likes

Looks like the ones with zero length are:

zork1-r2-sAS000C.z1
zork1-r15-sXXXXXX.z2
zork1-r15-sUG3AU5.z2
zork1-r20-sXXXXXX.z3
zork1-r23-s820428.z3
zork1-r25-s820515.z3

1 Like

I’m pretty sure Ozmoo will break if a game is > 512 KB.

1 Like

Not sure what to do with this information, but maybe it belongs here:

Calling a procedure with more parameters than there are local variables in the procedure is legal according to spec 1.1, but will break several Infocom interpreters. I haven’t checked Infocom’s standard, but either they didn’t allow it, or they just never produced story files that did this. I only tried this in v3.

1 Like

Hm, interesting. I suspect the I6 library does this freely, but I haven’t checked.

What happens in some Infocom interpreters is this:

[ RoutineA v1 v2 v3;
  RoutineB(10,11,12);
  print v1, ",", v2, ",", v3;
];

[ RoutineB v1 v2;
  rtrue;
];

Calling RoutineA prints: 0,0,12.

I can’t imagine why this is happening, but it does. We had some good fun finding the root cause of this when it happened in a game…

4 Likes

I have added all the issues raised here to the issue tracker on github. We’re up to 45, and I should probably get back to work on some of them.

1 Like