Z-machine standard: unclear aspects/ambiguities

I calculate based on the width of a zero for the current font. If the font size changes, then the effective screen size has changed. The Infocom era didn’t have to worry too much about font and screen size changes during a game. About the only thing the game can do with the screen information is to control the placement of things in the upper window (set_cursor and get_cursor don’t work in the lower window) which should always use a fixed pitch font. Variable pitch can be used in the lower window, but no cursor control is provided there. As always V6 is a whole different headache.

1 Like

That’s what happens if you hit “download” on the filenames at GitHub - heasm66/ZIL-Resources , rather than clicking through and using the on-page “download raw” button.

Thanks.

I’ve also got those spec files (in original Scribe/Runoff format) at https://eblong.com/infocom/#zil.

2 Likes

I’ve added it to the ZIL-Resources link above (I’ve also added my pdf-version of the Z-Machine Standards Document 1.1 for good measure) and submitted a new zip-file to IF Archive (where it should appear among unprocessed in a little while).

1 Like

In the Frotz core, declaring decode_text() with addr as zlong instead of zword will certainly fix the problem with overflowing the address. However, I think this is a sign of the actual problem existing elsewhere. I believe that the memory access macros (LOW_WORD, HIGH_WORD, etc) don’t check to see if the attempted access is actually legal as far as the host operating system is concerned.

1 Like

Finally, got myself something working here:

(Not sure why the serial number is blank, though - could be a bug somewhere in my code, will investigate when I have the time.)

Still a long way to go because this is basically just a hack that wires all output (including the log) to a Visual Studio test session - there is no proper backend and no way to input commands yet.

Regardless - huge thanks to everyone who’s contributed to this thread!

upd: And fixed. The values for opcodes inc_chk and dec_chk were mixed up. Serial number is 840726.

3 Likes

I was thinking of what Infocom’s terps would do when a string straddles the dynamic/static boundary. When I went over Beyond Zork passing invalid values to @get_prop_addr for insight on that rotating mirror bug and came to the conclusion that Frotz inherited a technically-wrong solution from Zip.

Per the Z-machine Standard, the maximum number of objects is 65,535. The mirror bug is triggered when the interpreter accesses object number 41,920 or thereabout. Beyond Zork’s static memory starts at offset 32,130. The address for that object is calculated to be at 588,158, which is somewhere far past the end of the zcode file itself. If I understand the thread on the mirror bug correctly, the source code incorrectly treats W?CIRCLET as an object and by accident this works correctly on an interpreter that doesn’t care. So the more correct way to address this is to check to make sure that the object’s location in memory is valid and THEN complain. But if the game is Beyond Zork, just return zero.

Zip (and Frotz) address the problem by returning zero from @get_prop_addr if the game is Beyond Zork AND if the object is higher than the arbitrarily-chosen number of 2000. It seems that the advantage to being so specific is that there is very little computation involved.

It looks like if someone actually tried to make a game that legitimately uses 41,920 objects, the memory required would be in excess of what’s supported by any of the Z-machine versions. Does anyone know more about this aspect of the Z-machine?

So back to ambiguities of whether or not a string straddling the border between static and dynamic memory should be allowed. The reason we make an exception for Beyond Zork’s mirror bug is that the improper behavior is in the wild for a long time and the official terp accidentally worked. The solution is also doesn’t require the terp to look for the end of the mistake; it’s all there. For this string-straddling issue to arise in the wild, someone would have to manually do it on purpose. Older releases of Inform might have allowed for this. When games were written with those older releases, incidents of Zip or Frotz crashing when a string straddles the dynamic/static boundary weren’t reported. So I’m inclined to conclude that no releases of Inform actually did this at any time. Nor am aware of any new zcode games doing this using some non-Inform language. Therefore the correct response would be for the interpreter to throw a fatal error and exit.

1 Like

Well the object tree must be in dynamic memory, which is a maximum of 64KB. Each object is 14 bytes, so if all dynamic memory was used for objects (which of course it couldn’t be), then there could be at most 4681 objects.

But there’s no record in the game file of the maximum object number. I expect that most interpreters are just written to assume that all object opcodes are used correctly. The only thing that could be checked is if the object address would calculate to be outside dynamic memory. That’s clearly a bug, but it probably shouldn’t be ignored in the general case. Special casing (or patching) Beyond Zork is probably the best option.

My ZVM doesn’t specifically check this, but the general dynamic memory write tests would error if trying to set an object number that’s too big.

2 Likes

I’ve edited the Frotz core code to check if an object number is actually valid and exit with a fatal error if not – with an exception made for Beyond Zork to return zero. Prior to this, no such validity check was done, so a segfault could occur in some other malformed z-code. The hard limit of 2000 objects was rather improper because code with more than 2000 objects can still theoretically work; subject to running out of memory at 4681 if not sooner. It now works as your describe your code as working.

