I’m not quite sure what’s being referred to here as “transient variables”. But presumably, if they are transient, then they do not necessarily have to be restored.
My system does auto-saves so i can tell you how that works. But it would be a different ball-game in others;
Firstly there is no difference (for me) between a “game save” and an “interpreter save”. They both actually work together. The terp performs the actual save IO. and the game sends the terp a bag of state, which it saves and restores. Additionally, the terp adds its own state into the “bag of state”. So both sides get to add whatever they like to the save.
save/load can be initiated by the terp or by the game.
It is the game’s responsibility to be able to restore a valid graphic and sound state from any “top level” position (eg the main game loop). This is achieved by the terp issuing a look command after a restore or auto-save continuation. This also refreshes the current title, location description and any items hanging around.
Sounds are either short, triggered by a game event or long like ambiences. Things like ambiences do not need to restore their previous exact position in the ambient loop. They are things like background traffic noise, chatter, summer breeze etc.
The graphic stack is basically re-issued by any look command. During normal play, this stack is treated as an update which will not (normally) make any changes to the actual on-screen state when already present.
In my terp, i do not just dump out the memory state of the VM (because it’s not a VM), that would be way too crude and inefficient.
So how does the game saving and restoring its state look like in your system? Is it a bunch of game-specific logic that has to be written by hand and updated every time some new object or variable is added, or are there “guardrails” that make it less tedious and more automatic? Dumping a VM’s memory wholesale is indeed crude (and requires having a well-defined VM in the first place), but it has the advantage of working pretty reliably for all games running on that VM without any effort by the author, with a few exceptions discussed above. (The flip side, of course, is that it’s tied to all the details of the VM memory layout, so literally any update to the game breaks existing saves.)
As the philosophy of Strand is streamlined development, it was essential in the system design that maintaining the game state required absolutely no work on the part of the game author.
Overview of game state.
The Timeline “Tape”
The state of all variables, objects and properties is retained at all times on a “timeline”. The timeline can be thought of as a length of “tape” upon which data can be written. At the start of the game, the timeline has the initial state of all properties and objects.
As the game is played, changes to the world (ie game state) are appended to the timeline. That is to say they are added and nothing prior is erased. This is done automatically with nothing required from the author.
At runtime, the game determines the value of any property by scanning from the end of the “tape” backward until a value is found (or not if absent).
The benefits of this approach are multi-fold:
undo is performed simply by erasing the last move (or moves) from the tape.
undo can be repeated until the entire game is unwound.
save-game is simply the content of the tape.
undo can be performed after a game restore.
redo is possible if the end tape is not erased but just rewound.
Very interesting! Undo/redo across save+restore, going forward and backwards through the timeline, and branching off new timelines from any past checkpoint are all very cool and – I think – not directly supported by any Glulx interpreter (short of the player manually saving every turn and keeping all of those save files around).
I’d like to teach a Glulx interpreter to do a similar thing at even finer granularity. Instead of a series of save games (that are also missing some information), it would be a “flight recorder” that allows replaying the full timeline of the game’s internal state, RNG outcomes, text input/output, Glk window arrangement, which images where displayed when & where, and so on. Besides generalizing autosaves and working across save/restore, it might be useful for tracking down tricky bugs discovered by testers (though this needs a dedicated debugger, like rr for native code). It’s harder than for Strand because the Glulx VM is so low-level, so approximately everything has to be recorded or made deterministic to enable replay. I think I have a workable plan for it, but it requires a much more sophisticated bytecode interpreter than I currently have, so who knows when I’ll actually get around to trying this idea.
One question: you wrote earlier that the terp can initiate saving the game, but the game logic has to accept a “look” command after restoring. Inform games can retreat into custom input loops where they only expect particular inputs (e.g., “press any key to continue” / a menu navigated by arrow keys / numbered choices) and wouldn’t be prepared to deal with a “look” command. This is not the only reason why interpreter-managed autosaves are a separate thing from “ordinary” game-initiated saves in the Glulx world, but it’s one reason. Does Strand even intend to support that sort of thing, and if so, how does it interact with the terp initiating save/restore?
I don’t think that would be feasible. I haven’t directly measured it, but based on other profiles each turn could involve tens or hundreds of thousands of writes to memory.
At best each turn’s undo state could include interpreter things like the random state, Glk details etc. But it wouldn’t be appropriate for a general purpose interpreter as people will want to be able to undo in order to get a different random result.
I didn’t mean to imply it would be implemented by logging every single write to RAM (or locals, or the stack). That would indeed be exceedingly expensive. Luckily, almost all of those values are computed deterministically from external inputs, RNG outputs, and a few other things that can vary from run to run or from machine to machine. So only those things need to be logged to enable accurate replay and the rest can be recomputed on the fly. Adding full VM snapshots could be a useful optimisation for replay, but is not strictly needed and so it could be omitted or done only every turn or once per 100 turns to limit space consumption.
The main challenges as I see it are correctness (identifying everything that needs to be logged) and writing the interpreter in a way that supports logging, not logging (normal play), and replaying without unnecessarily slowing down any of those three scenarios.
And yes, this would be opt-in and not be a replacement for normal undos. In fact, there’s no reason why it shouldn’t be possible to record and replay a play through (or several) across undos, save/restore, closing and relaunching the interpreter, and restarting the game. A transcript on steroids, if you will.
I discovered that it is necessary to allow certain commands during otherwise modal situations such as “continue” or during a choice list presentation;
This originally came about when implementing clickable words in the main text. Words that can be clicked appear underlined like HTML links. Such things are very useful and are usually used as a shortcut to examine items that are mentioned in location descriptions (and others).
But what do you do when the description has “links” and causes a “continue”? Worse, you cannot know where and when a continue will happen due to different sized screens. If they’re in a continue, can they still click “links” ?
Turns out they have to because, if instead you gray the links or some other technique, pressing continue will likely scroll the whole lot off the top and the link text will be gone. They would have to remember links were there and scroll the text back in order to see and use the links again. This would be rather annoying and somewhat defeat the convenience of links in text.
There are other situations as well as “continue” such as during conversation dialogue and modal choices.
This problem caused me a headache until i realised there is a class of commands that do not actually change the world, including “look”, “i” and “examine” something.
So i decided to allow these in modal situations. The upshot is that commands like “look” can be performed at any time without causing problems. Consequently, the UI can issue a “look” command to the game without worrying about whether it will be accepted.
Regarding your idea of making a timeline for Glulx;
It would be practical to have a delta-type storage between one move and the next. This is essentially what my timeline is. Changes to the world are written to the timeline along with “time” markers. If you engineered a clever Glulx delta, it might be highly feasible.