[Z-machine] RFC: Out-of-game saves and Quetzal

The topic of out-of-game saves came up in another thread (“SAVE and RESTORE Commands”), and I posted a small piece about my experience implementing them. It makes more sense to post this under its own topic, thus this post. I’d like to hear what other Z-machine interpreter authors think.

I’m using “out-of-game save” to refer to a save state created solely by the interpreter, and not at the request of @save. Such saves have the advantage of being usable at any point the interpreter wishes to make them available, not just when the game does. However, simply using a normal Quetzal file is a bad idea, because the stored PC will have a different meaning: with @save, it points to a store/branch byte, but for an out-of-game save it should point to the first byte of an instruction—whichever instruction was active at the point of the out-of-game save. In my case, this is @read.

The main problem is that there is no way to flag a Quetzal file as being “special” in a way that current interpreters will know not to load it. New chunks would be ignored, as would a new flag. Repeating myself from the above-mentioned thread:

How I’ve approached it is as follows: Quetzal 1.4 is used in its entirety with two changes:

  • The IFF format type is BFMS (modifies §2.1)
  • The PC must be set to the first byte of an instruction (modifies §5.8)

These new files are not recognized by current interpreters, because they expect IFZS, not BFMS; this avoids any problem of mixing the two types together. Another advantage is that this is blindingly easy to implement. If an out-of-game restore has been requested, search for BFMS; otherwise, IFZS. The rest of the code is the same, because the new file format is identical to Quetzal in almost all respects. In addition, an interpreter can easily figure out what type of save a file contains, and restore accordingly, so an out-of-game restore can load an in-game @save, and vice versa.

If no other authors decide to implement out-of-game saves, this is moot. But if anybody does, it would be useful to have them all be interoperable with each other.

I was imagining a new flag chunk for the IFZS document type, rather than creating a new document type. But your point about backwards compatibility makes sense.

I suggest one addition: the opcode in which the game was interrupted. You say it happens at @read, but it could occur at @read_char as well, and the interpreter might need to know which operation to restart. (Theoretically it could look at the PC, but redundant information is nicer than re-decoding the instruction.)

I expect to use this in Glulx as well. There, the game will always be interrupted in a @glk opcode, so this bit doesn’t matter.

(Really, I suppose, a Z-code game could be interrupted at a @save or @restore opcode – or @output_stream – while the user was in the middle of selecting a file! But in those cases, it’s probably better to save the state as it was at the most recent @read operation. Restoring to the middle of a file prompt would just be confusing.)

Well, @read is just where I do it; the idea was just that PC needed to be set to the first byte of an instruction, regardless of what that instruction happened to be. But…

I agree: it makes sense to strongly hint (at least) that an interpreter should place PC at a “suitable” location, which is probably @read or @read_char. If that’s the case, restoring can just begin execution at PC, exactly the way it does with Quetzal, apart from the branching/storing. You can use the same restore code for both types (I do).