Savegame backwards compatibility

As I prepare to launch the PataNoir app, my number one worry right now is that I will not be able to fix any bugs, or make any changes at all, no matter how small, without breaking existing savegames. This is OK when players are using interpreters, because it’s the players own responsibility to use the correct version of the story. However, when publishing apps on the app stores, pushing an update that breaks everyone’s saves with no warning is simply not acceptable.

I was wondering if there is a way to override Inform’s version checking and force the game to load saves made from an earlier version? I realize this will cause problems if I screw things up and actually update the state of the game, but that’s my problem then. The vast majority of changes I’ve made during testing have been bug fixes and small improvements that shouldn’t effect save state at all.

Perhaps it might be possible to fix the version code of the story file, so it isn’t incremented when compiling a new version? It’s the serial number that is used for this purpose, right?

Someone with a more thorough understanding of the save system might be able to give a better answer, but I don’t think it’s possible to fix the old saves at this point. It might be possible if all you did was trivial typo fixes and nothing else, but if you’ve made bug fixes it probably involved changing the code which in turn changes the story file structure and the save files break. This is doubly true with I7 that has enough layers to create a kind of ripple effect where small changes in the I7 code result in large changes in the story file.

What I would do is ship the updated app with two versions of the story file: the old one and the new one. Have the app detect if there are any save games. If there are none, there’s no problem and the app can use the new story file. If there are saves ask the player if they want to update, with a clear warning that updating will delete their existing save games. If they don’t want to update, keep using the old story file and place an “update” button somewhere in the app so that they can update later.

If one of your typo fixes changed the length of a string, that would be enough to change all the following addresses and break save files.

Juhana’s plan is the same one I’ve come up with. (I have of course thought about this for Hadean Lands.) I haven’t done the work on IosFizmo/IosGlulx to make it happen, though.

Thanks for your replies, both of you. I was hoping that the save files simply contained a description of current location, items carried, state of rule counter, etc. If it’s tied to specific addresses, then I suppose there is nothing to be done.

For future games, I might consider implementing my own save system using the Inform file writing/reading features. For a lot of games, a simple checkpoint system might be sufficient.

I’m sure Zarf will set me straight if I’m wrong (I’m not a real programmer), but this strikes me as a situation where an object-oriented programming language would have an edge over I7.

Consider: Your save system will have to record and save the state of every object in the model world that can change state. In I7, giving an object various states and then changing the state during gameplay is trivially easy. “The safe can be guarded or unguarded.” And then, in the game code, “Now the safe is unguarded.” The library changes the state of the object using its own internal mechanisms.

In an OO language, we would write something like ironSafe.guarded(nil). This would call the guarded() method on the ironSafe object. And we could write our own code for the guarded() method, which would, in addition to changing the state of the object itself, fire off a message to gameStatusRecorder, so that the save/load mechanism would know the current state of ironSafe.

My suspicion, without knowing the internals of I7, is that intercepting the state-change of an object will not be quite that tidy or straightforward to implement. Perhaps I’m wrong – it’s just an interesting question to ponder while drinking my morning cup of tea.

I believe the way it works right now, in most interpreters, is that a “saved game” is a record of all the ways in which the virtual machine’s memory differs from its starting state. So the interpreter doesn’t record that object #69105 has member variable 2 set to some value, it records that the memory at such-and-such a location was changed from 0x0000 to 0xFF03 at some point since the game started, so upon restoring the game that memory address should be changed appropriately. Anything which changes the layout of the objects in memory thus breaks all saved games, because the save doesn’t line up with the actual contents of memory.

A save system that tries its best to automatically adapt old save games to new story files isn’t impossible, but also comes with serious drawbacks. The #1 is that it would be extremely easy for authors to shoot themselves in the foot if they don’t have a crystal clear understanding of how the save system works. For example, what if release 2 of the game added the safe? How does the game know if the safe’s state should be “guarded” or “unguarded” after it has loaded a save file from release 1? The author would have to add extra logic that determined the state of the object at any arbitrary point in the game. Or what if the author changed the object’s name? The compiler can’t know that ironSafe is the same object that was previously called steelSafe. Etc etc.

These aren’t insurmountable problems but the author would have to be well aware of what they’re doing every time they released an update. The nice thing about the current system is that the save mechanism is one thing that the author doesn’t have to worry about at all, even though it comes with the cost of breaking old saves with updates.

This isn’t the case if you use checkpoints, though. For example, if the game is divided into independent chapters, then all you need to store is the index of the current chapter, and the player can start from the beginning of the chapter when reloading. That’s how a lot of graphical games work nowadays.

Even if the chapters are not completely independent, you only need to store the state that can be transferred between chapters, which will often boil down to a few Important Choices.

For less the linear games, this becomes less feasible, but it should still be possible to significantly cut down on the amount of savegame information, by considering the constraints of the game and only storing what you need to know. For example, if the player needs to be carrying specific items to advance the plot, you don’t need to store the inventory.

To be clear, I don’t think the current default behavior should be changed, for the reasons you stated. I just wish there was a way to override it, for expert authors who are 100% certain they know what they are doing.

I know of at least one game which does override the save system in that way (the AIF game that uncovered those buffer overflow problems in the I7 compiler). “Saving” manually writes an external file with a long list of numbers representing the state of all sorts of variables in the game. But it’s primarily an open-ended choice-based work where most choices can be represented by a boolean. A parser game that had to keep track of the positions of all sorts of objects as well would be trickier.

It would be a great deal of additional complication for a very small number of expert users. Like, a very small number.

Like, I thought about using checkpoint-based saves for Hadean Lands but decided I couldn’t be 100% sure of getting it right.

Simple chapter-based checkpoints are easy enough, though. Open an external file and write the chapter number into it. Then make sure you write your game so that it’s safe to jump to any chapter.

1 Like

If you want to get fancier with the external file, and write a larger (but still limited) information subset to it, that’s also possible.

1 Like

Last year I wrote up an essay on a symbolic save system. (Basically what Jim was referring to – as opposed to the memory-snapshot save system that we use now.) (“Object-oriented” per se is not all that relevant.)

It’s pretty much what Juhana said, with more detail.

1 Like