How Angus Duggan found enough spare memory for Elite-A's modifications
Elite is famous for using practically all of the available memory in the BBC Micro, and a quick glance at the memory usage for the BBC Micro cassette version shows just how tight things are. Angus Duggan's Elite-A is an extended version of the BBC Micro disc version, where the only way of fitting in all the enhanced features was to split the main codebase into two separate files, only one of which is loaded at any one time (one for when were are docked, and the other for when we are flying in space).
For example, Angus had to find a way of adding our current ship type to the Status Mode screen, and extra functionality takes up extra space:
So how did he manage to squeeze so many new features and changes into an already chock-full codebase? Here's a run-down of how he did it.
Removing unused code
--------------------
The main savings come from stripping out any unused code, of which there is a surprisingly large amount, particularly in the docked code. The original disc version retains a lot of flight-specific code in the docked code that is never actually called, so Angus removed the following routines to free up quite a bit of memory:
- ABORT, ABORT2, BAD, BULB, BUMP2, CIRCLE, cntr, CPIX2, CPIX4, DEEOR, DENGY, DET1, DKJ1, DOEXP, DV41, DV42, DVID3B2, EXNO, EXNO2, EXNO3, FLIP, Ghy, GTNMES, hyp, LAUN, LL164, MLS2, MLTU2, MLU1, MLU2, MU5, MU6, MUT1, MUT2, MUT3, MVT1, MVT3, MVT6, NwS1, PIX1, PIXEL2, plf2, REDU2, refund, SHD, SIGHT, SPBLB, SPBT, SPS1, SPS3, stack, TAS2, TAS3, TT147, TT214, TTX110, Ze, Unused block, Unused duplicate of MULTU, WP1, WPLS, wW
Not all of these routines are completely unused in the original, so in some cases there's also a bit of associated refactoring to enable the original routine to be removed. For example, the stack variable can only be removed because of some clever tweaking in the MEBRK and SVE routines, but in general, the routines removed from the docked code are flight-specific, doing things like updating the dashboard or processing galactic hyperdrive jumps, none of which are needed when we're docked.
There are also some savings to be made in the flight code, though there are fewer opportunities for pruning unused code there. Here's a list of the routines that Angus removed from the flight code:
- BAD, DEEOR, FLKB, GCASH, Main flight loop (Part 5 of 16), MUT3, ou2, ou3, PX3, SP1, SPS4, tnpr1, TTX110, Unused block
These removals are less to do with removing unused code, and more to do with streamlining. For example, ou2 and ou3 can be removed because their logic has been cleverly rolled into the OUCH routine, and part 5 of the main flight loop can be removed as it deals with the energy bomb, a feature that isn't present in Elite-A (the energy bomb is replaced by the hyperspace unit in Elite-A).
Interestingly, there is a routine that is unused in the original code that is still present in Elite-A (well, half of it is), so this one not only managed to slip past the original authors, but it also managed to slip past Angus. It's the Unused duplicate of MULTU, which Angus identified and removed from the docked code, while leaving the second half of the routine in the flight code. I make it 11 bytes, which might not sound like a lot to have missed, but in the flight code every single byte saved is a big win, so those 11 bytes represent quite a bit of effort.
Saving with subroutines
-----------------------
Another way to save a few bytes is to identify any commonly repeated code - such as the code to switch text tokens between Sentence Case and ALL CAPS - and see if any of these repeated blocks could be called as a subroutine, if we gave it a label (i.e. we're looking for an occurrence that ends with an RTS instruction). We can then replace all the other instances of this code with JSR calls to that single block. As long as the subroutine is longer than three bytes (i.e. the number of bytes in a JSR instruction), this will save us a small amount of memory for every conversion to a subroutine call.
There are quite a few routines in Elite-A that save bytes using this approach. Here's a selection:
Let's look at an example. The most popular of these is vdu_80, which switches to Sentence Case. This code appears ten times in the original docked code and 6 times in the original flight code, where it looks like this:
LDA #%10000000 STA QQ17
In Elite-A, this code is replaced by a call to the newly added label at vdu_80, like this:
JSR vdu_80
You can see an example of this modification in the TT69 routine in the flight code, for example.
This represents a saving of just one byte for each conversion to a JSR, as the first version takes up four bytes (QQ17 is in zero page), while the second version takes up three bytes. However, this still saves us nine bytes in the docked code and five bytes in the flight code, as we convert all but one occurrence of the repeated code to a JSR, and this is a significant amount when every single byte counts.
One byte here and another byte there
------------------------------------
There are also some savings to be had from bolting extra instructions onto the start of existing routines. For example, Angus bolted a CLC instruction onto the start of the pr2 routine in the flight code, and then replaced four occurrences of this code:
CLC JSR pr2
with this:
JSR pr2-1
So that's one extra byte for the new CLC instruction at pr2-1, and four bytes saved in the four calls, giving a total saving of three bytes.
Other examples of tiny savings that all contribute are:
- Converting two STA/LDA pairs to TAY/TYA saves four bytes in the flight version of NORM.
- The UNWISE routine in the docked code is quite a lot tighter than the version in the original version, saving an impressive 15 bytes.
- The SPS2 routine in the flight code has been moved, so the COMPAS routine is now just before the SP2 routine. This means we can drop the JSR SP2 instruction that was at the end of COMPAS, thus saving three precious bytes by letting COMPAS fall through into SP2 instead.
There are plenty of other little tweaks that save a byte here and there, eventually adding up to enough free space to support Elite-A's new features.
Three programs in one
---------------------
The most visually obvious feature in Elite-A is the Encyclopedia Galactica, which shows in-game information on ships, controls and equipment if you press CTRL-f6 when docked (see the deep dive on the Encyclopedia Galactica for more details). This is implemented as a totally separate program, so while the original disc version has two code files for docked and flight, Elite-A also has a third for the encyclopedia.
This means the encyclopedia doesn't have to worry about the limited memory in the docked and flight code, and the only impact on the rest of the codebase is an additional bit of code at the start of the docked code's TT25 routine, which shows the Data on System screen when F6 is pressed. The additional code checks to see whether CTRL is being held down, and if so, it loads the encyclopedia code by calling the encyclopedia routine.
Replacing CATD
--------------
In the original disc version, the CATD routine lives at &0D7A, and is one of the few persistent routines that lives in the same place, irrespective of whichever main code file is loaded (docked or flight). This routine refreshes the disc catalogue from sectors 0 and 1 on disc, which makes sure it's always up to date.
Elite-A, however, ditches the CATD routine altogether, and instead it locates the iff_index routine at the same address. This routine forms part of the I.F.F. system which upgrades the 3D scanner with more ship information, so it's only needed during flight and is therefore a strange candidate for using up this persistent space, but it does have the advantage of being very close to the size of the CATD routine, so that's presumably why it ended up here.
There is a downside to removing the CATD routine, however, as CATD is there to fix a bug when swapping between the flight and docked code. If we launch and immediately pause the game with COPY and then press Escape to restart the game before the disc has stopped spinning in the drive, then in Elite-A, this will crash the game with a "Not found" error. This is because the disc catalogue has been corrupted, so when Elite-A goes to load the the T.CODE file containing the docked code, it can't find it. In the original version, CATD is called first to reconstruct the catalogue in memory, so the load command works.
Still, replacing CATD did enable Angus to shoehorn more functionality into the flight code without having to eat into the flight code itself, so there is an upside.
See the deep dive on the I.F.F. system for more information on the iff_index routine.
Adding new code
---------------
With the above savings, Angus managed to find enough spare memory to add the following new routines into the docked code:
- confirm, count_offs, cour_buy, cour_dock, encyclopedia, n_buyship, n_load, n_name, n_price, new_details, new_offsets, new_ships, sell_jump, sell_yn, stay_here
and the following routines into the flight code:
Together with the encyclopedia code and numerous other small modifications to the existing routines, this is how Angus managed to squeeze Elite-A into the already crowded memory map of the original Elite. It's impressive stuff!