Memory usage is really tight in BBC Micro Revs
Revs is an extremely sophisticated piece of programming, so it's no surprise that it leaves very little spare memory when loaded into a standard BBC Micro. The complex loading system unfolds the game code into every nook and cranny, leaving hardly any room for anything else; the game manages to squeeze working game code into screen memory, hiding it right in the middle of the blue sky; and even the game text is tokenised to save precious memory.
Indeed, when author Geoff Crammond added a new feature, computer assisted steering (CAS), to the 1986 Superior Software release of Revs, he could only fit in the additional code by repurposing the memory used by the SetupGame routine, which is only run once and isn't needed again once the game has started. The Superior version reuses this memory to store the drivers' accumulated points, overwriting the setup code once it has served its purpose.
To say that Revs is a tight squeeze is a bit of an understatement. Let's take a deeper look at just how tight things are.
The Revs memory map
-------------------
When the cassette version of Revs is loaded, this is what the memory map of the BBC Micro Model B looks like. On the left is the memory map of the game running in the custom screen mode, during the driving part of the game, while the right side shows the game running in mode 7, when displaying the various game menus and leaderboards. The game starts in the mode 7 configuration, and switches to the left configuration when heading to the track.
+-----------------------------------+ &FFFF | | | Machine Operating System (MOS) | | | +-----------------------------------+ &C000 | | | Paged ROMs | | | Mode 7 +-----------------------------------+-- &8000 --------------------------+ | | | | | Screen memory | | | | | Game code (dash data 0-25) +-- &7C00 --------------------------+ | | | | | | | | | +---------- &7B00 = UpdateMirrors --+ &7768-&7BFF unused | | | | | Screen memory (dash data 25-40) | | | | | +-----------------------------------+-- &7768 --------------------------+ | | | | Screen memory (dash data 41-42) | Dash data 41-42 | | | | +-----------------------------------+-- &6C00 --------------------------+ | | | | Screen memory (track & horizon) | &6700-&6BFF unused | | | | +-----------------------------------+-- &6700 --------------------------+ | | | | Screen memory (game code in sky) | Game code | | | | +-----------------------------------+-- &5E40 = xVergeRightLo ----------+ | | | | Screen memory (race info text) | &5A80-&5E3F unused | | | | +-----------------------------------+-- &5A80 --------------------------+ | | | | Main game code & screen buffer | Main game code & dash data 0-40 | | | | +-----------------------------------+-- &0B00 --------------------------+ | | | Main variable workspace 2 | | | +-----------------------------------+ &0880 | | | MOS sound workspace | | | +-----------------------------------+ &0800 | | | Main variable workspace 1 | | | +-----------------------------------+ &0380 = objYawAngleLo | | | MOS VDU workspace | | | +-----------------------------------+ &0300 | | | MOS general workspace | | | +-----------------------------------+ &0200 | | | 6502 stack descends from &01FF | | | +-----------------------------------+ | | . . . . . . . . . . | | +-----------------------------------+ &01B8 | | | Stack variables workspace | | | +-----------------------------------+ &0100 = positionNumber | | | Zero page workspace | | | +-----------------------------------+ &0000 = carMoving
The left configuration shows dash data blocks #0 to #40 after they are moved out of the main game code by the CopyDashData routine, leaving space in the main game code for the screen buffer. The right configuration shows the dash data after it's been put back into the main game code, with only blocks #41 and #42 left in memory, where they remain untouched, ready for the next switch to the custom mode. For a detailed look at the dash data and the complex moving process, see the deep dive on the jigsaw puzzle binary.
Another item of note is the game code between &5E40 and &6700, which is used in both modes, but which is hidden in the custom screen's blue sky during races. For more information on the custom screen mode, see the deep dive on hidden secrets of the custom screen mode.
Note that the memory map shows the full 64K of addressable memory that's supported by the BBC's 6502 processor, but only the bottom 32K is writable RAM, and hence usable by Revs. The top 16K is mapped to the MOS (Machine Operating System) ROM, and the next 16K is mapped to the currently selected paged ROM, which might be anything from BBC BASIC to the VIEW word processor.
This 32K of RAM includes both screen memory and the various operating system workspaces, which can leave a pretty small amount for programs (especially in high resolution screen modes). Let's take a look at exactly how much unused memory there is in Revs.
Memory usage
------------
Here's a full breakdown of memory usage in bytes, once the Acornsoft version of Revs is loaded and we have started driving:
Used Unused Machine operating system (MOS) ROM 16,384 - Paged ROMs 16,384 - MOS general workspace in page 2 256 - MOS VDU workspace in page 3 128 - MOS sound workspace in page 8 128 - MOS zero page locations, &D0-&E1 and &E4-&FF inclusive 46 - ------ ------ Total MOS and ROM memory 33,326 - Memory for the custom screen mode 8,320 - 6502 stack, stack variables (at opposite ends of page 1) 256 - ------ ------ Total shared memory 8,576 - Main game code & screen buffer (in the dash data blocks) 20,352 279 Game code above screen memory (from UpdateMirrors and up) 1,280 1 Main variable workspace 1 (various tables, config) 1,152 52 Main variable workspace 2 (various tables, coordinates) 640 30 Zero page workspace (commonly used variables) 210 66 ------ ------ Total game code memory 23,634 428 Total memory 65,536
As you can see, Revs does not use every single last byte of the BBC Micro's usable memory - there are 428 unused bytes. Let's look at these in more detail.
Counting the free space
-----------------------
Even when Revs is using its custom screen mode, the game contains a few unused blocks of memory, though most of these are in small blocks of one or two bytes, often spacing out variables so they start on page-aligned or easily calculated addresses. For example, a number of the dash data blocks have unused bytes sandwiched between the preceding game code and the start of the dash data; some of these unused blocks are large enough that they could be used for game code or variables, though extending Revs in any meaningful way using these blocks would still be tricky.
I've managed to track down 428 unused bytes in the original Acornsoft version, once the game is fully loaded and we're driving around the track (there is a lot more unused memory when we're in the mode 7 game menus, but that doesn't count). For comparison, Elite only has 66 spare bytes, which sounds like a big difference, but Elite needs to use the filing system when running and Revs doesn't, so Elite's memory constraints are slightly higher (though conversely, that's why Revs could use zero page locations &90-&CF, which it doesn't). Whatever the difference, both games are impressively compact.
Here's a summary of all the unused bytes I know about.
Location(s) | Unused bytes | Section |
---|---|---|
Zero page: &90-&CF and &E2-&E3 | 66 | Zero page workspace |
After objectSize | 32 | Main variable workspace 1 |
After configAssist | 5 | Main variable workspace 1 |
After volumeLevel | 1 | Main variable workspace 1 |
After lapTenths | 2 | Main variable workspace 1 |
After lapSeconds | 2 | Main variable workspace 1 |
After lapMinutes | 2 | Main variable workspace 1 |
After segmentFlags | 8 | Main variable workspace 1 |
After totalRaceSeconds | 16 | Main variable workspace 2 |
After zSegmentCoordOLo | 4 | Main variable workspace 2 |
After zHelmetCoordLo | 3 | Main variable workspace 2 |
After zSegmentCoordOHi | 4 | Main variable workspace 2 |
After zHelmetCoordHi | 3 | Main variable workspace 2 |
Before soundData | 16 | Main game code |
After GetObjectDistance | 15 | Main game code |
After DrawSegmentEdge (part 7) | 10 | Main game code |
After vergeScale | 2 | Main game code |
After handPixels | 6 | Main game code |
After staDrawByte | 26 | Main game code |
After DrawFence (part 2) | 1 | Main game code |
After token18 | 1 | Main game code |
After CheckRestartKeys | 2 | Main game code |
After pixelsToLeft | 7 | Main game code |
After token11 | 5 | Main game code |
After token25 | 4 | Main game code |
After token51 | 4 | Main game code |
After token3 | 4 | Main game code |
After token0 | 4 | Main game code |
After token17 | 13 | Main game code |
After token35 | 5 | Main game code |
After token45 | 2 | Main game code |
After Print2DigitBCD | 1 | Main game code |
After token39 | 1 | Main game code |
After token49 | 3 | Main game code |
After dashDataOffset | 3 | Main game code |
After token30 | 2 | Main game code |
After token53 | 2 | Main game code |
After token38 | 3 | Main game code |
After mirrorSegment | 2 | Main game code |
After token24 | 4 | Main game code |
After token15 | 9 | Main game code |
After token34 | 12 | Main game code |
After token21 | 4 | Main game code |
After timeFromOption | 1 | Main game code |
After pointsForPlace | 2 | Main game code |
After token9 | 4 | Main game code |
After token12 | 4 | Main game code |
After token37 | 4 | Main game code |
After fillDataOffset | 7 | Main game code |
After token7 | 5 | Main game code |
After dashData31 | 16 | Main game code |
After yLookupLo | 7 | Main game code |
After driverNames1 | 3 | Main game code |
After ResetBestLapTime | 1 | Main game code |
After endMirror | 3 | Main game code |
After driverNames2 | 4 | Main game code |
After PrintHeader | 5 | Main game code |
After token1 | 18 | Main game code |
After token40 | 6 | Main game code |
After token52 | 2 | Main game code |
After FlushSoundBuffers | 1 | Main game code |
After sectionSteering | 6 | Main game code |
After vergePixelMask | 2 | Main game code |
After zRoadSignCoordHi | 3 | Main game code |
After yCursor | 2 | Main game code |
After DrawCarInMirror | 1 | Game code above screen memory |
Most of these blocks are tiny, but it would be feasible to add code or data into the larger blocks and connect the code with jumps and branches. Adding up the unused blocks with double-figure sizes gives 118 bytes, which has potential, if nothing else.
But it's certainly fair to say that Revs does an impressive job of squeezing itself into the standard BBC Micro, using pretty much everything the 32K micro has to offer. Looking at the finished game, you wouldn't expect anything less.