Details of how flicker-free Elite weaves its magic
There are two flicker-free flavours of Elite on this site: flicker-free ships and flicker free planets. Let's take a look at how they work.
Flicker-free ships
------------------
The improved ship-drawing algorithm in flicker-free Elite originally appeared in the 1986 Apple II and BBC Master versions of Elite. This is the algorithm that I backported to the original 1984 BBC Micro and Acorn Electron versions of Elite, as well as the 1985 versions for the 6502 Second Processor and Commodore 64, and the unofficial version for the Commodore Plus/4.
Once you've seen the smoother animation and flicker-free ships in BBC Master and Apple II Elite, it does tend to make the older versions look a little less sophisticated. Luckily, the changes that Bell and Braben made for these versions don't require any fancy hardware or more memory; instead, it's all down to an improved ship-drawing algorithm, as explained in the deep dive on flicker-free ship drawing (which you probably want to read before exploring the code below).
This means it's relatively straightforward to backport these changes into all the other versions of 6502 Elite, even the Acorn Electron version and Angus Duggan's Elite-A. Here's a comparison in the latter, with the standard version on the left, and the flicker-free version on the right:
The easiest way to play the flicker-free versions is via the Elite Compendium, as all of the Compendium versions include the flicker-free fix in combination with my other hacks - see the download page for details. I have also backported the algorithm to the Commodore 64 version; see the GitHub repository for flicker-free Elite on the Commodore 64 for details.
There are three steps to the backporting process:
- Free up enough memory for the flicker-free code and the LSNUM and LSNUM2 variables
- Copy the LSPUT line-drawing routine from the Master
- Update the SHPPT and LL9 routines to support the flicker-free algorithm
Interestingly, this backporting process is identical for all the original versions of Elite (i.e. cassette, disc, Electron, 6502 Second Processor and Elite-A). The only difference is that in some cases, we have to move routines around to prevent the extra code from breaking branch instructions that would otherwise have to reach too far. The code changes themselves are the same, as they all share the same SHPPT and LL9 routines.
This is true even for the 6502 Second Processor version, which normally uses a line-queueing system when drawing ships, but we can replace this with the same flicker-free code as the other versions because the flicker-free algorithm draws lines by calling LL30, and this routine still draws a line, even on the Second Processor version, so dropping in the new algorithm just works.
Let's look at these three steps in turn. To be specific, let's look at backporting the flicker-free algorithm to the BBC Micro cassette version. To see the code differences for the other versions, you can check out the flicker-free branches in the accompanying repositories, but the updated routines in all versions are essentially the same as in the code below.
1. Finding enough memory
------------------------
Altogether, the flicker-free changes only require a small number of extra bytes compared to the original versions - in the BBC Micro cassette version, for example, we only need to find eight more bytes. That said, memory usage is famously tight in Elite, so this is easier said than done.
Luckily, all the versions of Elite that have serious memory constraints also contain an unused routine that the authors forgot to remove - it's a duplicate of MULTU that is never called. Removing this routine (or just commenting out the required number of instructions) easily gives us the space we need, even in Elite-A, where only half of the duplicate routine is left in the flight code for us to remove.
We also need to find space in zero page for two new variables: LSNUM and LSNUM2. Elite already has one unused variable in zero page called XX14, so we can simply use this for LSNUM. XX14 is followed by RAT, which is a temporary variable that isn't used by the ship-drawing routine, so we can reuse the same location for both RAT and LSNUM2, as they won't clash (see below for the code that does this).
So, step 1 is to comment out a chunk of the duplicate MULTU routine, replace XX14 with LSNUM, and share RAT with LSNUM2. Simple.
2. Copying over LSPUT
---------------------
As part of the new algorithm in the Master, there's an extra routine called LSPUT, which draws a line, adding the line to the ship line heap (potentially replacing one of the lines already there), and then erasing the line that we just replaced. We need to port this over for the flicker-free code to call, which we can do by simply copying LSPUT directly from the Master, and inserting it after the last stage of the LL9 routine.
So, step 2 is to copy LSPUT from the Master and into the version we are updating. Easy.
3. Updating SHPPT and LL9
-------------------------
The SHPPT routine draws ships as points for when they are far away, and the LL9 routine draws them as wireframes when they are closer. As the final step of backporting the new algorithm, we need to update both of these routines so that instead of erasing the whole ship before redrawing the whole ship again, they instead use the new LSPUT routine to draw and erase ships one line at a time.
Below you can see every single code change that we need to make to convert the BBC Micro cassette version to the flicker-free algorithm (specifically, the changes are in SHPPT and parts 1, 9, 10, 11 and 12 of LL9).
So, step 3 is to make the changes below... and then we're done.
Exploring the code below
------------------------
To explore the code changes below, simply click a routine header to expand it, and click it again to shrink it back. Within the code that appears, the original and flicker-free code are shown side-by-side. You can tap one side to expand it, and tap it again to go back to showing both sides. Each side is sideways scrollable, so you can read the comments on small screens.
It might look like a lot of code, but that's because I've included all the routines in full, so you can see the differences in situ. There are surprisingly few differences between the two versions (especially when you realise that parts 2 to 8 of LL9 are completely unchanged). This is a delightfully simple change that makes a very noticeable improvement to the graphics, and without perceivable loss. That's well worth the effort of backporting, I'd say.
Name: XX14 Type: Variable Category: Zero page variable Summary: Unused variable that we can repurpose
Original
Flicker-free
.LSNUM SKIP 1 \ The pointer to the current position in the ship line \ heap as we work our way through the new ship's edges \ (and the corresponding old ship's edges) when drawing \ the ship in the main ship-drawing routine at LL9 .LSNUM2 SKIP 0 \ The size of the existing ship line heap for the ship \ we are drawing in LL9, i.e. the number of lines in the \ old ship that is currently shown on-screen and which \ we need to erase
Name: SHPPT Type: Subroutine Category: Drawing ships Summary: Draw a distant ship as a point rather than a full wireframe
Original
Flicker-free
JSR EE51 \ Call EE51 to remove the ship's wireframe from the \ screen, if there is one(Empty)
JSR PROJ \ Project the ship onto the screen, returning: \ \ * K3(1 0) = the screen x-coordinate \ * K4(1 0) = the screen y-coordinate \ * A = K4+1 ORA K3+1 \ If either of the high bytes of the screen coordinates BNE nono \ are non-zero, jump to nono as the ship is off-screen LDA K4 \ Set A = the y-coordinate of the dot CMP #Y*2-2 \ If the y-coordinate is bigger than the y-coordinate of BCS nono \ the bottom of the screen, jump to nono as the ship's \ dot is off the bottom of the space view
Original
Flicker-free
LDY #2 \ Call Shpt with Y = 2 to set up bytes 1-4 in the ship JSR Shpt \ lines space, aborting the call to LL9 if the dot is \ off the side of the screen. This call sets up the \ first row of the dot (i.e. a four-pixel dash) LDY #6 \ Set Y to 6 for the next call to Shpt LDA K4 \ Set A = y-coordinate of dot + 1 (so this is the second ADC #1 \ row of the two-pixel high dot) JSR Shpt \ Call Shpt with Y = 6 to set up bytes 5-8 in the ship \ lines space, aborting the call to LL9 if the dot is \ off the side of the screen. This call sets up the \ second row of the dot (i.e. another four-pixel dash, \ on the row below the first one)JSR Shpt \ Call Shpt to draw a horizontal 4-pixel dash for the \ first row of the dot (i.e. a four-pixel dash) LDA K4 \ Set A = y-coordinate of dot + 1 (so this is the second CLC \ row of the two-pixel high dot) ADC #1 JSR Shpt \ Call Shpt to draw a horizontal 4-pixel dash for the \ first row of the dot (i.e. a four-pixel dash)
LDA #%00001000 \ Set bit 3 of the ship's byte #31 to record that we ORA XX1+31 \ have now drawn something on-screen for this ship STA XX1+31
Original
Flicker-free
LDA #8 \ Set A = 8 so when we call LL18+2 next, byte #0 of the \ heap gets set to 8, for the 8 bytes we just stuck on \ the heap JMP LL81+2 \ Call LL81+2 to draw the ship's dot, returning from the \ subroutine using a tail call PLA \ Pull the return address from the stack, so the RTS PLA \ below actually returns from the subroutine that called \ LL9 (as we called SHPPT from LL9 with a JMP)JMP LSPUT \ Jump to LSPUT to draw any remaining lines that are \ still in the ship line heap and return from the \ subroutine using a tail call
.nono LDA #%11110111 \ Clear bit 3 of the ship's byte #31 to record that AND XX1+31 \ nothing is being drawn on-screen for this ship STA XX1+31
Original
Flicker-free
RTS \ Return from the subroutine .Shpt \ This routine sets up four bytes in the ship line heap, \ from byte Y-1 to byte Y+2. If the ship's screen point \ turns out to be off-screen, then this routine aborts \ the entire call to LL9, exiting via nono. The four \ bytes define a horizontal 4-pixel dash, for either the \ top or the bottom of the ship's dot STA (XX19),Y \ Store A in byte Y of the ship line heap INY \ Store A in byte Y+2 of the ship line heap INY STA (XX19),Y LDA K3 \ Set A = screen x-coordinate of the ship dot DEY \ Store A in byte Y+1 of the ship line heap STA (XX19),Y ADC #3 \ Set A = screen x-coordinate of the ship dot + 3 BCS nono-2 \ If the addition pushed the dot off the right side of \ the screen, jump to nono-2 to return from the parent \ subroutine early (i.e. LL9). This works because we \ called Shpt from above with a JSR, so nono-2 removes \ that return address from the stack, leaving the next \ return address exposed. LL9 called SHPPT with a JMP. \ so the next return address is the one that was put on \ the stack by the original call to LL9. So the RTS in \ nono will actually return us from the original call \ to LL9, thus aborting the entire drawing process DEY \ Store A in byte Y-1 of the ship line heap DEY STA (XX19),Y RTS \ Return from the subroutineJMP LSPUT \ Jump to LSPUT to draw any remaining lines that are \ still in the ship line heap and return from the \ subroutine .Shpt \ This routine draws a horizontal 4-pixel dash, for \ either the top or the bottom of the ship's dot STA Y1 \ Store A in both y-coordinates, as this is a horizontal STA Y2 \ dash at y-coordinate A LDA K3 \ Set A = screen x-coordinate of the ship dot STA X1 \ Store the x-coordinate of the ship dot in X1, as this \ is where the dash starts CLC \ Set A = screen x-coordinate of the ship dot + 3 ADC #3 BCC P%+4 \ If the addition overflowed, set A = 255, the LDA #255 \ x-coordinate of the right edge of the screen STA X2 \ Store the x-coordinate of the ship dot in X1, as this \ is where the dash starts JMP LSPUT \ Draw this edge using flicker-free animation, by first \ drawing the ship's new line and then erasing the \ corresponding old line from the screen, and return \ from the subroutine using a tail call
Name: LL9 (Part 1 of 12) Type: Subroutine Category: Drawing ships Summary: Draw ship: Check if ship is exploding, check if ship is in front
This routine draws the current ship on the screen. This part checks to see if the ship is exploding, or if it should start exploding, and if it does it sets things up accordingly. It also does some basic checks to see if we can see the ship, and if not it removes it from the screen. In this code, XX1 is used to point to the current ship's data block at INWK (the two labels are interchangeable). Arguments: XX1 XX1 shares its location with INWK, which contains the zero-page copy of the data block for this ship from the K% workspace INF The address of the data block for this ship in workspace K% XX19(1 0) XX19(1 0) shares its location with INWK(34 33), which contains the ship line heap address pointer XX0 The address of the blueprint for this ship Other entry points: EE51 Remove the current ship from the screen, called from SHPPT before drawing the ship as a point.LL25 JMP PLANET \ Jump to the PLANET routine, returning from the \ subroutine using a tail call .LL9 LDA TYPE \ If the ship type is negative then this indicates a BMI LL25 \ planet or sun, so jump to PLANET via LL25 above LDA #31 \ Set XX4 = 31 to store the ship's distance for later STA XX4 \ comparison with the visibility distance. We will \ update this value below with the actual ship's \ distance if it turns out to be visible on-screenOriginal
Flicker-free
(Empty)\ We now set things up for flicker-free ship plotting, \ by setting the following: \ \ LSNUM = offset to the first coordinate in the ship's \ line heap \ \ LSNUM2 = the number of bytes in the heap for the \ ship that's currently on-screen (or 0 if \ there is no ship currently on-screen) LDY #1 \ Set LSNUM = 1, the offset of the first set of line STY LSNUM \ coordinates in the ship line heap DEY \ Decrement Y to 0 LDA #%00001000 \ If bit 3 of the ship's byte #31 is set, then the ship BIT INWK+31 \ is currently being drawn on-screen, so skip the BNE P%+5 \ following two instructions LDA #0 \ The ship is not being drawn on screen, so set A = 0 \ so that LSNUM2 gets set to 0 below (as there are no \ existing coordinates on the ship line heap for this \ ship) EQUB &2C \ Skip the next instruction by turning it into \ &2C &B1 &BD, or BIT &BDB1 which does nothing apart \ from affect the flags LDA (XX19),Y \ Set LSNUM2 to the first byte of the ship's line heap, STA LSNUM2 \ which contains the number of bytes in the heapLDA #%00100000 \ If bit 5 of the ship's byte #31 is set, then the ship BIT XX1+31 \ is currently exploding, so jump down to EE28 BNE EE28 BPL EE28 \ If bit 7 of the ship's byte #31 is clear then the ship \ has not just been killed, so jump down to EE28 \ Otherwise bit 5 is clear and bit 7 is set, so the ship \ is not yet exploding but it has been killed, so we \ need to start an explosion ORA XX1+31 \ Clear bits 6 and 7 of the ship's byte #31, to stop the AND #%00111111 \ ship from firing its laser and to mark it as no longer STA XX1+31 \ having just been killed LDA #0 \ Set the ship's acceleration in byte #31 to 0, updating LDY #28 \ the byte in the workspace K% data block so we don't STA (INF),Y \ have to copy it back from INWK later LDY #30 \ Set the ship's pitch counter in byte #30 to 0, to stop STA (INF),Y \ the ship from pitching JSR EE51 \ Call EE51 to remove the ship from the screen \ We now need to set up a new explosion cloud. We \ initialise it with a size of 18 (which gets increased \ by 4 every time the cloud gets redrawn), and the \ explosion count (i.e. the number of particles in the \ explosion), which go into bytes 1 and 2 of the ship \ line heap. See DOEXP for more details of explosion \ clouds LDY #1 \ Set byte #1 of the ship line heap to 18, the initial LDA #18 \ size of the explosion cloud STA (XX19),Y LDY #7 \ Fetch byte #7 from the ship's blueprint, which LDA (XX0),Y \ determines the explosion count (i.e. the number of LDY #2 \ vertices used as origins for explosion clouds), and STA (XX19),Y \ store it in byte #2 of the ship line heap \ The following loop sets bytes 3-6 of the of the ship \ line heap to random numbers .EE55 INY \ Increment Y (so the loop starts at 3) JSR DORND \ Set A and X to random numbers STA (XX19),Y \ Store A in the Y-th byte of the ship line heap CPY #6 \ Loop back until we have randomised the 6th byte BNE EE55 .EE28 LDA XX1+8 \ Set A = z_sign .EE49 BPL LL10 \ If A is positive, i.e. the ship is in front of us, \ jump down to LL10 .LL14 \ The following removes the ship from the screen by \ redrawing it (or, if it is exploding, by redrawing the \ explosion cloud). We call it when the ship is no \ longer on-screen, is too far away to be fully drawn, \ and so on LDA XX1+31 \ If bit 5 of the ship's byte #31 is clear, then the AND #%00100000 \ ship is not currently exploding, so jump down to EE51 BEQ EE51 \ to redraw its wireframe LDA XX1+31 \ The ship is exploding, so clear bit 3 of the ship's AND #%11110111 \ byte #31 to denote that the ship is no longer being STA XX1+31 \ drawn on-screen JMP DOEXP \ Jump to DOEXP to display the explosion cloud, which \ will remove it from the screen, returning from the \ subroutine using a tail call .EE51 LDA #%00001000 \ If bit 3 of the ship's byte #31 is clear, then there BIT XX1+31 \ is already nothing being shown for this ship, so BEQ LL10-1 \ return from the subroutine (as LL10-1 contains an RTS) EOR XX1+31 \ Otherwise flip bit 3 of byte #31 and store it (which STA XX1+31 \ clears bit 3 as we know it was set before the EOR), so \ this sets this ship as no longer being drawn on-screenOriginal
Flicker-free
JMP LL155 \ Jump to LL155 to draw the ship, which removes it from \ the screen, returning from the subroutine using a \ tail callJMP LSPUT \ Jump to LSPUT to draw the ship, which removes it from \ the screen, returning from the subroutine using a \ tail callRTS \ Return from the subroutineName: LL9 (Part 9 of 12) Type: Subroutine Category: Drawing ships Summary: Draw ship: Draw laser beams if the ship is firing its laser at us
This part sets things up so we can loop through the edges in the next part. It also adds a line to the ship line heap, if the ship is firing at us. When we get here, the heap at XX3 contains all the visible vertex screen coordinates..LL72 LDA XX1+31 \ If bit 5 of the ship's byte #31 is clear, then the AND #%00100000 \ ship is not currently exploding, so jump down to EE31 BEQ EE31 LDA XX1+31 \ The ship is exploding, so set bit 3 of the ship's byte ORA #8 \ #31 to denote that we are drawing something on-screen STA XX1+31 \ for this ship JMP DOEXP \ Jump to DOEXP to display the explosion cloud, \ returning from the subroutine using a tail call .EE31Original
Flicker-free
LDA #%00001000 \ If bit 3 of the ship's byte #31 is clear, then there BIT XX1+31 \ is nothing already being shown for this ship, so skip BEQ LL74 \ to LL74 as we don't need to erase anything from the \ screen JSR LL155 \ Otherwise call LL155 to draw the existing ship, which \ removes it from the screenLDY #9 \ Fetch byte #9 of the ship's blueprint, which is the LDA (XX0),Y \ number of edges, and store it in XX20 STA XX20LDA #%00001000 \ Set bit 3 of A so the next instruction sets bit 3 of \ the ship's byte #31 to denote that we are drawing \ something on-screen for this ship .LL74 ORA XX1+31 \ Apply bit 3 of A to the ship's byte #31, so if there STA XX1+31 \ was no ship already on screen, the bit is clear, \ otherwise it is setOriginal
Flicker-free
LDY #9 \ Fetch byte #9 of the ship's blueprint, which is the LDA (XX0),Y \ number of edges, and store it in XX20 STA XX20 LDY #0 \ We are about to step through all the edges, using Y \ as a counter STY U \ Set U = 0 (though we increment it to 1 below) STY XX17 \ Set XX17 = 0, which we are going to use as a counter \ for stepping through the ship's edges INC U \ We are going to start calculating the lines we need to \ draw for this ship, and will store them in the ship \ line heap, using U to point to the end of the heap, so \ we start by setting U = 1LDY #0 \ Set XX17 = 0, which we are going to use as a counter STY XX17 \ for stepping through the ship's edgesBIT XX1+31 \ If bit 6 of the ship's byte #31 is clear, then the BVC LL170 \ ship is not firing its lasers, so jump to LL170 to \ skip the drawing of laser lines \ The ship is firing its laser at us, so we need to draw \ the laser lines LDA XX1+31 \ Clear bit 6 of the ship's byte #31 so the ship doesn't AND #%10111111 \ keep firing endlessly STA XX1+31 LDY #6 \ Fetch byte #6 of the ship's blueprint, which is the LDA (XX0),Y \ number * 4 of the vertex where the ship has its lasers TAY \ Put the vertex number into Y, where it can act as an \ index into list of vertex screen coordinates we added \ to the XX3 heap LDX XX3,Y \ Fetch the x_lo coordinate of the laser vertex from the STX XX15 \ XX3 heap into XX15 INX \ If X = 255 then the laser vertex is not visible, as BEQ LL170 \ the value we stored in part 2 wasn't overwritten by \ the vertex calculation in part 6 and 7, so jump to \ LL170 to skip drawing the laser lines \ We now build a laser beam from the ship's laser vertex \ towards our ship, as follows: \ \ XX15(1 0) = laser vertex x-coordinate \ \ XX15(3 2) = laser vertex y-coordinate \ \ XX15(5 4) = x-coordinate of the end of the beam \ \ XX12(1 0) = y-coordinate of the end of the beam \ \ The end of the laser beam will be set positioned to \ look good, rather than being directly aimed at us, as \ otherwise we would only see a flashing point of light \ as they unleashed their attack LDX XX3+1,Y \ Fetch the x_hi coordinate of the laser vertex from the STX XX15+1 \ XX3 heap into XX15+1 INX \ If X = 255 then the laser vertex is not visible, as BEQ LL170 \ the value we stored in part 2 wasn't overwritten by \ a vertex calculation in part 6 and 7, so jump to LL170 \ to skip drawing the laser beam LDX XX3+2,Y \ Fetch the y_lo coordinate of the laser vertex from the STX XX15+2 \ XX3 heap into XX15+2 LDX XX3+3,Y \ Fetch the y_hi coordinate of the laser vertex from the STX XX15+3 \ XX3 heap into XX15+3 LDA #0 \ Set XX15(5 4) = 0, so their laser beam fires to the STA XX15+4 \ left edge of the screen STA XX15+5 STA XX12+1 \ Set XX12(1 0) = the ship's z_lo coordinate, which will LDA XX1+6 \ effectively make the vertical position of the end of STA XX12 \ the laser beam move around as the ship moves in space LDA XX1+2 \ If the ship's x_sign is positive, skip the next BPL P%+4 \ instruction DEC XX15+4 \ The ship's x_sign is negative (i.e. it's on the left \ side of the screen), so switch the laser beam so it \ goes to the right edge of the screen by decrementing \ XX15(5 4) to 255 JSR LL145 \ Call LL145 to see if the laser beam needs to be \ clipped to fit on-screen, returning the clipped line's \ end-points in (X1, Y1) and (X2, Y2) BCS LL170 \ If the C flag is set then the line is not visible on \ screen, so jump to LL170 so we don't store this line \ in the ship line heapOriginal
Flicker-free
LDY U \ Fetch the ship line heap pointer, which points to the \ next free byte on the heap, into Y LDA XX15 \ Add X1 to the end of the heap STA (XX19),Y INY \ Increment the heap pointer LDA XX15+1 \ Add Y1 to the end of the heap STA (XX19),Y INY \ Increment the heap pointer LDA XX15+2 \ Add X2 to the end of the heap STA (XX19),Y INY \ Increment the heap pointer LDA XX15+3 \ Add Y2 to the end of the heap STA (XX19),Y INY \ Increment the heap pointer STY U \ Store the updated ship line heap pointer in UJSR LSPUT \ Draw the laser line using flicker-free animation, by \ first drawing the new laser line and then erasing the \ corresponding old line from the screenName: LL9 (Part 10 of 12) Type: Subroutine Category: Drawing ships Summary: Draw ship: Calculate the visibility of each of the ship's edges and, for flicker-free only, draw the visible ones
This part calculates which edges are visible - in other words, which lines we should draw - and clips them to fit on the screen. When we get here, the heap at XX3 contains all the visible vertex screen coordinates..LL170 LDY #3 \ Fetch byte #3 of the ship's blueprint, which contains CLC \ the low byte of the offset to the edges data LDA (XX0),Y ADC XX0 \ Set V = low byte edges offset + XX0 STA V LDY #16 \ Fetch byte #16 of the ship's blueprint, which contains LDA (XX0),Y \ the high byte of the offset to the edges data ADC XX0+1 \ Set V+1 = high byte edges offset + XX0+1 STA V+1 \ \ So V(1 0) now points to the start of the edges data \ for this shipOriginal
Flicker-free
LDA (V),Y \ Fetch byte #0 for this edge, which contains the \ visibility distance for this edge, beyond which the \ edge is not shown CMP XX4 \ If XX4 > the visibility distance, where XX4 contains BCC LL78 \ the ship's z-distance reduced to 0-31 (which we set in \ part 2), then this edge is too far away to be visible, \ so jump down to LL78 to move on to the next edge INY \ Increment Y to point to byte #1 LDA (V),Y \ Fetch byte #1 for this edge into A, so: \ \ A = %ffff ffff, where: \ \ * Bits 0-3 = the number of face 1 \ \ * Bits 4-7 = the number of face 2Original
Flicker-free
INY \ Increment Y to point to byte #2(Empty)STA P \ Store byte #1 into P AND #%00001111 \ Extract the number of face 1 into X TAX LDA XX2,X \ If XX2+X is non-zero then we decided in part 5 that BNE LL79 \ face 1 is visible, so jump to LL79 LDA P \ Fetch byte #1 for this edge into A LSR A \ Shift right four times to extract the number of face 2 LSR A \ from bits 4-7 into X LSR A LSR A TAX LDA XX2,X \ If XX2+X is zero then we decided in part 5 that BEQ LL78 \ face 2 is hidden, so jump to LL78 .LL79 \ We now build the screen line for this edge, as \ follows: \ \ XX15(1 0) = start x-coordinate \ \ XX15(3 2) = start y-coordinate \ \ XX15(5 4) = end x-coordinate \ \ XX12(1 0) = end y-coordinate \ \ We can then pass this to the line clipping routine \ before storing the resulting line in the ship line \ heapOriginal
Flicker-free
LDA (V),Y \ Fetch byte #2 for this edge into X, which contains TAX \ the number of the vertex at the start of the edge INY \ Increment Y to point to byte #3 LDA (V),Y \ Fetch byte #3 for this edge into Q, which contains STA Q \ the number of the vertex at the end of the edgeINY \ Increment Y to point to byte #2 LDA (V),Y \ Fetch byte #2 for this edge into X, which contains TAX \ the number of the vertex at the start of the edgeLDA XX3+1,X \ Fetch the x_hi coordinate of the edge's start vertex STA XX15+1 \ from the XX3 heap into XX15+1 LDA XX3,X \ Fetch the x_lo coordinate of the edge's start vertex STA XX15 \ from the XX3 heap into XX15 LDA XX3+2,X \ Fetch the y_lo coordinate of the edge's start vertex STA XX15+2 \ from the XX3 heap into XX15+2 LDA XX3+3,X \ Fetch the y_hi coordinate of the edge's start vertex STA XX15+3 \ from the XX3 heap into XX15+3Original
Flicker-free
LDX Q \ Set X to the number of the vertex at the end of the \ edge, which we stored in QINY \ Increment Y to point to byte #3 LDA (V),Y \ Fetch byte #3 for this edge into X, which contains TAX \ the number of the vertex at the end of the edgeLDA XX3,X \ Fetch the x_lo coordinate of the edge's end vertex STA XX15+4 \ from the XX3 heap into XX15+4 LDA XX3+3,X \ Fetch the y_hi coordinate of the edge's end vertex STA XX12+1 \ from the XX3 heap into XX11+1 LDA XX3+2,X \ Fetch the y_lo coordinate of the edge's end vertex STA XX12 \ from the XX3 heap into XX12 LDA XX3+1,X \ Fetch the x_hi coordinate of the edge's end vertex STA XX15+5 \ from the XX3 heap into XX15+5 JSR LL147 \ Call LL147 to see if the new line segment needs to be \ clipped to fit on-screen, returning the clipped line's \ end-points in (X1, Y1) and (X2, Y2) BCS LL78 \ If the C flag is set then the line is not visible on \ screen, so jump to LL78 so we don't store this line \ in the ship line heapOriginal
Flicker-free
(Empty)JSR LSPUT \ Draw this edge using flicker-free animation, by first \ drawing the ship's new line and then erasing the \ corresponding old line from the screenName: LL9 (Part 11 of 12) Type: Subroutine Category: Drawing ships Summary: Draw ship: Add all visible edges to the ship line heap
This part adds all the visible edges to the ship line heap, so we can draw them in part 12. Other entry points: LL81+2 Draw the contents of the ship line heap, used to draw the ship as a dot from SHPPTOriginal
Flicker-free
.LL80 LDY U \ Fetch the ship line heap pointer, which points to the \ next free byte on the heap, into Y LDA XX15 \ Add X1 to the end of the heap STA (XX19),Y INY \ Increment the heap pointer LDA XX15+1 \ Add Y1 to the end of the heap STA (XX19),Y INY \ Increment the heap pointer LDA XX15+2 \ Add X2 to the end of the heap STA (XX19),Y INY \ Increment the heap pointer LDA XX15+3 \ Add Y2 to the end of the heap STA (XX19),Y INY \ Increment the heap pointer STY U \ Store the updated ship line heap pointer in U CPY T1 \ If Y >= T1 then we have reached the maximum number of BCS LL81 \ edge lines that we can store in the ship line heap, so \ skip to LL81 so we don't loop back for the next edge .LL78 INC XX17 \ Increment the edge counter to point to the next edge LDY XX17 \ If Y >= XX20, which contains the number of edges in CPY XX20 \ the blueprint, jump to LL81 as we have processed all BCS LL81 \ the edges and don't need to loop back for the next one LDY #0 \ Set Y to point to byte #0 again, ready for the next \ edge LDA V \ Increment V by 4 so V(1 0) points to the data for the ADC #4 \ next edge STA V.LL78 LDA LSNUM \ If LSNUM >= CNT, skip to LL81 so we don't loop back CMP CNT \ for the next edge (CNT was set to the maximum heap BCS LL81 \ size for this ship in part 10, so this checks whether \ we have just run out of space in the ship line heap, \ and stops drawing edges if we have) LDA V \ Increment V by 4 so V(1 0) points to the data for the CLC \ next edge ADC #4 STA VBCC ll81 \ If the above addition didn't overflow, jump to ll81 INC V+1 \ Otherwise increment the high byte of V(1 0), as we \ just moved the V(1 0) pointer past a page boundary .ll81Original
Flicker-free
JMP LL75 \ Loop back to LL75 to process the next edge .LL81 \ We have finished adding lines to the ship line heap, \ so now we need to set the first byte of the heap to \ the number of bytes stored there LDA U \ Fetch the ship line heap pointer from U into A, which \ points to the end of the heap, and therefore contains \ the heap size LDY #0 \ Store A as the first byte of the ship line heap, so STA (XX19),Y \ the heap is now correctly set upINC XX17 \ Increment the edge counter to point to the next edge LDY XX17 \ If Y < XX20, which contains the number of edges in CPY XX20 \ the blueprint, loop back to LL75 to process the next BCC LL75 \ edge .LL81 JMP LSCLR \ Jump down to part 12 below to draw any remaining lines \ from the old ship that are still in the ship line heapName: LL9 (Part 12 of 12) Type: Subroutine Category: Drawing ships Summary: Draw ship: Draw all the visible edges from the ship line heap
This part draws the lines in the ship line heap, which is used both to draw the ship, and to remove it from the screen.Original
Flicker-free
.LL155 LDY #0 \ Fetch the first byte from the ship line heap into A, LDA (XX19),Y \ which contains the number of bytes in the heap STA XX20 \ Store the heap size in XX20 CMP #4 \ If the heap size is less than 4, there is nothing to BCC LL118-1 \ draw, so return from the subroutine (as LL118-1 \ contains an RTS) INY \ Set Y = 1, which we will use as an index into the ship \ line heap, starting at byte #1 (as byte #0 contains \ the heap size) .LL27.LSCLR LDY LSNUM \ Set Y to the offset in the line heap LSNUM .LSC1 CPY LSNUM2 \ If Y >= LSNUM2, jump to LSC2 to return from the ship BCS LSC2 \ drawing routine, because the index in Y is greater \ than the size of the existing ship line heap, which \ means we have alrady erased all the old ships lines \ when drawing the new ship \ If we get here then Y < LSNUM2, which means Y is \ pointing to an on-screen line from the old ship that \ we need to eraseLDA (XX19),Y \ Fetch the X1 line coordinate from the heap and store STA XX15 \ it in XX15 INY \ Increment the heap pointer LDA (XX19),Y \ Fetch the Y1 line coordinate from the heap and store STA XX15+1 \ it in XX15+1 INY \ Increment the heap pointer LDA (XX19),Y \ Fetch the X2 line coordinate from the heap and store STA XX15+2 \ it in XX15+2 INY \ Increment the heap pointer LDA (XX19),Y \ Fetch the Y2 line coordinate from the heap and store STA XX15+3 \ it in XX15+3 JSR LL30 \ Draw a line from (X1, Y1) to (X2, Y2) INY \ Increment the heap pointerOriginal
Flicker-free
CPY XX20 \ If the heap counter is less than the size of the heap, BCC LL27 \ loop back to LL27 to draw the next line from the heap RTS \ Return from the subroutineFlicker-free planets
--------------------In the original Apple II and BBC Master versions, planets still flicker, as they are displayed by a completely different part of the code. When the authors fixed the flicker, they only did so in the ship-drawing routines.
To get rid of this final bit of flicker, I have applied the same algorithm to the planet-drawing routines, so planets are also erased and redrawn one line at a time. Players of the BBC Micro disc version, BBC Master, 6502 Second Processor, Acorn Electron, Commodore 64, Commodore Plus/4 and Apple II can now enjoy both flicker-free ships and planets; unfortunately there isn't enough spare memory in all versions of Elite for this modification, so flicker-free planets are not supported on the BBC Micro cassette version or in Elite-A, where memory is really tight. See the page on playing flicker-free Elite for details of all the different versions.
The bulk of the flicker-free planet change is implemented in the DrawPlanetLine routine, which is injected into the BLINE routine to implement the same single erase/redraw algorithm as in the ship-drawing improvement (the BLINE routine manages the ball line heap for circles and ellipses). Because planet circles, craters, equators and meridians are all managed by the same heap, this fix reduces flicker for the entire planet, not just the circular outline.
Adding the algorithm to the ball line code does indeed reduce flicker, but it doesn't get rid of it entirely, because unlike ships, planets are quite often static, hanging in the sky as we fly towards them. This means that lines often get erased and redrawn in exactly the same place, which still leads to a small hint of flicker; the same issue affects ships, but because they are always moving, it is far less obvious.
To get around this, there is a further addition to the code in the form of the DrawNewPlanetLine routine, which checks whether or not a line has moved, and only erases and redraws the line if it is in a different place. If the line hasn't moved, then the erase/redraw process is skipped, so lines that don't move on-screen do not flicker. Due to memory constraints, this extra routine isn't present in the BBC Micro disc version, so planets do still have a slight shimmer in that version, though the flicker is still considerably improved over the original.
Here's the flicker-free BBC Master version, showing a rock-steady planet:
There is still a very tiny amount of flicker, which kicks in when the number of lines in the line heap changes. In this case we may erase a line in one place and redraw it in a different place, which does produce a tiny glimmer, but overall the extended algorithm does a pretty good job of fixing planet flicker. This effect is particularly noticeable when the planet is partially off-screen, and although the BBC Micro is quick enough to turn this into a slight glimmer, it is a bit more noticeable on the Commodore 64, Plus/4 and Apple II. But it's still a huge improvement over the original, flickery release, even if the flicker-free version isn't strictly free of flicker.
There is also the overlap effect that comes from drawing lines on top of each other, such as when the meridian or equator reaches the planet's edge; this is a consequence of the EOR-plotting approach used by Elite, rather than the erase/redraw flicker that this modification fixes, so it's still present.
To see the code behind this, you can search the elite-source.asm source file in the flicker-free branch of the elite-source-code-bbc-master repository. Look for "Mod:" to see the modifications for flicker-free planet drawing. You can also see the exact same modifications in the elite-source.asm source file in the flicker-free branch of the elite-source-code-apple-ii repository
If you want to see the modifications in the original BBC Micro Elite, which doesn't include the extra DrawNewPlanetLine routine, then you can search the elite-source-flight.asm source file in the flicker-free branch of the elite-source-code-bbc-micro-disc repository. Look for "Mod:" to see the modifications for both flicker-free ship and planet drawing.
Finally, for details of how the flicker-free improvements have been added to the Commodore 64 and Plus/4, you have two options. If you want to explore the code modifications, then search the elite-source.asm file in the flicker-free branch of the elite-source-code-commodore-64 repository. As with the other repositories, look for "Mod:" to see the modifications for both flicker-free ship and planet drawing.
Another option is the the flicker-free Commodore 64 Elite repository, which contains the same flicker-free code, but instead of having the modifications integrated into the original source code and the whole game being rebuilt, this repository patches the changes into the game binary. The end result is exactly the same, but the first repository is much easier to follow if you want to explore the modifications, while this second one is an interesting journey into patching. (The patching approach came first, before the Commodore 64 source code was released, which is why there are two repositories.)
Finding space on the Electron
-----------------------------Astonishingly, there is enough spare room in the Acorn Electron for the full version of flicker-free planets. Although the game binary uses up almost every single byte of space, leaving only 13 bytes of unused space, it turns out that not all of the game code is actually used, and when those parts are removed, there is not only enough space for flicker-free planets, but for some other improvements as well.
The Electron version still contains the same unused multiplication routines as the BBC Micro (a duplicate of MULTU and the unused MUT3 routine), so there are 28 unused bytes there that can be reused... and on top of that, the authors left the ARCTAN routine (70 bytes) and ACT table (32 bytes) intact, even though they are only ever used to draw meridians and equators on planets, a feature that isn't present in the Electron version. There are 11 unused bytes in part 2 of the main flight loop that are skipped using a JMP (so presumably this was code that was inserted at some point, and then deemed unnecessary, so it was just skipped), and there is a chunk of 39 bytes in the TT17 routine that enable you to move the crosshairs with a joystick, but as the Electron version doesn't support joysticks, this can also be removed (and in doing so, this fixes a bug in the original where you can still select joystick control, and in doing so break the crosshairs on the charts).
Another way of saving a few precious bytes is to move variables from main memory into zero page, as instructions that access zero page are one byte smaller than their equivalents that access main memory. There are two zero-page locations just after YY in Electron Elite that are no longer used - they are used to store the sun's centre axis coordinates in BBC Micro Elite, and the Electron version doesn't have suns - so we can move two variables from main memory into zero page. The best candidates are QQ29, which is used 29 times in the codebase, and QQ3, which is used 10 times, so moving these from the WP workspace into zero page frees up a total of 41 bytes in main memory (29 plus 10, plus one byte each for the variable).
Finally, there are some instructions that set the HFX variable in the LL164 routine, which can be removed as HFX isn't used anywhere; in the BBC Micro is it used to switch off the split-screen mode for the hyperspace effect, but there is no split-screen mode in the Electron, so that's another one that can be used. Indeed, there are various other optimisations that could free up a few more bytes, but there aren't any other minor BBC Micro features to port to the Electron; Thargoids, suns, meridians and craters all take up large amounts of code, so they will have to stay as BBC Micro-only features.
Altogether these tweaks free up enough memory to add the following features to Electron Elite:
- Flicker-free ships
- Flicker-free planets
- The escape capsule animation from the BBC Micro has been added, which is not present in the original Electron version
- There are now three sizes of stardust (like the BBC Micro) rather than two, with the addition of one-pixel stardust
- Planets are more high-fidelity, so the planet's circle looks more like the BBC Micro, and less like a 50p; this does slow things down a little, but overall the faster algorithm for flicker-free planets compensates for this
- For the SSD disc version, the black box that shows loading progress has been removed from the Acornsoft loading screen (it is still used to show loading progress in the UEF cassette version)
So the Electron version might not have Thargoids, suns or detailed planets, but it now has one feature that the standard BBC Micro version does not have room for: flicker-free planets. Head over to the downloads page to get hold of the improved version.
To see the code behind this, you can search the elite-source.asm source file in the flicker-free branch of the elite-source-code-acorn-electron repository. Look for "Mod:" to see the modifications for all the in-game features listed above.