I can’t look at any more costumes tonight, so I’ll write a longer answer.
This is going to be a very generalized look at the problem. It’s not Inform-specific. (In fact, Inform is organized the opposite of this way, as we’ll see.)
Imagine you have an IF game in some abstract sense. It’s a bundle of information. It’s not organized in a very regular way – not like a database schema of regular tables. Some objects have common properties (“location”) but some have object-specific properties, there are a bunch of ad-hoc global variables, etc.
To save the game state in a portable way, we have to give every bit of information a name and then save the big heap of named data. When we read the data back in with a new version, hopefully most of the names haven’t changed – they still exist in the new version and they mean the same thing. For the ones that have changed, we supply an upgrade routine that massages the data. (Makes the appropriate tweaks as it’s read in.)
Simple example. Every object has a location, which is another object. This models a simple containment tree. (Skip over I7’s complexity of different containment/support/incorporation relations.) So at startup time, we might have
LAMP.LOCATION = LIVINGROOM
PLAYER.LOCATION = EASTOFHOUSE
LEAFLET.LOCATION = PLAYER
DIAMOND.LOCATION = NULL
(The diamond has not been created yet so it starts off-stage.)
We can easily throw some global variables into the heap:
GLOBAL.SCORE = 0
GLOBAL.MAXSCORE = 325
In version 2, you decide to add a treasure, the emerald, which starts in the kitchen. Save files from version 1 aren’t going to mention the emerald at all, of course. So you need a function (give it a conventional name like V1TO2) which sets all the emerald properties when a v1 save file is loaded. It could also increase the max-score global to 350.
If you decide to delete the diamond in v3, that might be trickier. If it vanishes from the player’s inventory (or from the trophy cabinet), the V2TO3 routine might have to appropriately decrease the score. But what if the player handed the diamond to the troll as a bridge toll? You could bring the troll back into play, but if the player is on the far side of the bridge, the game could wind up in an impossible state. There are various ways to work around this; you’ll have to pick one.
(One option is always for the V2TO3 routine to say “Sorry, this is too large a version jump, I can’t restore v2 files.” Current IF systems effectively always fall back on this option. We’re trying to be nicer, but in practice we might wind up with some version jumps that are “save-safe” and some that aren’t.)
It’s critical, though perhaps not obvious, that the author has to be involved in the process of naming objects (and properties, and globals). It’s no use the compiler auto-assigning names OBJECT0, OBJECT1, OBJECT2, etc. Because first, that makes the V1TO2 function hellishly obfuscated; and second, what if the author inserts a new object early in the game? It throws off all the other numbers; now the V1TO2 has to really long because it’s reshuffling every single data bit. No author wants to write that. (And the compiler can’t auto-generate it because, well, that’s the problem we’re trying to solve in the first place!)
Now presumably every object (and global, etc) already has a “source code name” that the author made up. (Slightly tricky in I7, but again, skip that for now!) So this naming problem is in a sense already solved. But this means that we’re exposing the source code names in a way which the author isn’t used to. In a typical IF program, if you decide to rename a global variable, you do your context-sensitive search-and-replace and it’s done. But in this system, you have to also add a line to a V1TO2 function. The fact that the global changed names gets embedded in the program, and it has to stay there forever (or until you decide to stop supporting v1 save files).
So complexity accumulates in unpleasant ways. One more thing to remember, one more potential bug.
Let’s go back to substantive data changes (as opposed to renaming things). I’ve skipped an important distinction: static properties (which never change over the course of the game) versus dynamic properties (which do). Similarly we distinguish global constants from global variables.
Our life is much simpler if we don’t store constants and static properties in the save file at all. They are fixed in the game file at startup, and the restore mechanism never touches them. So we can update them in v2 without any trouble at all! The player launches the v2 game: all the static properties have the correct v2 value. The player restores a v1 save file: the static properties are not touched, they still have the correct v2 values. Copacetic.
You can go a long way with static properties. For example, in my Inform games, I always treat “description” as static; I (almost) never change an object’s description. If the object is mutable, I give it a description value like “[if in Kitchen]…[else]…[end if]”. That’s fine in the save system I’m describing. It’s code, but Inform already treats code as static.
So you can imagine changing an object’s description in v2 of a game. As long as you treat description as a static property, this will not require any V1TOV2 work; it’s a guaranteed safe fix.
In fact you can imagine a large class of updates which only involve updates to static data. (Fixing textual typos, fixing logic errors within functions, changing constants, etc.) This is the domain within which Inform could have safe, reliable version bumps with no extra work by the author.
Unfortunately, Inform is in no way engineered for this sort of thing. Inform doesn’t have much notion of static properties. (You can say “The weight of a treasure is always 12,” but there’s no way to convince Inform that descriptions are fixed for each individual object throughout the game.)
I say “unfortunately”, but really it’s kind of a feature. If you did decide to change a description property halfway through the game, would you want an extra stumbling block? You’d want to just do it, write an apologetic comment to yourself, and move on. And this gets back to the complexity thing. This save scheme seems awesome until you realize that every time you add a damn property you have to think about its save-and-restore strategy. IF games are full of ad-hoc properties. The languages are designed to make that easy.
Inform, in particular, is downright efflorescent with saved state. Every time you write “[first time]…[only]” or “[one of]…[stopping]”, bam, that’s a new flag. Every time you write a rule “if examining foo for the third time”, bam, that’s a new counter. A relation is implicitly a property, unless it’s a many-to-many relation, in which case it’s an array. Grammar tables are stored as arrays; there’s no built-in way to alter them during play, but the system allows the possibility.
Having to think about save-and-restore problems for every one of these features is an almighty pain in the spinal cord. It’s nice to think, oh, I will only make safe changes this version – but one little “[first time]…[only]” sneaks in…
In real life, bug fixes are messy. Last month I had a silly HL bug – “PUT ME IN BEAKER” sometimes succeeded. The fix was, logically, a purely static change: I reduced the scope of that rule from “things” (which included the player) to a smaller class. But the fix would still require a V1TOV2 rule because the player shouldn’t be in the beaker! To really be reliable, I’d have to check that case and move the player out. (And then there’s the case where you follow up by emptying the beaker, so the player winds up off-stage…)
Repairing game state is hard. It requires serious-ass debugging skills – it’s way harder than just finding the bug and fixing it. If you screw it up, well, your game is buggier than before – for players with v1 save files. And that’s when you say, screw this, I’ll just refuse to load v1 save files into v2.