Skip to navigation

Elite on the BBC Micro

Loader 1 source (6502SP version)

6502 SECOND PROCESSOR ELITE I/O LOADER (PART 1) SOURCE 6502 Second Processor Elite was written by Ian Bell and David Braben and is copyright Acornsoft 1985 The code on this site is identical to the version released on Ian Bell's personal website at http://www.elitehomepage.org/ The commentary is copyright Mark Moxon, and any misunderstandings or mistakes in the documentation are entirely my fault The terminology and notations used in this commentary are explained at https://www.bbcelite.com/about_site/terminology_used_in_this_commentary.html The deep dive articles referred to in this commentary can be found at https://www.bbcelite.com/deep_dives
This source file produces the following binary file: * output/ELITE.bin
INCLUDE "sources/elite-header.h.asm" _SOURCE_DISC = (_RELEASE = 1) _SNG45 = (_RELEASE = 2) _EXECUTIVE = (_RELEASE = 3)
Configuration variables
N% = 77 \ N% is set to the number of bytes in the VDU table, so \ we can loop through them in the loader below OSWRCH = &FFEE \ The address for the OSWRCH routine OSBYTE = &FFF4 \ The address for the OSBYTE routine OSWORD = &FFF1 \ The address for the OSWORD routine OSCLI = &FFF7 \ The address for the OSCLI routine VIA = &FE00 \ Memory-mapped space for accessing internal hardware, \ such as the video ULA, 6845 CRTC and 6522 VIAs (also \ known as SHEILA) IRQ1V = &0204 \ The IRQ1V vector that we intercept to implement the \ split-sceen mode
Name: ZP [View individually] Type: Workspace [Compare versions] Address: &0090 to &0095 Category: Workspaces Summary: Important variables used by the loader
ORG &0090 .ZP SKIP 2 \ Stores addresses used for moving content around .P SKIP 1 \ Temporary storage, used in a number of places .Q SKIP 1 \ Temporary storage, used in a number of places .YY SKIP 1 \ Temporary storage, used in a number of places .T SKIP 1 \ Temporary storage, used in a number of places
ELITE LOADER
IF _SNG45 OR _EXECUTIVE CODE% = &1FDC LOAD% = &1FDC ELIF _SOURCE_DISC CODE% = &2000 LOAD% = &2000 ENDIF ORG CODE% IF _SNG45 OR _EXECUTIVE EQUS "Copyright (c) Acornsoft Limited 1985" ENDIF
Name: B% [View individually] Type: Variable [Compare versions] Category: Screen mode Summary: VDU commands for setting the square mode 1 screen Deep dive: The split-screen mode Drawing monochrome pixels in mode 4
This block contains the bytes that get written by OSWRCH to set up the screen mode (this is equivalent to using the VDU statement in BASIC). It defines the whole screen using a square, monochrome mode 1 configuration; the mode 2 part for the dashboard is implemented in the IRQ1 routine. The top part of Elite's screen mode is based on mode 1 but with the following differences: * 64 columns, 31 rows (256 x 248 pixels) rather than 80, 32 * The horizontal sync position is at character 90 rather than 98, which pushes the screen to the right (which centres it as it's not as wide as the normal screen modes) * Screen memory goes from &4000 to &7EFF * The text window is 1 row high and 13 columns wide, and is at (2, 16) * The cursor is disabled This almost-square mode 1 variant makes life a lot easier when drawing to the screen, as there are 256 pixels on each row (or, to put it in screen memory terms, there are two pages of memory per row of pixels). There is also an interrupt-driven routine that switches the bytes-per-pixel setting from that of mode 1 to that of mode 2, when the raster reaches the split between the space view and the dashboard. See the deep dive on "The split-screen mode" for details.
.B% EQUB 22, 1 \ Switch to screen mode 1 EQUB 28 \ Define a text window as follows: EQUB 2, 17, 15, 16 \ \ * Left = 2 \ * Right = 15 \ * Top = 16 \ * Bottom = 17 \ \ i.e. 1 row high, 13 columns wide at (2, 16) EQUB 23, 0, 6, 31 \ Set 6845 register R6 = 31 EQUB 0, 0, 0 \ EQUB 0, 0, 0 \ This is the "vertical displayed" register, and sets \ the number of displayed character rows to 31. For \ comparison, this value is 32 for standard modes 1 and \ 2, but we claw back the last row for storing code just \ above the end of screen memory EQUB 23, 0, 12, &08 \ Set 6845 register R12 = &08 and R13 = &00 EQUB 0, 0, 0 \ EQUB 0, 0, 0 \ This sets 6845 registers (R12 R13) = &0800 to point EQUB 23, 0, 13, &00 \ to the start of screen memory in terms of character EQUB 0, 0, 0 \ rows. There are 8 pixel lines in each character row, EQUB 0, 0, 0 \ so to get the actual address of the start of screen \ memory, we multiply by 8: \ \ &0800 * 8 = &4000 \ \ So this sets the start of screen memory to &4000 EQUB 23, 0, 1, 64 \ Set 6845 register R1 = 64 EQUB 0, 0, 0 \ EQUB 0, 0, 0 \ This is the "horizontal displayed" register, which \ defines the number of character blocks per horizontal \ character row. For comparison, this value is 80 for \ modes 1 and 2, but our custom screen is not as wide at \ only 64 character blocks across EQUB 23, 0, 2, 90 \ Set 6845 register R2 = 90 EQUB 0, 0, 0 \ EQUB 0, 0, 0 \ This is the "horizontal sync position" register, which \ defines the position of the horizontal sync pulse on \ the horizontal line in terms of character widths from \ the left-hand side of the screen. For comparison this \ is 98 for modes 1 and 2, but needs to be adjusted for \ our custom screen's width EQUB 23, 0, 10, 32 \ Set 6845 register R10 = 32 EQUB 0, 0, 0 \ EQUB 0, 0, 0 \ This is the "cursor start" register, so this sets the \ cursor start line at 0, effectively disabling the \ cursor EQUB 23, 0, &87, 34 \ Set 6845 register R7 = 34 EQUB 0, 0, 0 \ EQUB 0, 0, 0 \ This is the "vertical sync position" register, which \ defines the row number where the vertical sync pulse \ is fired. This is aleady set to 34 for mode 1 and 2, \ so I'm not sure what this VDU sequence does, \ especially as the register number has bit 7 set (it's \ &87 rather than 7). More investigation needed!
Name: E% [View individually] Type: Variable [Compare versions] Category: Sound Summary: Sound envelope definitions
This table contains the sound envelope data, which is passed to OSWORD by the FNE macro to create the four sound envelopes used in-game. Refer to chapter 30 of the BBC Micro User Guide for details of sound envelopes and what all the parameters mean. The envelopes are as follows: * Envelope 1 is the sound of our own laser firing * Envelope 2 is the sound of lasers hitting us, or hyperspace * Envelope 3 is the first sound in the two-part sound of us dying, or the second sound in the two-part sound of us making hitting or killing an enemy ship * Envelope 4 is the sound of E.C.M. firing
.E% EQUB 1, 1, 0, 111, -8, 4, 1, 8, 8, -2, 0, -1, 126, 44 EQUB 2, 1, 14, -18, -1, 44, 32, 50, 6, 1, 0, -2, 120, 126 EQUB 3, 1, 1, -1, -3, 17, 32, 128, 1, 0, 0, -1, 1, 1 EQUB 4, 1, 4, -8, 44, 4, 6, 8, 22, 0, 0, -127, 126, 0
Name: FNE [View individually] Type: Macro Category: Sound Summary: Macro definition for defining a sound envelope
The following macro is used to define the four sound envelopes used in the game. It uses OSWORD 8 to create an envelope using the 14 parameters in the the I%-th block of 14 bytes at location E%. This OSWORD call is the same as BBC BASIC's ENVELOPE command. See variable E% for more details of the envelopes themselves.
MACRO FNE I% LDX #LO(E%+I%*14) \ Set (Y X) to point to the I%-th set of envelope data LDY #HI(E%+I%*14) \ in E% LDA #8 \ Call OSWORD with A = 8 to set up sound envelope I% JSR OSWORD ENDMACRO
Name: Elite loader [View individually] Type: Subroutine Category: Loader Summary: Check for a 6502 Second Processor, perform a number of OS calls, set up sound and run the second loader
.ENTRY CLD \ Clear the decimal flag, so we're not in decimal mode IF _SNG45 OR _EXECUTIVE NOP \ In SNG45, the release version of 6502 Second Processor NOP \ Elite, the detection code from the original source is NOP \ disabled and replaced by NOPs NOP \ NOP \ This is also true in the Executive version NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP LDA #234 \ Call OSBYTE with A = 234, X = 0 and Y = &FF, which LDX #0 \ detects whether Tube hardware is present, returning LDY #&FF \ X = 0 (not present) or X = &FF (present) JSR OSBYTE ELIF _SOURCE_DISC LDA #129 \ Call OSBYTE with A = 129, X = 0 and Y = &FF to detect LDX #0 \ the machine type. This call is undocumented and is not LDY #&FF \ the recommended way to determine the machine type JSR OSBYTE \ (OSBYTE 0 is the correct way), but this call returns \ the following: \ \ * X = Y = 0 if this is a BBC Micro with MOS 0.1 \ * X = Y = &FF if this is a BBC Micro with MOS 1.20 TXA \ If X is non-zero then jump to not0, as this is not MOS BNE not0 \ 0.1 TYA \ If Y is non-zero then jump to not0, as this is not MOS BNE not0 \ 0.1 JMP happy \ If we get here then this is a BBC Micro Model B with \ MOS 0.1, so jump to happy to continue the loading \ process (which is a bit odd as Elite doesn't work on \ MOS 0.1, so this just tries to load the game, which \ fails) .not0 \ If we get here, then this is not MOS 0.1. We now jump \ to blap2 only if X = &FF and Y = &FF INX \ Increment X, which will give us zero if X was &FF BNE blap1 \ If X is non-zero, then X was not &FF before the \ increment, so jump to blap1 INY \ Increment Y, which will give us zero if Y was &FF BEQ blap2 \ If Y is now 0, jump to blap2 for further checks .blap1 JMP happy \ If we get here then this is not a BBC Micro with MOS \ 1.20... so we jump to happy to continue the loading \ process, which is again a bit odd, as the game won't \ actually work (if you load this version of the game \ on a Master or BBC B+, it will try to run the game \ rather than giving an error - most odd) \JSR ZZZAP \ These instructions are commented out in the original \BRK \ source \BRK \EQUS " This program only runs on a BBC Micro with 6502 Second Processor" \EQUW &0C0A \BRK .blap2 \ If we get here then this is a BBC Micro with MOS 1.20, \ and we arrive here with X = 0 and Y = 0 LDA #234 \ Call OSBYTE with A = 234, X = 0 and Y = &FF, which DEY \ detects whether Tube hardware is present, returning JSR OSBYTE \ X = 0 (not present) or X = &FF (present) ENDIF TXA \ If X is non-zero (Tube is present) then jump to happy BNE happy \ to continue the loading process JSR ZZZAP \ Otherwise the Tube is not present, and we can't run \ the game, so call ZZZAP to blank out memory BRK \ Execute a BRK instruction to display the following \ system error, and stop everything BRK IF _SNG45 OR _EXECUTIVE EQUB &0A \ Print a line feed EQUB &16, &07 \ VDU 22, 7 (change to mode 7) EQUS "This program needs a 6502 2nd Processor" ELIF _SOURCE_DISC EQUS "This program needs a 6502 Second Processor" ENDIF EQUW &0D0A \ Print a line feed and a carriage return BRK .ZZZAP \ The following blanks out memory, so crackers can't \ just unplug their Tube, run the game, get an error \ message and then poke around in memory to discover \ the loader's secrets LDA #LO(happy) \ Set the low byte of ZP(1 0) to the low byte of happy STA ZP LDX #HI(happy) \ Set X to the high byte of happy, to act as a page \ counter LDY #0 \ Set Y = 0 to act as a byte counter .ZZZAPL STX ZP+1 \ Set the high byte of ZP(1 0) to X, so ZP(1 0) starts \ by pointing to the location happy, and will increase \ by a page every time we increment X STA (ZP),Y \ Store A (we don't care what it contains) in the Y-th \ byte of the block pointed to be ZP(1 0) INY \ Increment the byte counter BNE ZZZAPL \ Loop back until we reach a page boundary INX \ Increment the page counter to point to the next page CPX #(HI(MESS2)+1) \ Loop back until we have filled up to the end of the BNE ZZZAPL \ page containing MESS2, which is at the end of the \ loader code RTS \ Return from the subroutine .happy \ If we get here, then one of the following is true: \ \ * This is a BBC Micro Model B with MOS 0.1 \ (X = Y = 0) \ \ * This is not a BBC Micro with MOS 1.20 \ (X <> &FF and Y <> &FF) \ \ * This is a BBC Micro with MOS 1.20 and the Tube \ (X = Y = &FF and Tube hardware is detected) \ \ The odd thing is that the game only works on the last \ system, so you would think that the first two would \ give an error... but instead, we try to run the game \ and fail, which is all a bit strange \ \ That's what you get for using undocumented calls... LDA #16 \ Call OSBYTE with A = 16 and X = 3 to set the ADC to LDX #3 \ sample 3 channels from the joystick/Bitstik JSR OSBYTE LDA #190 \ Call OSBYTE with A = 190, X = 8 and Y = 0 to set the LDX #8 \ ADC conversion type to 8 bits, for the joystick JSR OSB LDA #200 \ Call OSBYTE with A = 200, X = 3 and Y = 0 to disable LDX #3 \ the ESCAPE key and clear memory if the BREAK key is JSR OSB \ pressed \LDA #144 \ These instructions are commented out in the original \LDX #255 \ source, but they would call OSBYTE with A = 144, \JSR OSB \ X = 255 and Y = 0 to move the screen down one line and \ turn screen interlace on LDA #225 \ Call OSBYTE with A = 225, X = 128 and Y = 0 to set LDX #128 \ the function keys to return ASCII codes for SHIFT-fn JSR OSB \ keys (i.e. add 128) LDA #13 \ Call OSBYTE with A = 13, X = 2 and Y = 0 to disable LDX #2 \ the "character entering buffer" event JSR OSB LDA #LO(B%) \ Set ZP(1 0) to point to the VDU code table at B% STA ZP LDA #HI(B%) STA ZP+1 LDY #0 \ We are now going to send the N% VDU bytes in the table \ at B% to OSWRCH to set up the special mode 1 screen \ that forms the basis for the split-screen mode .LOOP LDA (ZP),Y \ Pass the Y-th byte of the B% table to OSWRCH JSR OSWRCH INY \ Increment the loop counter CPY #N% \ Loop back for the next byte until we have done them BNE LOOP \ all (the number of bytes was set in N% above) LDA #20 \ Call OSBYTE with A = 20, X = 0 and Y = 0 to implode LDX #0 \ the soft character definitions, so they don't take up JSR OSB \ extra memory (by default, having a Second Processor \ present explodes the soft character definitions, so \ this reclaims 6 pages of memory) LDA #4 \ Call OSBYTE with A = 4, X = 1 and Y = 0 to disable LDX #1 \ cursor editing, so the cursor keys return ASCII values JSR OSB \ and can therefore be used in-game LDA #9 \ Call OSBYTE with A = 9, X = 0 and Y = 0 to disable LDX #0 \ flashing colours JSR OSB JSR PLL1 \ Call PLL1 to draw Saturn FNE 0 \ Set up sound envelopes 0-3 using the FNE macro FNE 1 FNE 2 FNE 3 LDX #LO(MESS1) \ Set (Y X) to point to MESS1 ("DIR E") LDY #HI(MESS1) JSR OSCLI \ Call OSCLI to run the OS command in MESS1, which \ changes the disc directory to E LDX #LO(MESS2) \ Set (Y X) to point to MESS2 ("R.I.ELITEa") LDY #HI(MESS2) JMP OSCLI \ Call OSCLI to run the OS command in MESS2, which *RUNs \ the second loader in I.ELITEa, returning from the \ subroutine using a tail call
Name: PLL1 [View individually] Type: Subroutine [Compare versions] Category: Drawing planets Summary: Draw Saturn on the loading screen Deep dive: Drawing Saturn on the loading screen
.PLL1 \ The following loop iterates CNT(1 0) times, i.e. &300 \ or 768 times, and draws the planet part of the \ loading screen's Saturn LDA VIA+&44 \ Read the 6522 System VIA T1C-L timer 1 low-order STA RAND+1 \ counter (SHEILA &44), which increments 1000 times a \ second so this will be pretty random, and store it in \ RAND+1 among the hard-coded random seeds in RAND JSR DORND \ Set A and X to random numbers, say A = r1 JSR SQUA2 \ Set (A P) = A * A \ = r1^2 STA ZP+1 \ Set ZP(1 0) = (A P) LDA P \ = r1^2 STA ZP JSR DORND \ Set A and X to random numbers, say A = r2 STA YY \ Set YY = A \ = r2 JSR SQUA2 \ Set (A P) = A * A \ = r2^2 TAX \ Set (X P) = (A P) \ = r2^2 LDA P \ Set (A ZP) = (X P) + ZP(1 0) ADC ZP \ STA ZP \ first adding the low bytes TXA \ And then adding the high bytes ADC ZP+1 BCS PLC1 \ If the addition overflowed, jump down to PLC1 to skip \ to the next pixel STA ZP+1 \ Set ZP(1 0) = (A ZP) \ = r1^2 + r2^2 LDA #1 \ Set ZP(1 0) = &4001 - ZP(1 0) - (1 - C) SBC ZP \ = 128^2 - ZP(1 0) STA ZP \ \ (as the C flag is clear), first subtracting the low \ bytes LDA #&40 \ And then subtracting the high bytes SBC ZP+1 STA ZP+1 BCC PLC1 \ If the subtraction underflowed, jump down to PLC1 to \ skip to the next pixel \ If we get here, then both calculations fitted into \ 16 bits, and we have: \ \ ZP(1 0) = 128^2 - (r1^2 + r2^2) \ \ where ZP(1 0) >= 0 JSR ROOT \ Set ZP = SQRT(ZP(1 0)) LDA ZP \ Set X = ZP >> 1 LSR A \ = SQRT(128^2 - (a^2 + b^2)) / 2 TAX LDA YY \ Set A = YY \ = r2 CMP #128 \ If YY >= 128, set the C flag (so the C flag is now set \ to bit 7 of A) ROR A \ Rotate A and set the sign bit to the C flag, so bits \ 6 and 7 are now the same, i.e. A is a random number in \ one of these ranges: \ \ %00000000 - %00111111 = 0 to 63 (r2 = 0 - 127) \ %11000000 - %11111111 = 192 to 255 (r2 = 128 - 255) \ \ The PIX routine flips bit 7 of A before drawing, and \ that makes -A in these ranges: \ \ %10000000 - %10111111 = 128-191 \ %01000000 - %01111111 = 64-127 \ \ so that's in the range 64 to 191 JSR PIX \ Draw a pixel at screen coordinate (X, -A), i.e. at \ \ (ZP / 2, -A) \ \ where ZP = SQRT(128^2 - (r1^2 + r2^2)) \ \ So this is the same as plotting at (x, y) where: \ \ r1 = random number from 0 to 255 \ r1 = random number from 0 to 255 \ (r1^2 + r1^2) < 128^2 \ \ y = r2, squished into 64 to 191 by negation \ \ x = SQRT(128^2 - (r1^2 + r1^2)) / 2 \ \ which is what we want .PLC1 DEC CNT \ Decrement the counter in CNT (the low byte) BNE PLL1 \ Loop back to PLL1 until CNT = 0 DEC CNT+1 \ Decrement the counter in CNT+1 (the high byte) BNE PLL1 \ Loop back to PLL1 until CNT+1 = 0 \ The following loop iterates CNT2(1 0) times, i.e. &1DD \ or 477 times, and draws the background stars on the \ loading screen .PLL2 JSR DORND \ Set A and X to random numbers, say A = r3 TAX \ Set X = A \ = r3 JSR SQUA2 \ Set (A P) = A * A \ = r3^2 STA ZP+1 \ Set ZP+1 = A \ = r3^2 / 256 JSR DORND \ Set A and X to random numbers, say A = r4 STA YY \ Set YY = r4 JSR SQUA2 \ Set (A P) = A * A \ = r4^2 ADC ZP+1 \ Set A = A + r3^2 / 256 \ = r4^2 / 256 + r3^2 / 256 \ = (r3^2 + r4^2) / 256 CMP #&11 \ If A < 17, jump down to PLC2 to skip to the next pixel BCC PLC2 LDA YY \ Set A = r4 JSR PIX \ Draw a pixel at screen coordinate (X, -A), i.e. at \ (r3, -r4), where (r3^2 + r4^2) / 256 >= 17 \ \ Negating a random number from 0 to 255 still gives a \ random number from 0 to 255, so this is the same as \ plotting at (x, y) where: \ \ x = random number from 0 to 255 \ y = random number from 0 to 255 \ (x^2 + y^2) div 256 >= 17 \ \ which is what we want .PLC2 DEC CNT2 \ Decrement the counter in CNT2 (the low byte) BNE PLL2 \ Loop back to PLL2 until CNT2 = 0 DEC CNT2+1 \ Decrement the counter in CNT2+1 (the high byte) BNE PLL2 \ Loop back to PLL2 until CNT2+1 = 0 \ The following loop iterates CNT3(1 0) times, i.e. &333 \ or 819 times, and draws the rings around the loading \ screen's Saturn .PLL3 JSR DORND \ Set A and X to random numbers, say A = r5 STA ZP \ Set ZP = r5 JSR SQUA2 \ Set (A P) = A * A \ = r5^2 STA ZP+1 \ Set ZP+1 = A \ = r5^2 / 256 JSR DORND \ Set A and X to random numbers, say A = r6 STA YY \ Set YY = r6 JSR SQUA2 \ Set (A P) = A * A \ = r6^2 STA T \ Set T = A \ = r6^2 / 256 ADC ZP+1 \ Set ZP+1 = A + r5^2 / 256 STA ZP+1 \ = r6^2 / 256 + r5^2 / 256 \ = (r5^2 + r6^2) / 256 LDA ZP \ Set A = ZP \ = r5 CMP #128 \ If A >= 128, set the C flag (so the C flag is now set \ to bit 7 of ZP, i.e. bit 7 of A) ROR A \ Rotate A and set the sign bit to the C flag, so bits \ 6 and 7 are now the same CMP #128 \ If A >= 128, set the C flag (so again, the C flag is \ set to bit 7 of A) ROR A \ Rotate A and set the sign bit to the C flag, so bits \ 5-7 are now the same, i.e. A is a random number in one \ of these ranges: \ \ %00000000 - %00011111 = 0-31 \ %11100000 - %11111111 = 224-255 \ \ In terms of signed 8-bit integers, this is a random \ number from -32 to 31. Let's call it r7 ADC YY \ Set X = A + YY TAX \ = r7 + r6 JSR SQUA2 \ Set (A P) = r7 * r7 TAY \ Set Y = A \ = r7 * r7 / 256 ADC ZP+1 \ Set A = A + ZP+1 \ = r7^2 / 256 + (r5^2 + r6^2) / 256 \ = (r5^2 + r6^2 + r7^2) / 256 BCS PLC3 \ If the addition overflowed, jump down to PLC3 to skip \ to the next pixel CMP #80 \ If A >= 80, jump down to PLC3 to skip to the next BCS PLC3 \ pixel CMP #32 \ If A < 32, jump down to PLC3 to skip to the next pixel BCC PLC3 TYA \ Set A = Y + T ADC T \ = r7^2 / 256 + r6^2 / 256 \ = (r6^2 + r7^2) / 256 CMP #16 \ If A > 16, skip to PL1 to plot the pixel BCS PL1 LDA ZP \ If ZP is positive (50% chance), jump down to PLC3 to BPL PLC3 \ skip to the next pixel .PL1 LDA YY \ Set A = YY \ = r6 JSR PIX \ Draw a pixel at screen coordinate (X, -A), where: \ \ X = (random -32 to 31) + r6 \ A = r6 \ \ Negating a random number from 0 to 255 still gives a \ random number from 0 to 255, so this is the same as \ plotting at (x, y) where: \ \ r5 = random number from 0 to 255 \ r6 = random number from 0 to 255 \ r7 = r5, squashed into -32 to 31 \ \ x = r5 + r7 \ y = r5 \ \ 32 <= (r5^2 + r6^2 + r7^2) / 256 <= 79 \ Draw 50% fewer pixels when (r6^2 + r7^2) / 256 <= 16 \ \ which is what we want .PLC3 DEC CNT3 \ Decrement the counter in CNT3 (the low byte) BNE PLL3 \ Loop back to PLL3 until CNT3 = 0 DEC CNT3+1 \ Decrement the counter in CNT3+1 (the high byte) BNE PLL3 \ Loop back to PLL3 until CNT3+1 = 0
Name: DORND [View individually] Type: Subroutine Category: Utility routines Summary: Generate random numbers Deep dive: Generating random numbers
Set A and X to random numbers. The C and V flags are also set randomly. This is a simplified version of the DORND routine in the main game code. It swaps the two calculations around and omits the ROL A instruction, but is otherwise very similar. See the DORND routine in the main game code for more details.
.DORND LDA RAND+1 \ r1´ = r1 + r3 + C TAX \ r3´ = r1 ADC RAND+3 STA RAND+1 STX RAND+3 LDA RAND \ X = r2´ = r0 TAX \ A = r0´ = r0 + r2 ADC RAND+2 STA RAND STX RAND+2 RTS \ Return from the subroutine
Name: RAND [View individually] Type: Variable Category: Drawing planets Summary: The random number seed used for drawing Saturn
.RAND EQUD &34785349
Name: SQUA2 [View individually] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate (A P) = A * A
Do the following multiplication of unsigned 8-bit numbers: (A P) = A * A This uses the same approach as routine SQUA2 in the main game code, which itself uses the MU11 routine to do the multiplication. See those routines for more details.
.SQUA2 BPL SQUA \ If A > 0, jump to SQUA EOR #&FF \ Otherwise we need to negate A for the SQUA algorithm CLC \ to work, so we do this using two's complement, by ADC #1 \ setting A = ~A + 1 .SQUA STA Q \ Set Q = A and P = A STA P \ Set P = A LDA #0 \ Set A = 0 so we can start building the answer in A LDY #8 \ Set up a counter in Y to count the 8 bits in P LSR P \ Set P = P >> 1 \ and C flag = bit 0 of P .SQL1 BCC SQ1 \ If C (i.e. the next bit from P) is set, do the CLC \ addition for this bit of P: ADC Q \ \ A = A + Q .SQ1 ROR A \ Shift A right to catch the next digit of our result, \ which the next ROR sticks into the left end of P while \ also extracting the next bit of P ROR P \ Add the overspill from shifting A to the right onto \ the start of P, and shift P right to fetch the next \ bit for the calculation into the C flag DEY \ Decrement the loop counter BNE SQL1 \ Loop back for the next bit until P has been rotated \ all the way RTS \ Return from the subroutine
Name: PIX [View individually] Type: Subroutine [Compare versions] Category: Drawing pixels Summary: Draw a single pixel at a specific coordinate
Draw a pixel at screen coordinate (X, -A). The sign bit of A gets flipped before drawing, and then the routine uses the same approach as the PIXEL routine in the main game code, except it plots a single pixel from TWOS instead of a two pixel dash from TWOS2. This applies to the top part of the screen (the four-colour mode 1 space view). See the PIXEL routine in the main game code for more details. Arguments: X The screen x-coordinate of the pixel to draw A The screen y-coordinate of the pixel to draw, negated
.PIX TAY \ Copy A into Y, for use later EOR #%10000000 \ Flip the sign of A LSR A \ Set ZP+1 = &40 + 2 * (A >> 3) LSR A LSR A ASL A ORA #&40 STA ZP+1 TXA \ Set (C ZP) = (X >> 2) * 8 EOR #%10000000 \ AND #%11111100 \ i.e. the C flag contains bit 8 of the calculation ASL A STA ZP BCC P%+4 \ If the C flag is set, i.e. bit 8 of the above INC ZP+1 \ calculation was a 1, increment ZP+1 so that ZP(1 0) \ points to the second page in this character row (i.e. \ the right half of the row) TYA \ Set Y = Y AND %111 AND #%00000111 TAY TXA \ Set X = X AND %111 AND #%00000111 TAX LDA TWOS,X \ Fetch a pixel from TWOS and poke it into ZP+Y STA (ZP),Y RTS \ Return from the subroutine
Name: TWOS [View individually] Type: Variable [Compare versions] Category: Drawing pixels Summary: Ready-made single-pixel character row bytes for mode 1
Ready-made bytes for plotting one-pixel points in mode 1 (the top part of the split screen). See the PIX routine for details.
.TWOS EQUB %10000000 EQUB %01000000 EQUB %00100000 EQUB %00010000 EQUB %00001000 EQUB %00000100 EQUB %00000010 EQUB %00000001
Name: CNT [View individually] Type: Variable [Compare versions] Category: Drawing planets Summary: A counter for use in drawing Saturn's planetary body
Defines the number of iterations of the PLL1 loop, which draws the planet part of the loading screen's Saturn.
.CNT EQUW &0300 \ The number of iterations of the PLL1 loop (768)
Name: CNT2 [View individually] Type: Variable Category: Drawing planets Summary: A counter for use in drawing Saturn's background stars
Defines the number of iterations of the PLL2 loop, which draws the background stars on the loading screen.
.CNT2 EQUW &01DD \ The number of iterations of the PLL2 loop (477)
Name: CNT3 [View individually] Type: Variable [Compare versions] Category: Drawing planets Summary: A counter for use in drawing Saturn's rings
Defines the number of iterations of the PLL3 loop, which draws the rings around the loading screen's Saturn.
.CNT3 EQUW &0333 \ The number of iterations of the PLL3 loop (819)
Name: ROOT [View individually] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate ZP = SQRT(ZP(1 0))
Calculate the following square root: ZP = SQRT(ZP(1 0)) This routine is identical to LL5 in the main game code - it even has the same label names. The only difference is that LL5 calculates Q = SQRT(R Q), but apart from the variables used, the instructions are identical, so see the LL5 routine in the main game code for more details on the algorithm used here.
.ROOT LDY ZP+1 \ Set (Y Q) = ZP(1 0) LDA ZP STA Q \ So now to calculate ZP = SQRT(Y Q) LDX #0 \ Set X = 0, to hold the remainder STX ZP \ Set ZP = 0, to hold the result LDA #8 \ Set P = 8, to use as a loop counter STA P .LL6 CPX ZP \ If X < ZP, jump to LL7 BCC LL7 BNE LL8 \ If X > ZP, jump to LL8 CPY #64 \ If Y < 64, jump to LL7 with the C flag clear, BCC LL7 \ otherwise fall through into LL8 with the C flag set .LL8 TYA \ Set Y = Y - 64 SBC #64 \ TAY \ This subtraction will work as we know C is set from \ the BCC above, and the result will not underflow as we \ already checked that Y >= 64, so the C flag is also \ set for the next subtraction TXA \ Set X = X - ZP SBC ZP TAX .LL7 ROL ZP \ Shift the result in Q to the left, shifting the C flag \ into bit 0 and bit 7 into the C flag ASL Q \ Shift the dividend in (Y S) to the left, inserting TYA \ bit 7 from above into bit 0 ROL A TAY TXA \ Shift the remainder in X to the left ROL A TAX ASL Q \ Shift the dividend in (Y S) to the left TYA ROL A TAY TXA \ Shift the remainder in X to the left ROL A TAX DEC P \ Decrement the loop counter BNE LL6 \ Loop back to LL6 until we have done 8 loops RTS \ Return from the subroutine
Name: OSB [View individually] Type: Subroutine Category: Utility routines Summary: A convenience routine for calling OSBYTE with Y = 0
.OSB LDY #0 \ Call OSBYTE with Y = 0, returning from the subroutine JMP OSBYTE \ using a tail call (so we can call OSB to call OSBYTE \ for when we know we want Y set to 0)
Name: MESS1 [View individually] Type: Variable Category: Loader Summary: The OS command string for changing the disc directory to E
.MESS1 EQUS "DIR E" EQUB 13
Name: MESS2 [View individually] Type: Variable Category: Loader Summary: The OS command string for loading and running the second part of the loader
.MESS2 EQUS "R.I.ELITEa" \ This is short for "*RUN I.ELITEa" EQUB 13
Save output/ELITE.bin
PRINT "S.ELITE ", ~CODE%, " ", ~P%, " ", ~LOAD%, " ", ~LOAD% SAVE "output/ELITE.bin", CODE%, P%, LOAD%