Skip to navigation


Multi-byte variables

Multi-byte variables

Aviator uses a lot of multi-byte values in its calculations. One variable, containing the plane's current position in 3D space, even contains a 32-bit value, spread out across four bytes. In the terminology of this site (explained in the terminology guide), this variable is:

  (xPlaneTop xPlaneHi xPlaneLo xPlaneBot)

This means that the top byte is stored in xPlaneTop, the high byte in xPlaneHi, the low byte in xPlaneLo and the bottom byte in xPlaneBot.

So far so good, but where are these locations? You might think that it would be convenient to store multi-byte values sequentially in memory, in little-endian order, with the least significant byte first, to mirror the way the 6502 stores its addresses. Or you might be tempted to go big-endian, with the most significant byte first, as that's how we humans write them down. For example, Elite stores a lot of numbers, and it tends to go for the little-endian approach; you can read more about this in my Elite project.

Aviator, however, blows all these conventions out of the water, and spreads its bytes all over the place. At first glance, this seems like a strange approach, but there is method in this apparent madness, because the individual bytes might be spread out, but they are often spread out in a pattern.

The pattern is simple. Given a multi-byte number, its high and low bytes are often stored with exactly 16 (&10) bytes between the location of each individual byte. For example, we have the following:

  • xTemp2Lo is at &0CA8, xTemp2Hi is at &0CB8
  • xVelocityPLo is at &0C03, xVelocityPHi is at &0C13
  • dxRotationLo is at &0C86, dxRotationHi is at &0C96

and so on. Structuring these 16-bit values like this means we can build routines like CopyPointToWork and CopyWorkToPoint, which take an offset in X that points to the low byte address, and update both the low and high bytes, because given the low byte address, we can simply add &10 to get the high byte address. Not only that, but if your source code uses direct memory addresses rather than labels, which was pretty common back in the day, this system makes it easier to remember the addresses of both the high and low bytes of your 16-bit variables.

An additional advantage of this system is that it is pretty easy to add an extra byte onto an existing variable - much easier than extending a sequential big-endian or little-endian variable, where you have no choice but to insert a byte at the variable's location. With the bytes so spread out as they are in Aviator, you just need to find a spare byte. Ideally it would be 16 bytes away from the existing locations, but it doesn't have to be if you aren't going to use the Copy routines. Indeed, the variable locations seem to show this extending of variables in action; look at xPlane, for example:

  • xPlaneBot is at &0C09
  • xPlaneLo is at &0CED
  • xPlaneHi is at &0CFD
  • xPlaneTop is at &0C6D

This seems to imply that (xPlaneHi xPlaneLo) came first and followed the 16-byte separation, and was then extended by xPlaneTop, which isn't 16 bytes away, but is at least a multiple of 16 bytes away... and finally, xPlaneBot was added, which is in a completely unrelated place. As the bottom byte is only used in one place for storing the fractional overflow of a calculation, this history makes sense.

Along with the source code clues hidden in the game binary, this is another interesting glimpse into how this game was written.