Skip to navigation

Elite on the BBC Micro

Elite C docked source (Disc version)

ELITE C FILE
CODE_C% = P% LOAD_C% = LOAD% +P% - CODE%
Name: HATB [View individually] Type: Variable [Compare versions] Category: Ship hanger Summary: Ship hanger group table
This table contains groups of ships to show in the ship hanger. A group of ships is shown half the time (the other half shows a solo ship), and each of the four groups is equally likely. The bytes for each ship in the group contain the following information: Byte #0 Non-zero = Ship type to draw 0 = don't draw anything Byte #1 Bits 0-7 = Ship's x_hi Bit 0 = Ship's z_hi (1 if clear, or 2 if set) Byte #2 Bits 0-7 = Ship's z_lo Bit 0 = Ship's x_sign Ths ship's y-coordinate is calculated in the has1 routine from the size of its targetable area. Ships of type 0 are not shown. Note that ship numbers are for the ship hanger blueprints at XX21 in the docked code, rather than the full set of ships in the flight code. They are: 1 = Cargo canister 2 = Shuttle 3 = Transporter 4 = Cobra Mk III 5 = Python 6 = Viper 7 = Krait 8 = Constrictor
.HATB \ Hanger group for X = 0 \ \ Shuttle (left) and Transporter (right) EQUB 2 \ Ship type in the hanger = 2 = Shuttle EQUB %01010100 \ x_hi = %01010100 = 84, z_hi = 1 -> x = -84 EQUB %00111011 \ z_lo = %00111011 = 59, x_sign = 1 z = +315 EQUB 3 \ Ship type in the hanger = 3 = Transporter EQUB %10000010 \ x_hi = %10000010 = 130, z_hi = 1 -> x = +130 EQUB %10110000 \ z_lo = %10110000 = 176, x_sign = 0 z = +432 EQUB 0 \ No third ship EQUB 0 EQUB 0 \ Hanger group for X = 9 \ \ Three cargo canisters (left, far right and forward, \ right) EQUB 1 \ Ship type in the hanger = 1 = Cargo canister EQUB %01010000 \ x_hi = %01010000 = 80, z_hi = 1 -> x = -80 EQUB %00010001 \ z_lo = %00010001 = 17, x_sign = 1 z = +273 EQUB 1 \ Ship type in the hanger = 1 = Cargo canister EQUB %11010001 \ x_hi = %11010001 = 209, z_hi = 2 -> x = +209 EQUB %00101000 \ z_lo = %00101000 = 40, x_sign = 0 z = +552 EQUB 1 \ Ship type in the hanger = 1 = Cargo canister EQUB %01000000 \ x_hi = %01000000 = 64, z_hi = 1 -> x = +64 EQUB %00000110 \ z_lo = %00000110 = 6, x_sign = 0 z = +262 \ Hanger group for X = 18 \ \ Transporter (right) and Cobra Mk III (left) EQUB 3 \ Ship type in the hanger = 3 = Transporter EQUB %01100000 \ x_hi = %01100000 = 96, z_hi = 1 -> x = +96 EQUB %10010000 \ z_lo = %10010000 = 144, x_sign = 0 z = +400 EQUB 4 \ Ship type in the hanger = 4 = Cobra Mk III EQUB %00010000 \ x_hi = %00010000 = 16, z_hi = 1 -> x = -16 EQUB %11010001 \ z_lo = %11010001 = 209, x_sign = 1 z = +465 EQUB 0 \ No third ship EQUB 0 EQUB 0 \ Hanger group for X = 27 \ \ Viper (right and forward) and Krait (left) EQUB 6 \ Ship type in the hanger = 6 = Viper EQUB %01010001 \ x_hi = %01010001 = 81, z_hi = 2 -> x = +81 EQUB %11111000 \ z_lo = %11111000 = 248, x_sign = 0 z = +760 EQUB 7 \ Ship type in the hanger = 7 = Krait EQUB %01100000 \ x_hi = %01100000 = 96, z_hi = 1 -> x = -96 EQUB %01110101 \ z_lo = %01110101 = 117, x_sign = 1 z = +373 EQUB 0 \ No third ship EQUB 0 EQUB 0
Name: HALL [View individually] Type: Subroutine [Compare versions] Category: Ship hanger Summary: Draw the ships in the ship hanger, then draw the hanger
Half the time this will draw one of the four pre-defined ship hanger groups in HATB, and half the time this will draw a solitary Sidewinder, Mamba, Krait or Adder on a random position. In all cases, the ships will be randomly spun around on the ground so they can face in any dirction, and larger ships are drawn higher up off the ground than smaller ships. The ships are drawn by the HAS1 routine, which uses the normal ship-drawing routine in LL9, and then the hanger background is drawn by sending an OSWORD 248 command to the I/O processor.
.HALL JSR UNWISE \ Call UNWISE to switch the main line-drawing routine \ between EOR and OR logic (in this case, switching it \ to OR logic so that it overwrites anything that's \ on-screen) LDA #0 \ Clear the top part of the screen, draw a white border, JSR TT66 \ and set the current view type in QQ11 to 0 (space \ view) JSR DORND \ Set A and X to random numbers BPL HA7 \ Jump to HA7 if A is positive (50% chance) AND #3 \ Reduce A to a random number in the range 0-3 STA T \ Set X = A * 8 + A ASL A \ = 9 * A ASL A \ ASL A \ so X is a random number, either 0, 9, 18 or 27 ADC T TAX \ The following double loop calls the HAS1 routine three \ times to display three ships on screen. For each call, \ the values passed to HAS1 in XX15+2 to XX15 are taken \ from the HATB table, depending on the value in X, as \ follows: \ \ * If X = 0, pass bytes #0 to #2 of HATB to HAS1 \ then bytes #3 to #5 \ then bytes #6 to #8 \ \ * If X = 9, pass bytes #9 to #11 of HATB to HAS1 \ then bytes #12 to #14 \ then bytes #15 to #17 \ \ * If X = 18, pass bytes #18 to #20 of HATB to HAS1 \ then bytes #21 to #23 \ then bytes #24 to #26 \ \ * If X = 27, pass bytes #27 to #29 of HATB to HAS1 \ then bytes #30 to #32 \ then bytes #33 to #35 \ \ Note that the values are passed in reverse, so for the \ first call, for example, where we pass bytes #0 to #2 \ of HATB to HAS1, we call HAS1 with: \ \ XX15 = HATB+2 \ XX15+1 = HATB+1 \ XX15+2 = HATB LDY #3 \ Set CNT2 = 3 to act as an outer loop counter going STY CNT2 \ from 3 to 1, so the HAL8 loop is run 3 times .HAL8 LDY #2 \ Set Y = 2 to act as an inner loop counter going from \ 2 to 0 .HAL9 LDA HATB,X \ Copy the X-th byte of HATB to the Y-th byte of XX15, STA XX15,Y \ as described above INX \ Increment X to point to the next byte in HATB DEY \ Decrement Y to point to the previous byte in XX15 BPL HAL9 \ Loop back to copy the next byte until we have copied \ three of them (i.e. Y was 3 before the DEY) TXA \ Store X on the stack so we can retrieve it after the PHA \ call to HAS1 (as it contains the index of the next \ byte in HATB JSR HAS1 \ Call HAS1 to draw this ship in the hanger PLA \ Restore the value of X, so X points to the next byte TAX \ in HATB after the three bytes we copied into XX15 DEC CNT2 \ Decrement the outer loop counter in CNT2 BNE HAL8 \ Loop back to HAL8 to do it 3 times, once for each ship \ in the HATB table LDY #128 \ Set Y = 128 to send as byte #2 of the parameter block \ to the OSWORD 248 command below, to tell the I/O \ processor that there are multiple ships in the hanger BNE HA9 \ Jump to HA9 to display the ship hanger (this BNE is \ effectively a JMP as Y is never zero) .HA7 \ If we get here, A is a positive random number in the \ range 0-127 LSR A \ Set XX15+1 = A / 2 (random number 0-63) STA XX15+1 JSR DORND \ Set XX15 = random number 0-255 STA XX15 JSR DORND \ Set XX15+2 = random number 0-7 AND #7 \ STA XX15+2 \ which is either 0 (no ships in the hanger) or one of \ the first 7 ship types in the ship hanger blueprints \ table, i.e. a cargo canister, Shuttle, Transporter, \ Cobra Mk III, Python, Viper or Krait JSR HAS1 \ Call HAS1 to draw this ship in the hanger, with the \ the following properties: \ \ * Random x-coordinate from -63 to +63 \ \ * Randomly chosen cargo canister, Shuttle, \ Transporter, Cobra Mk III, Python, Viper or Krait \ \ * Random z-coordinate from +256 to +639 LDY #0 \ Set Y = 0 to use in the following instruction, to tell \ the hanger-drawing routine that there is just one ship \ in the hanger, so it knows not to draw between the \ ships .HA9 STY YSAV \ Store Y in YSAV to specify whether there are multiple \ ships in the hanger JSR UNWISE \ Call UNWISE to switch the main line-drawing routine \ between EOR and OR logic (in this case, switching it \ back to EOR logic so that we can erase anything we \ draw on-screen) \ Fall through into HANGER to draw the hanger background
Name: HANGER [View individually] Type: Subroutine [Compare versions] Category: Ship hanger Summary: Display the ship hanger
This routine is called after the ships in the hanger have been drawn, so all it has to do is draw the hanger's background. The hanger background is made up of two parts: * The hanger floor consists of 11 screen-wide horizontal lines, which start out quite spaced out near the bottom of the screen, and bunch ever closer together as the eye moves up towards the horizon, where they merge to give a sense of perspective * The back wall of the hanger consists of 15 equally spaced vertical lines that join the horizon to the top of the screen The ships in the hangar have already been drawn by this point, so the lines are drawn so they don't overlap anything that's already there, which makes them look like they are behind and below the ships. This is achieved by drawing the lines in from the screen edges until they bump into something already on-screen. For the horizontal lines, when there are multiple ships in the hanger, this also means drawing lines between the ships, as well as in from each side.
.HANGER \ We start by drawing the floor LDX #2 \ We start with a loop using a counter in T that goes \ from 2 to 12, one for each of the 11 horizontal lines \ in the floor, so set the initial value in X .HAL1 STX XSAV \ Store the loop counter in XSAV LDA #130 \ Set A = 130 LDX XSAV \ Retrieve the loop counter from XSAV STX Q \ Set Q = T JSR DVID4 \ Calculate the following: \ \ (P R) = 256 * A / Q \ = 256 * 130 / T \ \ so P = 130 / T, and as the counter T goes from 2 to \ 12, P goes 65, 43, 32 ... 13, 11, 10, with the \ difference between two consecutive numbers getting \ smaller as P gets smaller \ \ We can use this value as a y-coordinate to draw a set \ of horizontal lines, spaced out near the bottom of the \ screen (high value of P, high y-coordinate, lower down \ the screen) and bunching up towards the horizon (low \ value of P, low y-coordinate, higher up the screen) LDA P \ Set Y = #Y + P CLC \ ADC #Y \ where #Y is the y-coordinate of the centre of the \ screen, so Y is now the horizontal pixel row of the \ line we want to draw to display the hanger floor LSR A \ Set A = A >> 3 LSR A LSR A ORA #&60 \ Each character row in Elite's screen mode takes up one \ page in memory (256 bytes), so we now OR with &60 to \ get the page containing the line STA SCH \ Store the screen page in the high byte of SC(1 0) LDA P \ Set the low byte of SC(1 0) to the y-coordinate mod 7, AND #7 \ which determines the pixel row in the character block STA SC \ we need to draw in (as each character row is 8 pixels \ high), so SC(1 0) now points to the address of the \ start of the horizontal line we want to draw LDY #0 \ Set Y = 0 so the call to HAS2 starts drawing the line \ in the first byte of the screen row, at the left edge \ of the screen JSR HAS2 \ Draw a horizontal line from the left edge of the \ screen, going right until we bump into something \ already on-screen, at which point stop drawing LDA #%00000100 \ Now to draw the same line but from the right edge of \ the screen, so set a pixel mask in A to check the \ sixth pixel of the last byte, so we skip the 2-pixel \ scren border at the right edge of the screen LDY #248 \ Set Y = 248 so the call to HAS3 starts drawing the \ line in the last byte of the screen row, at the right \ edge of the screen JSR HAS3 \ Draw a horizontal line from the right edge of the \ screen, going left until we bump into something \ already on-screen, at which point stop drawing LDY YSAV \ Fetch the value of YSAV, which gets set to 0 in the \ HALL routine above if there is only one ship BEQ HA2 \ If YSAV is zero, jump to HA2 to skip the following \ as there is only one ship in the hanger \ If we get here then there are multiple ships in the \ hanger, so we also need to draw the horizontal line in \ the gap between the ships JSR HAS2 \ Call HAS2 to a line to the right, starting with the \ third pixel of the pixel row at screen address SC(1 0) LDY #128 \ We now draw the line from the centre of the screen \ to the left. SC(1 0) points to the start address of \ the screen row, so we set Y to 128 so the call to \ HAS3 starts drawing from halfway along the row (i.e. \ from the centre of the screen) LDA #%01000000 \ We want to start drawing from the second pixel, to \ avoid the border, so we set a pixel mask accordingly JSR HAS3 \ Call HAS3, which draws a line from the halfway point \ across the left half of the screen, going left until \ we bump into something already on-screen, at which \ point it stops drawing .HA2 \ We have finished threading our horizontal line behind \ the ships already on-screen, so now for the next line LDX XSAV \ Fetch the loop counter from XSAV and increment it INX CPX #13 \ If the loop counter is less than 13 (i.e. T = 2 to 12) BCC HAL1 \ then loop back to HAL1 to draw the next line \ The floor is done, so now we move on to the back wall LDA #16 \ We want to draw 15 vertical lines, one every 16 pixels \ across the screen, with the first at x-coordinate 16, \ so set this in A to act as the x-coordinate of each \ line as we work our way through them from left to \ right, incrementing by 16 for each new line .HAL6 LDX #&60 \ Set the high byte of SC(1 0) to &60, the high byte of STX SCH \ the start of screen STA XSAV \ Store this value in XSAV, so we can retrieve it later AND #%11111000 \ Each character block contains 8 pixel rows, so to get \ the address of the first byte in the character block \ that we need to draw into, as an offset from the start \ of the row, we clear bits 0-2 STA SC \ Set the low byte of SC(1 0) to this value, so SC(1 0) \ now points to the address where the line starts LDX #%10000000 \ Set a mask in X to the first pixel the 8-pixel byte LDY #1 \ We are going to start drawing the line from the second \ pixel from the top (to avoid drawing on the 1-pixel \ border), so set Y to 1 to point to the second row in \ the first character block .HAL7 TXA \ Copy the pixel mask to A AND (SC),Y \ If the pixel we want to draw is non-zero (using A as a BNE HA6 \ mask), then this means it already contains something, \ so jump to HA6 to stop drawing this line TXA \ Copy the pixel mask to A again ORA (SC),Y \ OR the byte with the current contents of screen \ memory, so the pixel we want is set STA (SC),Y \ Store the updated pixel in screen memory INY \ Increment Y to point to the next row in the character \ block, i.e. the next pixel down CPY #8 \ Loop back to HAL7 to draw this next pixel until we BNE HAL7 \ have drawn all 8 in the character block INC SC+1 \ Point SC(1 0) to the next page in memory, i.e. the \ next character row LDY #0 \ Set Y = 0 to point to the first row in this character \ block BEQ HAL7 \ Loop back up to HAL7 to keep drawing the line (this \ BEQ is effectively a JMP as Y is always zero) .HA6 LDA XSAV \ Fetch the x-coordinate of the line we just drew from CLC \ XSAV into A, and add 16 so that A contains the ADC #16 \ x-coordinate of the next line to draw BNE HAL6 \ Loop back to HAL6 until we have run through the loop \ 60 times, by which point we are most definitely done RTS \ Return from the subroutine
Name: HAS1 [View individually] Type: Subroutine [Compare versions] Category: Ship hanger Summary: Draw a ship in the ship hanger
The ship's position within the hanger is determined by the arguments and the size of the ship's targetable area, as follows: * The x-coordinate is (x_sign x_hi 0) from the arguments, so the ship can be left of centre or right of centre * The y-coordinate is negative and is lower down the screen for smaller ships, so smaller ships are drawn closer to the ground (because they are) * The z-coordinate is positive, with both z_hi (which is 1 or 2) and z_lo coming from the arguments Arguments: XX15 Bits 0-7 = Ship's z_lo Bit 0 = Ship's x_sign XX15+1 Bits 0-7 = Ship's x_hi Bit 0 = Ship's z_hi (1 if clear, or 2 if set) XX15+2 Non-zero = Ship type to draw 0 = Don't draw anything
.HAS1 JSR ZINF \ Call ZINF to reset the INWK ship workspace and reset \ the orientation vectors, with nosev pointing out of \ the screen, so this puts the ship flat on the \ horizontal deck (the y = 0 plane) with its nose \ pointing towards us LDA XX15 \ Set z_lo = XX15 STA INWK+6 LSR A \ Set the sign bit of x_sign to bit 0 of A ROR INWK+2 LDA XX15+1 \ Set x_hi = XX15+1 STA INWK LSR A \ Set z_hi = 1 + bit 0 of XX15+1 LDA #1 ADC #0 STA INWK+7 LDA #%10000000 \ Set bit 7 of y_sign, so y is negative STA INWK+5 STA RAT2 \ Set RAT2 = %10000000, so the yaw calls in HAL5 below \ are negative LDA #&B \ Set the ship line heap pointer in INWK(35 34) to point STA INWK+34 \ to &0B00 JSR DORND \ We now perform a random number of small angle (3.6 STA XSAV \ degree) rotations to spin the ship on the deck while \ keeping it flat on the deck (a bit like spinning a \ bottle), so we set XSAV to a random number between 0 \ and 255 for the number of small yaw rotations to \ perform, so the ship could be pointing in any \ direction by the time we're done .HAL5 LDX #21 \ Rotate (sidev_x, nosev_x) by a small angle (yaw) LDY #9 JSR MVS5 LDX #23 \ Rotate (sidev_y, nosev_y) by a small angle (yaw) LDY #11 JSR MVS5 LDX #25 \ Rotate (sidev_z, nosev_z) by a small angle (yaw) LDY #13 JSR MVS5 DEC XSAV \ Decrement the yaw counter in XSAV BNE HAL5 \ Loop back to yaw a little more until we have yawed \ by the number of times in XSAV LDY XX15+2 \ Set Y = XX15+2, the ship type of the ship we need to \ draw BEQ HA1 \ If Y = 0, return from the subroutine (as HA1 contains \ an RTS) \ We now work our way through the ship blueprints table \ for the hanger, counting valid blueprints until we \ have found the Y-th valid blueprint (we do this as the \ hanger blueprint table at XX21 is not fully populated, \ so the Y-th ship is not necessarily at position Y) LDX #4 \ We can start looking from ship blueprint 3, because we \ don't show ship 1 (missile) or ship 2 (space station) \ in the hanger. Setting X to 4, which then gets \ incremented to 6, will start us at XX21(5 4), which is \ the address of ship blueprint 3 (escape pod) .hloop INX \ Increment X by 2 to point to the next blueprint in the INX \ table LDA XX21-2,X \ Set XX0(1 0) to the X-th address in the ship blueprint STA XX0 \ address lookup table at XX21, so XX0(1 0) now points LDA XX21-1,X \ to the blueprint for the ship we need to draw STA XX0+1 BEQ hloop \ If the high byte of the blueprint address is 0, then \ the blueprint for this ship is not available, so jump \ back to hloop to try the next ship along in the table DEY \ We have found a valid blueprint, so decrement the ship \ number that we are looking for in Y BNE hloop \ If Y is not yet zero, we still haven't found the Y-th \ valid blueprint, so loop back to hloop to try the next \ ship along in the table LDY #1 \ Set Q = ship byte #1 LDA (XX0),Y STA Q INY \ Set R = ship byte #2 LDA (XX0),Y \ STA R \ so (R Q) contains the ship's targetable area, which is \ a square number JSR LL5 \ Set Q = SQRT(R Q) LDA #100 \ Set y_lo = (100 - Q) / 2 SBC Q \ LSR A \ so the bigger the ship's targetable area, the smaller STA INWK+3 \ the magnitude of the y-coordinate, so because we set \ y_sign to be negative above, this means smaller ships \ are drawn lower down, i.e. closer to the ground, while \ larger ships are drawn higher up, as you would expect JSR TIDY \ Call TIDY to tidy up the orientation vectors, to \ prevent the ship from getting elongated and out of \ shape due to the imprecise nature of trigonometry \ in assembly language JMP LL9 \ Jump to LL9 to display the ship and return from the \ subroutine using a tail call
Name: HAS2 [View individually] Type: Subroutine [Compare versions] Category: Ship hanger Summary: Draw a hanger background line from left to right
This routine draws a line to the right, starting with the third pixel of the pixel row at screen address SC(1 0), and aborting if we bump into something that's already on-screen. HAL2 draws from the left edge of the screen to the halfway point, and then HAL3 takes over to draw from the halfway point across the right half of the screen. Other entry points: HA3 Contains an RTS
.HAS2 LDA #%00100000 \ Set A to the pixel pattern for a mode 4 character row \ byte with the third pixel set, so we start drawing the \ horizontal line just to the right of the 2-pixel \ border along the edge of the screen .HAL2 TAX \ Store A in X so we can retrieve it after the following \ check and again after updating screen memory AND (SC),Y \ If the pixel we want to draw is non-zero (using A as a BNE HA3 \ mask), then this means it already contains something, \ so we stop drawing because we have run into something \ that's already on-screen, and return from the \ subroutine (as HA3 contains an RTS) TXA \ Retrieve the value of A we stored above, so A now \ contains the pixel mask again ORA (SC),Y \ OR the byte with the current contents of screen \ memory, so the pixel we want is set to red (because \ we know the bits are already 0 from the above test) STA (SC),Y \ Store the updated pixel in screen memory TXA \ Retrieve the value of A we stored above, so A now \ contains the pixel mask again LSR A \ Shift A to the right to move on to the next pixel BCC HAL2 \ If bit 0 before the shift was clear (i.e. we didn't \ just do the fourth pixel in this block), loop back to \ HAL2 to check and draw the next pixel TYA \ Set Y = Y + 8 (as we know the C flag is set) to point ADC #7 \ to the next character block along TAY LDA #%10000000 \ Reset the pixel mask in A to the first pixel in the \ new 8-pixel character block BCC HAL2 \ If the above addition didn't overflow, jump back to \ HAL2 to keep drawing the line in the next character \ block .HA3 RTS \ The addition overflowed, so we have reached the last \ character block in this page of memory, which is the \ end of the line, so we return from the subroutine
Name: HAS3 [View individually] Type: Subroutine [Compare versions] Category: Ship hanger Summary: Draw a hanger background line from right to left
This routine draws a line to the left, starting with the pixel mask in A at screen address SC(1 0) and character block offset Y, and aborting if we bump into something that's already on-screen.
.HAS3 TAX \ Store A in X so we can retrieve it after the following \ check and again after updating screen memory AND (SC),Y \ If the pixel we want to draw is non-zero (using A as a BNE HA3 \ mask), then this means it already contains something, \ so we stop drawing because we have run into something \ that's already on-screen, and return from the \ subroutine (as HA3 contains an RTS) TXA \ Retrieve the value of A we stored above, so A now \ contains the pixel mask again ORA (SC),Y \ OR the byte with the current contents of screen \ memory, so the pixel we want is set to red (because \ we know the bits are already 0 from the above test) STA (SC),Y \ Store the updated pixel in screen memory TXA \ Retrieve the value of A we stored above, so A now \ contains the pixel mask again ASL A \ Shift A to the left to move to the next pixel to the \ left BCC HAS3 \ If bit 7 before the shift was clear (i.e. we didn't \ just do the first pixel in this block), loop back to \ HAS3 to check and draw the next pixel to the left TYA \ Set Y = Y - 8 (as we know the C flag is set) to point SBC #8 \ to the next character block to the left TAY LDA #%00000001 \ Set a mask in A to the last pixel in the 8-pixel byte BCS HAS3 \ If the above subtraction didn't underflow, jump back \ to HAS3 to keep drawing the line in the next character \ block to the left RTS \ Return from the subroutine
Name: UNWISE [View individually] Type: Subroutine [Compare versions] Category: Ship hanger Summary: Switch the main line-drawing routine between EOR and OR logic
This routine modifies the instructions in the main line-drawing routine at LOIN/LL30, flipping the drawing logic between the default EOR logic (which merges with whatever is already on screen, allowing us to erase anything we draw for animation purposes) and OR logic (which overwrites the screen, ignoring anything that's already there). We want to use OR logic for drawing the ship hanger, as it looks better and we don't need to animate it). The routine name, UNWISE, sums up this approach - if anything goes wrong, the results would be messy. Other entry points: HA1 Contains an RTS
.UNWISE LDA LIL2+2 \ Flip bit 6 of LIL2+2 to change the EOR (SC),Y in LIL2 EOR #%01000000 \ to an ORA (SC),Y (or back again) STA LIL2+2 LDA LIL3+2 \ Flip bit 6 of LIL3+2 to change the EOR (SC),Y in LIL3 EOR #%01000000 \ to an ORA (SC),Y (or back again) STA LIL3+2 LDA LIL5+2 \ Flip bit 6 of LIL2+2 to change the EOR (SC),Y in LIL5 EOR #%01000000 \ to an ORA (SC),Y (or back again) STA LIL5+2 LDA LIL6+2 \ Flip bit 6 of LIL2+2 to change the EOR (SC),Y in LIL6 EOR #%01000000 \ to an ORA (SC),Y (or back again) STA LIL6+2 .HA1 RTS \ Return from the subroutine
Name: LL164 [View individually] Type: Subroutine [Compare versions] Category: Drawing circles Summary: Make the hyperspace sound and draw the hyperspace tunnel
See the IRQ1 routine for details on the multi-coloured effect that's used.
.LL164 LDA #56 \ Call the NOISE routine with A = 56 to make the sound JSR NOISE \ of the hyperspace drive being engaged LDA #1 \ Set HFX to 1, which switches the screen mode to a full STA HFX \ mode 5 screen, therefore making the hyperspace rings \ multi-coloured and all zig-zaggy (see the IRQ1 routine \ for details) LDA #4 \ Set the step size for the hyperspace rings to 4, so \ there are more sections in the rings and they are \ quite round (compared to the step size of 8 used in \ the much more polygonal launch rings) JSR HFS2 \ Call HFS2 to draw the hyperspace tunnel rings DEC HFX \ Set HFX back to 0, so we switch back to the normal \ split-screen mode RTS \ Return from the subroutine
Name: LAUN [View individually] Type: Subroutine [Compare versions] Category: Drawing circles Summary: Make the launch sound and draw the launch tunnel
This is shown when launching from or docking with the space station.
.LAUN LDA #48 \ Call the NOISE routine with A = 48 to make the sound JSR NOISE \ of the ship launching from the station LDA #8 \ Set the step size for the launch tunnel rings to 8, so \ there are fewer sections in the rings and they are \ quite polygonal (compared to the step size of 4 used \ in the much rounder hyperspace rings) \ Fall through into HFS2 to draw the launch tunnel rings
Name: HFS2 [View individually] Type: Subroutine [Compare versions] Category: Drawing circles Summary: Draw the launch or hyperspace tunnel
The animation gets drawn like this. First, we draw a circle of radius 8 at the centre, and then double the radius, draw another circle, double the radius again and draw a circle, and we keep doing this until the radius is bigger than 160 (which goes beyond the edge of the screen, which is 256 pixels wide, equivalent to a radius of 128). We then repeat this whole process for an initial circle of radius 9, then radius 10, all the way up to radius 15. This has the effect of making the tunnel appear to be racing towards us as we hurtle out into hyperspace or through the space station's docking tunnel. The hyperspace effect is done in a full mode 5 screen, which makes the rings all coloured and zig-zaggy, while the launch screen is in the normal monochrome mode 4 screen. Arguments: A The step size of the straight lines making up the rings (4 for launch, 8 for hyperspace) Other entry points: HFS1 Don't clear the screen, and draw 8 concentric rings with the step size in STP
.HFS2 STA STP \ Store the step size in A JSR TTX66 \ Clear the screen and draw a white border JSR HFS1 \ Call HFS1 below and then fall through into the same \ routine, so this effectively runs HFS1 twice, and as \ HFS1 draws 8 concentric rings, this means we draw 16 \ of them in all .HFS1 LDX #X \ Set K3 = #X (the x-coordinate of the centre of the STX K3 \ screen) LDX #Y \ Set K4 = #Y (the y-coordinate of the centre of the STX K4 \ screen) LDX #0 \ Set X = 0 STX XX4 \ Set XX4 = 0, which we will use as a counter for \ drawing 8 concentric rings STX K3+1 \ Set the high bytes of K3(1 0) and K4(1 0) to 0 STX K4+1 .HFL5 JSR HFL1 \ Call HFL1 below to draw a set of rings, with each one \ twice the radius of the previous one, until they won't \ fit on-screen INC XX4 \ Increment the counter and fetch it into X LDX XX4 CPX #8 \ If we haven't drawn 8 sets of rings yet, loop back to BNE HFL5 \ HFL5 to draw the next ring RTS \ Return from the subroutine .HFL1 LDA XX4 \ Set K to the ring number in XX4 (0-7) + 8, so K has AND #7 \ a value of 8 to 15, which we will use as the starting CLC \ radius for our next set of rings ADC #8 STA K .HFL2 LDA #1 \ Set LSP = 1 to reset the ball line heap STA LSP JSR CIRCLE2 \ Call CIRCLE2 to draw a circle with the centre at \ (K3(1 0), K4(1 0)) and radius K ASL K \ Double the radius in K BCS HF8 \ If the radius had a 1 in bit 7 before the above shift, \ then doubling K will means the circle will no longer \ fit on the screen (which is width 256), so jump to \ HF8 to stop drawing circles LDA K \ If the radius in K <= 160, loop back to HFL2 to draw CMP #160 \ another one BCC HFL2 .HF8 RTS \ Return from the subroutine
Name: Unused block [View individually] Type: Variable Category: Utility routines Summary: These bytes appear to be unused (the same block appears in both the flight and docked code)
EQUB &8C, &E7 EQUB &8D, &ED EQUB &8A, &E6 EQUB &C1, &C8 EQUB &C8, &8B EQUB &E0, &8A EQUB &E6, &D6 EQUB &C5, &C6 EQUB &C1, &CA EQUB &95, &9D EQUB &9C, &97
Name: MU5 [View individually] Type: Subroutine Category: Maths (Arithmetic) Summary: Set K(3 2 1 0) = (A A A A) and clear the C flGag
In practice this is only called via a BEQ following an AND instruction, in which case A = 0, so this routine effectively does this: K(3 2 1 0) = 0
.MU5 STA K \ Set K(3 2 1 0) to (A A A A) STA K+1 STA K+2 STA K+3 CLC \ Clear the C flag RTS \ Return from the subroutine
Name: MLS2 [View individually] Type: Subroutine [Compare versions] Category: Maths (Arithmetic) Summary: Calculate (S R) = XX(1 0) and (A P) = A * ALP1
Calculate the following: (S R) = XX(1 0) (A P) = A * ALP1 where ALP1 is the magnitude of the current roll angle alpha, in the range 0-31.
.MLS2 LDX XX \ Set (S R) = XX(1 0), starting with the low bytes STX R LDX XX+1 \ And then doing the high bytes STX S LDX ALP1 \ This repeats the first two instructions of MLS1, which STX P \ is presumably unintentional (though it has no effect) \ Fall through into MLS1 to calculate (A P) = A * ALP1
Name: SQUA [View individually] Type: Subroutine Category: Maths (Arithmetic) Summary: Clear bit 7 of A and calculate (A P) = A * A
Do the following multiplication of unsigned 8-bit numbers, after first clearing bit 7 of A: (A P) = A * A
.SQUA AND #%01111111 \ Clear bit 7 of A and fall through into SQUA2 to set \ (A P) = A * A
Name: SQUA2 [View individually] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate (A P) = A * A
Do the following multiplication of unsigned 8-bit numbers: (A P) = A * A
.SQUA2 STA P \ Copy A into P and X TAX BNE MU11 \ If X = 0 fall through into MU1 to return a 0, \ otherwise jump to MU11 to return P * X
Name: MU1 [View individually] Type: Subroutine Category: Maths (Arithmetic) Summary: Copy X into P and A, and clear the C flag
Used to return a 0 result quickly from MULTU below.
.MU1 CLC \ Clear the C flag STX P \ Copy X into P and A TXA RTS \ Return from the subroutine
Name: MLU1 [View individually] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate Y1 = y_hi and (A P) = |y_hi| * Q for Y-th stardust
Do the following assignment, and multiply the Y-th stardust particle's y-coordinate with an unsigned number Q: Y1 = y_hi (A P) = |y_hi| * Q
.MLU1 LDA SY,Y \ Set Y1 the Y-th byte of SY STA Y1 \ Fall through into MLU2 to calculate: \ \ (A P) = |A| * Q
Name: MLU2 [View individually] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate (A P) = |A| * Q
Do the following multiplication of a sign-magnitude 8-bit number P with an unsigned number Q: (A P) = |A| * Q
.MLU2 AND #%01111111 \ Clear the sign bit in P, so P = |A| STA P \ Fall through into MULTU to calculate: \ \ (A P) = P * Q \ = |A| * Q
Name: MULTU [View individually] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate (A P) = P * Q
Do the following multiplication of unsigned 8-bit numbers: (A P) = P * Q
.MULTU LDX Q \ Set X = Q BEQ MU1 \ If X = Q = 0, jump to MU1 to copy X into P and A, \ clear the C flag and return from the subroutine using \ a tail call \ Otherwise fall through into MU11 to set (A P) = P * X
Name: MU11 [View individually] Type: Subroutine [Compare versions] Category: Maths (Arithmetic) Summary: Calculate (A P) = P * X Deep dive: Shift-and-add multiplication
Do the following multiplication of two unsigned 8-bit numbers: (A P) = P * X This uses the same shift-and-add approach as MULT1, but it's simpler as we are dealing with unsigned numbers in P and X. See the deep dive on "Shift-and-add multiplication" for a discussion of how this algorithm works.
.MU11 DEX \ Set T = X - 1 STX T \ \ We subtract 1 as the C flag will be set when we want \ to do an addition in the loop below LDA #0 \ Set A = 0 so we can start building the answer in A LDX #8 \ Set up a counter in X to count the 8 bits in P LSR P \ Set P = P >> 1 \ and C flag = bit 0 of P \ We are now going to work our way through the bits of \ P, and do a shift-add for any bits that are set, \ keeping the running total in A. We just did the first \ shift right, so we now need to do the first add and \ loop through the other bits in P .MUL6 BCC P%+4 \ If C (i.e. the next bit from P) is set, do the ADC T \ addition for this bit of P: \ \ A = A + T + C \ = A + X - 1 + 1 \ = A + X ROR A \ Shift A right to catch the next digit of our result, \ which the next ROR sticks into the left end of P while \ also extracting the next bit of P ROR P \ Add the overspill from shifting A to the right onto \ the start of P, and shift P right to fetch the next \ bit for the calculation into the C flag DEX \ Decrement the loop counter BNE MUL6 \ Loop back for the next bit until P has been rotated \ all the way RTS \ Return from the subroutine
Name: MU6 [View individually] Type: Subroutine Category: Maths (Arithmetic) Summary: Set P(1 0) = (A A)
In practice this is only called via a BEQ following an AND instruction, in which case A = 0, so this routine effectively does this: P(1 0) = 0
.MU6 STA P+1 \ Set P(1 0) = (A A) STA P RTS \ Return from the subroutine
Name: FMLTU2 [View individually] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate A = K * sin(A) Deep dive: The sine, cosine and arctan tables
Calculate the following: A = K * sin(A) Because this routine uses the sine lookup table SNE, we can also call this routine to calculate cosine multiplication. To calculate the following: A = K * cos(B) call this routine with B + 16 in the accumulator, as sin(B + 16) = cos(B).
.FMLTU2 AND #%00011111 \ Restrict A to bits 0-5 (so it's in the range 0-31) TAX \ Set Q = sin(A) * 256 LDA SNE,X STA Q LDA K \ Set A to the radius in K \ Fall through into FMLTU to do the following: \ \ (A ?) = A * Q \ = K * sin(A) * 256 \ \ which is equivalent to: \ \ A = K * sin(A)
Name: FMLTU [View individually] Type: Subroutine [Compare versions] Category: Maths (Arithmetic) Summary: Calculate A = A * Q / 256
Do the following multiplication of two unsigned 8-bit numbers, returning only the high byte of the result: (A ?) = A * Q or, to put it another way: A = A * Q / 256
.FMLTU EOR #%11111111 \ Flip the bits in A, set the C flag and rotate right, SEC \ so the C flag now contains bit 0 of A inverted, and P ROR A \ contains A inverted and shifted right by one, with bit STA P \ 7 set to a 1. We can now use P as our source of bits \ to shift right, just as in MU11, just with the logic \ reversed LDA #0 \ Set A = 0 so we can start building the answer in A .MUL3 BCS MU7 \ If C (i.e. the next bit from P) is set, do not do the \ addition for this bit of P, and instead skip to MU7 \ to just do the shifts ADC Q \ Do the addition for this bit of P: \ \ A = A + Q + C \ = A + Q ROR A \ Shift A right to catch the next digit of our result. \ If we were interested in the low byte of the result we \ would want to save the bit that falls off the end, but \ we aren't, so we can ignore it LSR P \ Shift P right to fetch the next bit for the \ calculation into the C flag BNE MUL3 \ Loop back to MUL3 if P still contains some set bits \ (so we loop through the bits of P until we get to the \ 1 we inserted before the loop, and then we stop) RTS \ Return from the subroutine .MU7 LSR A \ Shift A right to catch the next digit of our result, \ pushing a 0 into bit 7 as we aren't adding anything \ here (we can't use a ROR here as the C flag is set, so \ a ROR would push a 1 into bit 7) LSR P \ Fetch the next bit from P into the C flag BNE MUL3 \ Loop back to MUL3 if P still contains some set bits \ (so we loop through the bits of P until we get to the \ 1 we inserted before the loop, and then we stop) RTS \ Return from the subroutine
Name: Unused duplicate of MULTU [View individually] Type: Subroutine Category: Maths (Arithmetic) Summary: Unused duplicate of the MULTU routine
This is a duplicate of the MULTU routine, but with no entry label, so it can't be called by name. It is unused, and could have been culled to save a few bytes (24 to be precise), but it's still here. In the disc version it has the label MULTU6, but here in the cassette version it's unnamed, unloved and unvisited, through no fault of its own.
{ LDX Q BEQ MU1 DEX STX T LDA #0 LDX #8 LSR P .MUL6 BCC P%+4 ADC T ROR A ROR P DEX BNE MUL6 RTS }
Name: MLTU2 [View individually] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate (A P+1 P) = (A ~P) * Q Deep dive: Shift-and-add multiplication
Do the following multiplication of an unsigned 16-bit number and an unsigned 8-bit number: (A P+1 P) = (A ~P) * Q where ~P means P EOR %11111111 (i.e. P with all its bits flipped). In other words, if you wanted to calculate &1234 * &56, you would: * Set A to &12 * Set P to &34 EOR %11111111 = &CB * Set Q to &56 before calling MLTU2. This routine is like a mash-up of MU11 and FMLTU. It uses part of FMLTU's inverted argument trick to work out whether or not to do an addition, and like MU11 it sets up a counter in X to extract bits from (P+1 P). But this time we extract 16 bits from (P+1 P), so the result is a 24-bit number. The core of the algorithm is still the shift-and-add approach explained in MULT1, just with more bits. Returns: Q Q is preserved Other entry points: MLTU2-2 Set Q to X, so this calculates (A P+1 P) = (A ~P) * X
STX Q \ Store X in Q .MLTU2 EOR #%11111111 \ Flip the bits in A and rotate right, storing the LSR A \ result in P+1, so we now calculate (P+1 P) * Q STA P+1 LDA #0 \ Set A = 0 so we can start building the answer in A LDX #16 \ Set up a counter in X to count the 16 bits in (P+1 P) ROR P \ Set P = P >> 1 with bit 7 = bit 0 of A \ and C flag = bit 0 of P .MUL7 BCS MU21 \ If C (i.e. the next bit from P) is set, do not do the \ addition for this bit of P, and instead skip to MU21 \ to just do the shifts ADC Q \ Do the addition for this bit of P: \ \ A = A + Q + C \ = A + Q ROR A \ Rotate (A P+1 P) to the right, so we capture the next ROR P+1 \ digit of the result in P+1, and extract the next digit ROR P \ of (P+1 P) in the C flag DEX \ Decrement the loop counter BNE MUL7 \ Loop back for the next bit until P has been rotated \ all the way RTS \ Return from the subroutine .MU21 LSR A \ Shift (A P+1 P) to the right, so we capture the next ROR P+1 \ digit of the result in P+1, and extract the next digit ROR P \ of (P+1 P) in the C flag DEX \ Decrement the loop counter BNE MUL7 \ Loop back for the next bit until P has been rotated \ all the way RTS \ Return from the subroutine
Name: MUT3 [View individually] Type: Subroutine Category: Maths (Arithmetic) Summary: Unused routine that does the same as MUT2
This routine is never actually called, but it is identical to MUT2, as the extra instructions have no effect.
.MUT3 LDX ALP1 \ Set P = ALP1, though this gets overwritten by the STX P \ following, so this has no effect \ Fall through into MUT2 to do the following: \ \ (S R) = XX(1 0) \ (A P) = Q * A
Name: MUT2 [View individually] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate (S R) = XX(1 0) and (A P) = Q * A
Do the following assignment, and multiplication of two signed 8-bit numbers: (S R) = XX(1 0) (A P) = Q * A
.MUT2 LDX XX+1 \ Set S = XX+1 STX S \ Fall through into MUT1 to do the following: \ \ R = XX \ (A P) = Q * A
Name: MUT1 [View individually] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate R = XX and (A P) = Q * A
Do the following assignment, and multiplication of two signed 8-bit numbers: R = XX (A P) = Q * A
.MUT1 LDX XX \ Set R = XX STX R \ Fall through into MULT1 to do the following: \ \ (A P) = Q * A
Name: MULT1 [View individually] Type: Subroutine [Compare versions] Category: Maths (Arithmetic) Summary: Calculate (A P) = Q * A Deep dive: Shift-and-add multiplication
Do the following multiplication of two 8-bit sign-magnitude numbers: (A P) = Q * A
.MULT1 TAX \ Store A in X AND #%01111111 \ Set P = |A| >> 1 LSR A \ and C flag = bit 0 of A STA P TXA \ Restore argument A EOR Q \ Set bit 7 of A and T if Q and A have different signs, AND #%10000000 \ clear bit 7 if they have the same signs, 0 all other STA T \ bits, i.e. T contains the sign bit of Q * A LDA Q \ Set A = |Q| AND #%01111111 BEQ mu10 \ If |Q| = 0 jump to mu10 (with A set to 0) TAX \ Set T1 = |Q| - 1 DEX \ STX T1 \ We subtract 1 as the C flag will be set when we want \ to do an addition in the loop below \ We are now going to work our way through the bits of \ P, and do a shift-add for any bits that are set, \ keeping the running total in A. We already set up \ the first shift at the start of this routine, as \ P = |A| >> 1 and C = bit 0 of A, so we now need to set \ up a loop to sift through the other 7 bits in P LDA #0 \ Set A = 0 so we can start building the answer in A LDX #7 \ Set up a counter in X to count the 7 bits remaining \ in P .MUL4 BCC P%+4 \ If C (i.e. the next bit from P) is set, do the ADC T1 \ addition for this bit of P: \ \ A = A + T1 + C \ = A + |Q| - 1 + 1 \ = A + |Q| ROR A \ As mentioned above, this ROR shifts A right and \ catches bit 0 in C - giving another digit for our \ result - and the next ROR sticks that bit into the \ left end of P while also extracting the next bit of P \ for the next addition ROR P \ Add the overspill from shifting A to the right onto \ the start of P, and shift P right to fetch the next \ bit for the calculation DEX \ Decrement the loop counter BNE MUL4 \ Loop back for the next bit until P has been rotated \ all the way LSR A \ Rotate (A P) once more to get the final result, as ROR P \ we only pushed 7 bits through the above process ORA T \ Set the sign bit of the result that we stored in T RTS \ Return from the subroutine .mu10 STA P \ If we get here, the result is 0 and A = 0, so set \ P = 0 so (A P) = 0 RTS \ Return from the subroutine
Name: MULT12 [View individually] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate (S R) = Q * A
Calculate: (S R) = Q * A
.MULT12 JSR MULT1 \ Set (A P) = Q * A STA S \ Set (S R) = (A P) LDA P STA R RTS \ Return from the subroutine
Name: TAS3 [View individually] Type: Subroutine [Compare versions] Category: Maths (Geometry) Summary: Calculate the dot product of XX15 and an orientation vector
Calculate the dot product of the vector in XX15 and one of the orientation vectors, as determined by the value of Y. If vect is the orientation vector, we calculate this: (A X) = vect . XX15 = vect_x * XX15 + vect_y * XX15+1 + vect_z * XX15+2 Arguments: Y The orientation vector: * If Y = 10, calculate nosev . XX15 * If Y = 16, calculate roofv . XX15 * If Y = 22, calculate sidev . XX15 Returns: (A X) The result of the dot product
.TAS3 LDX INWK,Y \ Set Q = the Y-th byte of INWK, i.e. vect_x STX Q LDA XX15 \ Set A = XX15 JSR MULT12 \ Set (S R) = Q * A \ = vect_x * XX15 LDX INWK+2,Y \ Set Q = the Y+2-th byte of INWK, i.e. vect_y STX Q LDA XX15+1 \ Set A = XX15+1 JSR MAD \ Set (A X) = Q * A + (S R) \ = vect_y * XX15+1 + vect_x * XX15 STA S \ Set (S R) = (A X) STX R LDX INWK+4,Y \ Set Q = the Y+2-th byte of INWK, i.e. vect_z STX Q LDA XX15+2 \ Set A = XX15+2 \ Fall through into MAD to set: \ \ (A X) = Q * A + (S R) \ = vect_z * XX15+2 + vect_y * XX15+1 + \ vect_x * XX15
Name: MAD [View individually] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate (A X) = Q * A + (S R)
Calculate (A X) = Q * A + (S R)
.MAD JSR MULT1 \ Call MULT1 to set (A P) = Q * A \ Fall through into ADD to do: \ \ (A X) = (A P) + (S R) \ = Q * A + (S R)
Name: ADD [View individually] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate (A X) = (A P) + (S R) Deep dive: Adding sign-magnitude numbers
Add two 16-bit sign-magnitude numbers together, calculating: (A X) = (A P) + (S R)
.ADD STA T1 \ Store argument A in T1 AND #%10000000 \ Extract the sign (bit 7) of A and store it in T STA T EOR S \ EOR bit 7 of A with S. If they have different bit 7s BMI MU8 \ (i.e. they have different signs) then bit 7 in the \ EOR result will be 1, which means the EOR result is \ negative. So the AND, EOR and BMI together mean "jump \ to MU8 if A and S have different signs" \ If we reach here, then A and S have the same sign, so \ we can add them and set the sign to get the result LDA R \ Add the least significant bytes together into X: CLC \ ADC P \ X = P + R TAX LDA S \ Add the most significant bytes together into A. We ADC T1 \ stored the original argument A in T1 earlier, so we \ can do this with: \ \ A = A + S + C \ = T1 + S + C ORA T \ If argument A was negative (and therefore S was also \ negative) then make sure result A is negative by \ OR-ing the result with the sign bit from argument A \ (which we stored in T) RTS \ Return from the subroutine .MU8 \ If we reach here, then A and S have different signs, \ so we can subtract their absolute values and set the \ sign to get the result LDA S \ Clear the sign (bit 7) in S and store the result in AND #%01111111 \ U, so U now contains |S| STA U LDA P \ Subtract the least significant bytes into X: SEC \ SBC R \ X = P - R TAX LDA T1 \ Restore the A of the argument (A P) from T1 and AND #%01111111 \ clear the sign (bit 7), so A now contains |A| SBC U \ Set A = |A| - |S| \ At this point we have |A P| - |S R| in (A X), so we \ need to check whether the subtraction above was the \ the right way round (i.e. that we subtracted the \ smaller absolute value from the larger absolute \ value) BCS MU9 \ If |A| >= |S|, our subtraction was the right way \ round, so jump to MU9 to set the sign \ If we get here, then |A| < |S|, so our subtraction \ above was the wrong way round (we actually subtracted \ the larger absolute value from the smaller absolute \ value). So let's subtract the result we have in (A X) \ from zero, so that the subtraction is the right way \ round STA U \ Store A in U TXA \ Set X = 0 - X using two's complement (to negate a EOR #&FF \ number in two's complement, you can invert the bits ADC #1 \ and add one - and we know the C flag is clear as we TAX \ didn't take the BCS branch above, so the ADC will do \ the correct addition) LDA #0 \ Set A = 0 - A, which we can do this time using a SBC U \ a subtraction with the C flag clear ORA #%10000000 \ We now set the sign bit of A, so that the EOR on the \ next line will give the result the opposite sign to \ argument A (as T contains the sign bit of argument \ A). This is the same as giving the result the same \ sign as argument S (as A and S have different signs), \ which is what we want, as S has the larger absolute \ value .MU9 EOR T \ If we get here from the BCS above, then |A| >= |S|, \ so we want to give the result the same sign as \ argument A, so if argument A was negative, we flip \ the sign of the result with an EOR (to make it \ negative) RTS \ Return from the subroutine
Name: TIS1 [View individually] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate (A ?) = (-X * A + (S R)) / 96 Deep dive: Shift-and-subtract division
Calculate the following expression between sign-magnitude numbers, ignoring the low byte of the result: (A ?) = (-X * A + (S R)) / 96 This uses the same shift-and-subtract algorithm as TIS2, just with the quotient A hard-coded to 96. Returns: Q Gets set to the value of argument X
.TIS1 STX Q \ Set Q = X EOR #%10000000 \ Flip the sign bit in A JSR MAD \ Set (A X) = Q * A + (S R) \ = X * -A + (S R) .DVID96 TAX \ Set T to the sign bit of the result AND #%10000000 STA T TXA \ Set A to the high byte of the result with the sign bit AND #%01111111 \ cleared, so (A ?) = |X * A + (S R)| \ The following is identical to TIS2, except Q is \ hard-coded to 96, so this does A = A / 96 LDX #254 \ Set T1 to have bits 1-7 set, so we can rotate through STX T1 \ 7 loop iterations, getting a 1 each time, and then \ getting a 0 on the 8th iteration... and we can also \ use T1 to catch our result bits into bit 0 each time .DVL3 ASL A \ Shift A to the left CMP #96 \ If A < 96 skip the following subtraction BCC DV4 SBC #96 \ Set A = A - 96 \ \ Going into this subtraction we know the C flag is \ set as we passed through the BCC above, and we also \ know that A >= 96, so the C flag will still be set \ once we are done .DV4 ROL T1 \ Rotate the counter in T1 to the left, and catch the \ result bit into bit 0 (which will be a 0 if we didn't \ do the subtraction, or 1 if we did) BCS DVL3 \ If we still have set bits in T1, loop back to DVL3 to \ do the next iteration of 7 LDA T1 \ Fetch the result from T1 into A ORA T \ Give A the sign of the result that we stored above RTS \ Return from the subroutine
Name: DV42 [View individually] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate (P R) = 256 * DELTA / z_hi
Calculate the following division and remainder: P = DELTA / (the Y-th stardust particle's z_hi coordinate) R = remainder as a fraction of A, where 1.0 = 255 Another way of saying the above is this: (P R) = 256 * DELTA / z_hi DELTA is a value between 1 and 40, and the minimum z_hi is 16 (dust particles are removed at lower values than this), so this means P is between 0 and 2 (as 40 / 16 = 2.5, so the maximum result is P = 2 and R = 128. This uses the same shift-and-subtract algorithm as TIS2, but this time we keep the remainder. Arguments: Y The number of the stardust particle to process Returns: C flag The C flag is cleared
.DV42 LDA SZ,Y \ Fetch the Y-th dust particle's z_hi coordinate into A \ Fall through into DV41 to do: \ \ (P R) = 256 * DELTA / A \ = 256 * DELTA / Y-th stardust particle's z_hi
Name: DV41 [View individually] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate (P R) = 256 * DELTA / A
Calculate the following division and remainder: P = DELTA / A R = remainder as a fraction of A, where 1.0 = 255 Another way of saying the above is this: (P R) = 256 * DELTA / A This uses the same shift-and-subtract algorithm as TIS2, but this time we keep the remainder. Returns: C flag The C flag is cleared
.DV41 STA Q \ Store A in Q LDA DELTA \ Fetch the speed from DELTA into A \ Fall through into DVID4 to do: \ \ (P R) = 256 * A / Q \ = 256 * DELTA / A
Name: DVID4 [View individually] Type: Subroutine [Compare versions] Category: Maths (Arithmetic) Summary: Calculate (P R) = 256 * A / Q Deep dive: Shift-and-subtract division
Calculate the following division and remainder: P = A / Q R = remainder as a fraction of Q, where 1.0 = 255 Another way of saying the above is this: (P R) = 256 * A / Q This uses the same shift-and-subtract algorithm as TIS2, but this time we keep the remainder. Returns: C flag The C flag is cleared
.DVID4 LDX #8 \ Set a counter in X to count the 8 bits in A ASL A \ Shift A left and store in P (we will build the result STA P \ in P) LDA #0 \ Set A = 0 for us to build a remainder .DVL4 ROL A \ Shift A to the left BCS DV8 \ If the C flag is set (i.e. bit 7 of A was set) then \ skip straight to the subtraction CMP Q \ If A < Q skip the following subtraction BCC DV5 .DV8 SBC Q \ A >= Q, so set A = A - Q SEC \ Set the C flag, so that P gets a 1 shifted into bit 0 .DV5 ROL P \ Shift P to the left, pulling the C flag into bit 0 DEX \ Decrement the loop counter BNE DVL4 \ Loop back for the next bit until we have done all 8 \ bits of P JMP LL28+4 \ Jump to LL28+4 to convert the remainder in A into an \ integer representation of the fractional value A / Q, \ in R, where 1.0 = 255. LL28+4 always returns with the \ C flag cleared, and we return from the subroutine \ using a tail call
Name: DVID3B2 [View individually] Type: Subroutine [Compare versions] Category: Maths (Arithmetic) Summary: Calculate K(3 2 1 0) = (A P+1 P) / (z_sign z_hi z_lo) Deep dive: Shift-and-subtract division
Calculate the following: K(3 2 1 0) = (A P+1 P) / (z_sign z_hi z_lo) The actual division here is done as an 8-bit calculation using LL31, but this routine shifts both the numerator (the top part of the division) and the denominator (the bottom part of the division) around to get the multi-byte result we want. Specifically, it shifts both of them to the left as far as possible, keeping a tally of how many shifts get done in each one - and specifically, the difference in the number of shifts between the top and bottom (as shifting both of them once in the same direction won't change the result). It then divides the two highest bytes with the simple 8-bit routine in LL31, and shifts the result by the difference in the number of shifts, which acts as a scale factor to get the correct result. Returns: K(3 2 1 0) The result of the division X X is preserved
.DVID3B2 STA P+2 \ Set P+2 = A LDA INWK+6 \ Set Q = z_lo STA Q LDA INWK+7 \ Set R = z_hi STA R LDA INWK+8 \ Set S = z_sign STA S .DVID3B \ Given the above assignments, we now want to calculate \ the following to get the result we want: \ \ K(3 2 1 0) = P(2 1 0) / (S R Q) LDA P \ Make sure P(2 1 0) is at least 1 ORA #1 STA P LDA P+2 \ Set T to the sign of P+2 * S (i.e. the sign of the EOR S \ result) and store it in T AND #%10000000 STA T LDY #0 \ Set Y = 0 to store the scale factor LDA P+2 \ Clear the sign bit of P+2, so the division can be done AND #%01111111 \ with positive numbers and we'll set the correct sign \ below, once all the maths is done \ \ This also leaves A = P+2, which we use below .DVL9 \ We now shift (A P+1 P) left until A >= 64, counting \ the number of shifts in Y. This makes the top part of \ the division as large as possible, thus retaining as \ much accuracy as we can. When we come to return the \ final result, we shift the result by the number of \ places in Y, and in the correct direction CMP #64 \ If A >= 64, jump down to DV14 BCS DV14 ASL P \ Shift (A P+1 P) to the left ROL P+1 ROL A INY \ Increment the scale factor in Y BNE DVL9 \ Loop up to DVL9 (this BNE is effectively a JMP, as Y \ will never be zero) .DV14 \ If we get here, A >= 64 and contains the highest byte \ of the numerator, scaled up by the number of left \ shifts in Y STA P+2 \ Store A in P+2, so we now have the scaled value of \ the numerator in P(2 1 0) LDA S \ Set A = |S| AND #%01111111 BMI DV9 \ If bit 7 of A is set, jump down to DV9 to skip the \ left-shifting of the denominator (though this branch \ instruction has no effect as bit 7 of the above AND \ can never be set, which is why this instruction was \ removed from later versions) .DVL6 \ We now shift (S R Q) left until bit 7 of S is set, \ reducing Y by the number of shifts. This makes the \ bottom part of the division as large as possible, thus \ retaining as much accuracy as we can. When we come to \ return the final result, we shift the result by the \ total number of places in Y, and in the correct \ direction, to give us the correct result \ \ We set A to |S| above, so the following actually \ shifts (A R Q) DEY \ Decrement the scale factor in Y ASL Q \ Shift (A R Q) to the left ROL R ROL A BPL DVL6 \ Loop up to DVL6 to do another shift, until bit 7 of A \ is set and we can't shift left any further .DV9 \ We have now shifted both the numerator and denominator \ left as far as they will go, keeping a tally of the \ overall scale factor of the various shifts in Y. We \ can now divide just the two highest bytes to get our \ result STA Q \ Set Q = A, the highest byte of the denominator LDA #254 \ Set R to have bits 1-7 set, so we can pass this to STA R \ LL31 to act as the bit counter in the division LDA P+2 \ Set A to the highest byte of the numerator JSR LL31 \ Call LL31 to calculate: \ \ R = 256 * A / Q \ = 256 * numerator / denominator \ The result of our division is now in R, so we just \ need to shift it back by the scale factor in Y LDA #0 \ Set K(3 2 1) = 0 to hold the result (we populate K STA K+1 \ next) STA K+2 STA K+3 TYA \ If Y is positive, jump to DV12 BPL DV12 \ If we get here then Y is negative, so we need to shift \ the result R to the left by Y places, and then set the \ correct sign for the result LDA R \ Set A = R .DVL8 ASL A \ Shift (K+3 K+2 K+1 A) left ROL K+1 ROL K+2 ROL K+3 INY \ Increment the scale factor in Y BNE DVL8 \ Loop back to DVL8 until we have shifted left by Y \ places STA K \ Store A in K so the result is now in K(3 2 1 0) LDA K+3 \ Set K+3 to the sign in T, which we set above to the ORA T \ correct sign for the result STA K+3 RTS \ Return from the subroutine .DV13 \ If we get here then Y is zero, so we don't need to \ shift the result R, we just need to set the correct \ sign for the result LDA R \ Store R in K so the result is now in K(3 2 1 0) STA K LDA T \ Set K+3 to the sign in T, which we set above to the STA K+3 \ correct sign for the result RTS \ Return from the subroutine .DV12 BEQ DV13 \ We jumped here having set A to the scale factor in Y, \ so this jumps up to DV13 if Y = 0 \ If we get here then Y is positive and non-zero, so we \ need to shift the result R to the right by Y places \ and then set the correct sign for the result. We also \ know that K(3 2 1) will stay 0, as we are shifting the \ lowest byte to the right, so no set bits will make \ their way into the top three bytes LDA R \ Set A = R .DVL10 LSR A \ Shift A right DEY \ Decrement the scale factor in Y BNE DVL10 \ Loop back to DVL10 until we have shifted right by Y \ places STA K \ Store the shifted A in K so the result is now in \ K(3 2 1 0) LDA T \ Set K+3 to the sign in T, which we set above to the STA K+3 \ correct sign for the result RTS \ Return from the subroutine
Name: cntr [View individually] Type: Subroutine [Compare versions] Category: Dashboard Summary: Apply damping to the pitch or roll dashboard indicator
Apply damping to the value in X, where X ranges from 1 to 255 with 128 as the centre point (so X represents a position on a centre-based dashboard slider, such as pitch or roll). If the value is in the left-hand side of the slider (1-127) then it bumps the value up by 1 so it moves towards the centre, and if it's in the right-hand side, it reduces it by 1, also moving it towards the centre.
.cntr LDA DAMP \ If DAMP is non-zero, then keyboard damping is not BNE RE1 \ enabled, so jump to RE1 to return from the subroutine TXA \ If X < 128, then it's in the left-hand side of the BPL BUMP \ dashboard slider, so jump to BUMP to bump it up by 1, \ to move it closer to the centre DEX \ Otherwise X >= 128, so it's in the right-hand side BMI RE1 \ of the dashboard slider, so decrement X by 1, and if \ it's still >= 128, jump to RE1 to return from the \ subroutine, otherwise fall through to BUMP to undo \ the bump and then return .BUMP INX \ Bump X up by 1, and if it hasn't overshot the end of BNE RE1 \ the dashboard slider, jump to RE1 to return from the \ subroutine, otherwise fall through to REDU to drop \ it down by 1 again .REDU DEX \ Reduce X by 1, and if we have reached 0 jump up to BEQ BUMP \ BUMP to add 1, because we need the value to be in the \ range 1 to 255 .RE1 RTS \ Return from the subroutine
Name: BUMP2 [View individually] Type: Subroutine [Compare versions] Category: Dashboard Summary: Bump up the value of the pitch or roll dashboard indicator
Increase ("bump up") X by A, where X is either the current rate of pitch or the current rate of roll. The rate of pitch or roll ranges from 1 to 255 with 128 as the centre point. This is the amount by which the pitch or roll is currently changing, so 1 means it is decreasing at the maximum rate, 128 means it is not changing, and 255 means it is increasing at the maximum rate. These values correspond to the line on the DC or RL indicators on the dashboard, with 1 meaning full left, 128 meaning the middle, and 255 meaning full right. If bumping up X would push it past 255, then X is set to 255. If keyboard auto-recentre is configured and the result is less than 128, we bump X up to the mid-point, 128. This is the equivalent of having a roll or pitch in the left half of the indicator, when increasing the roll or pitch should jump us straight to the mid-point. Other entry points: RE2+2 Restore A from T and return from the subroutine
.BUMP2 STA T \ Store argument A in T so we can restore it later TXA \ Copy argument X into A CLC \ Clear the C flag so we can do addition without the \ C flag affecting the result ADC T \ Set X = A = argument X + argument A TAX BCC RE2 \ If the C flag is clear, then we didn't overflow, so \ jump to RE2 to auto-recentre and return the result LDX #255 \ We have an overflow, so set X to the maximum possible \ value of 255 .RE2 BPL RE3+2 \ If X has bit 7 clear (i.e. the result < 128), then \ jump to RE3+2 in routine REDU2 to do an auto-recentre, \ if configured, because the result is on the left side \ of the centre point of 128 \ Jumps to RE2+2 end up here LDA T \ Restore the original argument A from T into A RTS \ Return from the subroutine
Name: REDU2 [View individually] Type: Subroutine [Compare versions] Category: Dashboard Summary: Reduce the value of the pitch or roll dashboard indicator
Reduce X by A, where X is either the current rate of pitch or the current rate of roll. The rate of pitch or roll ranges from 1 to 255 with 128 as the centre point. This is the amount by which the pitch or roll is currently changing, so 1 means it is decreasing at the maximum rate, 128 means it is not changing, and 255 means it is increasing at the maximum rate. These values correspond to the line on the DC or RL indicators on the dashboard, with 1 meaning full left, 128 meaning the middle, and 255 meaning full right. If reducing X would bring it below 1, then X is set to 1. If keyboard auto-recentre is configured and the result is greater than 128, we reduce X down to the mid-point, 128. This is the equivalent of having a roll or pitch in the right half of the indicator, when decreasing the roll or pitch should jump us straight to the mid-point. Other entry points: RE3+2 Auto-recentre the value in X, if keyboard auto-recentre is configured
.REDU2 STA T \ Store argument A in T so we can restore it later TXA \ Copy argument X into A SEC \ Set the C flag so we can do subtraction without the \ C flag affecting the result SBC T \ Set X = A = argument X - argument A TAX BCS RE3 \ If the C flag is set, then we didn't underflow, so \ jump to RE3 to auto-recentre and return the result LDX #1 \ We have an underflow, so set X to the minimum possible \ value, 1 .RE3 BPL RE2+2 \ If X has bit 7 clear (i.e. the result < 128), then \ jump to RE2+2 above to return the result as is, \ because the result is on the left side of the centre \ point of 128, so we don't need to auto-centre \ Jumps to RE3+2 end up here \ If we get here, then we need to apply auto-recentre, \ if it is configured LDA DJD \ If keyboard auto-recentre is disabled, then BNE RE2+2 \ jump to RE2+2 to restore A and return LDX #128 \ If keyboard auto-recentre is enabled, set X to 128 BMI RE2+2 \ (the middle of our range) and jump to RE2+2 to \ restore A and return
Name: PDESC [View individually] Type: Subroutine [Compare versions] Category: Text Summary: Print the system's extended description or a mission 1 directive Deep dive: Extended system descriptions Extended text tokens
This prints a specific system's extended description. This is called the "pink volcanoes string" in a comment in the original source, and the "goat soup" recipe by Ian Bell on his website (where he also refers to the species string as the "pink felines" string). For some special systems, when you are docked at them, the procedurally generated extended description is overridden and a text token from the RUTOK table is shown instead. If mission 1 is in progress, then a number of systems along the route of that mission's story will show custom mission-related directives in place of that system's normal "goat soup" phrase. Arguments: ZZ The system number (0-255)
.PDESC LDA QQ8 \ If either byte in QQ18(1 0) is non-zero, meaning that ORA QQ8+1 \ the distance from the current system to the selected BNE PD1 \ is non-zero, jump to PD1 to show the standard "goat \ soup" description \ If we get here, then the current system is the same as \ the selected system and we are docked, so now to check \ whether there is a special override token for this \ system LDY #NRU% \ Set Y as a loop counter as we work our way through the \ system numbers in RUPLA, starting at NRU% (which is \ the number of entries in RUPLA, 26) and working our \ way down to 1 .PDL1 LDA RUPLA-1,Y \ Fetch the Y-th byte from RUPLA-1 into A (we use \ RUPLA-1 because Y is looping from 26 to 1 CMP ZZ \ If A doesn't match the system whose description we BNE PD2 \ are printing (in ZZ), junp to PD2 to keep looping \ through the system numbers in RUPLA \ If we get here we have found a match for this system \ number in RUPLA LDA RUGAL-1,Y \ Fetch the Y-th byte from RUGAL-1 into A AND #%01111111 \ Extract bits 0-6 of A CMP GCNT \ If the result does not equal the current galaxy BNE PD2 \ number, jump to PD2 to keep looping through the system \ numbers in RUPLA LDA RUGAL-1,Y \ Fetch the Y-th byte from RUGAL-1 into A, once again BMI PD3 \ If bit 7 is set, jump to PD3 to print the extended \ token in A from the second table in RUTOK LDA TP \ Fetch bit 0 of TP into the C flag, and skip to PD1 if LSR A \ it is clear (i.e. if mission 1 is not in progress) to BCC PD1 \ print the "goat soup" extended description \ If we get here then mission 1 is in progress, so we \ print out the corresponding token from RUTOK JSR MT14 \ Call MT14 to switch to justified text LDA #1 \ Set A = 1 so that extended token 1 (an empty string) \ gets printed below instead of token 176, followed by \ the Y-th token in RUTOK EQUB &2C \ Skip the next instruction by turning it into \ &2C &A9 &B0, or BIT &B0A9, which does nothing apart \ from affect the flags .PD3 LDA #176 \ Print extended token 176 ("{lower case}{justify} JSR DETOK2 \ {single cap}") TYA \ Print the extended token in Y from the second table JSR DETOK3 \ in RUTOK LDA #177 \ Set A = 177 so when we jump to PD4 in the next \ instruction, we print token 177 (".{cr}{left align}") BNE PD4 \ Jump to PD4 to print the extended token in A and \ return from the subroutine using a tail call .PD2 DEY \ Decrement the byte counter in Y BNE PDL1 \ Loop back to check the next byte in RUPLA until we \ either find a match for the system in ZZ, or we fall \ through into the "goat soup" extended description \ routine .PD1 \ We now print the "goat soup" extended description LDX #3 \ We now want to seed the random number generator with \ the s1 and s2 16-bit seeds from the current system, so \ we get the same extended description for each system \ every time we call PDESC, so set a counter in X for \ copying 4 bytes { .PDL1 \ This label is a duplicate of the label above (which is \ why we need to surround it with braces, as BeebAsm \ doesn't allow us to redefine labels, unlike BBC BASIC) LDA QQ15+2,X \ Copy QQ15+2 to QQ15+5 (s1 and s2) to RAND to RAND+3 STA RAND,X DEX \ Decrement the loop counter BPL PDL1 \ Loop back to PDL1 until we have copied all LDA #5 \ Set A = 5, so we print extended token 5 in the next \ instruction ("{lower case}{justify}{single cap}[86-90] \ IS [140-144].{cr}{left align}" } .PD4 JMP DETOK \ Print the extended token given in A, and return from \ the subroutine using a tail call
Name: BRIEF2 [View individually] Type: Subroutine Category: Missions Summary: Start mission 2
.BRIEF2 LDA TP \ Set bit 2 of TP to indicate mission 2 is in progress ORA #%00000100 \ but plans have not yet been picked up STA TP LDA #11 \ Set A = 11 so the call to BRP prints extended token 11 \ (the initial contact at the start of mission 2, asking \ us to head for Ceerdi for a mission briefing) \ Fall through into BRP to print the extended token in A \ and show the Status Mode screen
Name: BRP [View individually] Type: Subroutine [Compare versions] Category: Missions Summary: Print an extended token and show the Status Mode screen
.BRP JSR DETOK \ Print the extended token in A JMP BAY \ Jump to BAY to go to the docking bay (i.e. show the \ Status Mode screen) and return from the subroutine \ using a tail call
Name: BRIEF3 [View individually] Type: Subroutine Category: Missions Summary: Receive the briefing and plans for mission 2
.BRIEF3 LDA TP \ Set bits 1 and 3 of TP to indicate that mission 1 is AND #%11110000 \ complete, and mission 2 is in progress and the plans ORA #%00001010 \ have been picked up STA TP LDA #222 \ Set A = 222 so the call to BRP prints extended token \ 222 (the briefing for mission 2 where we pick up the \ plans we need to take to Birera) BNE BRP \ Jump to BRP to print the extended token in A and show \ the Status Mode screen), returning from the subroutine \ using a tail call (this BNE is effectively a JMP as A \ is never zero)
Name: DEBRIEF2 [View individually] Type: Subroutine Category: Missions Summary: Finish mission 2
.DEBRIEF2 LDA TP \ Set bit 2 of TP to indicate mission 2 is complete (so ORA #%00000100 \ both bits 2 and 3 are now set) STA TP LDA #2 \ Set ENGY to 2 so our energy banks recharge at twice STA ENGY \ the speed, as our mission reward is a special navy \ energy unit INC TALLY+1 \ Award 256 kill points for completing the mission LDA #223 \ Set A = 223 so the call to BRP prints extended token \ 223 (the thank you message at the end of mission 2) BNE BRP \ Jump to BRP to print the extended token in A and show \ the Status Mode screen), returning from the subroutine \ using a tail call (this BNE is effectively a JMP as A \ is never zero)
Name: DEBRIEF [View individually] Type: Subroutine [Compare versions] Category: Missions Summary: Finish mission 1
Other entry points: BRPS Print the extended token in A, show the Status Mode screen and return from the subroutine
.DEBRIEF LSR TP \ Clear bit 0 of TP to indicate that mission 1 is no ASL TP \ longer in progress, as we have completed it INC TALLY+1 \ Award 256 kill points for completing the mission LDX #LO(50000) \ Increase our cash reserves by the generous mission LDY #HI(50000) \ reward of 5,000 CR JSR MCASH LDA #15 \ Set A = 15 so the call to BRP prints extended token 15 \ (the thank you message at the end of mission 1) .BRPS BNE BRP \ Jump to BRP to print the extended token in A and show \ the Status Mode screen, returning from the subroutine \ using a tail call (this BNE is effectively a JMP as A \ is never zero)
Name: BRIEF [View individually] Type: Subroutine [Compare versions] Category: Missions Summary: Start mission 1 and show the mission briefing
This routine does the following: * Clear the screen * Display "INCOMING MESSAGE" in the middle of the screen * Wait for 2 seconds * Clear the screen * Show the Constrictor rolling and pitching in the middle of the screen * Do this for 64 loop iterations * Move the ship away from us and up until it's near the top of the screen * Show the mission 1 briefing in extended token 10 The mission briefing ends with a "{display ship, wait for key press}" token, which calls the PAUSE routine. This continues to display the rotating ship, waiting until a key is pressed, and then removes the ship from the screen.
.BRIEF LSR TP \ Set bit 0 of TP to indicate that mission 1 is now in SEC \ progress ROL TP JSR BRIS \ Call BRIS to clear the screen, display "INCOMING \ MESSAGE" and wait for 2 seconds JSR ZINF \ Call ZINF to reset the INWK ship workspace LDA #CON \ Set the ship type in TYPE to the Constrictor STA TYPE JSR NWSHP \ Add a new Constrictor to the local bubble (in this \ case, the briefing screen) LDA #1 \ Move the text cursor to column 1 STA XC STA INWK+7 \ Set z_hi = 1, the distance at which we show the \ rotating ship JSR TT66 \ Clear the top part of the screen, draw a white border, \ and set the current view type in QQ11 to 1 LDA #64 \ Set the main loop counter to 64, so the ship rotates STA MCNT \ for 64 iterations through MVEIT .BRL1 LDX #%01111111 \ Set the ship's roll counter to a positive roll that STX INWK+29 \ doesn't dampen STX INWK+30 \ Set the ship's pitch counter to a positive pitch that \ doesn't dampen JSR LL9 \ Draw the ship on screen JSR MVEIT \ Call MVEIT to rotate the ship in space DEC MCNT \ Decrease the counter in MCNT BNE BRL1 \ Loop back to keep moving the ship until we have done \ all 64 iterations .BRL2 LSR INWK \ Halve x_lo so the Constrictor moves towards the centre INC INWK+6 \ Increment z_lo so the Constrictor moves away from us BEQ BR2 \ If z_lo = 0 (i.e. it just went past 255), jump to BR2 \ to show the briefing INC INWK+6 \ Increment z_lo so the Constrictor moves a bit further \ away from us BEQ BR2 \ If z_lo = 0 (i.e. it just went past 255), jump out of \ the loop to BR2 to stop moving the ship up the screen \ and show the briefing LDX INWK+3 \ Set X = y_lo + 1 INX CPX #112 \ If X < 112 then skip the next instruction BCC P%+4 LDX #112 \ X is bigger than 112, so set X = 112 so that X has a \ maximum value of 112 STX INWK+3 \ Set y_lo = X \ = y_lo + 1 \ \ so the ship moves up the screen (as space coordinates \ have the y-axis going up) JSR LL9 \ Draw the ship on screen JSR MVEIT \ Call MVEIT to move and rotate the ship in space JMP BRL2 \ Loop back to keep moving the ship up the screen and \ away from us .BR2 INC INWK+7 \ Increment z_hi, to keep the ship at the same distance \ as we just incremented z_lo past 255 LDA #10 \ Set A = 10 so the call to BRP prints extended token 10 \ (the briefing for mission 1 where we find out all \ about the stolen Constrictor) BNE BRPS \ Jump to BRP via BRPS to print the extended token in A \ and show the Status Mode screen), returning from the \ subroutine using a tail call (this BNE is effectively \ a JMP as A is never zero)
Name: BRIS [View individually] Type: Subroutine Category: Missions Summary: Clear the screen, display "INCOMING MESSAGE" and wait for 2 seconds
.BRIS LDA #216 \ Print extended token 216 ("{clear screen}{tab 6}{move JSR DETOK \ to row 10, white, lower case}{white}{all caps}INCOMING \ MESSAGE" LDY #100 \ Delay for 100 vertical syncs (100/50 = 2 seconds) and JMP DELAY \ return from the subroutine using a tail call
Name: PAUSE [View individually] Type: Subroutine Category: Keyboard Summary: Display a rotating ship, waiting until a key is pressed, then remove the ship from the screen
.PAUSE JSR PAS1 \ Call PAS1 to display the rotating ship at space \ coordinates (0, 112, 256) and scan the keyboard, \ returning the internal key number in X (or 0 for no \ key press) BNE PAUSE \ If a key was already being held down when we entered \ this routine, keep looping back up to PAUSE, until \ the key is released .PAL1 JSR PAS1 \ Call PAS1 to display the rotating ship at space \ coordinates (0, 112, 256) and scan the keyboard, \ returning the internal key number in X (or 0 for no \ key press) BEQ PAL1 \ Keep looping up to PAL1 until a key is pressed LDA #0 \ Set the ship's AI flag to 0 (no AI) so it doesn't get STA INWK+31 \ any ideas of its pwn LDA #1 \ Clear the top part of the screen, draw a white border, JSR TT66 \ and set the current view type in QQ11 to 1 JSR LL9 \ Draw the ship on screen to remove it \ Fall through into MT23 to move to row 10, switch to \ white text, and switch to lower case when printing \ extended tokens
Name: MT23 [View individually] Type: Subroutine Category: Text Summary: Move to row 10, switch to white text, and switch to lower case when printing extended tokens Deep dive: Extended text tokens
.MT23 LDA #10 \ Set A = 10, so when we fall through into MT29, the \ text cursor gets moved to row 10 EQUB &2C \ Skip the next instruction by turning it into \ &2C &A9 &06, or BIT &06A9, which does nothing apart \ from affect the flags \ Fall through into MT29 to move to the row in A, switch \ to white text, and switch to lower case
Name: MT29 [View individually] Type: Subroutine [Compare versions] Category: Text Summary: Move to row 6, switch to white text, and switch to lower case when printing extended tokens Deep dive: Extended text tokens
This routine sets the following: * YC = 6 (move to row 6) Then it calls WHITETEXT to switch to white text, before jumping to MT13 to switch to lower case when printing extended tokens.
.MT29 LDA #6 \ Move the text cursor to row 6 STA YC JMP MT13 \ Jump to MT13 to set bit 7 of DTW6 and bit 5 of DTW1, \ returning from the subroutine using a tail call
Name: PAS1 [View individually] Type: Subroutine [Compare versions] Category: Keyboard Summary: Display a rotating ship at space coordinates (0, 112, 256) and scan the keyboard
Returns: X If a key is being pressed, X contains the internal key number, otherwise it contains 0 A Contains the same as X
.PAS1 LDA #112 \ Set y_lo = 112 STA INWK+3 LDA #0 \ Set x_lo = 0 STA INWK STA INWK+6 \ Set z_lo = 0 LDA #2 \ Set z_hi = 1, so (z_hi z_lo) = 256 STA INWK+7 JSR LL9 \ Draw the ship on screen JSR MVEIT \ Call MVEIT to move and rotate the ship in space JMP RDKEY \ Scan the keyboard for a key press and return the \ internal key number in X (or 0 for no key press), \ returning from the subroutine using a tail call
Name: PAUSE2 [View individually] Type: Subroutine [Compare versions] Category: Keyboard Summary: Wait until a key is pressed, ignoring any existing key press
Returns: X The internal key number of the key that was pressed
.PAUSE2 JSR RDKEY \ Scan the keyboard for a key press and return the \ internal key number in X (or 0 for no key press) BNE PAUSE2 \ If a key was already being held down when we entered \ this routine, keep looping back up to PAUSE2, until \ the key is released JSR RDKEY \ Any pre-existing key press is now gone, so we can \ start scanning the keyboard again, returning the \ internal key number in X (or 0 for no key press) BEQ PAUSE2 \ Keep looping up to PAUSE2 until a key is pressed RTS \ Return from the subroutine
Name: SIGHT [View individually] Type: Subroutine [Compare versions] Category: Flight Summary: Draw the laser crosshairs
.SIGHT LDY VIEW \ Fetch the laser power for our new view LDA LASER,Y BEQ BOL1-1 \ If it is zero (i.e. there is no laser fitted to this \ view), jump to BOL1-1 to return from the subroutine \ (as BOL1-1 contains &60, which is the opcode for an \ RTS) LDA #128 \ Set QQ19 to the x-coordinate of the centre of the STA QQ19 \ screen LDA #Y-24 \ Set QQ19+1 to the y-coordinate of the centre of the STA QQ19+1 \ screen, minus 24 (because TT15 will add 24 to the \ coordinate when it draws the crosshairs) LDA #20 \ Set QQ19+2 to size 20 for the crosshairs size STA QQ19+2 JSR TT15 \ Call TT15 to draw crosshairs of size 20 just to the \ left of the middle of the screen LDA #10 \ Set QQ19+2 to size 10 for the crosshairs size STA QQ19+2 JMP TT15 \ Call TT15 to draw crosshairs of size 10 at the same \ location, which will remove the centre part from the \ laser crosshairs, leaving a gap in the middle, and \ return from the subroutine using a tail call
Name: TT66 [View individually] Type: Subroutine [Compare versions] Category: Utility routines Summary: Clear the screen and set the current view type
Clear the top part of the screen, draw a white border, and set the current view type in QQ11 to A. Arguments: A The type of the new current view (see QQ11 for a list of view types)
.TT66 STA QQ11 \ Set the current view type in QQ11 to A \ Fall through into TTX66 to clear the screen and draw a \ white border
Name: TTX66 [View individually] Type: Subroutine [Compare versions] Category: Utility routines Summary: Clear the top part of the screen and draw a white border
Clear the top part of the screen (the space view) and draw a white border along the top and sides. Other entry points: BOL1-1 Contains an RTS
.TTX66 JSR MT2 \ Switch to Sentence Case when printing extended tokens LDA #%10000000 \ Set bit 7 of QQ17 to switch to Sentence Case STA QQ17 STA DTW2 \ Set bit 7 of DTW2 to indicate we are not currently \ printing a word ASL A \ Set LASCT to 0, as 128 << 1 = %10000000 << 1 = 0. This STA LASCT \ stops any laser pulsing STA DLY \ Set the delay in DLY to 0, to indicate that we are \ no longer showing an in-flight message, so any new \ in-flight messages will be shown instantly STA de \ Clear de, the flag that appends " DESTROYED" to the \ end of the next text token, so that it doesn't LDX #&60 \ Set X to the screen memory page for the top row of the \ screen (as screen memory starts at &6000) .BOL1 JSR ZES1 \ Call ZES1 to zero-fill the page in X, which clears \ that character row on the screen INX \ Increment X to point to the next page, i.e. the next \ character row CPX #&78 \ Loop back to BOL1 until we have cleared page &7700, BNE BOL1 \ the last character row in the space view part of the \ screen (the space view) LDY #1 \ Move the text cursor to row 1 STY YC LDA QQ11 \ If this is not a space view, jump to tt66 to skip BNE tt66 \ displaying the view name LDY #11 \ Move the text cursor to row 11 STY XC LDA VIEW \ Load the current view into A: \ \ 0 = front \ 1 = rear \ 2 = left \ 3 = right ORA #&60 \ OR with &60 so we get a value of &60 to &63 (96 to 99) JSR TT27 \ Print recursive token 96 to 99, which will be in the \ range "FRONT" to "RIGHT" JSR TT162 \ Print a space LDA #175 \ Print recursive token 15 ("VIEW ") JSR TT27 .tt66 LDX #0 \ Set (X1, Y1) to (0, 0) STX X1 STX Y1 STX QQ17 \ Set QQ17 = 0 to switch to ALL CAPS DEX \ Set X2 = 255 STX X2 JSR HLOIN \ Draw a horizontal line from (X1, Y1) to (X2, Y1), so \ that's (0, 0) to (255, 0), along the very top of the \ screen LDA #2 \ Set X1 = X2 = 2 STA X1 STA X2 JSR BOS2 \ Call BOS2 below, which will call BOS1 twice, and then \ fall through into BOS2 again, so we effectively do \ BOS1 four times, decrementing X1 and X2 each time \ before calling LOIN, so this whole loop-within-a-loop \ mind-bender ends up drawing these four lines: \ \ (1, 0) to (1, 191) \ (0, 0) to (0, 191) \ (255, 0) to (255, 191) \ (254, 0) to (254, 191) \ \ So that's a 2-pixel wide vertical border along the \ left edge of the upper part of the screen, and a \ 2-pixel wide vertical border along the right edge .BOS2 JSR BOS1 \ Call BOS1 below and then fall through into it, which \ ends up running BOS1 twice. This is all part of the \ loop-the-loop border-drawing mind-bender explained \ above .BOS1 LDA #0 \ Set Y1 = 0 STA Y1 LDA #2*Y-1 \ Set Y2 = 2 * #Y - 1. The constant #Y is 96, the STA Y2 \ y-coordinate of the mid-point of the space view, so \ this sets Y2 to 191, the y-coordinate of the bottom \ pixel row of the space view DEC X1 \ Decrement X1 and X2 DEC X2 JMP LOIN \ Draw a line from (X1, Y1) to (X2, Y2), and return from \ the subroutine using a tail call
Name: DELAY [View individually] Type: Subroutine [Compare versions] Category: Utility routines Summary: Wait for a specified time, in 1/50s of a second
Wait for the number of vertical syncs given in Y, so this effectively waits for Y/50 of a second (as the vertical sync occurs 50 times a second). Arguments: Y The number of vertical sync events to wait for
.DELAY JSR WSCAN \ Call WSCAN to wait for the vertical sync, so the whole \ screen gets drawn DEY \ Decrement the counter in Y BNE DELAY \ If Y isn't yet at zero, jump back to DELAY to wait \ for another vertical sync RTS \ Return from the subroutine
Name: CLYNS [View individually] Type: Subroutine [Compare versions] Category: Utility routines Summary: Clear the bottom three text rows of the mode 4 screen
Clear some space at the bottom of the screen and move the text cursor to column 1, row 21. Specifically, this zeroes the following screen locations: &7507 to &75F0 &7607 to &76F0 &7707 to &77F0 which clears the three bottom text rows of the mode 4 screen (rows 21 to 23), clearing each row from text column 1 to 30 (so it doesn't overwrite the box border in columns 0 and 32, or the last usable column in column 31). Returns: A A is set to 0 Y Y is set to 0
.CLYNS LDA #%11111111 \ Set DTW2 = %11111111 to denote that we are not STA DTW2 \ currently printing a word LDA #20 \ Move the text cursor to row 20, near the bottom of STA YC \ the screen JSR TT67 \ Print a newline, which will move the text cursor down \ a line (to row 21) and back to column 1 LDA #&75 \ Set the two-byte value in SC to &7507 STA SC+1 LDA #7 STA SC LDA #0 \ Call LYN to clear the pixels from &7507 to &75F0 JSR LYN INC SC+1 \ Increment SC+1 so SC points to &7607 JSR LYN \ Call LYN to clear the pixels from &7607 to &76F0 INC SC+1 \ Increment SC+1 so SC points to &7707 INY \ Move the text cursor to column 1 (as LYN sets Y to 0) STY XC \ Fall through into LYN to clear the pixels from &7707 \ to &77F0
Name: LYN [View individually] Type: Subroutine [Compare versions] Category: Utility routines Summary: Clear most of a row of pixels
Set pixels 0-233 to the value in A, starting at the pixel pointed to by SC. Arguments: A The value to store in pixels 1-233 (the only value that is actually used is A = 0, which clears those pixels) Returns: Y Y is set to 0 Other entry points: SC5 Contains an RTS
.LYN LDY #233 \ Set up a counter in Y to count down from pixel 233 .EE2 STA (SC),Y \ Store A in the Y-th byte after the address pointed to \ by SC DEY \ Decrement Y BNE EE2 \ Loop back until Y is zero .SC5 RTS \ Return from the subroutine
Name: CPIX4 [View individually] Type: Subroutine [Compare versions] Category: Drawing pixels Summary: Draw a double-height dot on the dashboard
Draw a double-height mode 5 dot (2 pixels high, 2 pixels wide). Arguments: X1 The screen pixel x-coordinate of the bottom-left corner of the dot Y1 The screen pixel y-coordinate of the bottom-left corner of the dot COL The colour of the dot as a mode 5 character row byte
.CPIX4 JSR CPIX2 \ Call CPIX2 to draw a single-height dash at (X1, Y1) DEC Y1 \ Decrement Y1 \ Fall through into CPIX2 to draw a second single-height \ dash on the pixel row above the first one, to create a \ double-height dot
Name: CPIX2 [View individually] Type: Subroutine [Compare versions] Category: Drawing pixels Summary: Draw a single-height dot on the dashboard Deep dive: Drawing colour pixels in mode 5
Draw a single-height mode 5 dash (1 pixel high, 2 pixels wide). Arguments: X1 The screen pixel x-coordinate of the dash Y1 The screen pixel y-coordinate of the dash COL The colour of the dash as a mode 5 character row byte
.CPIX2 LDA Y1 \ Fetch the y-coordinate into A TAY \ Store the y-coordinate in Y LSR A \ Set A = A / 8, so A now contains the character row we LSR A \ need to draw in (as each character row contains 8 LSR A \ pixel rows) ORA #&60 \ Each character row in Elite's screen mode takes up one \ page in memory (256 bytes), so we now OR with &60 to \ get the page containing the dash (see the comments in \ routine TT26 for more discussion about calculating \ screen memory addresses) STA SCH \ Store the screen page in the high byte of SC(1 0) LDA X1 \ Each character block contains 8 pixel rows, so to get AND #%11111000 \ the address of the first byte in the character block \ that we need to draw into, as an offset from the start \ of the row, we clear bits 0-2 STA SC \ Store the address of the character block in the low \ byte of SC(1 0), so now SC(1 0) points to the \ character block we need to draw into TYA \ Set Y to just bits 0-2 of the y-coordinate, which will AND #%00000111 \ be the number of the pixel row we need to draw into TAY \ within the character block LDA X1 \ Copy bits 0-1 of X1 to bits 1-2 of X, and clear the C AND #%00000110 \ flag in the process (using the LSR). X will now be LSR A \ a value between 0 and 3, and will be the pixel number TAX \ in the character row for the left pixel in the dash. \ This is because each character row is one byte that \ contains 4 pixels, but covers 8 screen coordinates, so \ this effectively does the division by 2 that we need LDA CTWOS,X \ Fetch a mode 5 1-pixel byte with the pixel position AND COL \ at X, and AND with the colour byte so that pixel takes \ on the colour we want to draw (i.e. A is acting as a \ mask on the colour byte) EOR (SC),Y \ Draw the pixel on-screen using EOR logic, so we can STA (SC),Y \ remove it later without ruining the background that's \ already on-screen LDA CTWOS+1,X \ Fetch a mode 5 1-pixel byte with the pixel position \ at X+1, so we can draw the right pixel of the dash BPL CP1 \ The CTWOS table has an extra row at the end of it that \ repeats the first value, %10001000, so if we have not \ fetched that value, then the right pixel of the dash \ is in the same character block as the left pixel, so \ jump to CP1 to draw it LDA SC \ Otherwise the left pixel we drew was at the last ADC #8 \ position of four in this character block, so we add STA SC \ 8 to the screen address to move onto the next block \ along (as there are 8 bytes in a character block). \ The C flag was cleared above, so this ADC is correct LDA CTWOS+1,X \ Refetch the mode 5 1-pixel byte, as we just overwrote \ A (the byte will still be the fifth byte from the \ table, which is correct as we want to draw the \ leftmost pixel in the next character along as the \ dash's right pixel) .CP1 AND COL \ Apply the colour mask to the pixel byte, as above EOR (SC),Y \ Draw the dash's right pixel according to the mask in STA (SC),Y \ A, with the colour in COL, using EOR logic, just as \ above RTS \ Return from the subroutine
Name: WSCAN [View individually] Type: Subroutine [Compare versions] Category: Screen mode Summary: Wait for the vertical sync
Wait for vertical sync to occur on the video system - in other words, wait for the screen to start its refresh cycle, which it does 50 times a second (50Hz).
.WSCAN LDA #0 \ Set DL to 0 STA DL LDA DL \ Loop round these two instructions until DL is no BEQ P%-2 \ longer 0 (DL gets set to 30 in the LINSCN routine, \ which is run when vertical sync has occurred on the \ video system, so DL will change to a non-zero value \ at the start of each screen refresh) RTS \ Return from the subroutine
Save output/ELTC.bin
PRINT "ELITE C" PRINT "Assembled at ", ~CODE_C% PRINT "Ends at ", ~P% PRINT "Code size is ", ~(P% - CODE_C%) PRINT "Execute at ", ~LOAD% PRINT "Reload at ", ~LOAD_C% PRINT "S.ELTC ", ~CODE_C%, " ", ~P%, " ", ~LOAD%, " ", ~LOAD_C% SAVE "output/T.ELTC.bin", CODE_C%, P%, LOAD%