A-code upward compatibility of saved games

A-code makes it fairly simple to preserve upward compatibility of saved games – a feature I found indispensable in alpha and beta testing, and generally useful after that.

You can just stick to following the five rules outlined below, and ignore ignore all accompanying explanations, but it may be useful to have some idea as to why those rules need to be followed. For that you need to have some grasp of A-code's internal concept of refnos (short for reference numbers).

Refnos were briefly explained in the main description of the A-code language, so just briefly...

A refno (or in full a reference number) is a number uniquely assigned by A-code to value-bearing declared entities – i.e. to objects, locations, variables and texts. The important point is that these refnos are allocated sequentially within each of these four categories in the order of entity declarations. Thus in a game source which mixes randomly object, place, variable and text declarations, objects will be numbered consecutively in their order of declaration, followed by places in their order of declaration etc.

It is by preserving associations of refnos to game entities within these categories that saved games are kept upwardly compatible. So let's consider what specifically would or would not break such compatibility. For simplicity, I'll stick with talking about objects, but exactly the same points apply within other categories that can have non-constant values associated with them - places, variables or texts.

If a previously declared object is removed in a later version of game's code, then all objects declared after the removed one will have their refnos reduced by one. If there are such objects, compatibility breaks. If there aren't (i.e. if the last declared object was removed, it does not break.

Similarly, if an object is added, then refnos of all objects declared later in the game's code will be increased by one. If there are such objects, compatibility breaks. If there aren't (i.e. if the added object is the last object declaration, it does not break. So...

Rule 1: do not add objects (places, variables or texts) except at the end of all object (place, variable or text) declarations.

What about removing objects. As should be obvious from the above, that's a bad idea. While it may seem that removing object(s) from the end of object declarations, the internal mechanics of the A-code kernel preclude this too. Thus...

Rule 2: The overall number of objects (places, variables or texts) may increase (as per rule 1), but decreasing it will break compatibility.

If you do find that an object is no longer required, don't remove it. Just leave it there, possibly giving it a different name, e.g. SPARE.OBJ.1 or something equally obvious. Better still, make its new name start with a dot: .SPARE.OBJ.1 -- this will stop the acdc translator complaining about an object being declared but not used. If later on you do want to re-use that spare slot for some other object, you can do so, but you cannot assume its value to be zero after a game is reloaded – that game may have been saved before you removed the original object.

As explained in the language description document, in addition to an integer value all objects, places and variables carry a bit-screen of flags. Flags have to be declared as symbolic names, but as far as the A-code kernel is concerned, such names are just synonyms for integer (non-negative) bit-screen offsets. Just as for refno association with entities, the association of these values with flag symbolic names must be preserved if saved games are upwardly compatible.

Therefore Rules 1 and 2, stated above, apply to flag declarations too (separately for object, location and variable flag sets), but there is an additional constraint, which can be stated as

Rule 3: do not change bit-screen byte-sizes!

Simple version: declare up front a few spare flags in each of the three categories – object, location and variable. To avoid acdc complaining about the extra flags not being used, give them names starting with a dot (e.g. .SPARE.OBJ.FLAG.1 etc) or wiith "spare.." (e.g. SPARE..O1).

Complex version: you may have some spare flags in each flag set, even if you did not declare any spare ones. This needs unpacking and to do so, I need to explain a bit about internal data structures involved.

While A-code only permits a single flags declaration for both of objects and places, multiple flag sets of various sizes can be declared for variables. It may seem puzzling that bit-screens carried by variables are not somehow associated with particular variables (except possibly via code comments). The simple explanation is that there is in fact just one bit-screen for variables too. All the separate declarations of it merely define different synonyms for the same bit offsets. Thus e.g.

FLAGS VARIABLE
      FLAG1
      FLAG2
FLAGS VARIABLE
      FLAG3
      FLAG4
      FLAG5

define one bit-screen of three bits, where FLAG1 and FLAG3 are both synonyms for 0, FLAG2 and FLAG4 are both synonyms for 1, and FLAG5 is a synonym for 2.

To make things more complicated, for historical reasons, location and object bit-screens automatically reserve the first three bits for kernel's use. Thus

FLAGS OBJECT
      OFLAG1
      OFLAG2

declares a bit-screen of five bits, with OFLAG1 as a symbolic name for 3 and OFLAG2 as a symbolic name for 4.

And as a final complication, bit-screens are actually allocated in 8-bit bytes. Hence in the above examples, the object bit-screen will have three spare flags (offsets 5, 6 and 7), while the variable bit-screen will have 5 spare (offsets 3, 4, 5, 6 and 7).

We are now ready to calculate "bit-screen byte-sizes" that have to be preserved. If the longest variable bit-screen declared by game's code has N flags, the size of the bit-screen is the smallest number of 8-bit bytes containing at least N bits. For both place and object bit-screens it is the smallest number of bytes containing at least N + 3 bits.

Thus, for example, if you have 17 distinct (non-synonymous) object flags, the number of bits actually uses is 20, leaving you with four spare flags. Of course, there is nothing to stop you declaring explicitly some spare flags (with names prefixed with a . to keep acdc happy) for potential future use.

Rules 1 and 2 above have dealt with declared texts, but an additional, related consideration applies to in-line texts.

Rule 4: do not use text morphing features in in-line texts.

By this I mean texts which have an internal dynamic: incrementing, cycling or randomised – in the case of in-line texts signalled respectively by 'i:', 'c:' and 'r:' at the beginning of the text. The 'f:' prefix, signalling that the in-line text is a fragment, is benign.

The reason for this restriction is simple: by their very nature, the order of declaration of inline texts is what it is. While the acdc translator can group them all safely at the end of text refnos, it can do nothing about their ordering. Therefore adding an in-line text necessarily increments refnos of all succeeding in-line texts. If such texts have no internal dynamic, this is not a problem, since being nameless, they cannot be referenced from elsewhere in the code.

And that's really it. In practice, while working on and testing Adv770 I found that preserving upward compatibility between game versions was not hard. If the kernel ever changes in a way which would enforce upward compatibility break, this will be signalled by a change in its major version number.


Back to the documentation index
To the Mipmip home page
Feel free to leave a comment!
Mike Arnautov (27 March 2023)