Since the Z-Machine Standards Document was updated in 2014, various issues with the specification have been brought to light. Sometimes these are genuine errors with the text, sometimes they are simply issues of clarity, where the specification is unclear or difficult to follow.
The project to fix these problems was housed on my personal github account, which wasn’t ideal in any regards, since the project is largely a community affair, and more importantly, when I got busy nobody could move forward with it.
The purpose of this thread to try to collate and discuss any issues, so that a consensus can be reached before a revised Standard is uploaded to its proper place. If anyone has anything to say about any aspect of the specification that needs revising, it would be helpful to bring it up here.
There are a lot more issues than just those that have been filed on Github - just see the recent discussions!
I don’t think we need to spend a lot of effort rounding them all up right now, but that’s because I’d prefer to see smaller but more frequent updates of the spec. Rather that racking our brains trying to remember every ambiguity we’ve seen, it would be better to just post on Github when it comes up again in the future, and we can then discuss what to do at that time.
I would love to see more clarity around interrupts. There are a lot of potential gotcha scenarios for new interpreter authors.
Currently we have: save and save_undo are not allowed during interrupts (dictionary of opcodes) Newline interrupts should not attempt to print anything. (section 22.214.171.124.2) Two very confusing usages of the term ‘interrupted’ in the description of the read opcode that have nothing to do with interrupts but refer to preloaded input (dictionary of opcodes) - These should be changed.
I’d propose that it be formalized which opcodes are forbidden within each of the interrupt types. For example I’d like to see catch/throw be considered illegal in all interrupts. Or at the very least illegal to throw past the original interrupt frame. Infocom’s yzip document states that CATCH/THROW were not intended to work with them and I doubt most interpreters could handle throwing out of an interrupt, especially if it were due to timed input.
I think it would be helpful to state that read/read_char can legally be used within a timed input interrupt, as this is required by Border Zone. In that game code execution is never allowed to return to the original timed read instruction, being prevented via quit, restore, or restart. Again I imagine most interpreters would not deal well with being allowed to return to the original input operation as they do not track multiple ones and forcing terp authors to do so seems onerous. Perhaps it should also be illegal to use a timed input operation within an interrupt, as this is not used anywhere I am aware of and allowing it would complicate implementations.
Sound interrupts have the potential to be truly asynchronous and present some unique issues. A sound interrupt could occur during a timed input or newline interrupt, complicating things if the sound interrupt printed anything. Also it is unclear what should happen if multiple sound interrupts happen due to the sound/music split in the z-machine. Are they queued, or does one interrupt the other?
I just wrote a short z6 program to test interrupts, using newline interrupts and timed input interrupts, and I can confirm that the newline interrupt routine interrupts the timed input interrupt routine, in both Frotz and Infocom’s DOS interpreter for Zork Zero.
Having thought about this a whole lot (this isn’t the first time interrupts interrupting interrupts has come up), and having finally managed to test it on an Infocom interpreter, my suggestion is:
Interrupt routines can interrupt interrupt routines. An interrupt routine may do whatever that type of interrupt routine is allowed to do, even if it interrupts a routine that is not allowed to do such things.
This is likely the way most modern interpreters work, it seems to be how Infocom’s interpreters work, and it’s not very likely to cause problems.
What things are each type allowed to do? The only mentions in the standard are save/save_undo being illegal, and newline interrupts shouldn’t print. That seems woefully insufficient.
Let’s try some simple interrupt testing scenarios that don’t involve multiple interrupts or any newline interrupts:
CATCH a stack frame. Start a timed read. Within the interrupt routine THROW to the previously caught frame. Does the terp accurately track we are no longer in a read operation, and that we are no longer in an interrupt situation?
Start playing a sound that will trigger an interrupt when finished. Start a read and type some input. The sound ends and the sound interrupt fires, prints some stuff, and exits. Does our input line get redrawn properly as it would when returning from a timed input interrupt?
Start a timed read. Have the interrupt routine start another read. Complete the second read and allow the timeout routine to return. Is info regarding the original read state lost?
It gets a lot more complicated when you include newline interrupts or allow read/read_char (timed or otherwise) from within sound or newline interrupts, especially if there was already a read/read_char in progress.
I’d say the following minimum specification is warranted (in addition to save/save_undo already being illegal):
Within an interrupt: 1) Catch and throw are illegal 2) Printing is illegal except in timed input interrupts. 3) Read and read_char operations are illegal except in timed input interrupts where they are legal only if execution of the interrupt is never allowed to return to the original timed input operation, i.e. it must never return and instead must quit, restore, restart, or enter an infinite loop.
Edit: The above rules actually prevent the most complex nested scenarios. Sound interrupts can still occur at any time, but cannot themselves trigger the other two types. Input interrupts can still trigger newline interrupts via printing. All other nesting is effectively prevented.
Alternatively: If sound interrupts should be allowed to print, then they too can trigger newline interrupts and the interpreter must also handle the redrawing of input lines if a sound interrupt prints during a read, which is quite likely to happen.
I don’t mean that all interrupts should be allowed to do whatever. Simply that (for instance), say we allow printing during timed interrupts but not during sound interrupts. If a timed interrupts interrupts a sound interrupt, the timed interrupt can print stuff, even though it’s in the middle of a sound interrupt which can’t.
Overall, I think your suggestion for prohibiting interrupts from certain behaviours is fine. I’m all for avoiding making interrupts more complex for interpreter writers, and anything that doesn’t require modification of current interpreters is good.
The only thing I’d say is, it’s worth looking at games that already exist which use these features to see if any of them already do things that we want to prohibit, such as printing during sound interrupts.
Also, printing during sound interrupts is probably not really all that bad. The only thing terp authors likely need be aware of is the redrawing of input lines when needed. Game authors would need to avoid multiple interrupts printing in the middle of each other, but that’s not a terp issue really.
To argue with my previous position: A cool feature of sound interrupts being able to print would be to launch a song and print its title in the upper window as the current song ends.
I haven’t implemented timed input in ZVM yet, so I never took the time to fully understand the Z-Machine’s interrupts. But I’d recommend looking at what existing games need, and banning any combination of features that isn’t already needed. It would be better to keep those cans of worms firmly unopened if possible.
I would appreciate if someone could correct the standards document regarding Atari 8-bit story files. In the current version, Stefan Jokisch is cited on the structure and logic behind Atari 8-bit story files but he is wrong unfortunately. See this post I made in 2021 as a reference Z-machine standards / Atari 8-bit targets
Ah yes. I see it now. Back when I read it I thought the question mark documented that the purpose of the flag is unclear.
I don’t know if bit two being set and all others being cleared is equivalent to integer 4 as I am not familiar to with ASM or Z-machine bytecode. What I do know however is that this flag was only relevant for the Atari 8-bit and literally every Infocom story file I investigated, and I investigated them all, has integer 4 set. Excuse me if that sounds a bit cheeky now but: the Infocom standards document should document the Infocom standards, right? So if Infocom always set this flag to integer 4 when they split story files, why not documenting it like that? I have to admit that I don’t get that.
One also needs to consider that we do have a special situation here. I made story files run again in Infocom’s 2-disk Atari 8-bit interpreter but I always set the integer for this flag to 4, so to confirm if it is the same one likely would need to alter one story file by setting bit two and clearing all the others to see what happens.
The really important part though that needs correction is this one:
Blockquote and were divided into the resident part on one disc and the rest on another. (This discovery was announced by Stefan Jokisch on 26 August 1997 and sees the end of one of the very few Z-machine mysteries left when Standard 1.0 was first published.)
Because that part is definitely wrong as already stated in my linked post.
You’ll have to document it per bits, because the flags have different meanings. I don’t know if Deadline was available for Atari 8-bit but if it was, the integer value of byte 1 would be 6 (bit 1 and bit 2 set).
It is 4.
A hypothetical game which was split across disks and did not support a status line would have 20 (4 for split disks, plus 16 for no status line).
A game that was split across disks and supported split screen (only some Seastalker releases used a split screen) would have 36 (4 for split disks plus 32 for split screen).
It is entirely possible that all the split disk story files just coincidentally had all other flags zero, making the number always 4.
The standard actually is an amalgam of Infocom and modern behavior. It is designed such that Infocom era games will play under an interpreter that follows the standard, but it does not mean that it describes exactly the behavior of Infocom’s own interpreters. Infocom’s own interpreters weren’t always consistent with each other.
It was definitely available for the Atari 8-bit, as this is the version I bought and played way back in the dim distant past. I think this may have been the first of the two-disk games, as Starcross and the Zork trilogy were one-disk games.