Generalizing this “return zero” solution to all z-code is the wrong approach as it hides symptoms of errors that would otherwise be hard to track down.

I’m now checking over my solution to the problem of a string straddling the border between dynamic and static memory. I’m calling the approach of allowing it to happen a Bad Thing for the same reason of not generalizing the solution to the rotating mirror bug. See Error when print_addr gets a string that goes past 64K (#276) · Issues · David Griffith / frotz · GitLab

Is it really a Bad Thing though? We already need to print strings both above and below the boundary anyway. If the choice is printing a string vs. killing the game, I’d lean towards the solution that lets the player continue to play the game, and maybe throw up some sort of diagnostic error if desired.

2 Likes

It’s a Bad Thing because, per the Z-machine standard, addresses within dynamic memory are expressed as a 16-bit value. If dynamic memory is at its maximum (64K), trying to address memory past that point causes the that value to roll over to zero. I’m not aware of any machine, real or virtual, that will allow memory accesses past the overflow of an address pointer. For systems and hardware that don’t really enforce limits on where you can read or write (ie, most bare metal and real-mode DOS), you risk summoning Nasal Demons if you violate those limits.

I’d love to know how this test program was created.

This reminds me of DOS’ high memory area, which was in a way accessing memory past the overflow of an address pointer.

2 Likes

That’s one way to look at it, certainly, but another is that string decoding can take place in both dynamic/static memory and also high memory. There are no divisions within high memory limiting it 64K chunks, meaning a string can run over the limits of 64K “pages” if you will. Indeed, a string can even be longer than 64K. Since the Z-machine must therefore be able to deal with addresses larger than two-bytes to read strings, it seems rather artificial to limit it to 64K in only one specific case. My interpreters don’t read strings differently in dynamic vs high memory and it would take a decent amount of work to make them context aware enough to generate an error here.

2 Likes

If you’re referring to the one I posted above, I used the assembler from my ZDevtools, with the following program:

start
print "String starts at "
print_num &String
new_line
print_addr String
read_char 1 -> sp
quit
seek 0xfd20
label String
string "This string crosses the 64K boundary^"

The seek call there was tweaked until I got a good value. I’ve added a new seekabs directive (seek to absolute address) to the assembler to make this easier, but it’s not in any released version yet.

The consensus seems to be the opposite: the specified address of print_addr must be under 64K, but there can be any end point.

I’ve always understood it as referring to the width of the status window (or what width the status window would have once it was opened). So proportional fonts don’t matter.

The height of the screen is another matter. Most interpreters I think would add the height of the status window (in rows), to the height of the main window according to its font. Both the windows could have different fonts or sizes, and they might have padding that isn’t accounted for. In practical terms this means you can’t guarantee that you can expand the status window to any exact proportion of the screen, whether that’s 100% or any other fraction.

Section 8.4.3 is more complex. If the units aren’t just set to 1, well it’s not clear exactly what to do. As the remarks say, version 5 interpreters should just use units of 1. Measurements in units can only really be reliably used in v6 where they’re set to pixels, and you can measure the window margins etc.

3 Likes

I see, so you’re essentially levearging S8.7.2.4 here, and if 1 unit is 1 character, then the width in characters = width in units, which makes them the same value both in V4 and in V5.

BTW, it’s interesting that the V3 screen model does not say anything about which font should be used in the upper window, but then it doesn’t provide width and height information either.

So the height in units in V5 would also be the same value as in V4 in lines, correct?

That’s what I was thinking too, since V6 appears to use both units and pixels, and if they are not the same, it might become too confusing (especially for an already complex screen model such as V6).

1 Like

Only one V3 Infocom game used the upper window: Seastalker. Not even all releases of Seastalker used it. The game uses the upper window for the sonarscope display and so must use a fixed width font.

I think every platform only had fixed font until Apple Macintosh arrived in 1984.

1 Like

That is correct. Infocom had to go back and add fixed-width flags to ASCII-art output in their earlier games. A comment (in one of the source directories, I forget) said these were “specifically for the Mac version”.

1 Like

I suppose some more discussion is in order because this would put something down in stone that’s currently ambiguous. To do that, we need to keep the Z-machine Standard up to date. The last time David Fillmore (@Marvin) did anything on Github was in 2019 and the last time he logged in here was in 2021. Does anyone know what’s up with him? I think maintenance for the Z-machine standard should be housed under Interactive Fiction Technology Foundation · GitHub.

1 Like

I got kinda busy with real life stuff.

4 Likes