Skip to navigation

BBC Micro Elite

Elite B source

ELITE B FILE Produces the binary file ELTB.bin that gets loaded by elite-bcfs.asm.
CODE_B% = P% LOAD_B% = LOAD% + P% - CODE% Q% = _ENABLE_MAX_COMMANDER
Name: NA% [View individually] Type: Variable Category: Save and load Summary: The data block for the last saved commander
Contains the last saved commander data, with the name at NA% and the data at NA%+8 onwards. The size of the data block is given in NT% (which also includes the two checksum bytes that follow this block. This block is initially set up with the default commander, which can be maxed out for testing purposes by setting Q% to TRUE. The commander's name is stored at NA%, and can be up to 7 characters long (the DFS filename limit). It is terminated with a carriage return character, ASCII 13.
.NA% { EQUS "JAMESON" \ Default commander name EQUB 13 \ Terminated by a carriage return; commander name can \ be up to 7 characters (the DFS limit for file names) \ NA%+8 - the start of the commander data block \ \ This block contains the last saved commander data \ block. As the game is played it uses an identical \ block at location TP to store the current commander \ state, and that block is copied here when the game is \ saved. Conversely, when the game starts up, the block \ here is copied to TP, which restores the last saved \ commander when we die \ \ The initial state of this block defines the default \ commander. Q% can be set to TRUE to give the default \ commander lots of credits and equipment EQUB 0 \ Mission status. The disc version of the game has two \ missions, and this byte contains the status of those \ missions (the possible values are 0, 1, 2, &A, &E). As \ the cassette version doesn't have missions, this byte \ will always be zero, which means no missions have been \ started \ \ Note that this byte must not have bit 7 set, or \ loading this commander will cause the game to restart EQUB 20 \ QQ0 = current system X-coordinate (Lave) EQUB 173 \ QQ1 = current system Y-coordinate (Lave) EQUW &5A4A \ QQ21 = Seed w0 for system 0 in galaxy 0 (Tibedied) EQUW &0248 \ QQ21 = Seed w1 for system 0 in galaxy 0 (Tibedied) EQUW &B753 \ QQ21 = Seed w2 for system 0 in galaxy 0 (Tibedied) IF Q% EQUD &00CA9A3B \ CASH = Amount of cash (100,000,000 Cr) ELSE EQUD &E8030000 \ CASH = Amount of cash (100 Cr) ENDIF EQUB 70 \ QQ14 = Fuel level EQUB 0 \ COK = Competition code EQUB 0 \ GCNT = Galaxy number, 0-7 EQUB POW+(128 AND Q%) \ LASER = Front laser IF Q% OR _FIX_REAR_LASER EQUB (POW+128) AND Q% \ LASER+1 = Rear laser, as in ELITEB source ELSE EQUB POW \ LASER+1 = Rear laser, as in extracted ELTB binary ENDIF EQUB 0 \ LASER+2 = Left laser EQUB 0 \ LASER+3 = Right laser EQUW 0 \ These bytes are unused (they were originally used for \ up/down lasers, but they were dropped) EQUB 22+(15 AND Q%) \ CRGO = Cargo capacity EQUD 0 \ QQ20 = Contents of cargo hold (17 bytes) EQUD 0 EQUD 0 EQUD 0 EQUB 0 EQUB Q% \ ECM = E.C.M. EQUB Q% \ BST = Fuel scoops ("barrel status") EQUB Q% AND 127 \ BOMB = Energy bomb EQUB Q% AND 1 \ ENGY = Energy/shield level EQUB Q% \ DKCMP = Docking computer EQUB Q% \ GHYP = Galactic hyperdrive EQUB Q% \ ESCP = Escape pod EQUD FALSE \ These four bytes are unused EQUB 3+(Q% AND 1) \ NOMSL = Number of missiles EQUB FALSE \ FIST = Legal status ("fugitive/innocent status") EQUB 16 \ AVL = Market availability (17 bytes) EQUB 15 EQUB 17 EQUB 0 EQUB 3 EQUB 28 EQUB 14 EQUB 0 EQUB 0 EQUB 10 EQUB 0 EQUB 17 EQUB 58 EQUB 7 EQUB 9 EQUB 8 EQUB 0 EQUB 0 \ QQ26 = Random byte that changes for each visit to a \ system, for randomising market prices EQUW 0 \ TALLY = Number of kills EQUB 128 \ SVC = Save count }
Name: CHK2 [View individually] Type: Variable Category: Save and load Summary: Second checksum byte for the saved commander data file
Second checksum byte, see elite-checksum.py for more details.
IF NOT(_FIX_REAR_LASER) CH% = &92 \ The correct value for the released game ELSE CH% = &3 \ The figure in the ELTB binary on the source disc ENDIF .CHK2 { EQUB CH% EOR &A9 \ Commander checksum byte, EOR'd with &A9 to make it \ harder to tamper with the checksum byte }
Name: CHK [View individually] Type: Variable Category: Save and load Summary: First checksum byte for the saved commander data file
Commander checksum byte, see elite-checksum.py for more details.
.CHK { EQUB CH% } PRINT "CH% = ", ~CH%
Name: UNIV [View individually] Type: Variable Category: Universe Summary: Table of pointers to the local universe's ship data blocks
See the deep dive on "Ship data blocks" for details on ship data blocks, and the deep dive on "The local bubble of universe" for details of how Elite stores the local universe in K%, FRIN and UNIV.
.UNIV { FOR I%, 0, NOSH EQUW K% + I% * NI% \ Address of block no. I%, of size NI%, in workspace K% NEXT }
Name: TWOS [View individually] Type: Variable Category: Drawing pixels Summary: Ready-made single-pixel character row bytes for mode 4
Ready-made bytes for plotting one-pixel points in mode 4 (the top part of the split screen). See the PIXEL routine for details.
.TWOS { EQUB %10000000 EQUB %01000000 EQUB %00100000 EQUB %00010000 EQUB %00001000 EQUB %00000100 EQUB %00000010 EQUB %00000001 }
Name: TWOS2 [View individually] Type: Variable Category: Drawing pixels Summary: Ready-made double-pixel character row bytes for mode 4
Ready-made bytes for plotting two-pixel dashes in mode 4 (the top part of the split screen). See the PIXEL routine for details.
.TWOS2 { EQUB %11000000 EQUB %01100000 EQUB %00110000 EQUB %00011000 EQUB %00001100 EQUB %00000110 EQUB %00000011 EQUB %00000011 }
Name: CTWOS [View individually] Type: Variable Category: Drawing pixels Summary: Ready-made single-pixel character row bytes for mode 5
Ready-made bytes for plotting one-pixel points in mode 5 (the bottom part of the split screen). See the dashboard routines SCAN, DIL2 and CPIX2 for details. There is one extra row to support the use of CTWOS+1,X indexing in the CPIX2 routine. The extra row is a repeat of the first row, and saves us from having to work out whether CTWOS+1+X needs to be wrapped around when drawing a two-pixel dash that crosses from one character block into another. See CPIX2 for more details.
.CTWOS { EQUB %10001000 EQUB %01000100 EQUB %00100010 EQUB %00010001 EQUB %10001000 }
Name: LOIN (Part 1 of 7) [View individually] Type: Subroutine Category: Drawing lines Summary: Draw a line: Calculate the line gradient in the form of deltas
This routine has multiple stages. This stage calculates the line deltas. Returns: Y Y is preserved Other entry points: LL30 LL30 is a synonym for LOIN and draws a line from (X1, Y1) to (X2, Y2) HL6 Contains an RTS
.LL30 .LOIN { STY YSAV \ Store Y into YSAV, so we can preserve it across the \ call to this subroutine LDA #128 \ Set S = 128, which is the starting point for the STA S \ slope error (representing half a pixel) ASL A \ Set SWAP = 0, as %10000000 << 1 = 0 STA SWAP LDA X2 \ Set A = X2 - X1 SBC X1 \ = delta_x \ \ This subtraction works as the ASL A above sets the C \ flag BCS LI1 \ If X2 > X1 then A is already positive and we can skip \ the next three instructions EOR #%11111111 \ Negate the result in A by flipping all the bits and ADC #1 \ adding 1, i.e. using two's complement to make it \ positive SEC \ Set the C flag, ready for the subtraction below .LI1 STA P \ Store A in P, so P = |X2 - X1|, or |delta_x| LDA Y2 \ Set A = Y2 - Y1 SBC Y1 \ = delta_y \ \ This subtraction works as we either set the C flag \ above, or we skipped that SEC instruction with a BCS BCS LI2 \ If Y2 > Y1 then A is already positive and we can skip \ the next two instructions EOR #%11111111 \ Negate the result in A by flipping all the bits and ADC #1 \ adding 1, i.e. using two's complement to make it \ positive .LI2 STA Q \ Store A in Q, so Q = |Y2 - Y1|, or |delta_y| CMP P \ If Q < P, jump to STPX to step along the x-axis, as BCC STPX \ the line is closer to being horizontal than vertical JMP STPY \ Otherwise Q >= P so jump to STPY to step along the \ y-axis, as the line is closer to being vertical than \ horizontal
Name: LOIN (Part 2 of 7) [View individually] Type: Subroutine Category: Drawing lines Summary: Draw a line: Line has a shallow gradient, step right along x-axis
If we get here, then: * |delta_y| < |delta_x| * The line is closer to being horizontal than vertical * We are going to step right along the x-axis * We potentially swap coordinates to make sure X1 < X2
.STPX LDX X1 \ Set X = X1 CPX X2 \ If X1 < X2, jump down to LI3, as the coordinates are BCC LI3 \ already in the order that we want DEC SWAP \ Otherwise decrement SWAP from 0 to &FF, to denote that \ we are swapping the coordinates around LDA X2 \ Swap the values of X1 and X2 STA X1 STX X2 TAX \ Set X = X1 LDA Y2 \ Swap the values of Y1 and Y2 LDY Y1 STA Y1 STY Y2 .LI3 \ By this point we know the line is horizontal-ish and \ X1 < X2, so we're going from left to right as we go \ from X1 to X2 LDA Y1 \ Set A = Y1 / 8, so A now contains the character row LSR A \ that will contain our horizontal line LSR A LSR A ORA #&60 \ As A < 32, this effectively adds &60 to A, which gives \ us the screen address of the character row (as each \ character row takes up 256 bytes, and the first \ character row is at screen address &6000, or page &60) STA SCH \ Store the page number of the character row in SCH, so \ the high byte of SC is set correctly for drawing the \ start of our line LDA Y1 \ Set Y = Y1 mod 8, which is the pixel row within the AND #7 \ character block at which we want to draw the start of TAY \ our line (as each character block has 8 rows) TXA \ Set A = bits 3-7 of X1 AND #%11111000 STA SC \ Store this value in SC, so SC(1 0) now contains the \ screen address of the far left end (x-coordinate = 0) \ of the horizontal pixel row that we want to draw the \ start of our line on TXA \ Set X = X1 mod 8, which is the horizontal pixel number AND #7 \ within the character block where the line starts (as TAX \ each pixel line in the character block is 8 pixels \ wide) LDA TWOS,X \ Fetch a 1-pixel byte from TWOS where pixel X is set, STA R \ and store it in R LDA Q \ Set A = |delta_y| \ The following calculates: \ \ Q = A / P \ = |delta_y| / |delta_x| \ \ using the same shift-and-subtract algorithm that's \ documented in TIS2 LDX #%11111110 \ Set Q to have bits 1-7 set, so we can rotate through 7 STX Q \ loop iterations, getting a 1 each time, and then \ getting a 0 on the 8th iteration... and we can also \ use Q to catch our result bits into bit 0 each time .LIL1 ASL A \ Shift A to the left BCS LI4 \ If bit 7 of A was set, then jump straight to the \ subtraction CMP P \ If A < P, skip the following subtraction BCC LI5 .LI4 SBC P \ A >= P, so set A = A - P SEC \ Set the C flag to rotate into the result in Q .LI5 ROL Q \ Rotate the counter in Q 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 LIL1 \ If we still have set bits in Q, loop back to TIL2 to \ do the next iteration of 7 \ We now have: \ \ Q = A / P \ = |delta_y| / |delta_x| \ \ and the C flag is clear LDX P \ Set X = P + 1 INX \ = |delta_x| + 1 \ \ We add 1 so we can skip the first pixel plot if the \ line is being drawn with swapped coordinates LDA Y2 \ Set A = Y2 - Y1 - 1 (as the C flag is clear following SBC Y1 \ the above division) BCS DOWN \ If Y2 >= Y1 - 1 then jump to DOWN, as we need to draw \ the line to the right and down
Name: LOIN (Part 3 of 7) [View individually] Type: Subroutine Category: Drawing lines Summary: Draw a shallow line going right and up or left and down
If we get here, then: * The line is going right and up (no swap) or left and down (swap) * X1 < X2 and Y1-1 > Y2 * Draw from (X1, Y1) at bottom left to (X2, Y2) at top right
LDA SWAP \ If SWAP > 0 then we swapped the coordinates above, so BNE LI6 \ jump down to LI6 to skip plotting the first pixel DEX \ Decrement the counter in X because we're about to plot \ the first pixel .LIL2 \ We now loop along the line from left to right, using X \ as a decreasing counter, and at each count we plot a \ single pixel using the pixel mask in R LDA R \ Fetch the pixel byte from R EOR (SC),Y \ Store R into screen memory at SC(1 0), using EOR STA (SC),Y \ logic so it merges with whatever is already on-screen .LI6 LSR R \ Shift the single pixel in R to the right to step along \ the x-axis, so the next pixel we plot will be at the \ next x-coordinate along BCC LI7 \ If the pixel didn't fall out of the right end of R \ into the C flag, then jump to LI7 ROR R \ Otherwise we need to move over to the next character \ block, so first rotate R right so the set C flag goes \ back into the left end, giving %10000000 LDA SC \ Add 8 to SC, so SC(1 0) now points to the next ADC #8 \ character along to the right STA SC .LI7 LDA S \ Set S = S + Q ADC Q STA S BCC LIC2 \ If the addition didn't overflow, jump to LIC2 DEY \ Otherwise we just overflowed, so decrement Y to move \ to the pixel line above BPL LIC2 \ If Y is positive we are still within the same \ character block, so skip to LIC2 DEC SCH \ Otherwise we need to move up into the character block LDY #7 \ above, so decrement the high byte of the screen \ address and set the pixel line to the last line in \ that character block .LIC2 DEX \ Decrement the counter in X BNE LIL2 \ If we haven't yet reached the right end of the line, \ loop back to LIL2 to plot the next pixel along LDY YSAV \ Restore Y from YSAV, so that it's preserved RTS \ Return from the subroutine
Name: LOIN (Part 4 of 7) [View individually] Type: Subroutine Category: Drawing lines Summary: Draw a shallow line going right and down or left and up
If we get here, then: * The line is going right and down (no swap) or left and up (swap) * X1 < X2 and Y1-1 <= Y2 * Draw from (X1, Y1) at top left to (X2, Y2) at bottom right
.DOWN LDA SWAP \ If SWAP = 0 then we didn't swap the coordinates above, BEQ LI9 \ so jump down to LI9 to skip plotting the first pixel DEX \ Decrement the counter in X because we're about to plot \ the first pixel .LIL3 \ We now loop along the line from left to right, using X \ as a decreasing counter, and at each count we plot a \ single pixel using the pixel mask in R LDA R \ Fetch the pixel byte from R EOR (SC),Y \ Store R into screen memory at SC(1 0), using EOR STA (SC),Y \ logic so it merges with whatever is already on-screen .LI9 LSR R \ Shift the single pixel in R to the right to step along \ the x-axis, so the next pixel we plot will be at the \ next x-coordinate along BCC LI10 \ If the pixel didn't fall out of the right end of R \ into the C flag, then jump to LI10 ROR R \ Otherwise we need to move over to the next character \ block, so first rotate R right so the set C flag goes \ back into the left end, giving %10000000 LDA SC \ Add 8 to SC, so SC(1 0) now points to the next ADC #8 \ character along to the right STA SC .LI10 LDA S \ Set S = S + Q ADC Q STA S BCC LIC3 \ If the addition didn't overflow, jump to LIC3 INY \ Otherwise we just overflowed, so increment Y to move \ to the pixel line below CPY #8 \ If Y < 8 we are still within the same character block, BNE LIC3 \ so skip to LIC3 INC SCH \ Otherwise we need to move down into the character LDY #0 \ block below, so increment the high byte of the screen \ address and set the pixel line to the first line in \ that character block .LIC3 DEX \ Decrement the counter in X BNE LIL3 \ If we haven't yet reached the right end of the line, \ loop back to LIL3 to plot the next pixel along LDY YSAV \ Restore Y from YSAV, so that it's preserved RTS \ Return from the subroutine
Name: LOIN (Part 5 of 7) [View individually] Type: Subroutine Category: Drawing lines Summary: Draw a line: Line has a steep gradient, step up along y-axis
If we get here, then: * |delta_y| >= |delta_x| * The line is closer to being vertical than horizontal * We are going to step up along the y-axis * We potentially swap coordinates to make sure Y1 >= Y2
.STPY LDY Y1 \ Set A = Y = Y1 TYA LDX X1 \ Set X = X1 CPY Y2 \ If Y1 >= Y2, jump down to LI15, as the coordinates are BCS LI15 \ already in the order that we want DEC SWAP \ Otherwise decrement SWAP from 0 to &FF, to denote that \ we are swapping the coordinates around LDA X2 \ Swap the values of X1 and X2 STA X1 STX X2 TAX \ Set X = X1 LDA Y2 \ Swap the values of Y1 and Y2 STA Y1 STY Y2 TAY \ Set Y = A = Y1 .LI15 \ By this point we know the line is vertical-ish and \ Y1 >= Y2, so we're going from top to bottom as we go \ from Y1 to Y2 LSR A \ Set A = Y1 / 8, so A now contains the character row LSR A \ that will contain our horizontal line LSR A ORA #&60 \ As A < 32, this effectively adds &60 to A, which gives \ us the screen address of the character row (as each \ character row takes up 256 bytes, and the first \ character row is at screen address &6000, or page &60) STA SCH \ Store the page number of the character row in SCH, so \ the high byte of SC is set correctly for drawing the \ start of our line TXA \ Set A = bits 3-7 of X1 AND #%11111000 STA SC \ Store this value in SC, so SC(1 0) now contains the \ screen address of the far left end (x-coordinate = 0) \ of the horizontal pixel row that we want to draw the \ start of our line on TXA \ Set X = X1 mod 8, which is the horizontal pixel number AND #7 \ within the character block where the line starts (as TAX \ each pixel line in the character block is 8 pixels \ wide) LDA TWOS,X \ Fetch a 1-pixel byte from TWOS where pixel X is set, STA R \ and store it in R LDA Y1 \ Set Y = Y1 mod 8, which is the pixel row within the AND #7 \ character block at which we want to draw the start of TAY \ our line (as each character block has 8 rows) LDA P \ Set A = |delta_x| \ The following calculates: \ \ P = A / Q \ = |delta_x| / |delta_y| \ \ using the same shift-and-subtract algorithm \ documented in TIS2 LDX #1 \ Set Q to have bits 1-7 clear, so we can rotate through STX P \ 7 loop iterations, getting a 1 each time, and then \ getting a 1 on the 8th iteration... and we can also \ use P to catch our result bits into bit 0 each time .LIL4 ASL A \ Shift A to the left BCS LI13 \ If bit 7 of A was set, then jump straight to the \ subtraction CMP Q \ If A < Q, skip the following subtraction BCC LI14 .LI13 SBC Q \ A >= Q, so set A = A - Q SEC \ Set the C flag to rotate into the result in Q .LI14 ROL P \ Rotate the counter in P 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) BCC LIL4 \ If we still have set bits in P, loop back to TIL2 to \ do the next iteration of 7 \ We now have: \ \ P = A / Q \ = |delta_x| / |delta_y| \ \ and the C flag is set LDX Q \ Set X = Q + 1 INX \ = |delta_y| + 1 \ \ We add 1 so we can skip the first pixel plot if the \ line is being drawn with swapped coordinates LDA X2 \ Set A = X2 - X1 - 1 (as the C flag is clear following SBC X1 \ the above division) BCC LFT \ If X2 < X1 - 1 then jump to LFT, as we need to draw \ the line to the left and down
Name: LOIN (Part 6 of 7) [View individually] Type: Subroutine Category: Drawing lines Summary: Draw a steep line going up and left or down and right
If we get here, then: * The line is going up and left (no swap) or down and right (swap) * X1 < X2 and Y1 >= Y2 * Draw from (X1, Y1) at top left to (X2, Y2) at bottom right
CLC \ Clear the C flag LDA SWAP \ If SWAP = 0 then we didn't swap the coordinates above, BEQ LI17 \ so jump down to LI17 to skip plotting the first pixel DEX \ Decrement the counter in X because we're about to plot \ the first pixel .LIL5 \ We now loop along the line from left to right, using X \ as a decreasing counter, and at each count we plot a \ single pixel using the pixel mask in R LDA R \ Fetch the pixel byte from R EOR (SC),Y \ Store R into screen memory at SC(1 0), using EOR STA (SC),Y \ logic so it merges with whatever is already on-screen .LI17 DEY \ Decrement Y to step up along the y-axis BPL LI16 \ If Y is positive we are still within the same \ character block, so skip to LI16 DEC SCH \ Otherwise we need to move up into the character block LDY #7 \ above, so decrement the high byte of the screen \ address and set the pixel line to the last line in \ that character block .LI16 LDA S \ Set S = S + P ADC P STA S BCC LIC5 \ If the addition didn't overflow, jump to LIC5 LSR R \ Otherwise we just overflowed, so shift the single \ pixel in R to the right, so the next pixel we plot \ will be at the next x-coordinate along BCC LIC5 \ If the pixel didn't fall out of the right end of R \ into the C flag, then jump to LIC5 ROR R \ Otherwise we need to move over to the next character \ block, so first rotate R right so the set C flag goes \ back into the left end, giving %10000000 LDA SC \ Add 8 to SC, so SC(1 0) now points to the next ADC #8 \ character along to the right STA SC .LIC5 DEX \ Decrement the counter in X BNE LIL5 \ If we haven't yet reached the right end of the line, \ loop back to LIL5 to plot the next pixel along LDY YSAV \ Restore Y from YSAV, so that it's preserved RTS \ Return from the subroutine
Name: LOIN (Part 7 of 7) [View individually] Type: Subroutine Category: Drawing lines Summary: Draw a steep line going up and right or down and left
If we get here, then: * The line is going up and right (no swap) or down and left (swap) * X1 >= X2 and Y1 >= Y2 * Draw from (X1, Y1) at bottom left to (X2, Y2) at top right
.LFT LDA SWAP \ If SWAP = 0 then we didn't swap the coordinates above, BEQ LI18 \ jump down to LI18 to skip plotting the first pixel DEX \ Decrement the counter in X because we're about to plot \ the first pixel .LIL6 LDA R \ Fetch the pixel byte from R EOR (SC),Y \ Store R into screen memory at SC(1 0), using EOR STA (SC),Y \ logic so it merges with whatever is already on-screen .LI18 DEY \ Decrement Y to step up along the y-axis BPL LI19 \ If Y is positive we are still within the same \ character block, so skip to LI19 DEC SCH \ Otherwise we need to move up into the character block LDY #7 \ above, so decrement the high byte of the screen \ address and set the pixel line to the last line in \ that character block .LI19 LDA S \ Set S = S + P ADC P STA S BCC LIC6 \ If the addition didn't overflow, jump to LIC6 ASL R \ Otherwise we just overflowed, so shift the single \ pixel in R to the left, so the next pixel we plot \ will be at the previous x-coordinate BCC LIC6 \ If the pixel didn't fall out of the left end of R \ into the C flag, then jump to LIC6 ROL R \ Otherwise we need to move over to the next character \ block, so first rotate R left so the set C flag goes \ back into the right end, giving %0000001 LDA SC \ Subtract 7 from SC, so SC(1 0) now points to the SBC #7 \ previous character along to the left STA SC CLC .LIC6 DEX \ Decrement the counter in X BNE LIL6 \ If we haven't yet reached the left end of the line, \ loop back to LIL6 to plot the next pixel along LDY YSAV \ Restore Y from YSAV, so that it's preserved .^HL6 RTS \ Return from the subroutine }
Name: NLIN3 [View individually] Type: Subroutine Category: Drawing lines Summary: Print a title and a horizontal line at row 19 to box it in
This routine print a text token at the cursor position and draws a horizontal line at pixel row 19. It is used for the Status Mode screen, the Short-range Chart, the Market Price screen and the Equip Ship screen.
.NLIN3 { JSR TT27 \ Print the text token in A \ Fall through into NLIN4 to draw a horizontal line at \ pixel row 19 }
Name: NLIN4 [View individually] Type: Subroutine Category: Drawing lines Summary: Draw a horizontal line at pixel row 19 to box in a title
This routine is used on the Inventory screen to draw a horizontal line at pixel row 19 to box in the title.
.NLIN4 { LDA #19 \ Jump to NLIN2 to draw a horizontal line at pixel row BNE NLIN2 \ 19, returning from the subroutine with using a tail \ call (this BNE is effectively a JMP as A will never \ be zero) }
Name: NLIN [View individually] Type: Subroutine Category: Drawing lines Summary: Draw a horizontal line at pixel row 23 to box in a title
Draw a horizontal line at pixel row 23 and move the text cursor down one line.
.NLIN { LDA #23 \ Set A = 23 so NLIN2 below draws a horizontal line at \ pixel row 23 INC YC \ Move the text cursor down one line \ Fall through into NLIN2 to draw the horizontal line \ at row 23 }
Name: NLIN2 [View individually] Type: Subroutine Category: Drawing lines Summary: Draw a screen-wide horizontal line at the pixel row in A
This draws a line from (2, A) to (254, A), which is almost screen-wide and fits in nicely between the white borders without clashing with it. Arguments: A The pixel row on which to draw the horizontal line
.NLIN2 { STA Y1 \ Set (X1, Y1) = (2, A) LDX #2 STX X1 LDX #254 \ Set X2 = 254 STX X2 BNE HLOIN \ Call HLOIN to draw a horizontal line from (2, A) to \ (254, A) and return from the subroutine (this BNE is \ effectively a JMP as A will never be zero) }
Name: HLOIN2 [View individually] Type: Subroutine Category: Drawing lines Summary: Remove a line from the sun line heap and draw it on-screen
Specifically, this does the following: * Set X1 and X2 to the x-coordinates of the ends of the horizontal line with centre YY(1 0) and length A to the left and right * Set the Y-th byte of the LSO block to 0 (i.e. remove this line from the sun line heap) * Draw a horizontal line from (X1, Y) to (X2, Y) Arguments: YY(1 0) The x-coordinate of the centre point of the line A The half-width of the line, i.e. the contents of the Y-th byte of the sun line heap Y The number of the entry in the sun line heap (which is also the y-coordinate of the line) Returns: Y Y is preserved
.HLOIN2 { JSR EDGES \ Call EDGES to calculate X1 and X2 for the horizontal \ line centred on YY(1 0) and with half-width A STY Y1 \ Set Y1 = Y LDA #0 \ Set the Y-th byte of the LSO block to 0 STA LSO,Y \ Fall through into HLOIN to draw a horizontal line from \ (X1, Y) to (X2, Y) }
Name: HLOIN [View individually] Type: Subroutine Category: Drawing lines Summary: Draw a horizontal line from (X1, Y1) to (X2, Y1)
We do not draw a pixel at the end point (X2, X1). To understand how this routine works, you might find it helpful to read the deep dive on "Drawing monochrome pixels in mode 4". Returns: Y Y is preserved
.HLOIN { STY YSAV \ Store Y into YSAV, so we can preserve it across the \ call to this subroutine LDX X1 \ Set X = X1 CPX X2 \ If X1 = X2 then the start and end points are the same, BEQ HL6 \ so return from the subroutine (as HL6 contains an RTS) BCC HL5 \ If X1 < X2, jump to HL5 to skip the following code, as \ (X1, Y1) is already the left point LDA X2 \ Swap the values of X1 and X2, so we know that (X1, Y1) STA X1 \ is on the left and (X2, Y1) is on the right STX X2 TAX \ Set X = X1 .HL5 DEC X2 \ Decrement X2 LDA Y1 \ Set A = Y1 / 8, so A now contains the character row LSR A \ that will contain our horizontal line LSR A LSR A ORA #&60 \ As A < 32, this effectively adds &60 to A, which gives \ us the screen address of the character row (as each \ character row takes up 256 bytes, and the first \ character row is at screen address &6000, or page &60) STA SCH \ Store the page number of the character row in SCH, so \ the high byte of SC is set correctly for drawing our \ line LDA Y1 \ Set A = Y1 mod 8, which is the pixel row within the AND #7 \ character block at which we want to draw our line (as \ each character block has 8 rows) STA SC \ Store this value in SC, so SC(1 0) now contains the \ screen address of the far left end (x-coordinate = 0) \ of the horizontal pixel row that we want to draw our \ horizontal line on TXA \ Set Y = bits 3-7 of X1 AND #%11111000 TAY .HL1 TXA \ Set T = bits 3-7 of X1, which will contain the AND #%11111000 \ the character number of the start of the line * 8 STA T LDA X2 \ Set A = bits 3-7 of X2, which will contain the AND #%11111000 \ the character number of the end of the line * 8 SEC \ Set A = A - T, which will contain the number of SBC T \ character blocks we need to fill - 1 * 8 BEQ HL2 \ If A = 0 then the start and end character blocks are \ the same, so the whole line fits within one block, so \ jump down to HL2 to draw the line \ Otherwise the line spans multiple characters, so we \ start with the left character, then do any characters \ in the middle, and finish with the right character LSR A \ Set R = A / 8, so R now contains the number of LSR A \ character blocks we need to fill - 1 LSR A STA R LDA X1 \ Set X = X1 mod 8, which is the horizontal pixel number AND #7 \ within the character block where the line starts (as TAX \ each pixel line in the character block is 8 pixels \ wide) LDA TWFR,X \ Fetch a ready-made byte with X pixels filled in at the \ right end of the byte (so the filled pixels start at \ point X and go all the way to the end of the byte), \ which is the shape we want for the left end of the \ line EOR (SC),Y \ Store this into screen memory at SC(1 0), using EOR STA (SC),Y \ logic so it merges with whatever is already on-screen, \ so we have now drawn the line's left cap TYA \ Set Y = Y + 8 so (SC),Y points to the next character ADC #8 \ block along, on the same pixel row as before TAY LDX R \ Fetch the number of character blocks we need to fill \ from R DEX \ Decrement the number of character blocks in X BEQ HL3 \ If X = 0 then we only have the last block to do (i.e. \ the right cap), so jump down to HL3 to draw it CLC \ Otherwise clear the C flag so we can do some additions \ while we draw the character blocks with full-width \ lines in them .HLL1 LDA #%11111111 \ Store a full-width 8-pixel horizontal line in SC(1 0) EOR (SC),Y \ so that it draws the line on-screen, using EOR logic STA (SC),Y \ so it merges with whatever is already on-screen TYA \ Set Y = Y + 8 so (SC),Y points to the next character ADC #8 \ block along, on the same pixel row as before TAY DEX \ Decrement the number of character blocks in X BNE HLL1 \ Loop back to draw more full-width lines, if we have \ any more to draw .HL3 LDA X2 \ Now to draw the last character block at the right end AND #7 \ of the line, so set X = X2 mod 8, which is the TAX \ horizontal pixel number where the line ends LDA TWFL,X \ Fetch a ready-made byte with X pixels filled in at the \ left end of the byte (so the filled pixels start at \ the left edge and go up to point X), which is the \ shape we want for the right end of the line EOR (SC),Y \ Store this into screen memory at SC(1 0), using EOR STA (SC),Y \ logic so it merges with whatever is already on-screen, \ so we have now drawn the line's right cap LDY YSAV \ Restore Y from YSAV, so that it's preserved across the \ call to this subroutine RTS \ Return from the subroutine .HL2 \ If we get here then the entire horizontal line fits \ into one character block LDA X1 \ Set X = X1 mod 8, which is the horizontal pixel number AND #7 \ within the character block where the line starts (as TAX \ each pixel line in the character block is 8 pixels \ wide) LDA TWFR,X \ Fetch a ready-made byte with X pixels filled in at the STA T \ right end of the byte (so the filled pixels start at \ point X and go all the way to the end of the byte) LDA X2 \ Set X = X2 mod 8, which is the horizontal pixel number AND #7 \ where the line ends TAX LDA TWFL,X \ Fetch a ready-made byte with X pixels filled in at the \ left end of the byte (so the filled pixels start at \ the left edge and go up to point X) AND T \ We now have two bytes, one (T) containing pixels from \ the starting point X1 onwards, and the other (A) \ containing pixels up to the end point at X2, so we can \ get the actual line we want to draw by AND'ing them \ together. For example, if we want to draw a line from \ point 2 to point 5, we would have this: \ \ T = %00111111 \ A = %11111100 \ T AND A = %00111100 \ \ so if we stick T AND A in screen memory, that's what \ we do here, setting A = A AND T EOR (SC),Y \ Store our horizontal line byte into screen memory at STA (SC),Y \ SC(1 0), using EOR logic so it merges with whatever is \ already on-screen LDY YSAV \ Restore Y from YSAV, so that it's preserved RTS \ Return from the subroutine }
Name: TWFL [View individually] Type: Variable Category: Drawing lines Summary: Ready-made character rows for left end of horizontal line
Ready-made bytes for plotting horizontal line end caps in mode 4 (the top part of the split screen). This table provides a byte with pixels at the left end, which is used for the right end of the line. See the HLOIN routine for details.
.TWFL { EQUB %10000000 EQUB %11000000 EQUB %11100000 EQUB %11110000 EQUB %11111000 EQUB %11111100 EQUB %11111110 }
Name: TWFR [View individually] Type: Variable Category: Drawing lines Summary: Ready-made character rows for right end of horizontal line
Ready-made bytes for plotting horizontal line end caps in mode 4 (the top part of the split screen). This table provides a byte with pixels at the right end, which is used for the left end of the line. See the HLOIN routine for details.
.TWFR { EQUB %11111111 EQUB %01111111 EQUB %00111111 EQUB %00011111 EQUB %00001111 EQUB %00000111 EQUB %00000011 EQUB %00000001 }
Name: PX3 [View individually] Type: Subroutine Category: Drawing pixels Summary: Plot a single pixel at (X, Y) within a character block
This routine is called from PIXEL to set 1 pixel within a character block for a distant point (i.e. where the distance ZZ >= &90). See the PIXEL routine for details, as this routine is effectively part of PIXEL. Arguments: X The x-coordinate of the pixel within the character block Y The y-coordinate of the pixel within the character block SC(1 0) The screen address of the character block T1 The value of Y to restore on exit, so Y is preserved by the call to PIXEL
.PX3 { LDA TWOS,X \ Fetch a 1-pixel byte from TWOS and EOR it into SC+Y EOR (SC),Y STA (SC),Y LDY T1 \ Restore Y from T1, so Y is preserved by the routine RTS \ Return from the subroutine }
Name: PIX1 [View individually] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate (YY+1 SYL+Y) = (A P) + (S R) and draw stardust particle
Calculate the following: (YY+1 SYL+Y) = (A P) + (S R) and draw a stardust particle at (X1,Y1) with distance ZZ. Arguments: (A P) A is the angle ALPHA or BETA, P is always 0 (S R) YY(1 0) or YY(1 0) + Q * A Y Stardust particle number X1 The x-coordinate offset Y1 The y-coordinate offset ZZ The distance of the point (further away = smaller point)
.PIX1 { JSR ADD \ Set (A X) = (A P) + (S R) STA YY+1 \ Set YY+1 to A, the high byte of the result TXA \ Set SYL+Y to X, the low byte of the result STA SYL,Y \ Fall through into PIX1 to draw the stardust particle \ at (X1,Y1) }
Name: PIXEL2 [View individually] Type: Subroutine Category: Drawing pixels Summary: Draw a stardust particle relative to the screen centre
Draw a point (X1, Y1) from the middle of the screen with a size determined by a distance value. Used to draw stardust particles. Arguments: X1 The x-coordinate offset Y1 The y-coordinate offset (positive means up the screen from the centre, negative means down the screen) ZZ The distance of the point (further away = smaller point)
.PIXEL2 { LDA X1 \ Fetch the x-coordinate offset into A BPL PX1 \ If the x-coordinate offset is positive, jump to PX1 \ to skip the following negation EOR #%01111111 \ The x-coordinate offset is negative, so flip all the CLC \ bits apart from the sign bit and add 1, to negate ADC #1 \ it to a positive number, i.e. A is now |X1| .PX1 EOR #%10000000 \ Set X = -|A| TAX \ = -|X1| LDA Y1 \ Fetch the y-coordinate offset into A and clear the AND #%01111111 \ sign bit, so A = |Y1| CMP #96 \ If |Y1| >= 96 then it's off the screen (as 96 is half BCS PX4 \ the screen height), so return from the subroutine (as \ PX4 contains an RTS) LDA Y1 \ Fetch the y-coordinate offset into A BPL PX2 \ If the y-coordinate offset is positive, jump to PX2 \ to skip the following negation EOR #%01111111 \ The y-coordinate offset is negative, so flip all the ADC #1 \ bits apart from the sign bit and subtract 1, to negate \ it to a positive number, i.e. A is now |Y1| .PX2 STA T \ Set A = 97 - A LDA #97 \ = 97 - |Y1| SBC T \ \ so if Y is positive we display the point up from the \ centre, while a negative Y means down from the centre \ Fall through into PIXEL to draw the stardust at the \ screen coordinates in (X, A) }
Name: PIXEL [View individually] Type: Subroutine Category: Drawing pixels Summary: Draw a 1-pixel dot, 2-pixel dash or 4-pixel square
Draw a point at screen coordinate (X, A) with the point size determined by the distance in ZZ. This applies to the top part of the screen (the monochrome mode 4 portion). Arguments: X The screen x-coordinate of the point to draw A The screen y-coordinate of the point to draw ZZ The distance of the point (further away = smaller point) Returns: Y Y is preserved Other entry points: PX4 Contains an RTS
.PIXEL { STY T1 \ Store Y in T1 TAY \ Copy A into Y, for use later LSR A \ Set SCH = &60 + A >> 3 LSR A LSR A ORA #&60 STA SCH TXA \ Set SC = (X >> 3) * 8 AND #%11111000 STA SC TYA \ Set Y = Y AND %111 AND #%00000111 TAY TXA \ Set X = X AND %111 AND #%00000111 TAX LDA ZZ \ If distance in ZZ >= 144, then this point is a very CMP #144 \ long way away, so jump to PX3 to fetch a 1-pixel point BCS PX3 \ from TWOS and EOR it into SC+Y LDA TWOS2,X \ Otherwise fetch a 2-pixel dash from TWOS2 and EOR it EOR (SC),Y \ into SC+Y STA (SC),Y LDA ZZ \ If distance in ZZ >= 80, then this point is a medium CMP #80 \ distance away, so jump to PX13 to stop drawing, as a BCS PX13 \ 2-pixel dash is enough \ Otherwise we keep going to draw another 2 pixel point \ either above or below the one we just drew, to make a \ 4-pixel square DEY \ Reduce Y by 1 to point to the pixel row above the one BPL PX14 \ we just plotted, and if it is still positive, jump to \ PX14 to draw our second 2-pixel dash LDY #1 \ Reducing Y by 1 made it negative, which means Y was \ 0 before we did the DEY above, so set Y to 1 to point \ to the pixel row after the one we just plotted .PX14 LDA TWOS2,X \ Fetch a 2-pixel dash from TWOS2 and EOR it into this EOR (SC),Y \ second row to make a 4-pixel square STA (SC),Y .PX13 LDY T1 \ Restore Y from T1, so Y is preserved by the routine .^PX4 RTS \ Return from the subroutine }
Name: BLINE [View individually] Type: Subroutine Category: Drawing circles Summary: Draw a circle segment and add it to the ball line heap
Draw a single segment of a circle, adding the point to the ball line heap. Arguments: CNT The number of this segment STP The step size for the circle K6(1 0) The x-coordinate of the new point on the circle, as a screen coordinate (T X) The y-coordinate of the new point on the circle, as an offset from the centre of the circle FLAG Set to &FF for the first call, so it sets up the first point in the heap but waits until the second call before drawing anything (as we need two points, i.e. two calls, before we can draw a line) K The circle's radius K3(1 0) Pixel x-coordinate of the centre of the circle K4(1 0) Pixel y-coordinate of the centre of the circle SWAP If non-zero, we swap (X1, Y1) and (X2, Y2) Returns: CNT CNT is updated to CNT + STP A The new value of CNT FLAG Set to 0
.BLINE { TXA \ Set K6(3 2) = (T X) + K4(1 0) ADC K4 \ = y-coord of centre + y-coord of new point STA K6+2 \ LDA K4+1 \ so K6(3 2) now contains the y-coordinate of the new ADC T \ point on the circle but as a screen coordinate, to go STA K6+3 \ along with the screen y-coordinate in K6(1 0) LDA FLAG \ If FLAG = 0, jump down to BL1 BEQ BL1 INC FLAG \ Flag is &FF so this is the first call to BLINE, so \ increment FLAG to set it to 0, as then the next time \ we call BLINE it can draw the first line, from this \ point to the next .BL5 \ The following inserts a &FF marker into the LSY2 line \ heap to indicate that the next call to BLINE should \ store both the (X1, Y1) and (X2, Y2) points. We do \ this on the very first call to BLINE (when FLAG is \ &FF), and on subsequent calls if the segment does not \ fit on-screen, in which case we don't draw or store \ that segment, and we start a new segment with the next \ call to BLINE that does fit on-screen LDY LSP \ If byte LSP-1 of LSY2 = &FF, jump to BL7 to tidy up LDA #&FF \ and return from the subroutine, as the point that has CMP LSY2-1,Y \ been passed to BLINE is the start of a segment, so all BEQ BL7 \ we need to do is save the coordinate in K5, without \ moving the pointer in LSP STA LSY2,Y \ Otherwise we just tried to plot a segment but it \ didn't fit on-screen, so put the &FF marker into the \ heap for this point, so the next call to BLINE starts \ a new segment INC LSP \ Increment LSP to point to the next point in the heap BNE BL7 \ Jump to BL7 to tidy up and return from the subroutine \ (this BNE is effectively a JMP, as LSP will never be \ zero) .BL1 LDA K5 \ Set XX15 = K5 = x_lo of previous point STA XX15 LDA K5+1 \ Set XX15+1 = K5+1 = x_hi of previous point STA XX15+1 LDA K5+2 \ Set XX15+2 = K5+2 = y_lo of previous point STA XX15+2 LDA K5+3 \ Set XX15+3 = K5+3 = y_hi of previous point STA XX15+3 LDA K6 \ Set XX15+4 = x_lo of new point STA XX15+4 LDA K6+1 \ Set XX15+5 = x_hi of new point STA XX15+5 LDA K6+2 \ Set XX12 = y_lo of new point STA XX12 LDA K6+3 \ Set XX12+1 = y_hi of new point STA XX12+1 JSR LL145 \ Call LL145 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 BL5 \ If the C flag is set then the line is not visible on \ screen anyway, so jump to BL5, to avoid drawing and \ storing this line LDA SWAP \ If SWAP = 0, then we didn't have to swap the line BEQ BL9 \ coordinates around during the clipping process, so \ jump to BL9 to skip the following swap LDA X1 \ Otherwise the coordinates were swapped by the call to, LDY X2 \ LL145 above, so we swap (X1, Y1) and (X2, Y2) back STA X2 \ again STY X1 LDA Y1 LDY Y2 STA Y2 STY Y1 .BL9 LDY LSP \ Set Y = LSP LDA LSY2-1,Y \ If byte LSP-1 of LSY2 is not &FF, jump down to BL8 CMP #&FF \ to skip the following (X1, Y1) code BNE BL8 \ Byte LSP-1 of LSY2 is &FF, which indicates that we \ need to store (X1, Y1) in the heap LDA X1 \ Store X1 in the LSP-th byte of LSX2 STA LSX2,Y LDA Y1 \ Store Y1 in the LSP-th byte of LSY2 STA LSY2,Y INY \ Increment Y to point to the next byte in LSX2/LSY2 .BL8 LDA X2 \ Store X2 in the LSP-th byte of LSX2 STA LSX2,Y LDA Y2 \ Store Y2 in the LSP-th byte of LSX2 STA LSY2,Y INY \ Increment Y to point to the next byte in LSX2/LSY2 STY LSP \ Update LSP to point to the same as Y JSR LOIN \ Draw a line from (X1, Y1) to (X2, Y2) LDA XX13 \ If XX13 is non-zero, jump up to BL5 to add a &FF BNE BL5 \ marker to the end of the line heap. XX13 is non-zero \ after the call to the clipping routine LL145 above if \ the end of the line was clipped, meaning the next line \ sent to BLINE can't join onto the end but has to start \ a new segment, and that's what inserting the &FF \ marker does .BL7 LDA K6 \ Copy the data for this step point from K6(3 2 1 0) STA K5 \ into K5(3 2 1 0), for use in the next call to BLINE: LDA K6+1 \ STA K5+1 \ * K5(1 0) = screen x-coordinate of this point LDA K6+2 \ STA K5+2 \ * K5(3 2) = screen y-coordinate of this point LDA K6+3 \ STA K5+3 \ They now become the "previous point" in the next call LDA CNT \ Set CNT = CNT + STP CLC ADC STP STA CNT RTS \ Return from the subroutine }
Name: FLIP [View individually] Type: Subroutine Category: Stardust Summary: Reflect the stardust particles in the screen diagonal
Swap the x- and y-coordinates of all the stardust particles and draw the new set of particles. Called by LOOK1 when we switch views. This is a quick way of making the stardust field in the new view feel different without having to generate a whole new field. If you look carefully at the stardust field when you switch views, you can just about see that the new field is a reflection of the previous field in the screen diagonal, i.e. in the line from bottom left to top right. This is the line where x = y when the origin is in the middle of the screen, and positive x and y are right and up, which is the coordinate system we use for stardust).
.FLIP { \LDA MJ \ These instructions are commented out in the original \BNE FLIP-1 \ source. They would have the effect of not swapping the \ stardust if we had mis-jumped into witchspace LDY NOSTM \ Set Y to the current number of stardust particles, so \ we can use it as a counter through all the stardust .FLL1 LDX SY,Y \ Copy the Y-th particle's y-coordinate from SY+Y into X LDA SX,Y \ Copy the Y-th particle's x-coordinate from SX+Y into STA Y1 \ both Y1 and the particle's y-coordinate STA SY,Y TXA \ Copy the Y-th particle's original y-coordinate into STA X1 \ both X1 and the particle's x-coordinate, so the x- and STA SX,Y \ y-coordinates are now swapped and (X1, Y1) contains \ the particle's new coordinates LDA SZ,Y \ Fetch the Y-th particle's distance from SZ+Y into ZZ STA ZZ JSR PIXEL2 \ Draw a stardust particle at (X1,Y1) with distance ZZ DEY \ Decrement the counter to point to the next particle of \ stardust BNE FLL1 \ Loop back to FLL1 until we have moved all the stardust \ particles RTS \ Return from the subroutine }
Name: STARS [View individually] Type: Subroutine Category: Stardust Summary: The main routine for processing the stardust
Called at the very end of the main flight loop.
.STARS { \LDA #&FF \ These instructions are commented out in the original \STA COL \ source, but they would set the stardust colour to \ white. That said, COL is only used when updating the \ dashboard, so this would have no effect - perhaps it's \ left over from experiments with a colour top part of \ the screen? Who knows... LDX VIEW \ Load the current view into X: \ \ 0 = front \ 1 = rear \ 2 = left \ 3 = right BEQ STARS1 \ If this 0, jump to STARS1 to process the stardust for \ the front view DEX \ If this is view 2 or 3, jump to STARS2 (via ST11) to BNE ST11 \ process the stardust for the left or right views JMP STARS6 \ Otherwise this is the rear view, so jump to STARS6 to \ process the stardust for the rear view .ST11 JMP STARS2 \ Jump to STARS2 for the left or right views, as it's \ too far for the branch instruction above }
Name: STARS1 [View individually] Type: Subroutine Category: Stardust Summary: Process the stardust for the front view
This moves the stardust towards us according to our speed (so the dust rushes past us), and applies our current pitch and roll to each particle of dust, so the stardust moves correctly when we steer our ship. When a stardust particle rushes past us and falls off the side of the screen, its memory is recycled as a new particle that's positioned randomly on-screen.
.STARS1 { LDY NOSTM \ Set Y to the current number of stardust particles, so \ we can use it as a counter through all the stardust \ In the following, we're going to refer to the 16-bit \ space coordinates of the current particle of stardust \ (i.e. the Y-th particle) like this: \ \ x = (x_hi x_lo) \ y = (y_hi y_lo) \ z = (z_hi z_lo) \ \ These values are stored in (SX+Y SXL+Y), (SY+Y SYL+Y) \ and (SZ+Y SZL+Y) respectively .^STL1 JSR DV42 \ Call DV42 to set the following: \ \ (P R) = 256 * DELTA / z_hi \ = 256 * speed / z_hi \ \ The maximum value returned is P = 2 and R = 128 (see \ DV42 for an explanation) LDA R \ Set A = R, so now: \ \ (P A) = 256 * speed / z_hi LSR P \ Rotate (P A) right by 2 places, which sets P = 0 (as P ROR A \ has a maximum value of 2) and leaves: LSR P \ ROR A \ A = 64 * speed / z_hi ORA #1 \ Make sure A is at least 1, and store it in Q, so we STA Q \ now have result 1 above: \ \ Q = 64 * speed / z_hi LDA SZL,Y \ We now calculate the following: SBC DELT4 \ STA SZL,Y \ (z_hi z_lo) = (z_hi z_lo) - DELT4(1 0) \ \ starting with the low bytes LDA SZ,Y \ And then we do the high bytes STA ZZ \ SBC DELT4+1 \ We also set ZZ to the original value of z_hi, which we STA SZ,Y \ use below to remove the existing particle \ \ So now we have result 2 above: \ \ z = z - DELT4(1 0) \ = z - speed * 64 JSR MLU1 \ Call MLU1 to set: \ \ Y1 = y_hi \ \ (A P) = |y_hi| * Q \ \ So Y1 contains the original value of y_hi, which we \ use below to remove the existing particle \ We now calculate: \ \ (S R) = YY(1 0) = (A P) + y STA YY+1 \ First we do the low bytes with: LDA P \ ADC SYL,Y \ YY+1 = A STA YY \ R = YY = P + y_lo STA R \ \ so we get this: \ \ (? R) = YY(1 0) = (A P) + y_lo LDA Y1 \ And then we do the high bytes with: ADC YY+1 \ STA YY+1 \ S = YY+1 = y_hi + YY+1 STA S \ \ so we get our result: \ \ (S R) = YY(1 0) = (A P) + (y_hi y_lo) \ = |y_hi| * Q + y \ \ which is result 3 above, and (S R) is set to the new \ value of y LDA SX,Y \ Set X1 = A = x_hi STA X1 \ \ So X1 contains the original value of x_hi, which we \ use below to remove the existing particle JSR MLU2 \ Set (A P) = |x_hi| * Q \ We now calculate: \ \ XX(1 0) = (A P) + x STA XX+1 \ First we do the low bytes: LDA P \ ADC SXL,Y \ XX(1 0) = (A P) + x_lo STA XX LDA X1 \ And then we do the high bytes: ADC XX+1 \ STA XX+1 \ XX(1 0) = XX(1 0) + (x_hi 0) \ \ so we get our result: \ \ XX(1 0) = (A P) + x \ = |x_hi| * Q + x \ \ which is result 4 above, and we also have: \ \ A = XX+1 = (|x_hi| * Q + x) / 256 \ \ i.e. A is the new value of x, divided by 256 EOR ALP2+1 \ EOR with the flipped sign of the roll angle alpha, so \ A has the opposite sign to the flipped roll angle \ alpha, i.e. it gets the same sign as alpha JSR MLS1 \ Call MLS1 to calculate: \ \ (A P) = A * ALP1 \ = (x / 256) * alpha JSR ADD \ Call ADD to calculate: \ \ (A X) = (A P) + (S R) \ = (x / 256) * alpha + y \ = y + alpha * x / 256 STA YY+1 \ Set YY(1 0) = (A X) to give: STX YY \ \ YY(1 0) = y + alpha * x / 256 \ \ which is result 5 above, and we also have: \ \ A = YY+1 = y + alpha * x / 256 \ \ i.e. A is the new value of y, divided by 256 EOR ALP2 \ EOR A with the correct sign of the roll angle alpha, \ so A has the opposite sign to the roll angle alpha JSR MLS2 \ Call MLS2 to calculate: \ \ (S R) = XX(1 0) \ = x \ \ (A P) = A * ALP1 \ = -y / 256 * alpha JSR ADD \ Call ADD to calculate: \ \ (A X) = (A P) + (S R) \ = -y / 256 * alpha + x STA XX+1 \ Set XX(1 0) = (A X), which gives us result 6 above: STX XX \ \ x = x - alpha * y / 256 LDX BET1 \ Fetch the pitch magnitude into X LDA YY+1 \ Set A to y_hi and set it to the flipped sign of beta EOR BET2+1 JSR MULTS-2 \ Call MULTS-2 to calculate: \ \ (A P) = X * A \ = -beta * y_hi STA Q \ Store the high byte of the result in Q, so: \ \ Q = -beta * y_hi / 256 JSR MUT2 \ Call MUT2 to calculate: \ \ (S R) = XX(1 0) = x \ \ (A P) = Q * A \ = (-beta * y_hi / 256) * (-beta * y_hi / 256) \ = (beta * y / 256) ^ 2 ASL P \ Double (A P), store the top byte in A and set the C ROL A \ flag to bit 7 of the original A, so this does: STA T \ \ (T P) = (A P) << 1 \ = 2 * (beta * y / 256) ^ 2 LDA #0 \ Set bit 7 in A to the sign bit from the A in the ROR A \ calculation above and apply it to T, so we now have: ORA T \ \ (A P) = (A P) * 2 \ = 2 * (beta * y / 256) ^ 2 \ \ with the doubling retaining the sign of (A P) JSR ADD \ Call ADD to calculate: \ \ (A X) = (A P) + (S R) \ = 2 * (beta * y / 256) ^ 2 + x STA XX+1 \ Store the high byte A in XX+1 TXA STA SXL,Y \ Store the low byte X in x_lo \ So (XX+1 x_lo) now contains: \ \ x = x + 2 * (beta * y / 256) ^ 2 \ \ which is result 7 above LDA YY \ Set (S R) = YY(1 0) = y STA R LDA YY+1 \JSR MAD \ These instructions are commented out in the original \STA S \ source \STX R STA S LDA #0 \ Set P = 0 STA P LDA BETA \ Set A = -beta, so: EOR #%10000000 \ \ (A P) = (-beta 0) \ = -beta * 256 JSR PIX1 \ Call PIX1 to calculate the following: \ \ (YY+1 y_lo) = (A P) + (S R) \ = -beta * 256 + y \ \ i.e. y = y - beta * 256, which is result 8 above \ \ PIX1 also draws a particle at (X1, Y1) with distance \ ZZ, which will remove the old stardust particle, as we \ set X1, Y1 and ZZ to the original values for this \ particle during the calculations above \ We now have our newly moved stardust particle at \ x-coordinate (XX+1 x_lo) and y-coordinate (YY+1 y_lo) \ and distance z_hi, so we draw it if it's still on \ screen, otherwise we recycle it as a new bit of \ stardust and draw that LDA XX+1 \ Set X1 and x_hi to the high byte of XX in XX+1, so STA X1 \ the new x-coordinate is in (x_hi x_lo) and the high STA SX,Y \ byte is in X1 AND #%01111111 \ If |x_hi| >= 120 then jump to KILL1 to recycle this CMP #120 \ particle, as it's gone off the side of the screen, BCS KILL1 \ and re-join at STC1 with the new particle LDA YY+1 \ Set Y1 and y_hi to the high byte of YY in YY+1, so STA SY,Y \ the new x-coordinate is in (y_hi y_lo) and the high STA Y1 \ byte is in Y1 AND #%01111111 \ If |y_hi| >= 120 then jump to KILL1 to recycle this CMP #120 \ particle, as it's gone off the top or bottom of the BCS KILL1 \ screen, and re-join at STC1 with the new particle LDA SZ,Y \ If z_hi < 16 then jump to KILL1 to recycle this CMP #16 \ particle, as it's so close that it's effectively gone BCC KILL1 \ past us, and re-join at STC1 with the new particle STA ZZ \ Set ZZ to the z-coordinate in z_hi .STC1 JSR PIXEL2 \ Draw a stardust particle at (X1,Y1) with distance ZZ, \ i.e. draw the newly moved particle at (x_hi, y_hi) \ with distance z_hi DEY \ Decrement the loop counter to point to the next \ stardust particle BEQ P%+5 \ If we have just done the last particle, skip the next \ instruction to return from the subroutine JMP STL1 \ We have more stardust to process, so jump back up to \ STL1 for the next particle RTS \ Return from the subroutine .KILL1 \ Our particle of stardust just flew past us, so let's \ recycle that particle, starting it at a random \ position that isn't too close to the centre point JSR DORND \ Set A and X to random numbers ORA #4 \ Make sure A is at least 4 and store it in Y1 and y_hi, STA Y1 \ so the new particle starts at least 4 pixels above or STA SY,Y \ below the centre of the screen JSR DORND \ Set A and X to random numbers ORA #8 \ Make sure A is at least 8 and store it in X1 and x_hi, STA X1 \ so the new particle starts at least 8 pixels either STA SX,Y \ side of the centre of the screen JSR DORND \ Set A and X to random numbers ORA #144 \ Make sure A is at least 144 and store it in ZZ and STA SZ,Y \ z_hi so the new particle starts in the far distance STA ZZ LDA Y1 \ Set A to the new value of y_hi. This has no effect as \ STC1 starts with a jump to PIXEL2, which starts with a \ LDA instruction JMP STC1 \ Jump up to STC1 to draw this new particle }
Name: STARS6 [View individually] Type: Subroutine Category: Stardust Summary: Process the stardust for the rear view
This routine is very similar to STARS1, which processes stardust for the front view. The main difference is that the direction of travel is reversed, so the signs in the calculations are different, as well as the order of the first batch of calculations. When a stardust particle falls away into the far distance, it is removed from the screen and its memory is recycled as a new particle, positioned randomly along one of the four edges of the screen. See STARS1 for an explanation of the maths used in this routine. The calculations are as follows: 1. q = 64 * speed / z_hi 2. x = x - |x_hi| * q 3. y = y - |y_hi| * q 4. z = z + speed * 64 5. y = y - alpha * x / 256 6. x = x + alpha * y / 256 7. x = x - 2 * (beta * y / 256) ^ 2 8. y = y + beta * 256
.STARS6 { LDY NOSTM \ Set Y to the current number of stardust particles, so \ we can use it as a counter through all the stardust .STL6 JSR DV42 \ Call DV42 to set the following: \ \ (P R) = 256 * DELTA / z_hi \ = 256 * speed / z_hi \ \ The maximum value returned is P = 2 and R = 128 (see \ DV42 for an explanation) LDA R \ Set A = R, so now: \ \ (P A) = 256 * speed / z_hi LSR P \ Rotate (P A) right by 2 places, which sets P = 0 (as P ROR A \ has a maximum value of 2) and leaves: LSR P \ ROR A \ A = 64 * speed / z_hi ORA #1 \ Make sure A is at least 1, and store it in Q, so we STA Q \ now have result 1 above: \ \ Q = 64 * speed / z_hi LDA SX,Y \ Set X1 = A = x_hi STA X1 \ \ So X1 contains the original value of x_hi, which we \ use below to remove the existing particle JSR MLU2 \ Set (A P) = |x_hi| * Q \ We now calculate: \ \ XX(1 0) = x - (A P) STA XX+1 \ First we do the low bytes: LDA SXL,Y \ SBC P \ XX(1 0) = x_lo - (A P) STA XX LDA X1 \ And then we do the high bytes: SBC XX+1 \ STA XX+1 \ XX(1 0) = (x_hi 0) - XX(1 0) \ \ so we get our result: \ \ XX(1 0) = x - (A P) \ = x - |x_hi| * Q \ \ which is result 2 above, and we also have: JSR MLU1 \ Call MLU1 to set: \ \ Y1 = y_hi \ \ (A P) = |y_hi| * Q \ \ So Y1 contains the original value of y_hi, which we \ use below to remove the existing particle \ We now calculate: \ \ (S R) = YY(1 0) = y - (A P) STA YY+1 \ First we do the low bytes with: LDA SYL,Y \ SBC P \ YY+1 = A STA YY \ R = YY = y_lo - P STA R \ \ so we get this: \ \ (? R) = YY(1 0) = y_lo - (A P) LDA Y1 \ And then we do the high bytes with: SBC YY+1 \ STA YY+1 \ S = YY+1 = y_hi - YY+1 STA S \ \ so we get our result: \ \ (S R) = YY(1 0) = (y_hi y_lo) - (A P) \ = y - |y_hi| * Q \ \ which is result 3 above, and (S R) is set to the new \ value of y LDA SZL,Y \ We now calculate the following: ADC DELT4 \ STA SZL,Y \ (z_hi z_lo) = (z_hi z_lo) + DELT4(1 0) \ \ starting with the low bytes LDA SZ,Y \ And then we do the high bytes STA ZZ \ ADC DELT4+1 \ We also set ZZ to the original value of z_hi, which we STA SZ,Y \ use below to remove the existing particle \ \ So now we have result 4 above: \ \ z = z + DELT4(1 0) \ = z + speed * 64 LDA XX+1 \ EOR x with the correct sign of the roll angle alpha, EOR ALP2 \ so A has the opposite sign to the roll angle alpha JSR MLS1 \ Call MLS1 to calculate: \ \ (A P) = A * ALP1 \ = (-x / 256) * alpha JSR ADD \ Call ADD to calculate: \ \ (A X) = (A P) + (S R) \ = (-x / 256) * alpha + y \ = y - alpha * x / 256 STA YY+1 \ Set YY(1 0) = (A X) to give: STX YY \ \ YY(1 0) = y - alpha * x / 256 \ \ which is result 5 above, and we also have: \ \ A = YY+1 = y - alpha * x / 256 \ \ i.e. A is the new value of y, divided by 256 EOR ALP2+1 \ EOR with the flipped sign of the roll angle alpha, so \ A has the opposite sign to the flipped roll angle \ alpha, i.e. it gets the same sign as alpha JSR MLS2 \ Call MLS2 to calculate: \ \ (S R) = XX(1 0) \ = x \ \ (A P) = A * ALP1 \ = y / 256 * alpha JSR ADD \ Call ADD to calculate: \ \ (A X) = (A P) + (S R) \ = y / 256 * alpha + x STA XX+1 \ Set XX(1 0) = (A X), which gives us result 6 above: STX XX \ \ x = x + alpha * y / 256 LDA YY+1 \ Set A to y_hi and set it to the flipped sign of beta EOR BET2+1 LDX BET1 \ Fetch the pitch magnitude into X JSR MULTS-2 \ Call MULTS-2 to calculate: \ \ (A P) = X * A \ = beta * y_hi STA Q \ Store the high byte of the result in Q, so: \ \ Q = beta * y_hi / 256 LDA XX+1 \ Set S = x_hi STA S EOR #%10000000 \ Flip the sign of A, so A now contains -x JSR MUT1 \ Call MUT1 to calculate: \ \ R = XX = x_lo \ \ (A P) = Q * A \ = (beta * y_hi / 256) * (-beta * y_hi / 256) \ = (-beta * y / 256) ^ 2 ASL P \ Double (A P), store the top byte in A and set the C ROL A \ flag to bit 7 of the original A, so this does: STA T \ \ (T P) = (A P) << 1 \ = 2 * (-beta * y / 256) ^ 2 LDA #0 \ Set bit 7 in A to the sign bit from the A in the ROR A \ calculation above and apply it to T, so we now have: ORA T \ \ (A P) = -2 * (beta * y / 256) ^ 2 \ \ with the doubling retaining the sign of (A P) JSR ADD \ Call ADD to calculate: \ \ (A X) = (A P) + (S R) \ = -2 * (beta * y / 256) ^ 2 + x STA XX+1 \ Store the high byte A in XX+1 TXA STA SXL,Y \ Store the low byte X in x_lo \ So (XX+1 x_lo) now contains: \ \ x = x - 2 * (beta * y / 256) ^ 2 \ \ which is result 7 above LDA YY \ Set (S R) = YY(1 0) = y STA R LDA YY+1 STA S \EOR #128 \ These instructions are commented out in the original \JSR MAD \ source \STA S \STX R LDA #0 \ Set P = 0 STA P LDA BETA \ Set A = beta, so (A P) = (beta 0) = beta * 256 JSR PIX1 \ Call PIX1 to calculate the following: \ \ (YY+1 y_lo) = (A P) + (S R) \ = beta * 256 + y \ \ i.e. y = y + beta * 256, which is result 8 above \ \ PIX1 also draws a particle at (X1, Y1) with distance \ ZZ, which will remove the old stardust particle, as we \ set X1, Y1 and ZZ to the original values for this \ particle during the calculations above \ We now have our newly moved stardust particle at \ x-coordinate (XX+1 x_lo) and y-coordinate (YY+1 y_lo) \ and distance z_hi, so we draw it if it's still on \ screen, otherwise we recycle it as a new bit of \ stardust and draw that LDA XX+1 \ Set X1 and x_hi to the high byte of XX in XX+1, so STA X1 \ the new x-coordinate is in (x_hi x_lo) and the high STA SX,Y \ byte is in X1 LDA YY+1 \ Set Y1 and y_hi to the high byte of YY in YY+1, so STA SY,Y \ the new x-coordinate is in (y_hi y_lo) and the high STA Y1 \ byte is in Y1 AND #%01111111 \ If |y_hi| >= 110 then jump to KILL6 to recycle this CMP #110 \ particle, as it's gone off the top or bottom of the BCS KILL6 \ screen, and re-join at STC6 with the new particle LDA SZ,Y \ If z_hi >= 160 then jump to KILL6 to recycle this CMP #160 \ particle, as it's so far away that it's too far to BCS KILL6 \ see, and re-join at STC1 with the new particle STA ZZ \ Set ZZ to the z-coordinate in z_hi .STC6 JSR PIXEL2 \ Draw a stardust particle at (X1,Y1) with distance ZZ, \ i.e. draw the newly moved particle at (x_hi, y_hi) \ with distance z_hi DEY \ Decrement the loop counter to point to the next \ stardust particle BEQ ST3 \ If we have just done the last particle, skip the next \ instruction to return from the subroutine JMP STL6 \ We have more stardust to process, so jump back up to \ STL6 for the next particle .ST3 RTS \ Return from the subroutine .KILL6 JSR DORND \ Set A and X to random numbers AND #%01111111 \ Clear the sign bit of A to get |A| ADC #10 \ Make sure A is at least 10 and store it in z_hi and STA SZ,Y \ ZZ, so the new particle starts close to us STA ZZ LSR A \ Divide A by 2 and randomly set the C flag BCS ST4 \ Jump to ST4 half the time LSR A \ Randomly set the C flag again LDA #252 \ Set A to either +126 or -126 (252 >> 1) depending on ROR A \ the C flag, as this is a sign-magnitude number with \ the C flag rotated into its sign bit STA X1 \ Set x_hi and X1 to A, so this particle starts on STA SX,Y \ either the left or right edge of the screen JSR DORND \ Set A and X to random numbers STA Y1 \ Set y_hi and Y1 to random numbers, so the particle STA SY,Y \ starts anywhere along either the left or right edge JMP STC6 \ Jump up to STC6 to draw this new particle .ST4 JSR DORND \ Set A and X to random numbers STA X1 \ Set x_hi and X1 to random numbers, so the particle STA SX,Y \ starts anywhere along the x-axis LSR A \ Randomly set the C flag LDA #230 \ Set A to either +115 or -115 (230 >> 1) depending on ROR A \ the C flag, as this is a sign-magnitude number with \ the C flag rotated into its sign bit STA Y1 \ Set y_hi and Y1 to A, so the particle starts anywhere STA SY,Y \ along either the top or bottom edge of the screen BNE STC6 \ Jump up to STC6 to draw this new particle (this BNE is \ effectively a JMP as A will never be zero) }
Name: PRXS [View individually] Type: Variable Category: Equipment Summary: Equipment prices
Equipment prices are stored as 10 * the actual value, so we can support prices with fractions of credits (0.1 Cr). This is used for the price of fuel only.
.PRXS { EQUW 1 \ 0 Fuel, calculated in EQSHP 140.0 Cr (full tank) EQUW 300 \ 1 Missile 30.0 Cr EQUW 4000 \ 2 Large Cargo Bay 400.0 Cr EQUW 6000 \ 3 E.C.M. System 600.0 Cr EQUW 4000 \ 4 Extra Pulse Lasers 400.0 Cr EQUW 10000 \ 5 Extra Beam Lasers 1000.0 Cr EQUW 5250 \ 6 Fuel Scoops 525.0 Cr EQUW 10000 \ 7 Escape Pod 1000.0 Cr EQUW 9000 \ 8 Energy Bomb 900.0 Cr EQUW 15000 \ 9 Energy Unit 1500.0 Cr EQUW 10000 \ 10 Docking Computer 1000.0 Cr EQUW 50000 \ 11 Galactic Hyperspace 5000.0 Cr }
Name: STATUS [View individually] Type: Subroutine Category: Status Summary: Show the Status Mode screen (red key f8)
{ .st4 \ We call this from st5 below with the high byte of the \ kill tally in A, which is non-zero, and want to return \ with the following in X, depending on our rating: \ \ Competent = 6 \ Dangerous = 7 \ Deadly = 8 \ Elite = 9 \ \ The high bytes of the top tier ratings are as follows, \ so this a relatively simple calculation: \ \ Competent = 1 to 2 \ Dangerous = 2 to 9 \ Deadly = 10 to 24 \ Elite = 25 and up LDX #9 \ Set X to 9 for an Elite rating CMP #25 \ If A >= 25, jump to st3 to print out our rating, as we BCS st3 \ are Elite DEX \ Decrement X to 8 for a Deadly rating CMP #10 \ If A >= 10, jump to st3 to print out our rating, as we BCS st3 \ are Deadly DEX \ Decrement X to 7 for a Dangerous rating CMP #2 \ If A >= 2, jump to st3 to print out our rating, as we BCS st3 \ are Dangerous DEX \ Decrement X to 6 for a Competent rating BNE st3 \ Jump to st3 to print out our rating, as we are \ Competent (this BNE is effectively a JMP as A will \ never be zero) .^STATUS LDA #8 \ Clear the top part of the screen, draw a white border, JSR TT66 \ and set the current view type in QQ11 to 8 (Status \ Mode screen) JSR TT111 \ Select the system closest to galactic coordinates \ (QQ9, QQ10) LDA #7 \ Move the text cursor to column 7 STA XC LDA #126 \ Print recursive token 126, which prints the top JSR NLIN3 \ four lines of the Status Mode screen: \ \ COMMANDER {commander name} \ \ \ Present System : {current system name} \ Hyperspace System : {selected system name} \ Condition : \ \ and draw a horizontal line at pixel row 19 to box \ in the title LDA #15 \ Set A to token 129 ("{switch to sentence case} \ DOCKED") LDY QQ12 \ Fetch the docked status from QQ12, and if we are BNE st6 \ docked, jump to st6 to print "Docked" for our \ ship's condition LDA #230 \ Otherwise we are in space, so start off by setting A \ to token 70 ("GREEN") LDY MANY+AST \ Set Y to the number of asteroids in our local bubble \ of universe LDX FRIN+2,Y \ The ship slots at FRIN are ordered with the first two \ slots reserved for the planet and sun/space station, \ and then any ships, so if the slot at FRIN+2+Y is not \ empty (i.e is non-zero), then that means the number of \ non-asteroids in the vicinity is at least 1 BEQ st6 \ So if X = 0, there are no ships in the vicinity, so \ jump to st6 to print "Green" for our ship's condition LDY ENERGY \ Otherwise we have ships in the vicinity, so we load \ our energy levels into Y CPY #128 \ Set the C flag if Y >= 128, so C is set if we have \ more than half of our energy banks charged ADC #1 \ Add 1 + C to A, so if C is not set (i.e. we have low \ energy levels) then A is set to token 231 ("RED"), \ and if C is set (i.e. we have healthy energy levels) \ then A is set to token 232 ("YELLOW") .st6 JSR plf \ Print the text token in A (which contains our ship's \ condition) followed by a newline LDA #125 \ Print recursive token 125, which prints the next JSR spc \ three lines of the Status Mode screen: \ \ Fuel: {fuel level} Light Years \ Cash: {cash right-aligned to width 9} Cr \ Legal Status: \ \ followed by a space LDA #19 \ Set A to token 133 ("CLEAN") LDY FIST \ Fetch our legal status, and if it is 0, we are clean, BEQ st5 \ so jump to st5 to print "Clean" CPY #50 \ Set the C flag if Y >= 50, so C is set if we have \ a legal status of 50+ (i.e. we are a fugitive) ADC #1 \ Add 1 + C to A, so if C is not set (i.e. we have a \ legal status between 1 and 49) then A is set to token \ 134 ("OFFENDER"), and if C is set (i.e. we have a \ legal status of 50+) then A is set to token 135 \ ("FUGITIVE") .st5 JSR plf \ Print the text token in A (which contains our legal \ status) followed by a newline LDA #16 \ Print recursive token 130 ("RATING:") JSR spc LDA TALLY+1 \ Fetch the high byte of the kill tally, and if it is BNE st4 \ not zero, then we have more than 256 kills, so jump \ to st4 to work out whether we are Competent, \ Dangerous, Deadly or Elite \ Otherwise we have fewer than 256 kills, so we are one \ of Harmless, Mostly Harmless, Poor, Average or Above \ Average TAX \ Set X to 0 (as A is 0) LDA TALLY \ Set A = lower byte of tally / 4 LSR A LSR A .st5L \ We now loop through bits 2 to 7, shifting each of them \ off the end of A until there are no set bits left, and \ incrementing X for each shift, so at the end of the \ process, X contains the position of the leftmost 1 in \ A. Looking at the rank values in TALLY: \ \ Harmless = %00000000 to %00000011 \ Mostly Harmless = %00000100 to %00000111 \ Poor = %00001000 to %00001111 \ Average = %00010000 to %00011111 \ Above Average = %00100000 to %11111111 \ \ we can see that the values returned by this process \ are: \ \ Harmless = 1 \ Mostly Harmless = 2 \ Poor = 3 \ Average = 4 \ Above Average = 5 INX \ Increment X for each shift LSR A \ Shift A to the right BNE st5L \ Keep looping around until A = 0, which means there are \ no set bits left in A .st3 TXA \ A now contains our rating as a value of 1 to 9, so \ transfer X to A, so we can print it out CLC \ Print recursive token 135 + A, which will be in the ADC #21 \ range 136 ("HARMLESS") to 144 ("---- E L I T E ----") JSR plf \ followed by a newline LDA #18 \ Print recursive token 132, which prints the next bit JSR plf2 \ of the Status Mode screen: \ \ EQUIPMENT: \ \ followed by a newline and an indent of 6 characters LDA CRGO \ If our ship's cargo capacity is < 26 (i.e. we do not CMP #26 \ have a cargo bay extension), skip the following two BCC P%+7 \ instructions LDA #107 \ We do have a cargo bay extension, so print recursive JSR plf2 \ token 107 ("LARGE CARGO{switch to sentence case} \ BAY"), followed by a newline and an indent of 6 \ characters LDA BST \ If we don't have fuel scoops fitted, skip the BEQ P%+7 \ following two instructions LDA #111 \ We do have a fuel scoops fitted, so print recursive JSR plf2 \ token 111 ("FUEL SCOOPS"), followed by a newline and \ an indent of 6 characters LDA ECM \ If we don't have an E.C.M. fitted, skip the following BEQ P%+7 \ two instructions LDA #108 \ We do have an E.C.M. fitted, so print recursive token JSR plf2 \ 108 ("E.C.M.SYSTEM"), followed by a newline and an \ indent of 6 characters LDA #113 \ We now cover the four pieces of equipment whose flags STA XX4 \ are stored in BOMB through BOMB+3, and whose names \ correspond with text tokens 113 through 116: \ \ BOMB+0 = BOMB = token 113 = Energy bomb \ BOMB+1 = ENGY = token 114 = Energy unit \ BOMB+2 = DKCMP = token 115 = Docking computer \ BOMB+3 = GHYP = token 116 = Galactic hyperdrive \ \ We can print these out using a loop, so we set XX4 to \ 113 as a counter (and we also set A as well, to pass \ through to plf2) .stqv TAY \ Fetch byte BOMB+0 through BOMB+4 for values of XX4 LDX BOMB-113,Y \ from 113 through 117 BEQ P%+5 \ If it is zero then we do not own that piece of \ equipment, so skip the next instruction JSR plf2 \ Print the recursive token in A from 113 ("ENERGY \ BOMB") through 116 ("GALACTIC HYPERSPACE "), followed \ by a newline and an indent of 6 characters INC XX4 \ Increment the counter (and A as well) LDA XX4 CMP #117 \ If A < 117, loop back up to stqv to print the next BCC stqv \ piece of equipment LDX #0 \ Now to print our ship's lasers, so set a counter in X \ to count through the four views (0 = front, 1 = rear, \ 2 = left, 3 = right) .st STX CNT \ Store the view number in CNT LDY LASER,X \ Fetch the laser power for view X, and if we do not BEQ st1 \ have a laser fitted to that view, jump to st1 to move \ on to the next one TXA \ Print recursive token 96 + X, which will print from 96 CLC \ ("FRONT") through to 99 ("RIGHT"), followed by a space ADC #96 JSR spc LDA #103 \ Set A to token 103 ("PULSE LASER") LDX CNT \ If the laser power for view X has bit 7 clear, then it LDY LASER,X \ is a pulse laser, so skip the following instruction BPL P%+4 LDA #104 \ Set A to token 104 ("BEAM LASER") JSR plf2 \ Print the text token in A (which contains our legal \ status) followed by a newline and an indent of 6 \ characters .st1 LDX CNT \ Increment the counter in X and CNT to point to the INX \ next view CPX #4 \ If this isn't the last of the four views, jump back up BCC st \ to st to print out the next one RTS \ Return from the subroutine }
Name: plf2 [View individually] Type: Subroutine Category: Text Summary: Print text followed by a newline and indent of 6 characters
Print a text token followed by a newline, and indent the next line to text column 6. Arguments: A The text token to be printed
.plf2 { JSR plf \ Print the text token in A followed by a newline LDX #6 \ Set the text cursor to column 6 STX XC RTS \ Return from the subroutine }
Name: TENS [View individually] Type: Variable Category: Text Summary: A constant used when printing large numbers in BPRNT
Contains the four low bytes of the value 100,000,000,000 (100 billion). The maximum number of digits that we can print with the BPRNT routine is 11, so the biggest number we can print is 99,999,999,999. This maximum number plus 1 is 100,000,000,000, which in hexadecimal is: & 17 48 76 E8 00 The TENS variable contains the lowest four bytes in this number, with the least significant byte first, i.e. 00 E8 76 48. This value is used in the BPRNT routine when working out which decimal digits to print when printing a number.
.TENS { EQUD &00E87648 }
Name: pr2 [View individually] Type: Subroutine Category: Text Summary: Print an 8-bit number, left-padded to 3 digits, and optional point
Print the 8-bit number in X to 3 digits, left-padding with spaces for numbers with fewer than 3 digits (so numbers < 100 are right-aligned). Optionally include a decimal point. Arguments: X The number to print C flag If set, include a decimal point Other entry points: pr2+2 Print the 8-bit number in X to the number of digits in A
.pr2 { LDA #3 \ Set A to the number of digits (3) LDY #0 \ Zero the Y register, so we can fall through into TT11 \ to print the 16-bit number (Y X) to 3 digits, which \ effectively prints X to 3 digits as the high byte is \ zero }
Name: TT11 [View individually] Type: Subroutine Category: Text Summary: Print a 16-bit number, left-padded to n digits, and optional point
Print the 16-bit number in (Y X) to a specific number of digits, left-padding with spaces for numbers with fewer digits (so lower numbers will be right- aligned). Optionally include a decimal point. Arguments: X The low byte of the number to print Y The high byte of the number to print A The number of digits C flag If set, include a decimal point
.TT11 { STA U \ We are going to use the BPRNT routine (below) to \ print this number, so we store the number of digits \ in U, as that's what BPRNT takes as an argument LDA #0 \ BPRNT takes a 32-bit number in K to K+3, with the STA K \ most significant byte first (big-endian), so we set STA K+1 \ the two most significant bytes to zero (K and K+1) STY K+2 \ and store (Y X) in the least two significant bytes STX K+3 \ (K+2 and K+3), so we are going to print the 32-bit \ number (0 0 Y X) \ Finally we fall through into BPRNT to print out the \ number in K to K+3, which now contains (Y X), to 3 \ digits (as U = 3), using the same carry flag as when \ pr2 was called to control the decimal point }
Name: BPRNT [View individually] Type: Subroutine Category: Text Summary: Print a 32-bit number, left-padded to n digits, and optional point
Print the 32-bit number stored in K(0 1 2 3) to a specific number of digits, left-padding with spaces for numbers with fewer digits (so lower numbers are right-aligned). Optionally include a decimal point. Arguments: K(0 1 2 3) The number to print, stored with the most significant byte in K and the least significant in K+3 (big-endian, which is not the same way that 6502 assembler stores addresses) U The maximum number of digits to print, including the decimal point (spaces will be used on the left to pad out the result to this width, so the number is right- aligned to this width). The maximum number of characters including any decimal point must be 11 or less C flag If set, include a decimal point followed by one fractional digit (i.e. show the number to 1 decimal place). In this case, the number in K to K+3 contains 10 * the number we end up printing, so to print 123.4, we would pass 1234 in K to K+3 and would set the C flag
.BPRNT { LDX #11 \ Set T to the maximum number of digits allowed (11 STX T \ characters, which is the number of digits in 10 \ billion); we will use this as a flag when printing \ characters in TT37 below PHP \ Make a copy of the status register (in particular \ the carry flag) so we can retrieve it later BCC TT30 \ If the carry flag is clear, we do not want to print a \ decimal point, so skip the next two instructions DEC T \ As we are going to show a decimal point, decrement DEC U \ both the number of characters and the number of \ digits (as one of them is now a decimal point) .TT30 LDA #11 \ Set A to 11, the maximum number of digits allowed SEC \ Set the carry flag so we can do subtraction without \ the carry flag affecting the result STA XX17 \ Store the maximum number of digits allowed (11) in \ XX17 SBC U \ Set U = 11 - U + 1, so U now contains the maximum STA U \ number of digits minus the number of digits we want INC U \ to display, plus 1 (so this is the number of digits \ we should skip before starting to print the number \ itself, and the plus 1 is there to ensure we at least \ print one digit) LDY #0 \ In the main loop below, we use Y to count the number \ of times we subtract 10 billion to get the leftmost \ digit, so set this to zero (see below TT36 for an \ of how this algorithm works) STY S \ In the main loop below, we use location S as an \ 8-bit overflow for the 32-bit calculations, so \ we need to set this to 0 before joining the loop JMP TT36 \ Jump to TT36 to start the process of printing this \ number's digits .TT35 \ This subroutine multiplies K(S 0 1 2 3) by 10 and \ stores the result back in K(S 0 1 2 3), using the \ (K * 2) + (K * 2 * 2 * 2) approach described above ASL K+3 \ Set K(S 0 1 2 3) = K(S 0 1 2 3) * 2 by rotating left ROL K+2 ROL K+1 ROL K ROL S LDX #3 \ Now we want to make a copy of the newly doubled K in \ XX15, so we can use it for the first (K * 2) in the \ equation above, so set up a counter in X for copying \ four bytes, starting with the last byte in memory \ (i.e. the least significant) .tt35 LDA K,X \ Copy the X-th byte of K(0 1 2 3) to the X-th byte of STA XX15,X \ XX15(0 1 2 3), so that XX15 will contain a copy of \ K(0 1 2 3) once we've copied all four bytes DEX \ Decrement the loop counter so we move to the next \ byte, going from least significant (3) to most \ significant (0) BPL tt35 \ Loop back to copy the next byte LDA S \ Store the value of location S, our overflow byte, in STA XX15+4 \ XX15+4, so now XX15(4 0 1 2 3) contains a copy of \ K(S 0 1 2 3), which is the value of (K * 2) that we \ want ASL K+3 \ Now to calculate the (K * 2 * 2 * 2) part. We still ROL K+2 \ have (K * 2) in K(S 0 1 2 3), so we just need to ROL K+1 \ it twice. This is the first one, so we do this: ROL K \ ROL S \ K(S 0 1 2 3) = K(S 0 1 2 3) * 2 = K * 4 ASL K+3 \ And then we do it again, so that means: ROL K+2 \ ROL K+1 \ K(S 0 1 2 3) = K(S 0 1 2 3) * 2 = K * 8 ROL K ROL S CLC \ Clear the carry flag so we can do addition without \ the carry flag affecting the result LDX #3 \ By now we've got (K * 2) in XX15(4 0 1 2 3) and \ (K * 8) in K(S 0 1 2 3), so the final step is to add \ these two 32-bit numbers together to get K * 10. \ So we set a counter in X for four bytes, starting \ with the last byte in memory (i.e. the least \ significant) .tt36 LDA K,X \ Fetch the X-th byte of K into A ADC XX15,X \ Add the X-th byte of XX15 to A, with carry STA K,X \ Store the result in the X-th byte of K DEX \ Decrement the loop counter so we move to the next \ byte, going from least significant (3) to most \ significant (0) BPL tt36 \ Loop back to add the next byte LDA XX15+4 \ Finally, fetch the overflow byte from XX15(4 0 1 2 3) ADC S \ And add it to the overflow byte from K(S 0 1 2 3), \ with carry STA S \ And store the result in the overflow byte from \ K(S 0 1 2 3), so now we have our desired result that \ K(S 0 1 2 3) is now K(S 0 1 2 3) * 10 LDY #0 \ In the main loop below, we use Y to count the number \ of times we subtract 10 billion to get the leftmost \ digit, so set this to zero .TT36 \ This is the main loop of our digit-printing routine. \ In the following loop, we are going to count the \ number of times that we can subtract 10 million in Y, \ which we have already set to 0 LDX #3 \ Our first calculation concerns 32-bit numbers, so \ set up a counter for a four-byte loop SEC \ Set the carry flag so we can do subtraction without \ the carry flag affecting the result .tt37 \ Now we loop thorough each byte in turn to do this: \ \ XX15(4 0 1 2 3) = K(S 0 1 2 3) - 100,000,000,000 LDA K,X \ Subtract the X-th byte of TENS (i.e. 10 billion) from SBC TENS,X \ the X-th byte of K STA XX15,X \ Store the result in the X-th byte of XX15 DEX \ Decrement the loop counter so we move to the next \ byte, going from least significant (3) to most \ significant (0) BPL tt37 \ Loop back to subtract from the next byte LDA S \ Subtract the fifth byte of 10 billion (i.e. &17) from SBC #&17 \ the fifth (overflow) byte of K, which is S STA XX15+4 \ Store the result in the overflow byte of XX15 BCC TT37 \ If subtracting 10 billion took us below zero, jump to \ TT37 to print out this digit, which is now in Y LDX #3 \ We now want to copy XX15(4 0 1 2 3) back to \ K(S 0 1 2 3), so we can loop back up to do the next \ subtraction, so set up a counter for a four-byte loop .tt38 LDA XX15,X \ Copy the X-th byte of XX15(0 1 2 3) to the X-th byte STA K,X \ of K(0 1 2 3), so that K will contain a copy of \ XX15(0 1 2 3) once we've copied all four bytes DEX \ Decrement the loop counter so we move to the next \ byte, going from least significant (3) to most \ significant (0) BPL tt38 \ Loop back to copy the next byte LDA XX15+4 \ Store the value of location XX15+4, our overflow STA S \ byte in S, so now K(S 0 1 2 3) contains a copy of \ XX15(4 0 1 2 3) INY \ We have now managed to subtract 10 billion from our \ number, so increment Y, which is where we are keeping \ count of the number of subtractions so far JMP TT36 \ Jump back to TT36 to subtract the next 10 billion .TT37 TYA \ If we get here then Y contains the digit that we want \ to print (as Y has now counted the total number of \ subtractions of 10 billion), so transfer Y into A BNE TT32 \ If the digit is non-zero, jump to TT32 to print it LDA T \ Otherwise the digit is zero. If we are already \ printing the number then we will want to print a 0, \ but if we haven't started printing the number yet, \ then we probably don't, as we don't want to print \ leading zeroes unless this is the only digit before \ the decimal point \ \ To help with this, we are going to use T as a flag \ that tells us whether we have already started \ printing digits: \ \ * If T <> 0 we haven't printed anything yet \ \ * If T = 0 then we have started printing digits \ \ We initially set T to the maximum number of \ characters allowed at, less 1 if we are printing a \ decimal point, so the first time we enter the digit \ printing routine at TT37, it is definitely non-zero BEQ TT32 \ If T = 0, jump straight to the print routine at TT32, \ as we have already started printing the number, so we \ definitely want to print this digit too DEC U \ We initially set U to the number of digits we want to BPL TT34 \ skip before starting to print the number. If we get \ here then we haven't printed any digits yet, so \ decrement U to see if we have reached the point where \ we should start printing the number, and if not, jump \ to TT34 to set up things for the next digit LDA #' ' \ We haven't started printing any digits yet, but we BNE tt34 \ have reached the point where we should start printing \ our number, so call TT26 (via tt34) to print a space \ so that the number is left-padded with spaces (this \ BNE is effectively a JMP as A will never be zero) .TT32 LDY #0 \ We are printing an actual digit, so first set T to 0, STY T \ to denote that we have now started printing digits as \ opposed to spaces CLC \ The digit value is in A, so add ASCII "0" to get the ADC #'0' \ ASCII character number to print .tt34 JSR TT26 \ Print the character in A and fall through into TT34 \ to get things ready for the next digit .TT34 DEC T \ Decrement T but keep T >= 0 (by incrementing it BPL P%+4 \ again if the above decrement made T negative) INC T DEC XX17 \ Decrement the total number of characters to print in \ XX17 BMI RR3+1 \ If it is negative, we have printed all the characters \ so return from the subroutine (as RR3 contains an \ ORA #&60 instruction, so RR3+1 is &60, which is the \ opcode for an RTS) BNE P%+10 \ If it is positive (> 0) loop back to TT35 (via the \ last instruction in this subroutine) to print the \ next digit PLP \ If we get here then we have printed the exact number \ of digits that we wanted to, so restore the carry \ flag that we stored at the start of BPRNT BCC P%+7 \ If carry is clear, we don't want a decimal point, so \ look back to TT35 (via the last instruction in this \ subroutine) to print the next digit LDA #'.' \ Print the decimal point JSR TT26 JMP TT35 \ Loop back to TT35 to print the next digit }
Name: BELL [View individually] Type: Subroutine Category: Sound Summary: Make a beep sound
This is the standard system beep as made bu VDU 7.
.BELL { LDA #7 \ Control code 7 makes a beep, so load this into A so \ we can fall through into the TT27 print routine to \ actually make the sound }
Name: TT26 [View individually] Type: Subroutine Category: Text Summary: Print a character at the text cursor (WRCHV points here)
Print a character at the text cursor (XC, YC), do a beep, print a newline, or delete left (backspace). WRCHV is set to point here by elite-loader.asm. Arguments: A The character to be printed. Can be one of the following: * 7 (beep) * 10-13 (line feeds and carriage returns) * 32-95 (ASCII capital letters, numbers and punctuation) * 127 (delete the character to the left of the text cursor and move the cursor to the left) XC Contains the text column to print at (the x-coordinate) YC Contains the line number to print on (the y-coordinate) Returns: A A is preserved X X is preserved Y Y is preserved C flag Carry is cleared Other entry points: RR3+1 Contains an RTS RREN Prints the character definition pointed to by P(2 1) at the screen address pointed to by (A SC). Used by the BULB routine rT9 Contains an RTS
.TT26 { STA K3 \ Store the A, X and Y registers, so we can restore STY YSAV2 \ them at the end (so they don't get changed by this STX XSAV2 \ routine) LDY QQ17 \ Load the QQ17 flag, which contains the text printing \ flags CPY #255 \ If QQ17 = 255 then printing is disabled, so jump to BEQ RR4 \ RR4, which doesn't print anything, it just restores \ the registers and returns from the subroutine CMP #7 \ If this is a beep character (A = 7), jump to R5, BEQ R5 \ which will emit the beep, restore the registers and \ return from the subroutine CMP #32 \ If this is an ASCII character (A >= 32), jump to RR1 BCS RR1 \ below, which will print the character, restore the \ registers and return from the subroutine CMP #10 \ If this is control code 10 (line feed) then jump to BEQ RRX1 \ RRX1, which will move down a line, restore the \ registers and return from the subroutine LDX #1 \ If we get here, then this is control code 11-13, of STX XC \ which only 13 is used. This code prints a newline, \ which we can achieve by moving the text cursor \ to the start of the line (carriage return) and down \ one line (line feed). These two lines do the first \ bit by setting XC = 1, and we then fall through into \ the line feed routine that's used by control code 10 .RRX1 INC YC \ Print a line feed, simply by incrementing the row \ number (y-coordinate) of the text cursor, which is \ stored in YC BNE RR4 \ Jump to RR4 to restore the registers and return from \ the subroutine (this BNE is effectively a JMP as Y \ will never be zero) .RR1 \ If we get here, then the character to print is an \ ASCII character in the range 32-95. The quickest way \ to display text on-screen is to poke the character \ pixel by pixel, directly into screen memory, so \ that's what the rest of this routine does \ \ The first step, then, is to get hold of the bitmap \ definition for the character we want to draw on the \ screen (i.e. we need the pixel shape of this \ character). The OS ROM contains bitmap definitions \ of the BBC's ASCII characters, starting from &C000 \ for space (ASCII 32) and ending with the £ symbol \ (ASCII 126) \ \ There are 32 characters' definitions in each page of \ memory, as each definition takes up 8 bytes (8 rows \ of 8 pixels) and 32 * 8 = 256 bytes = 1 page. So: \ \ ASCII 32-63 are defined in &C000-&C0FF (page &C0) \ ASCII 64-95 are defined in &C100-&C1FF (page &C1) \ ASCII 96-126 are defined in &C200-&C2F0 (page &C2) \ \ The following code reads the relevant character \ bitmap from the above locations in ROM and pokes \ those values into the correct position in screen \ memory, thus printing the character on-screen \ \ It's a long way from 10 PRINT "Hello world!":GOTO 10 \LDX #LO(K3) \ These instructions are commented out in the original \INX \ source, but they call OSWORD &A, which reads the \STX P+1 \ character bitmap for the character number in K3 and \DEX \ stores it in the block at K3+1, while also setting \LDY #HI(K3) \ P+1 to point to the character definition. This is \STY P+2 \ exactly what the following uncommented code does, \LDA #10 \ just without calling OSWORD. Presumably the code \JSR OSWORD \ below is faster than using the system call, as this \ version takes up 15 bytes, while the version below \ (which ends with STA P+1 and SYX P+2) is 17 bytes. \ Every efficiency saving helps, especially as this \ routine is run each time the game prints a character \ \ If you want to switch this code back on, uncomment \ the above block, and comment out the code below from \ TAY to STX P+2. You will also need to uncomment the \ LDA YC instruction a few lines down (in RR2), just to \ make sure the rest of the code doesn't shift in \ memory. To be honest I can't see a massive difference \ in speed, but there you go TAY \ Copy the character number from A to Y, as we are \ about to pull A apart to work out where this \ character definition lives in the ROM \ Now we want to set X to point to the relevant page \ number for this character - i.e. &C0, &C1 or &C2. \ The following logic is easier to follow if we look \ at the three character number ranges in binary: \ \ Bit # 76543210 \ \ 32 = %00100000 Page &C0 \ 63 = %00111111 \ \ 64 = %01000000 Page &C1 \ 95 = %01011111 \ \ 96 = %01100000 Page &C2 \ 125 = %01111101 \ \ We'll refer to this below LDX #&BF \ Set X to point to the first font page in ROM minus 1, \ which is &C0 - 1, or &BF ASL A \ If bit 6 of the character is clear (A is 32-63) ASL A \ then skip the following instruction BCC P%+4 LDX #&C1 \ A is 64-126, so set X to point to page &C1 ASL A \ If bit 5 of the character is clear (A is 64-95) BCC P%+3 \ then skip the following instruction INX \ Increment X \ \ By this point, we started with X = &BF, and then \ we did the following: \ \ If A = 32-63: skip then INX so X = &C0 \ If A = 64-95: X = &C1 then skip so X = &C1 \ If A = 96-126: X = &C1 then INX so X = &C2 \ \ In other words, X points to the relevant page. But \ what about the value of A? That gets shifted to the \ left three times during the above code, which \ multiplies the number by 8 but also drops bits 7, 6 \ and 5 in the process. Look at the above binary \ figures and you can see that if we cleared bits 5-7, \ then that would change 32-53 to 0-31... but it would \ do exactly the same to 64-95 and 96-125. And because \ we also multiply this figure by 8, A now points to \ the start of the character's definition within its \ page (because there are 8 bytes per character \ definition) \ \ Or, to put it another way, X contains the high byte \ (the page) of the address of the definition that we \ want, while A contains the low byte (the offset into \ the page) of the address STA P+1 \ Store the address of this character's definition in STX P+2 \ P(2 1) LDA XC \ Fetch XC, the x-coordinate (column) of the text ASL A \ cursor, multiply by 8, and store in SC. As each ASL A \ character is 8 bits wide, and the special screen mode ASL A \ Elite uses for the top part of the screen is 256 STA SC \ bits across with one bit per pixel, this value is \ not only the screen address offset of the text cursor \ from the left side of the screen, it's also the least \ significant byte of the screen address where we want \ to print this character, as each row of on-screen \ pixels corresponds to one page. To put this more \ explicitly, the screen starts at &6000, so the \ text rows are stored in screen memory like this: \ \ Row 1: &6000 - &60FF YC = 1, XC = 0 to 31 \ Row 2: &6100 - &61FF YC = 2, XC = 0 to 31 \ Row 3: &6200 - &62FF YC = 3, XC = 0 to 31 \ \ and so on LDA YC \ Fetch YC, the y-coordinate (row) of the text cursor CPY #127 \ If the character number (which is in Y) <> 127, then BNE RR2 \ skip to RR2 to print that character, otherwise this is \ the delete character, so continue on DEC XC \ We want to delete the character to the left of the \ text cursor and move the cursor back one, so let's \ do that by decrementing YC. Note that this doesn't \ have anything to do with the actual deletion below, \ we're just updating the cursor so it's in the right \ position following the deletion ADC #&5E \ A contains YC (from above) and the carry flag is set TAX \ (from the CPY #127 above), so these instructions do \ this: \ \ X = YC + &5E + 1 \ = YC + &5F \ \ Because YC starts at 0 for the first text row, this \ means that X will be &5F for row 0, &60 for row 1 and \ so on. In other words, X is now set to the page number \ for the row before the one containing the text cursor, \ and given that we set SC above to point to the offset \ in memory of the text cursor within the row's page, \ this means that (X SC) now points to the character \ above the text cursor LDY #&F8 \ Set Y = &F8, so the following call to ZES2 will count \ Y upwards from &F8 to &FF JSR ZES2 \ Call ZES2, which zero-fills from address (X SC) + Y to \ (X SC) + &FF. (X SC) points to the character above the \ text cursor, and adding &FF to this would point to the \ cursor, so adding &F8 points to the character before \ the cursor, which is the one we want to delete. So \ this call zero-fills the character to the left of the \ cursor, which erases it from the screen BEQ RR4 \ We are done deleting, so restore the registers and \ return from the subroutine (this BNE is effectively \ a JMP as ZES2 always returns with the zero flag set) .RR2 \ Now to actually print the character INC XC \ Once we print the character, we want to move the text \ cursor to the right, so we do this by incrementing \ XC. Note that this doesn't have anything to do \ with the actual printing below, we're just updating \ the cursor so it's in the right position following \ the print \LDA YC \ This instruction is commented out in the original \ source. It isn't required because we only just did a \ LDA YC before jumping to RR2, so this is presumably \ an example of the authors squeezing the code to save \ 2 bytes and 3 cycles \ \ If you want to re-enable the commented block near the \ start of this routine, you should uncomment this \ instruction as well CMP #24 \ If the text cursor is on the screen (i.e. YC < 24, so BCC RR3 \ we are on rows 1-23), then jump to RR3 to print the \ character JSR TTX66 \ Otherwise we are off the bottom of the screen, so \ clear the screen and draw a white border JMP RR4 \ And restore the registers and return from the \ subroutine .^RR3 ORA #&60 \ A contains the value of YC - the screen row where we \ want to print this character - so now we need to \ convert this into a screen address, so we can poke \ the character data to the right place in screen \ memory. We already stored the least significant byte \ of this screen address in SC above (see the STA SC \ instruction above), so all we need is the most \ significant byte. As mentioned above, in Elite's \ square mode 4 screen, each row of text on-screen \ takes up exactly one page, so the first row is page \ &60xx, the second row is page &61xx, so we can get \ the page for character (XC, YC) by OR'ing with &60. \ To see this in action, consider that our two values \ are, in binary: \ \ YC is between: %00000000 \ and: %00010111 \ &60 is: %01100000 \ \ so YC OR &60 effectively adds &60 to YC, giving us \ the page number that we want .^RREN STA SC+1 \ Store the page number of the destination screen \ location in SC+1, so SC now points to the full screen \ location where this character should go LDY #7 \ We want to print the 8 bytes of character data to the \ screen (one byte per row), so set up a counter in Y \ to count these bytes .RRL1 LDA (P+1),Y \ The character definition is at P(2 1) - we set this up \ above - so load the Y-th byte from P(2 1) EOR (SC),Y \ If we EOR this value with the existing screen \ contents, then it's reversible (so reprinting the \ same character in the same place will revert the \ screen to what it looked like before we printed \ anything); this means that printing a white pixel on \ onto a white background results in a black pixel, but \ that's a small price to pay for easily erasable text STA (SC),Y \ Store the Y-th byte at the screen address for this \ character location DEY \ Decrement the loop counter BPL RRL1 \ Loop back for the next byte to print to the screen .RR4 LDY YSAV2 \ We're done printing, so restore the values of the LDX XSAV2 \ A, X and Y registers that we saved above and clear LDA K3 \ the carry flag, so everything is back to how it was CLC .^rT9 RTS \ Return from the subroutine .R5 JSR BEEP \ Call the BEEP subroutine to make a short, high beep JMP RR4 \ Jump to RR4 to restore the registers and return from \ the subroutine using a tail call }
Name: DIALS (Part 1 of 4) [View individually] Type: Subroutine Category: Dashboard Summary: Update the dashboard: speed indicator
This routine updates the dashboard. First we draw all the indicators in the right part of the dashboard, from top (speed) to bottom (energy banks), and then we move on to the left part, again drawing from top (forward shield) to bottom (altitude). This first section starts us off with the speedometer in the top right.
.DIALS { LDA #&D0 \ Set SC(1 0) = &78D0, which is the screen address for STA SC \ the character block containing the left end of the LDA #&78 \ top indicator in the right part of the dashboard, the STA SC+1 \ one showing our speed JSR PZW \ Call PZW to set A to the colour for dangerous values \ and X to the colour for safe values STX K+1 \ Set K+1 (the colour we should show for low values) to \ X (the colour to use for safe values) STA K \ Set K (the colour we should show for high values) to \ A (the colour to use for dangerous values) \ The above sets the following indicators to show red \ for high values and yellow/white for low values LDA #14 \ Set T1 to 14, the threshold at which we change the STA T1 \ indicator's colour LDA DELTA \ Fetch our ship's speed into A, in the range 0-40 \LSR A \ Draw the speed indicator using a range of 0-31, and JSR DIL-1 \ increment SC to point to the next indicator (the roll \ indicator). The LSR is commented out as it isn't \ required with a call to DIL-1, so perhaps this was \ originally a call to DIL that got optimised
Name: DIALS (Part 2 of 4) [View individually] Type: Subroutine Category: Dashboard Summary: Update the dashboard: pitch and roll indicators
LDA #0 \ Set R = P = 0 for the low bytes in the call to the ADD STA R \ routine below STA P LDA #8 \ Set S = 8, which is the value of the centre of the STA S \ roll indicator LDA ALP1 \ Fetch the roll angle alpha as a value between 0 and LSR A \ 31, and divide by 4 to get a value of 0 to 7 LSR A ORA ALP2 \ Apply the roll sign to the value, and flip the sign, EOR #%10000000 \ so it's now in the range -7 to +7, with a positive \ roll angle alpha giving a negative value in A JSR ADD \ We now add A to S to give us a value in the range 1 to \ 15, which we can pass to DIL2 to draw the vertical \ bar on the indicator at this position. We use the ADD \ routine like this: \ \ (A X) = (A 0) + (S 0) \ \ and just take the high byte of the result. We use ADD \ rather than a normal ADC because ADD separates out the \ sign bit and does the arithmetic using absolute values \ and separate sign bits, which we want here rather than \ the two's complement that ADC uses JSR DIL2 \ Draw a vertical bar on the roll indicator at offset A \ and increment SC to point to the next indicator (the \ pitch indicator) LDA BETA \ Fetch the pitch angle beta as a value between -8 and \ +8 LDX BET1 \ Fetch the magnitude of the pitch angle beta, and if it BEQ P%+4 \ is 0 (i.e. we are not pitching), skip the next \ instruction SBC #1 \ The pitch angle beta is non-zero, so set A = A - 1 \ (the C flag is set by the call to DIL2 above, so we \ don't need to do a SEC). This gives us a value of A \ from -7 to +7 because these are magnitude-based \ numbers with sign bits, rather than two's complement \ numbers JSR ADD \ We now add A to S to give us a value in the range 1 to \ 15, which we can pass to DIL2 to draw the vertical \ bar on the indicator at this position (see the JSR ADD \ above for more on this) JSR DIL2 \ Draw a vertical bar on the pitch indicator at offset A \ and increment SC to point to the next indicator (the \ four energy banks)
Name: DIALS (Part 3 of 4) [View individually] Type: Subroutine Category: Dashboard Summary: Update the dashboard: four energy banks
This and the next section only run once every four iterations of the main loop, so while the speed, pitch and roll indicators update every iteration, the other indicators update less often.
LDA MCNT \ If the main loop counter has either of bits 0 and 1 AND #%00000011 \ set, return from the subroutine (as rT9 contains an BNE rT9 \ RTS), so we fall through to the following in one of \ every four main loop iterations LDY #0 \ Set Y = 0, for use in various places below JSR PZW \ Call PZW to set A to the colour for dangerous values \ and X to the colour for safe values STX K \ Set K (the colour we should show for high values) to X \ (the colour to use for safe values) STA K+1 \ Set K+1 (the colour we should show for low values) to \ A (the colour to use for dangerous values) \ The above sets the following indicators to show red \ for low values and yellow/white for high values, which \ we use not only for the energy banks, but also for the \ shield levels and current fuel LDX #3 \ Set up a counter in X so we can zero the four bytes at \ XX12, so we can then calculate each of the four energy \ banks' values before drawing them later STX T1 \ Set T1 to 3, the threshold at which we change the \ indicator's colour .DLL23 STY XX12,X \ Set the X-th byte of XX12 to 0 DEX \ Decrement the counter BPL DLL23 \ Loop back for the next byte until the four bytes at \ XX12 are all zeroed LDX #3 \ Set up a counter in X to loop through the 4 energy \ bank indicators, so we can calculate each of the four \ energy banks' values and store them in XX12 LDA ENERGY \ Set A = Q = ENERGY / 4, so they are both now in the LSR A \ range 0-63 (so that's a maximum of 16 in each of the LSR A \ banks, and a maximum of 15 in the top bank) STA Q \ Set Q to A, so we can use Q to hold the remaining \ energy as we work our way through each bank, from the \ full ones at the bottom to the empty ones at the top .DLL24 SEC \ Set A = A - 16 to reduce the energy count by a full SBC #16 \ bank BCC DLL26 \ If the C flag is clear then A < 16, so this bank is \ not full to the brim, and is therefore the last one \ with any energy in it, so jump to DLL26 STA Q \ This bank is full, so update Q with the energy of the \ remaining banks LDA #16 \ Store this bank's level in XX12 as 16, as it is full, STA XX12,X \ with XX12+3 for the bottom bank and XX12+0 for the top LDA Q \ Set A to the remaining energy level again DEX \ Decrement X to point to the next bank, i.e. the one \ above the bank we just processed BPL DLL24 \ Loop back to DLL24 until we have either processed all \ four banks, or jumped out early to DLL26 if the top \ banks have no charge BMI DLL9 \ Jump to DLL9 as we have processed all four banks (this \ BMI is effectively a JMP as A will never be positive) .DLL26 LDA Q \ If we get here then the bank we just checked is not STA XX12,X \ fully charged, so store its value in XX12 (using Q, \ which contains the energy of the remaining banks - \ i.e. this one) \ Now that we have the four energy bank values in XX12, \ we can draw them, starting with the top bank in XX12 \ and looping down to the bottom bank in XX12+3, using Y \ as a loop counter, which was set to 0 above .DLL9 LDA XX12,Y \ Fetch the value of the Y-th indicator, starting from \ the top STY P \ Store the indicator number in P for retrieval later JSR DIL \ Draw the energy bank using a range of 0-15, and \ increment SC to point to the next indicator (the \ next energy bank down) LDY P \ Restore the indicator number into Y INY \ Increment the indicator number CPY #4 \ Check to see if we have drawn the last energy bank BNE DLL9 \ Loop back to DLL9 if we have more banks to draw, \ otherwise we are done
Name: DIALS (Part 4 of 4) [View individually] Type: Subroutine Category: Dashboard Summary: Update the dashboard: shields, fuel, laser & cabin temp, altitude
LDA #&78 \ Set SC(1 0) = &7816, which is the screen address for STA SC+1 \ the character block containing the left end of the LDA #16 \ top indicator in the left part of the dashboard, the STA SC \ one showing the forward shield LDA FSH \ Draw the forward shield indicator using a range of JSR DILX \ 0-255, and increment SC to point to the next indicator \ (the aft shield) LDA ASH \ Draw the aft shield indicator using a range of 0-255, JSR DILX \ and increment SC to point to the next indicator (the \ fuel level) LDA QQ14 \ Draw the fuel level indicator using a range of 0-63, JSR DILX+2 \ and increment SC to point to the next indicator (the \ cabin temperature) JSR PZW \ Call PZW to set A to the colour for dangerous values \ and X to the colour for safe values STX K+1 \ Set K+1 (the colour we should show for low values) to \ X (the colour to use for safe values) STA K \ Set K+1 (the colour we should show for high values) to \ A (the colour to use for dangerous values) \ The above sets the following indicators to show red \ for high values and yellow/white for low values, which \ we use for the cabin and laser temperature bars LDX #11 \ Set T1 to 11, the threshold at which we change the STX T1 \ cabin and laser temperature indicators' colours LDA CABTMP \ Draw the cabin temperature indicator using a range of JSR DILX \ 0-255, and increment SC to point to the next indicator \ (the laser temperature) LDA GNTMP \ Draw the laser temperature indicator using a range of JSR DILX \ 0-255, and increment SC to point to the next indicator \ (the altitude) LDA #240 \ Set T1 to 240, the threshold at which we change the STA T1 \ altitude indicator's colour. As the altitude has a \ range of 0-255, pixel 16 will not be filled in, and \ 240 would change the colour when moving between pixels \ 15 and 16, so this effectively switches off the colour \ change for the altitude indicator STA K+1 \ Set K+1 (the colour we should show for low values) to \ 240, or &F0 (dashboard colour 2, yellow/white), so the \ altitude indicator always shows in this colour LDA ALTIT \ Draw the altitude indicator using a range of 0-255 JSR DILX JMP COMPAS \ We have now drawn all the indicators, so jump to \ COMPAS to draw the compass, returning from the \ subroutine using a tail call
Name: PZW [View individually] Type: Subroutine Category: Dashboard Summary: Fetch the current dashboard colours, to support flashing
Set A and X to the colours we should use for indicators showing dangerous and safe values respectively. This enables us to implement flashing indicators, which is one of the game's configurable options. If flashing is enabled, the colour returned in A (dangerous values) will be red for 8 iterations of the main loop, and yellow/white for the next 8, before going back to red. If we always use PZW to decide which colours we should use when updating indicators, flashing colours will be automatically taken care of for us. The values returned are &F0 for yellow/white and &0F for red. These are mode 5 bytes that contain 4 pixels, with the colour of each pixel given in two bits, the high bit from the first nibble (bits 4-7) and the low bit from the second nibble (bits 0-3). So in &F0 each pixel is %10, or colour 2 (yellow or white, depending on the dashboard palette), while in &0F each pixel is %01, or colour 1 (red). Returns: A The colour to use for indicators with dangerous values X The colour to use for indicators with safe values
.PZW LDX #&F0 \ Set X to dashboard colour 2 (yellow/white) LDA MCNT \ A will be non-zero for 8 out of every 16 main loop AND #%00001000 \ counts, when bit 4 is set, so this is what we use to \ flash the "danger" colour AND FLH \ A will be zeroed if flashing colours are disabled BEQ P%+4 \ If A is zero, skip to the LDA instruction below TXA \ Otherwise flashing colours are enabled and it's the \ main loop iteration where we flash them, so set A to \ colour 2 (yellow/white) and use the BIT trick below to \ return from the subroutine EQUB &2C \ Skip the next instruction by turning it into \ &2C &A9 &0F, or BIT &0FA9, which does nothing bar \ affecting the flags LDA #&0F \ Set A to dashboard colour 1 (red) RTS \ Return from the subroutine }
Name: DILX [View individually] Type: Subroutine Category: Dashboard Summary: Update a bar-based indicator on the dashboard
The range of values shown on the indicator depends on which entry point is called. For the default entry point of DILX, the range is 0-255 (as the value passed in A is one byte). The other entry points are shown below. Arguments: A The value to be shown on the indicator (so the larger the value, the longer the bar) T1 The threshold at which we change the indicator's colour from the low value colour to the high value colour. The threshold is in pixels, so it should have a value from 0-16, as each bar indicator is 16 pixels wide K The colour to use when A is a high value, as a 4-pixel mode 5 character row byte K+1 The colour to use when A is a low value, as a 4-pixel mode 5 character row byte SC(1 0) The screen address of the first character block in the indicator Other entry points: DILX+2 The range of the indicator is 0-64 (for the fuel indicator) DIL-1 The range of the indicator is 0-32 (for the speed indicator) DIL The range of the indicator is 0-16 (for the energy banks)
.DILX { LSR A \ If we call DILX, we set A = A / 16, so A is 0-15 LSR A LSR A \ If we call DILX+2, we set A = A / 4, so A is 0-15 LSR A \ If we call DIL-1, we set A = A / 2, so A is 0-15 .^DIL \ If we call DIL, we leave A alone, so A is 0-15 STA Q \ Store the indicator value in Q, now reduced to 0-15, \ which is the length of the indicator to draw in pixels LDX #&FF \ Set R = &FF, to use as a mask for drawing each row of STX R \ each character block of the bar, starting with a full \ character's width of 4 pixels CMP T1 \ If A >= T1 then we have passed the threshold where we BCS DL30 \ change bar colour, so jump to DL30 to set A to the \ "high value" colour LDA K+1 \ Set A to K+1, the "low value" colour to use BNE DL31 \ Jump down to DL31 (this BNE is effectively a JMP as A \ will never be zero) .DL30 LDA K \ Set A to K, the "high value" colour to use .DL31 STA COL \ Store the colour of the indicator in COL LDY #2 \ We want to start drawing the indicator on the third \ line in this character row, so set Y to point to that \ row's offset LDX #3 \ Set up a counter in X for the width of the indicator, \ which is 4 characters (each of which is 4 pixel wide, \ to give a total width of 16 pixels) .DL1 LDA Q \ Fetch the indicator value (0-15) from Q into A CMP #4 \ If Q < 4, then we need to draw the end cap of the BCC DL2 \ indicator, which is less than a full character's \ width, so jump down to DL2 to do this SBC #4 \ Otherwise we can draw a 4-pixel wide block, so STA Q \ subtract 4 from Q so it contains the amount of the \ indicator that's left to draw after this character LDA R \ Fetch the shape of the indicator row that we need to \ display from R, so we can use it as a mask when \ painting the indicator. It will be &FF at this point \ (i.e. a full 4-pixel row) .DL5 AND COL \ Fetch the 4-pixel mode 5 colour byte from COL, and \ only keep pixels that have their equivalent bits set \ in the mask byte in A STA (SC),Y \ Draw the shape of the mask on pixel row Y of the \ character block we are processing INY \ Draw the next pixel row, incrementing Y STA (SC),Y INY \ And draw the third pixel row, incrementing Y STA (SC),Y TYA \ Add 6 to Y, so Y is now 8 more than when we started CLC \ this loop iteration, so Y now points to the address ADC #6 \ of the first line of the indicator bar in the next TAY \ character block (as each character is 8 bytes of \ screen memory) DEX \ Decrement the loop counter for the next character \ block along in the indicator BMI DL6 \ If we just drew the last character block then we are \ done drawing, so jump down to DL6 to finish off BPL DL1 \ Loop back to DL1 to draw the next character block of \ the indicator (this BPL is effectively a JMP as A will \ never be negative following the previous BMI) .DL2 EOR #3 \ If we get here then we are drawing the indicator's STA Q \ end cap, so Q is < 4, and this EOR flips the bits, so \ instead of containing the number of indicator columns \ we need to fill in on the left side of the cap's \ character block, Q now contains the number of blank \ columns there should be on the right side of the cap's \ character block LDA R \ Fetch the current mask from R, which will be &FF at \ this point, so we need to turn Q of the columns on the \ right side of the mask to black to get the correct end \ cap shape for the indicator .DL3 ASL A \ Shift the mask left and clear bits 0 and 4, which has AND #%11101111 \ the effect of shifting zeroes from the left into each \ nibble (i.e. xxxx xxxx becomes xxx0 xxx0, which blanks \ out the last column in the 4-pixel mode 5 character \ block) DEC Q \ Decrement the counter for the number of columns to \ blank out BPL DL3 \ If we still have columns to blank out in the mask, \ loop back to DL3 until the mask is correct for the \ end cap PHA \ Store the mask byte on the stack while we use the \ accumulator for a bit LDA #0 \ Change the mask so no bits are set, so the characters STA R \ after the one we're about to draw will be all blank LDA #99 \ Set Q to a high number (99, why not) so we will keep STA Q \ drawing blank characters until we reach the end of \ the indicator row PLA \ Restore the mask byte from the stack so we can use it \ to draw the end cap of the indicator JMP DL5 \ Jump back up to DL5 to draw the mask byte on-screen .DL6 INC SC+1 \ Increment the high byte of SC to point to the next \ character row on-screen (as each row takes up exactly \ one page of 256 bytes) - so this sets up SC to point \ to the next indicator, i.e. the one below the one we \ just drew .DL9 \ This label is not used but is in the original source RTS \ Return from the subroutine }
Name: DIL2 [View individually] Type: Subroutine Category: Dashboard Summary: Update the roll or pitch indicator on the dashboard
The indicator can show a vertical bar in 16 positions, with a value of 8 showing the bar in the middle of the indicator. In practice this routine is only ever called with A in the range 1 to 15, so the vertical bar never appears in the leftmost position (though it does appear in the rightmost). Arguments: A The offset of the vertical bar to show in the indicator, from 0 at the far left, to 8 in the middle, and 15 at the far right Returns: C flag C flag is set
.DIL2 { LDY #1 \ We want to start drawing the vertical indicator bar on \ the second line in the indicator's character block, so \ set Y to point to that row's offset STA Q \ Store the offset of the vertical bar to draw in Q \ We are now going to work our way along the indicator \ on the dashboard, from left to right, working our way \ along one character block at a time. Y will be used as \ a pixel row counter to work our way through the \ character blocks, so each time we draw a character \ block, we will increment Y by 8 to move on to the next \ block .DLL10 SEC \ Set A = Q - 4, so that A contains the offset of the LDA Q \ vertical bar from the start of this character block SBC #4 BCS DLL11 \ If Q >= 4 then the character block we are drawing does \ not contain the vertical indicator bar, so jump to \ DLL11 to draw a blank character block LDA #&FF \ Set A to a high number (and &FF is as high as they go) LDX Q \ Set X to the offset of the vertical bar, which is \ within this character block as Q < 4 STA Q \ Set Q to a high number (&FF, why not) so we will keep \ drawing blank characters after this one until we reach \ the end of the indicator row LDA CTWOS,X \ CTWOS is a table of ready-made 1-pixel mode 5 bytes, \ just like the TWOS and TWOS2 tables for mode 4 (see \ the PIXEL routine for details of how they work). This \ fetches a mode 5 1-pixel byte with the pixel position \ at X, so the pixel is at the offset that we want for \ our vertical bar AND #&F0 \ The 4-pixel mode 5 colour byte &F0 represents four \ pixels of colour %10 (3), which is yellow in the \ normal dashboard palette and white if we have an \ escape pod fitted. We AND this with A so that we only \ keep the pixel that matches the position of the \ vertical bar (i.e. A is acting as a mask on the \ 4-pixel colour byte) BNE DLL12 \ If A is non-zero then we have something to draw, so \ jump to DLL12 to skip the following and move on to the \ drawing .DLL11 \ If we get here then we want to draw a blank for this \ character block STA Q \ Update Q with the new offset of the vertical bar, so \ it becomes the offset after the character block we \ are about to draw LDA #0 \ Change the mask so no bits are set, so all of the \ character blocks we display from now on will be blank .DLL12 STA (SC),Y \ Draw the shape of the mask on pixel row Y of the \ character block we are processing INY \ Draw the next pixel row, incrementing Y STA (SC),Y INY \ And draw the third pixel row, incrementing Y STA (SC),Y INY \ And draw the fourth pixel row, incrementing Y STA (SC),Y TYA \ Add 5 to Y, so Y is now 8 more than when we started CLC \ this loop iteration, so Y now points to the address ADC #5 \ of the first line of the indicator bar in the next TAY \ character block (as each character is 8 bytes of \ screen memory) CPY #30 \ If Y < 30 then we still have some more character BCC DLL10 \ blocks to draw, so loop back to DLL10 to display the \ next one along INC SC+1 \ Increment the high byte of SC to point to the next \ character row on-screen (as each row takes up exactly \ one page of 256 bytes) - so this sets up SC to point \ to the next indicator, i.e. the one below the one we \ just drew RTS \ Return from the subroutine }
Name: TVT1 [View individually] Type: Variable Category: Screen mode Summary: Palette data for space and the two dashboard colour schemes
Palette bytes for use with the split-screen mode (see IRQ1 below for more details). Palette data is given as a set of bytes, with each byte mapping a logical colour to a physical one. In each byte, the logical colour is given in bits 4-7 and the physical colour in bits 0-3. See p.379 of the Advanced User Guide for details of how palette mapping works, as in modes 4 and 5 we have to do multiple palette commands to change the colours correctly, and the physical colour value is EOR'd with 7, just to make things even more confusing. Similarly, the palette at TVT1+16 is for the monochrome space view, where logical colour 1 is mapped to physical colour 0 EOR 7 = 7 (white), and logical colour 0 is mapped to physical colour 7 EOR 7 = 0 (black). Each of these mappings requires six calls to SHEILA+&21 - see p.379 of the Advanced User Guide for an explanation. The mode 5 palette table has two blocks which overlap. The block used depends on whether or not we have an escape pod fitted. The block at TVT1 is used for the standard dashboard colours, while TVT1+8 is used for the dashboard when an escape pod is fitted. The colours are as follows: Normal (TVT1) Escape pod (TVT1+8) Colour 0 Black Black Colour 1 Red Red Colour 2 Yellow White Colour 3 Green Cyan
.TVT1 EQUB &D4, &C4 \ This block of palette data is used to create two EQUB &94, &84 \ palettes used in three different places, all of them EQUB &F5, &E5 \ redefining four colours in mode 5: EQUB &B5, &A5 \ \ 12 bytes from TVT1 (i.e. the first 6 rows): applied EQUB &76, &66 \ when the T1 timer runs down at the switch from the EQUB &36, &26 \ space view to the dashboard, so this is the standard \ dashboard palette EQUB &E1, &F1 \ EQUB &B1, &A1 \ 8 bytes from TVT1+8 (i.e. the last 4 rows): applied \ when the T1 timer runs down at the switch from the \ space view to the dashboard, and we have an escape \ pod fitted, so this is the escape pod dashboard \ palette \ \ 8 bytes from TVT1+8 (i.e. the last 4 rows): applied \ at vertical sync in LINSCN when HFX is non-zero, to \ create the hyperspace effect in LINSCN (where the \ whole screen is switched to mode 5 at vertical sync) EQUB &F0, &E0 \ 12 bytes of palette data at TVT1+16, used to set the EQUB &B0, &A0 \ mode 4 palette in LINSCN when we hit vertical sync, EQUB &D0, &C0 \ so the palette is set to monochrome when we start to EQUB &90, &80 \ draw the first row of the screen EQUB &77, &67 EQUB &37, &27
Name: IRQ1 [View individually] Type: Subroutine Category: Screen mode Summary: The main screen-mode interrupt handler (IRQ1V points here)
The main interrupt handler, which implements Elite's split-screen mode. IRQ1V is set to point to IRQ1 by elite-loader.asm.
{ .LINSCN \ This is called from the interrupt handler below, at \ the start of each vertical sync (i.e. when the screen \ refresh starts) LDA #30 \ Set the line scan counter to a non-zero value, so STA DL \ routines like WSCAN can set DL to 0 and then wait for \ it to change to non-zero to catch the vertical sync STA SHEILA+&44 \ Set 6522 System VIA T1C-L timer 1 low-order counter \ (SHEILA &44) to 30 LDA #VSCAN \ Set 6522 System VIA T1C-L timer 1 high-order counter STA SHEILA+&45 \ (SHEILA &45) to VSCAN (57) to start the T1 counter \ counting down from 14622 at a rate of 1 MHz LDA HFX \ If HFX is non-zero, jump to VNT1 to set the mode 5 BNE VNT1 \ palette instead of switching to mode 4, which will \ have the effect of blurring and colouring the top \ screen. This is how the white hyperspace rings turn \ to colour when we do a hyperspace jump, and is \ triggered by setting HFX to 1 in routine LL164 LDA #%00001000 \ Set Video ULA control register (SHEILA+&20) to STA SHEILA+&20 \ %00001000, which is the same as switching to mode 4 \ (i.e. the top part of the screen) but with no cursor .VNT3 LDA TVT1+16,Y \ Copy the Y-th palette byte from TVT1+16 to SHEILA+&21 STA SHEILA+&21 \ to map logical to actual colours for the bottom part \ of the screen (i.e. the dashboard) DEY \ Decrement the palette byte counter BPL VNT3 \ Loop back to VNT3 until we have copied all the \ palette bytes LDA LASCT \ Decrement the value of LASCT, but if we go too far BEQ P%+5 \ and it becomes negative, bump it back up again (this DEC LASCT \ controls the pulsing of pulse lasers) LDA SVN \ If SVN is non-zero, we are in the process of saving BNE jvec \ the commander file, so jump to jvec to pass control \ to the next interrupt handler, so we don't break file \ saving by blocking the interrupt chain PLA \ Otherwise restore Y from the stack TAY LDA SHEILA+&41 \ Read 6522 System VIA input register IRA (SHEILA &41) LDA &FC \ Set A to the interrupt accumulator save register, \ which restores A to the value it had on entering the \ interrupt RTI \ Return from interrupts, so this interrupt is not \ passed on to the next interrupt handler, but instead \ the interrupt terminates here .^IRQ1 TYA \ Store Y on the stack PHA LDY #11 \ Set Y as a counter for 12 bytes, to use when setting \ the dashboard palette below LDA #%00000010 \ Read the 6522 System VIA status byte bit 1, which is BIT SHEILA+&4D \ set if vertical sync has occurred on the video system BNE LINSCN \ If we are on the vertical sync pulse, jump to LINSCN \ to set up the timers to enable us to switch the \ screen mode between the space view and dashboard BVC jvec \ Read the 6522 System VIA status byte bit 6, which is \ set if timer 1 has timed out. We set the timer in \ LINSCN above, so this means we only run the next bit \ if the screen redraw has reached the boundary between \ the mode 4 and mode 5 screens (i.e. the top of the \ dashboard). Otherwise bit 6 is clear and we aren't at \ the boundary, so we jump to jvec to pass control to \ the next interrupt handler ASL A \ Double the value in A to 4 STA SHEILA+&20 \ Set Video ULA control register (SHEILA+&20) to \ %00000100, which is the same as switching to mode 5, \ (i.e. the bottom part of the screen) but with no \ cursor LDA ESCP \ If escape pod fitted, jump to VNT1 to set the mode 5 BNE VNT1 \ palette differently (so the dashboard is a different \ colour if we have an escape pod) LDA TVT1,Y \ Copy the Y-th palette byte from TVT1 to SHEILA+&21 STA SHEILA+&21 \ to map logical to actual colours for the bottom part \ of the screen (i.e. the dashboard) DEY \ Decrement the palette byte counter BPL P%-7 \ Loop back to the LDA TVT1,Y instruction until we have \ copied all the palette bytes .jvec PLA \ Restore Y from the stack TAY JMP (VEC) \ Jump to the address in VEC, which was set to the \ original IRQ1V vector by elite-loader.asm, so this \ instruction passes control to the next interrupt \ handler .VNT1 LDY #7 \ Set Y as a counter for 8 bytes LDA TVT1+8,Y \ Copy the Y-th palette byte from TVT1+8 to SHEILA+&21 STA SHEILA+&21 \ to map logical to actual colours for the bottom part \ of the screen (i.e. the dashboard) DEY \ Decrement the palette byte counter BPL VNT1+2 \ Loop back to the LDA TVT1+8,Y instruction until we \ have copied all the palette bytes BMI jvec \ Jump up to jvec to pass control to the next interrupt \ handler (this BMI is effectively a JMP as we didn't \ loop back with the BPL above, so BMI is always true) }
Name: ESCAPE [View individually] Type: Subroutine Category: Flight Summary: Launch our escape pod
This routine displays our doomed Cobra Mk III disappearing off into the ether before arranging our replacement ship. Called when we press Escape during flight and have an escape pod fitted.
.ESCAPE { LDA MJ \ Store the value of MJ on the stack (the "are we in PHA \ witchspace?" flag) JSR RES2 \ Reset a number of flight variables and workspaces LDX #CYL \ Set the current ship type to a Cobra Mk III, so we STX TYPE \ can show our ship disappear into the distance when we \ eject in our pod JSR FRS1 \ Call FRS1 to launch the Cobra Mk III straight ahead, \ like a missile launch, but with our ship instead LDA #8 \ Set the Cobra's byte #27 (speed) to 8 STA INWK+27 LDA #194 \ Set the Cobra's byte #30 (pitch counter) to 194, so it STA INWK+30 \ pitches as we pull away LSR A \ Set the Cobra's byte #32 (AI flag) to %01100001, so it STA INWK+32 \ has no AI, and we can use this value as a counter to \ do the following loop 97 times .ESL1 JSR MVEIT \ Call MVEIT to move the Cobra in space JSR LL9 \ Call LL9 to draw the Cobra on-screen DEC INWK+32 \ Decrement the counter in byte #32 BNE ESL1 \ Loop back to keep moving the Cobra until the AI flag \ is 0, which gives it time to drift away from our pod JSR SCAN \ Call SCAN to remove all ships from the scanner JSR RESET \ Call RESET to reset our ship and various controls PLA \ Restore the witchspace flag from before the escape pod BEQ P%+5 \ launch, and if we were in normal space, skip the \ following instruction JMP DEATH \ Launching an escape pod in witchspace is fatal, so \ jump to DEATH to begin the funeral and return from the \ subroutine using a tail call LDX #16 \ We lose all our cargo when using our escape pod, so \ up a counter in X so we can zero the 17 cargo slots \ in QQ20 .ESL2 STA QQ20,X \ Set the X-th byte of QQ20 to zero (as we know A = 0 \ from the BEQ above), so we no longer have any of item \ type X in the cargo hold DEX \ Decrement the counter BPL ESL2 \ Loop back to ESL2 until we have emptied the entire \ cargo hold STA FIST \ Launching an escape pod also clears our criminal \ record, so set our legal status in FIST to 0 ("clean") STA ESCP \ The escape pod is a one-use item, so set ESCP to 0 so \ we no longer have one fitted LDA #70 \ Our replacement ship is delivered with a full tank of STA QQ14 \ fuel, so set the current fuel level in QQ14 to 70, or \ 7.0 light years JMP BAY \ Go to the docking bay (i.e. show the Status Mode \ screen) and return from the subroutine with a tail \ call }
Save output/ELTB.bin
PRINT "ELITE B" PRINT "Assembled at ", ~CODE_B% PRINT "Ends at ", ~P% PRINT "Code size is ", ~(P% - CODE_B%) PRINT "Execute at ", ~LOAD% PRINT "Reload at ", ~LOAD_B% PRINT "S.ELTB ", ~CODE_B%, " ", ~P%, " ", ~LOAD%, " ", ~LOAD_B% SAVE "output/ELTB.bin", CODE_B%, P%, LOAD%