iNCURSION release 3 — a save format that survives new content
Folks, R3 is a huge change, and it diverges us a bit from upstream.
I made a change to give the flame tongue the burn-undead power its own description has always promised, and that required adding a new resource to the game data. I did it, and then found out it had wrecked my save game. Not the file — the file was fine. The game was just reading it wrong.
Why? Every resource is stored by position, and the lists sit end to end in one numbering space. Add one thing anywhere, and everything after it slides down a slot. My save still held the same numbers; those numbers now pointed at different things. So my Orc Ranger/Rogue/Druid of Zurvash loaded as a Lizardfolk Priest/Ranger/Bard of Xel. Bad trade.
So I spent a very long time and an obscene number of tokens building a new save format that survives additions and changes without invalidating save games. It records every resource's name next to its position, so the game can work out where things moved to instead of guessing. R3 loads your R2 save and converts it the first time you save. You do not have to do anything.
From then on, new content — monsters, spells, gods, dungeons, terrain types — can be added without invalidating your save, as long as it goes on the end of its list. Adding something in the middle is now refused outright, with a message naming exactly what moved, instead of silently handing you a different god.
One thing to get right: upgrade to R3 before R4. R3 is deliberately content-identical to R2 so your old save converts cleanly. Jump straight from R2 to R4 and you hit the same wall I did.
If you want the gory details, my IT oompa-loompa explains it below. The oompa-loompa is Claude — the engine work in this release was done with AI assistance.
Oh, and the flame tongue still does not burn undead. I took that back out so R3 would ship clean. R4 puts it back.
The gory details
Every reference to game content inside a save was a bare number, and that number was a position. The lists — races, classes, gods, monsters, spells, effects, and sixteen more — sit end to end in one numbering space, so adding a single entry to any list shifts every entry in every list after it. Your save still holds the old numbers. The game reads them against the new lists. That is the entire bug, and it is how one new sword effect turned an orc into a lizardfolk.
A save now carries a manifest: the length of all 21 lists, and every entry's name in order. A reference on disk is still a plain number, but the reader no longer trusts it. It turns that number back into "list 7, position 325" using the lengths the save recorded, then forward into today's numbering using the lengths the game actually loaded. Names are consulted only to decide whether that conversion is safe.
The part worth showing off: the check refuses only on positive evidence that something moved — a slide, where consecutive positions hold the name recorded one place over, or a shuffle, the same names in a different order. Everything else loads. Rename every god in the game and your save still opens, because a rename moves nothing. And when it does refuse, it says what it found:
module slot 0 Effect array slid at position 187: the save's manifest recorded "Goblinoid Summoning" there and the loaded module has "Animal Summoning" — a resource was inserted in the middle of the array
Two things fell out along the way. A save that cannot be written is now refused before the existing file is touched, so a failed save no longer leaves you holding a truncated file. And the format is tagged records rather than raw struct dumps, which took a 2.4 MB save down to 360 KB.
New content goes on the end of its list. That is the only rule, and the game enforces it.