Skip to navigation

Elite on the BBC Micro and NES

Elite F parasite source

[Elite-A]

ELITE F FILE
CODE_F% = P% LOAD_F% = LOAD% + P% - CODE%
Name: SFX [Show more] Type: Variable Category: Sound Summary: Sound data
Context: See this variable on its own page References: This variable is used as follows: * NOS1 uses SFX

Sound data. To make a sound, the NOS1 routine copies the four relevant sound bytes to XX16, and NO3 then makes the sound. The sound numbers are shown in the table, and are always multiples of 8. Generally, sounds are made by calling the NOISE routine with the sound number in A. These bytes are passed to OSWORD 7, and are the equivalents to the parameters passed to the SOUND keyword in BASIC. The parameters therefore have these meanings: channel/flush, amplitude (or envelope number if 1-4), pitch, duration For the channel/flush parameter, the high nibble of the low byte is the flush control (where a flush control of 0 queues the sound, and a flush control of 1 makes the sound instantly), while the low nibble of the low byte is the channel number. When written in hexadecimal, the first figure gives the flush control, while the second is the channel (so &13 indicates flush control = 1 and channel = 3). So when we call NOISE with A = 40 to make a long, low beep, then this is effectively what the NOISE routine does: SOUND &13, &F4, &0C, &08 which makes a sound with flush control 1 on channel 3, and with amplitude &F4 (-12), pitch &0C (2) and duration &08 (8). Meanwhile, to make the hyperspace sound, the NOISE routine does this: SOUND &10, &02, &60, &10 which makes a sound with flush control 1 on channel 0, using envelope 2, and with pitch &60 (96) and duration &10 (16). The four sound envelopes (1-4) are set up by the loading process.
.SFX EQUB &12, &01, &00, &10 \ 0 - Lasers fired by us EQUB &12, &02, &2C, &08 \ 8 - We're being hit by lasers EQUB &11, &03, &F0, &18 \ 16 - We died 1 / We made a hit or kill 2 EQUB &10, &F1, &07, &1A \ 24 - We died 2 / We made a hit or kill 1 EQUB &03, &F1, &BC, &01 \ 32 - Short, high beep EQUB &13, &F4, &0C, &08 \ 40 - Long, low beep EQUB &10, &F1, &06, &0C \ 48 - Missile launched / Ship launched from station EQUB &10, &02, &60, &10 \ 56 - Hyperspace drive engaged EQUB &13, &04, &C2, &FF \ 64 - E.C.M. on EQUB &13, &00, &00, &00 \ 72 - E.C.M. off
Name: RESET [Show more] Type: Subroutine Category: Start and end Summary: Reset most variables
Context: See this subroutine on its own page References: This subroutine is called as follows: * RSHIPS calls RESET * TITLE calls RESET

Reset our ship and various controls, recharge shields and energy, and then fall through into RES2 to reset the stardust and the ship workspace at INWK. In this subroutine, this means zero-filling the following locations: * Pages &9, &A, &B, &C and &D * BETA to BETA+8, which covers the following: * BETA, BET1 - Set pitch to 0 * XC, YC - Set text cursor to (0, 0) * QQ22 - Set hyperspace counters to 0 * ECMA - Turn E.C.M. off * ALP1, ALP2 - Set roll signs to 0 It also sets QQ12 to &FF, to indicate we are docked, recharges the shields and energy banks, and then falls through into RES2.
.RESET JSR ZERO \ Zero-fill pages &9, &A, &B, &C and &D, which clears \ the ship data blocks, the ship line heap, the ship \ slots for the local bubble of universe, and various \ flight and ship status variables LDX #8 \ Set up a counter for zeroing BETA through BETA+8 .SAL3 STA BETA,X \ Zero the X-th byte after BETA DEX \ Decrement the loop counter BPL SAL3 \ Loop back for the next byte to zero TXA \ X is now negative - i.e. &FF - so this sets A and QQ12 STA QQ12 \ to &FF to indicate we are docked LDX #2 \ We're now going to recharge both shields and the \ energy bank, which live in the three bytes at FSH, \ ASH (FSH+1) and ENERGY (FSH+2), so set a loop counter \ in X for 3 bytes .REL5 STA FSH,X \ Set the X-th byte of FSH to &FF to charge up that \ shield/bank DEX \ Decrement the loop counter BPL REL5 \ Loop back to REL5 until we have recharged both shields \ and the energy bank \ Fall through into RES2 to reset the stardust and ship \ workspace at INWK
Name: RES2 [Show more] Type: Subroutine Category: Start and end Summary: Reset a number of flight variables and workspaces
Context: See this subroutine on its own page References: This subroutine is called as follows: * BRBR calls RES2 * DEATH calls RES2 * DEATH2 calls RES2 * DOENTRY calls RES2 * DOENTRYS calls RES2 * ESCAPE calls RES2 * INBAY calls RES2 * Main flight loop (Part 9 of 16) calls RES2 * MJP calls RES2 * TT110 calls RES2 * TT18 calls RES2

This is called after we launch from a space station, arrive in a new system after hyperspace, launch an escape pod, or die a cold, lonely death in the depths of space.
Returns: Y Y is set to &FF
.RES2 LDA #NOST \ Reset NOSTM, the number of stardust particles, to the STA NOSTM \ maximum allowed (18) LDX #&FF \ Reset LSX2 and LSY2, the ball line heaps used by the STX LSX2 \ BLINE routine for drawing circles, to &FF, to set the STX LSY2 \ heap to empty STX MSTG \ Reset MSTG, the missile target, to &FF (no target) LDA #128 \ Set the current pitch and roll rates to the mid-point, STA JSTX \ 128 STA JSTY STA ALP2 \ Reset ALP2 (roll sign) and BET2 (pitch sign) STA BET2 \ to negative, i.e. pitch and roll negative ASL A \ This sets A to 0 STA ALP2+1 \ Reset ALP2+1 (flipped roll sign) and BET2+1 (flipped STA BET2+1 \ pitch sign) to positive, i.e. pitch and roll negative STA MCNT \ Reset MCNT (the main loop counter) to 0 STA QQ22+1 \ Set the on-screen hyperspace counter to 0 LDA #3 \ Reset DELTA (speed) to 3 STA DELTA STA ALPHA \ Reset ALPHA (roll angle alpha) to 3 STA ALP1 \ Reset ALP1 (magnitude of roll angle alpha) to 3 LDA SSPR \ Fetch the "space station present" flag, and if we are BEQ P%+5 \ not inside the safe zone, skip the next instruction JSR SPBLB \ Light up the space station bulb on the dashboard LDA ECMA \ Fetch the E.C.M. status flag, and if E.C.M. is off, BEQ yu \ skip the next instruction JSR ECMOF \ Turn off the E.C.M. sound .yu JSR WPSHPS \ Wipe all ships from the scanner JSR ZERO \ Zero-fill pages &9, &A, &B, &C and &D, which clears \ the ship data blocks, the ship line heap, the ship \ slots for the local bubble of universe, and various \ flight and ship status variables LDA #LO(LS%) \ We have reset the ship line heap, so we now point STA SLSP \ SLSP to LS% (the byte below the ship blueprints at D%) LDA #HI(LS%) \ to indicate that the heap is empty STA SLSP+1 JSR DIALS \ Update the dashboard JSR U% \ Call U% to clear the key logger \ Finally, fall through into ZINF to reset the INWK \ ship workspace
Name: ZINF [Show more] Type: Subroutine Category: Universe Summary: Reset the INWK workspace and orientation vectors Deep dive: Orientation vectors
Context: See this subroutine on its own page References: This subroutine is called as follows: * BRIEF calls ZINF * DOKEY_FLIGHT calls ZINF * FRS1 calls ZINF * HAS1 calls ZINF * KS4 calls ZINF * rand_posn calls ZINF * ships_ag calls ZINF * SOLAR calls ZINF

Zero-fill the INWK ship workspace and reset the orientation vectors, with nosev pointing out of the screen, towards us.
Returns: Y Y is set to &FF
.ZINF LDY #NI%-1 \ There are NI% bytes in the INWK workspace, so set a \ counter in Y so we can loop through them LDA #0 \ Set A to 0 so we can zero-fill the workspace .ZI1 STA INWK,Y \ Zero the Y-th byte of the INWK workspace DEY \ Decrement the loop counter BPL ZI1 \ Loop back for the next byte, ending when we have \ zero-filled the last byte at INWK, which leaves Y \ with a value of &FF \ Finally, we reset the orientation vectors as follows: \ \ sidev = (1, 0, 0) \ roofv = (0, 1, 0) \ nosev = (0, 0, -1) \ \ 96 * 256 (&6000) represents 1 in the orientation \ vectors, while -96 * 256 (&E000) represents -1. We \ already set the vectors to zero above, so we just \ need to set up the high bytes of the diagonal values \ and we're done. The negative nosev makes the ship \ point towards us, as the z-axis points into the screen LDA #96 \ Set A to represent a 1 (in vector terms) STA INWK+18 \ Set byte #18 = roofv_y_hi = 96 = 1 STA INWK+22 \ Set byte #22 = sidev_x_hi = 96 = 1 ORA #%10000000 \ Flip the sign of A to represent a -1 STA INWK+14 \ Set byte #14 = nosev_z_hi = -96 = -1 RTS \ Return from the subroutine
Name: msblob [Show more] Type: Subroutine Category: Dashboard Summary: Display the dashboard's missile indicators in green
Context: See this subroutine on its own page References: This subroutine is called as follows: * BR1 (Part 2 of 2) calls msblob * EQSHP calls msblob * FRMIS calls msblob * n_buyship calls msblob * SOS1 calls msblob

Display the dashboard's missile indicators, with all the missiles reset to green/cyan (i.e. not armed or locked).
Returns: X X is set to &FF Y Y is set to 0
.msblob LDX #3 \ Set up a loop counter in X to count through all four \ missile indicators (in Elite-A the missile indicators \ are numbered 0-3 rather than 1-4) .ss LDY #0 \ If X >= NOMSL, then jump down to miss_miss with Y = 0 CPX NOMSL \ to draw the missile indicator at position X in black BCS miss_miss LDY #&EE \ Set the colour of the missile indicator to green/cyan .miss_miss JSR MSBAR \ Draw the missile indicator at position X in colour Y, \ and return with Y = 0 DEX \ Decrement the counter to point to the next missile BPL ss \ Loop back to ss if we still have missiles to draw, \ ending when X = &FF RTS \ Return from the subroutine
Name: me2 [Show more] Type: Subroutine Category: Flight Summary: Remove an in-flight message from the space view
Context: See this subroutine on its own page References: This subroutine is called as follows: * Main game loop (Part 2 of 6) calls me2
.me2 LDA MCH \ Fetch the token number of the current message into A JSR MESS \ Call MESS to print the token, which will remove it \ from the screen as printing uses EOR logic LDA #0 \ Set the delay in DLY to 0, so any new in-flight STA DLY \ messages will be shown instantly JMP me3 \ Jump back into the main spawning loop at me3
Name: Main game loop (Part 2 of 6) [Show more] Type: Subroutine Category: Main loop Summary: Update the main loop counters Deep dive: Program flow of the main game loop Ship data blocks Fixing ship positions
Context: See this subroutine on its own page References: This subroutine is called as follows: * Main game loop (Part 6 of 6) calls via TT100 * me2 calls via me3

This section covers the following: * Update the main loop counters
Other entry points: TT100 The entry point for the start of the main game loop, which calls the main flight loop and the moves into the spawning routine me3 Used by me2 to jump back into the main game loop after printing an in-flight message
.TT100 DEC DLY \ Decrement the delay counter in DLY, so any in-flight \ messages get removed once the counter reaches zero BEQ me2 \ If DLY is now 0, jump to me2 to remove any in-flight \ message from the space view, and once done, return to \ me3 below, skipping the following two instructions BPL me3 \ If DLY is positive, jump to me3 to skip the next \ instruction INC DLY \ If we get here, DLY is negative, so we have gone too \ and need to increment DLY back to 0 .me3 DEC MCNT \ Decrement the main loop counter in MCNT
Name: Main game loop (Part 5 of 6) [Show more] Type: Subroutine Category: Main loop Summary: Cool down lasers, make calls to update the dashboard Deep dive: Program flow of the main game loop The dashboard indicators
Context: See this subroutine on its own page References: This subroutine is called as follows: * Main game loop (Part 6 of 6) calls via MLOOP

This is the first half of the minimal game loop, which we iterate when we are docked. This section covers the following: * Cool down lasers * Make calls to update the dashboard
Other entry points: MLOOP The entry point for the main game loop. This entry point comes after the call to the main flight loop and spawning routines, so it marks the start of the main game loop for when we are docked (as we don't need to call the main flight loop or spawning routines if we aren't in space)
.MLOOP LDX #&FF \ Set the stack pointer to &01FF, which is the standard TXS \ location for the 6502 stack, so this instruction \ effectively resets the stack LDY #2 \ Wait for 2/50 of a second (0.04 seconds), to slow the JSR DELAY \ main loop down a bit JSR TT17 \ Scan the keyboard for the cursor keys or joystick, \ returning the cursor's delta values in X and Y and \ the key pressed in A
Name: Main game loop (Part 6 of 6) [Show more] Type: Subroutine Category: Main loop Summary: Process non-flight key presses (red function keys, docked keys) Deep dive: Program flow of the main game loop
Context: See this subroutine on its own page References: This subroutine is called as follows: * BAY calls via FRCE * TT219 calls via FRCE

This is the second half of the minimal game loop, which we iterate when we are docked. This section covers the following: * Process more key presses (red function keys, docked keys etc.) It also supports joining the main loop with a key already "pressed", so we can jump into the main game loop to perform a specific action. In practice, this is used when we enter the docking bay in BAY to display Status Mode (red key f8), and when we finish buying or selling cargo in BAY2 to jump to the Inventory (red key f9).
Other entry points: FRCE The entry point for the main game loop if we want to jump straight to a specific screen, by pretending to "press" a key, in which case A contains the internal key number of the key we want to "press"
.FRCE JSR TT102 \ Call TT102 to process the key pressed in A LDA QQ12 \ Fetch the docked flag from QQ12 into A BNE MLOOP \ If we are docked, loop back up to MLOOP just above \ to restart the main loop, but skipping all the flight \ and spawning code in the top part of the main loop JMP TT100 \ Otherwise jump to TT100 to restart the main loop from \ the start
Name: DORND [Show more] Type: Subroutine Category: Maths (Arithmetic) Summary: Generate random numbers Deep dive: Generating random numbers Fixing ship positions
Set A and X to random numbers (though note that X is set to the random number that was returned in A the last time DORND was called). The C and V flags are also set randomly. If we want to generate a repeatable sequence of random numbers, when generating explosion clouds, for example, then we call DORND2 to ensure that the value of the C flag on entry doesn't affect the outcome, as otherwise we might not get the same sequence of numbers if the C flag changes.
.DORND LDA RAND \ Calculate the next two values f2 and f3 in the feeder ROL A \ sequence: TAX \ ADC RAND+2 \ * f2 = (f1 << 1) mod 256 + C flag on entry STA RAND \ * f3 = f0 + f2 + (1 if bit 7 of f1 is set) STX RAND+2 \ * C flag is set according to the f3 calculation LDA RAND+1 \ Calculate the next value m2 in the main sequence: TAX \ ADC RAND+3 \ * A = m2 = m0 + m1 + C flag from feeder calculation STA RAND+1 \ * X = m1 STX RAND+3 \ * C and V flags set according to the m2 calculation RTS \ Return from the subroutine
Name: brkd [Show more] Type: Variable Category: Utility routines Summary: The brkd counter for error handling
Context: See this variable on its own page References: This variable is used as follows: * BRBR uses brkd * TITLE uses brkd

This counter starts at zero, and is decremented whenever the BRKV handler at BRBR prints an error message. It is incremented every time an error message is printed out as part of the TITLE routine.
.brkd EQUB 0
Name: INBAY [Show more] Type: Subroutine Category: Loader Summary: Restart the game upon death
Context: See this subroutine on its own page References: This subroutine is called as follows: * DEATH2 calls INBAY
.INBAY LDA #0 \ Set save_lock to 0 to indicate there are no unsaved STA save_lock \ changes in the commander file STA dockedp \ Set dockedp to 0 to indicate that we are docked JSR BRKBK \ Call BRKBK to set BRKV to point to the BRBR routine JSR RES2 \ Reset a number of flight variables and workspaces JMP BR1 \ Jump to BR1 to restart the game
Name: boot_in [Show more] Type: Subroutine Category: Loader Summary: The entry point for the game
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

This routine is at the execution address for the parasite code (&2E93), so it is called when the parasite code in file 2.T is loaded and run.
.boot_in IF _BUG_FIX LDA #&9C \ Send command &9C to the I/O processor: JSR tube_write \ \ savews() \ \ which will switch the MOS character definitions into \ &C000 so we can print properly ENDIF LDA #0 \ Set save_lock to 0 to indicate there are no unsaved STA save_lock \ changes in the commander file STA SSPR \ Set the "space station present" flag to 0, as we are \ no longer in the space station's safe zone STA ECMA \ Set ECMA to 0 to indicate that no E.C.M. is currently \ running STA dockedp \ Set dockedp to 0 to indicate that we are docked JMP BEGIN \ Jump to BEGIN to initialise the configuration \ variables and start the game
Name: BRBR [Show more] Type: Subroutine Category: Utility routines Summary: The standard BRKV handler for the game
Context: See this subroutine on its own page References: This subroutine is called as follows: * BRKBK calls BRBR

This routine is used to display error messages, before restarting the game. When called, it makes a beep and prints the system error message in the block pointed to by (&FD &FE), which is where the MOS will put any system errors. It then waits for a key press and restarts the game. BRKV is set to this routine in the loader, when the docked code is loaded, and at the end of the SVE routine after the disc access menu has been processed. In other words, this is the standard BRKV handler for the game, and it's swapped out to MEBRK for disc access operations only. When it is the BRKV handler, the routine can be triggered using a BRK instruction. The main differences between this routine and the MEBRK handler that is used during disc access operations are that this routine restarts the game rather than returning to the disc access menu, and this handler decrements the brkd counter.
.BRBR DEC brkd \ Decrement the brkd counter BNE BR1 \ If the brkd counter is non-zero, jump to BR1 to \ restart the game JSR RES2 \ Reset a number of flight variables and workspaces \ and fall through into the entry code for the game \ to restart from the title screen
Name: BEGIN [Show more] Type: Subroutine Category: Loader Summary: Initialise the configuration variables and start the game
Context: See this subroutine on its own page References: This subroutine is called as follows: * boot_in calls BEGIN
.BEGIN JSR BRKBK \ Call BRKBK to set BRKV to point to the BRBR routine LDX #(CATF-COMC) \ We start by zeroing all the configuration variables \ between COMC and CATF, to set them to their default \ values, so set a counter in X for CATF - COMC bytes LDA #0 \ Set A = 0 so we can zero the variables .BEL1 STA COMC,X \ Zero the X-th configuration variable DEX \ Decrement the loop counter BPL BEL1 \ Loop back to BEL1 to zero the next byte, until we have \ zeroed them all LDA #127 \ Set BSTK = 127 (positive) to disable the Delta 14B STA BSTK \ joystick \ Fall through into TT170 to start the game
Name: BR1 (Part 1 of 2) [Show more] Type: Subroutine Category: Start and end Summary: Show the "Load New Commander (Y/N)?" screen and start the game
Context: See this subroutine on its own page References: This subroutine is called as follows: * BRBR calls BR1 * DK4 calls BR1 * INBAY calls BR1 * TT102 calls via QU5

BRKV is set to point to BR1 by the loading process. Other entry points: QU5 Restart the game using the last saved commander without asking whether to load a new commander file
.BR1 LDX #10 \ Install ship number 10 (Cobra Mk III) into blueprint LDY #CYL \ position #CYL (11) so it can be shown on the first JSR install_ship \ title screen LDX #19 \ Install ship number 19 (Krait) into blueprint position LDY #KRA \ #KRA (19) so it can be shown on the second title JSR install_ship \ screen LDX #&FF \ Set the stack pointer to &01FF, which is the standard TXS \ location for the 6502 stack, so this instruction \ effectively resets the stack LDX #3 \ Set XC = 3 (set text cursor to column 3) STX XC JSR FX200 \ Disable the ESCAPE key and clear memory if the BREAK \ key is pressed (*FX 200,3) LDX #CYL \ Call TITLE to show a rotating Cobra Mk III (#CYL) and LDA #6 \ token 6 ("LOAD NEW {single cap}COMMANDER {all caps} JSR TITLE \ (Y/N)?{sentence case}{cr}{cr}"), returning with the \ internal number of the key pressed in A CMP #&44 \ Did we press "Y"? If not, jump to QU5, otherwise BNE QU5 \ continue on to load a new commander JSR DFAULT \ Call DFAULT to reset the current commander data block \ to the last saved commander JSR SVE \ Call SVE to load a new commander into the last saved \ commander data block .QU5 JSR DFAULT \ Call DFAULT to reset the current commander data block \ to the last saved commander
Name: BR1 (Part 2 of 2) [Show more] Type: Subroutine Category: Start and end Summary: Show the "Press Fire or Space, Commander" screen and start the game
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

BRKV is set to point to BR1 by the loading process.
JSR msblob \ Reset the dashboard's missile indicators so none of \ them are targeted LDA #7 \ Call TITLE to show a rotating Krait (#KRA) and token LDX #KRA \ 7 ("PRESS SPACE OR FIRE,{single cap}COMMANDER.{cr} JSR TITLE \ {cr}"), returning with the internal number of the key \ pressed in A JSR ping \ Set the target system coordinates (QQ9, QQ10) to the \ current system coordinates (QQ0, QQ1) we just loaded JSR hyp1 \ Arrive in the system closest to (QQ9, QQ10) \ Fall through into the docking bay routine below
Name: BAY [Show more] Type: Subroutine Category: Status Summary: Go to the docking bay (i.e. show the Status Mode screen)
Context: See this subroutine on its own page References: This subroutine is called as follows: * BRP calls BAY * DOENTRY calls BAY * EQSHP calls BAY * menu calls BAY * n_buyship calls BAY * sell_jump calls BAY * ships_ag calls BAY * stay_here calls BAY * trading calls BAY * TT102 calls BAY

We end up here after the start-up process (load commander etc.), as well as after a successful save, an escape pod launch, a successful docking, the end of a cargo sell, and various errors (such as not having enough cash, entering too many items when buying, trying to fit an item to your ship when you already have it, running out of cargo space, and so on).
.BAY LDA #&FF \ Set QQ12 = &FF (the docked flag) to indicate that we STA QQ12 \ are docked LDA #f8 \ Jump into the main loop at FRCE, setting the key JMP FRCE \ that's "pressed" to red key f8 (so we show the Status \ Mode screen)
Name: DFAULT [Show more] Type: Subroutine Category: Start and end Summary: Reset the current commander data block to the last saved commander
Context: See this subroutine on its own page References: This subroutine is called as follows: * BR1 (Part 1 of 2) calls DFAULT
.DFAULT LDX #NT%+8 \ The size of the last saved commander data block is NT% \ bytes, and it is preceded by the 8 bytes of the \ commander name (seven characters plus a carriage \ return). The commander data block at NAME is followed \ by the commander data block, so we need to copy the \ name and data from the "last saved" buffer at NA% to \ the current commander workspace at NAME. So we set up \ a counter in X for the NT% + 8 bytes that we want to \ copy .QUL1 LDA NA%-1,X \ Copy the X-th byte of NA%-1 to the X-th byte of STA NAME-1,X \ NAME-1 (the -1 is because X is counting down from \ NT% + 8 to 1) DEX \ Decrement the loop counter BNE QUL1 \ Loop back for the next byte of the commander data \ block STX QQ11 \ X is 0 by the end of the above loop, so this sets QQ11 \ to 0, which means we will be showing a view without a \ boxed title at the top (i.e. we're going to use the \ screen layout of a space view in the following) \ If the commander check below fails, we keep jumping \ back to here to crash the game with an infinite loop JSR update_pod \ Update the dashboard colours to reflect whether we now \ have an escape pod JSR CHECK \ Call the CHECK subroutine to calculate the checksum \ for the current commander block at NA%+8 and put it \ in A CMP CHK \ Test the calculated checksum against CHK IF _REMOVE_CHECKSUMS NOP \ If we have disabled checksums, then ignore the result NOP \ of the comparison and fall through into the next part ELSE BNE P%-6 \ If the calculated checksum does not match CHK, then \ loop back to repeat the check - in other words, we \ enter an infinite loop here, as the checksum routine \ will keep returning the same incorrect value ENDIF JMP n_load \ Jump to n_load to load the blueprint for the current \ ship type, returning from the subroutine using a tail \ call
Name: TITLE [Show more] Type: Subroutine Category: Start and end Summary: Display a title screen with a rotating ship and prompt
Context: See this subroutine on its own page References: This subroutine is called as follows: * BR1 (Part 1 of 2) calls TITLE * BR1 (Part 2 of 2) calls TITLE

Display the title screen, with a rotating ship and a text token at the bottom of the screen.
Arguments: A The number of the recursive token to show below the rotating ship (see variable QQ18 for details of recursive tokens) X The type of the ship to show (see variable XX21 for a list of ship types)
Returns: X If a key is being pressed, X contains the internal key number, otherwise it contains 0
.TITLE PHA \ Store the token number on the stack for later STX TYPE \ Store the ship type in location TYPE JSR RESET \ Reset our ship so we can use it for the rotating \ title ship LDA #1 \ Clear the top part of the screen, draw a white border, JSR TT66 \ and set the current view type in QQ11 to 1 DEC QQ11 \ Decrement QQ11 to 0, so from here on we are using a \ space view LDA #96 \ Set nosev_z hi = 96 (96 is the value of unity in the STA INWK+14 \ rotation vector) LDA #219 \ Set A = 219 as the distance that the ship starts at STA INWK+7 \ Set z_hi, the high byte of the ship's z-coordinate, \ to 96, which is the distance at which the rotating \ ship starts out before coming towards us LDX #127 \ Set roll counter = 127, so don't dampen the roll and STX INWK+29 \ make the roll direction clockwise STX INWK+30 \ Set pitch counter = 127, so don't dampen the pitch and \ set the pitch direction to dive INX \ Set QQ17 to 128 (so bit 7 is set) to switch to STX QQ17 \ Sentence Case, with the next letter printing in upper \ case LDA TYPE \ Set up a new ship, using the ship type in TYPE JSR NWSHP LDY #6 \ Move the text cursor to column 6 STY XC LDA #30 \ Print recursive token 144 ("---- E L I T E ----") JSR plf \ followed by a newline LDY #6 \ Move the text cursor to column 6 again STY XC INC YC \ Move the text cursor down a row LDA PATG \ If PATG = 0, skip the following two lines, which BEQ awe \ print the author credits (PATG can be toggled by \ pausing the game and pressing "X") LDA #13 \ Print extended token 13 ("BY D.BRABEN & I.BELL") JSR DETOK INC YC \ Move the text cursor down two rows INC YC LDA #3 \ Move the text cursor to column 3 STA XC LDA #114 \ Print extended token 114 (" MODIFIED BY A.J.C.DUGGAN") JSR DETOK .awe LDA brkd \ If brkd = 0, jump to BRBR2 to skip the following, as BEQ BRBR2 \ we do not have a system error message to display INC brkd \ Increment the brkd counter LDA #7 \ Move the text cursor to column 7 STA XC LDA #10 \ Move the text cursor to row 10 STA YC \ The following loop prints out the null-terminated \ message pointed to by (&FD &FE), which is the MOS \ error message pointer - so this prints the error \ message on the next line LDY #0 \ Set Y = 0 to act as a character counter JSR OSWRCH \ Print the character in A (which contains a line feed \ on the first loop iteration), and then any non-zero \ characters we fetch from the error message INY \ Increment the loop counter LDA (&FD),Y \ Fetch the Y-th byte of the block pointed to by \ (&FD &FE), so that's the Y-th character of the message \ pointed to by the MOS error message pointer BNE P%-6 \ If the fetched character is non-zero, loop back to the \ JSR OSWRCH above to print it, and keep looping until \ we fetch a zero (which marks the end of the message) .BRBR2 JSR CLYNS \ Clear the bottom three text rows of the upper screen, \ and move the text cursor to column 1 on row 21, i.e. \ the start of the top row of the three bottom rows. \ It also returns with Y = 0 STY DELTA \ Set DELTA = 0 (i.e. ship speed = 0) STY JSTK \ Set JSTK = 0 (i.e. keyboard, not joystick) PLA \ Restore the recursive token number we stored on the \ stack at the start of this subroutine JSR DETOK \ Print the extended token in A LDA #12 \ Set A to extended token 12 LDX #7 \ Move the text cursor to column 7 STX XC JSR DETOK \ Print extended token 12 ("({single cap}C) ACORNSOFT \ 1984") .TLL2 LDA INWK+7 \ If z_hi (the ship's distance) is 1, jump to TL1 to CMP #1 \ skip the following decrement BEQ TL1 DEC INWK+7 \ Decrement the ship's distance, to bring the ship \ a bit closer to us .TL1 JSR MVEIT \ Move the ship in space according to the orientation \ vectors and the new value in z_hi LDA #128 \ Set z_lo = 128, so the closest the ship gets to us is STA INWK+6 \ z_hi = 1, z_lo = 128, or 256 + 128 = 384 ASL A \ Set A = 0 STA INWK \ Set x_lo = 0, so the ship remains in the screen centre STA INWK+3 \ Set y_lo = 0, so the ship remains in the screen centre JSR LL9 \ Call LL9 to display the ship DEC MCNT \ Decrement the main loop counter JSR scan_fire \ Call scan_fire to check whether the joystick's fire \ button is being pressed, which clears bit 4 in A if \ the fire button is being pressed, and sets it if it \ is not being pressed BEQ TL2 \ If the joystick fire button is pressed, jump to TL2 JSR RDKEY \ Scan the keyboard for a key press BEQ TLL2 \ If no key was pressed, loop back up to move/rotate \ the ship and check again for a key press RTS \ Return from the subroutine .TL2 DEC JSTK \ Joystick fire button was pressed, so set JSTK to &FF \ (it was set to 0 above), to disable keyboard and \ enable joysticks RTS \ Return from the subroutine
Name: CHECK [Show more] Type: Subroutine Category: Save and load Summary: Calculate the checksum for the last saved commander data block Deep dive: Commander save files
Context: See this subroutine on its own page References: This subroutine is called as follows: * DFAULT calls CHECK * SVE calls CHECK

The checksum for the last saved commander data block is saved as part of the commander file, in two places (CHK AND CHK2), to protect against file tampering. This routine calculates the checksum and returns it in A. This algorithm is also implemented in elite-checksum.py.
Returns: A The checksum for the last saved commander data block
.CHECK LDX #NT%-2 \ Set X to the size of the commander data block, less \ 2 (to omit the checksum bytes and the save count) SEC \ Set the C flag to increase the checksum value by 1, \ so the Elite-A checksum is subtly different to the \ standard version's checksum TXA \ Seed the checksum calculation by setting A to the \ size of the commander data block, less 2 \ We now loop through the commander data block, \ starting at the end and looping down to the start \ (so at the start of this loop, the X-th byte is the \ last byte of the commander data block, i.e. the save \ count) .QUL2 ADC NA%+7,X \ Add the X-1-th byte of the data block to A, plus the \ C flag EOR NA%+8,X \ EOR A with the X-th byte of the data block DEX \ Decrement the loop counter BNE QUL2 \ Loop back for the next byte in the calculation, until \ we have added byte #0 and EOR'd with byte #1 of the \ data block RTS \ Return from the subroutine
Name: TRNME [Show more] Type: Subroutine Category: Save and load Summary: Copy the last saved commander's name from INWK to NA%
Context: See this subroutine on its own page References: This subroutine is called as follows: * SVE calls TRNME
.TRNME LDX #7 \ The commander's name can contain a maximum of 7 \ characters, and is terminated by a carriage return, \ so set up a counter in X to copy 8 characters .GTL1 LDA INWK+5,X \ Copy the X-th byte of INWK+5 to the X-th byte of NA% STA NA%,X DEX \ Decrement the loop counter BPL GTL1 \ Loop back until we have copied all 8 bytes \ Fall through into TR1 to copy the name back from NA% \ to INWK. This isn't necessary as the name is already \ there, but it does save one byte, as we don't need an \ RTS here
Name: TR1 [Show more] Type: Subroutine Category: Save and load Summary: Copy the last saved commander's name from NA% to INWK
Context: See this subroutine on its own page References: This subroutine is called as follows: * GTNMEW calls TR1
.TR1 LDX #7 \ The commander's name can contain a maximum of 7 \ characters, and is terminated by a carriage return, \ so set up a counter in X to copy 8 characters .GTL2 LDA NA%,X \ Copy the X-th byte of NA% to the X-th byte of INWK+5 STA INWK+5,X DEX \ Decrement the loop counter BPL GTL2 \ Loop back until we have copied all 8 bytes RTS \ Return from the subroutine
Name: GTNMEW [Show more] Type: Subroutine Category: Save and load Summary: Fetch the name of a commander file to save or load
Context: See this subroutine on its own page References: This subroutine is called as follows: * SVE calls GTNMEW

Get the commander's name for loading or saving a commander file. The name is stored in the INWK workspace and is terminated by a return character (13). If ESCAPE is pressed or a blank name is entered, then the name stored is set to the name from the last saved commander block.
Returns: INWK The full filename, including drive and directory, in the form ":0.E.JAMESON", for example, terminated by a return character (13)
Other entry points: GTNME Skip the delay at the start of the routine
.GTNMEW LDY #8 \ Wait for 8/50 of a second (0.16 seconds) JSR DELAY .GTNME LDX #4 \ First we want to copy the drive and directory part of \ the commander file from S1% (which equals NA%-5), so \ set a counter in x for 5 bytes, as the string is of \ the form ":0.E." .GTL3 LDA NA%-5,X \ Copy the X-th byte from NA%-5 to INWK STA INWK,X DEX \ Decrement the loop counter BPL GTL3 \ Loop back until the whole drive and directory string \ has been copied to INWK to INWK+4 LDA #7 \ The call to MT26 below uses the OSWORD block at RLINE STA RLINE+2 \ to fetch the line, and RLINE+2 defines the maximum \ line length allowed, so this changes the maximum \ length to 7 (as that's the longest commander name \ allowed) LDA #8 \ Print extended token 8 ("{single cap}COMMANDER'S JSR DETOK \ NAME? ") JSR MT26 \ Call MT26 to fetch a line of text from the keyboard \ to INWK+5, with the text length in Y, so INWK now \ contains the full pathname of the file, as in \ ":0.E.JAMESON", for example LDA #9 \ Reset the maximum length in RLINE+2 to the original STA RLINE+2 \ value of 9 TYA \ The OSWORD call returns the length of the commander's \ name in Y, so transfer this to A BEQ TR1 \ If A = 0, no name was entered, so jump to TR1 to copy \ the last saved commander's name from NA% to INWK \ and return from the subroutine there RTS \ Return from the subroutine
Name: MT26 [Show more] Type: Subroutine Category: Text Summary: Fetch a line of text from the keyboard Deep dive: Extended text tokens
Context: See this subroutine on its own page References: This subroutine is called as follows: * DELT calls MT26 * GTNMEW calls MT26 * JMTB calls MT26

If ESCAPE is pressed or a blank name is entered, then an empty string is returned. Returns: Y The size of the entered text, or 0 if none was entered or if ESCAPE was pressed INWK+5 The entered text, terminated by a carriage return C flag Set if ESCAPE was pressed
.MT26 LDA #&8A \ Send command &8A to the I/O processor: JSR tube_write \ \ =write_fe4e(value) \ \ which sets the 6522 System VIA interrupt enable \ register IER (SHEILA &4E) to the specified value and \ returns the value in A when done LDA #%10000001 \ Send the parameter to the I/O processor: JSR tube_write \ \ * value = %10000001 \ \ to clear bit 1 of IER (i.e. enable the CA2 interrupt, \ which comes from the keyboard) JSR tube_read \ Set A to the response from the I/O processor JSR FLKB \ Call FLKB to flush the keyboard buffer LDX #LO(RLINE) \ Set (Y X) to point to the RLINE parameter block LDY #HI(RLINE) LDA #0 \ Call OSWORD with A = 0 to read a line from the current JSR OSWORD \ input stream (i.e. the keyboard) BCC P%+4 \ The C flag will be set if we pressed ESCAPE when \ entering the name, otherwise it will be clear, so \ skip the next instruction if ESCAPE is not pressed LDY #0 \ ESCAPE was pressed, so set Y = 0 (as the OSWORD call \ returns the length of the entered string in Y) LDA #&8A \ Send command &8A to the I/O processor: JSR tube_write \ \ =write_fe4e(value) \ \ which sets the 6522 System VIA interrupt enable \ register IER (SHEILA &4E) to the specified value and \ returns the value in A when done LDA #%00000001 \ Send the parameter to the I/O processor: JSR tube_write \ \ * value = %00000001 \ \ to set bit 1 of IER (i.e. disable the CA2 interrupt, \ which comes from the keyboard) JSR tube_read \ Set A to the response from the I/O processor, so we \ know the register has been set JMP FEED \ Jump to FEED to print a newline, returning from the \ subroutine using a tail call
Name: RLINE [Show more] Type: Variable Category: Text Summary: The OSWORD configuration block used to fetch a line of text from the keyboard
Context: See this variable on its own page References: This variable is used as follows: * GTNMEW uses RLINE * MT26 uses RLINE
.RLINE EQUW INWK+5 \ The address to store the input, so the text entered \ will be stored in INWK+5 as it is typed EQUB 9 \ Maximum line length = 9, as that's the maximum size \ for a commander's name including a directory name EQUB '!' \ Allow ASCII characters from "!" through to "{" in EQUB '{' \ the input
Name: ZERO [Show more] Type: Subroutine Category: Utility routines Summary: Reset the local bubble of universe and ship status
Context: See this subroutine on its own page References: This subroutine is called as follows: * RES2 calls ZERO * RESET calls ZERO

This resets the following workspaces to zero: * WP workspace variables from FRIN to de, which include the ship slots for the local bubble of universe, and various flight and ship status variables (only a portion of the LSX/LSO sun line heap is cleared)
.ZERO LDX #(de-FRIN) \ We're going to zero the UP workspace variables from \ FRIN to de, so set a counter in X for the correct \ number of bytes LDA #0 \ Set A = 0 so we can zero the variables .ZEL2 STA FRIN,X \ Zero the X-th byte of FRIN to de DEX \ Decrement the loop counter BPL ZEL2 \ Loop back to zero the next variable until we have done \ them all RTS \ Return from the subroutine
Name: ZEBC [Show more] Type: Subroutine Category: Utility routines Summary: Zero-fill pages &B and &C
Context: See this subroutine on its own page References: This subroutine is called as follows: * LOD calls ZEBC * SVE calls ZEBC
.ZEBC LDX #&C \ Call ZES1 with X = &C to zero-fill page &C JSR ZES1 DEX \ Decrement X to &B \ Fall through into ZES1 to zero-fill page &B
Name: ZES1 [Show more] Type: Subroutine Category: Utility routines Summary: Zero-fill the page whose number is in X
Context: See this subroutine on its own page References: This subroutine is called as follows: * ZEBC calls ZES1

Arguments: X The page we want to zero-fill
.ZES1 LDY #0 \ If we set Y = SC = 0 and fall through into ZES2 STY SC \ below, then we will zero-fill 255 bytes starting from \ SC - in other words, we will zero-fill the whole of \ page X
Name: ZES2 [Show more] Type: Subroutine Category: Utility routines Summary: Zero-fill a specific page
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

Zero-fill from address (X SC) + Y to (X SC) + &FF.
Arguments: Y The offset from (X SC) where we start zeroing, counting up to &FF SC The low byte (i.e. the offset into the page) of the starting point of the zero-fill
Returns: Z flag Z flag is set
.ZES2 LDA #0 \ Load A with the byte we want to fill the memory block \ with - i.e. zero STX SC+1 \ We want to zero-fill page X, so store this in the \ high byte of SC, so the 16-bit address in SC and \ SC+1 is now pointing to the SC-th byte of page X .ZEL1 STA (SC),Y \ Zero the Y-th byte of the block pointed to by SC, \ so that's effectively the Y-th byte before SC INY \ Increment the loop counter BNE ZEL1 \ Loop back to zero the next byte RTS \ Return from the subroutine
Name: CTLI [Show more] Type: Variable Category: Save and load Summary: The OS command string for cataloguing a disc
Context: See this variable on its own page References: This variable is used as follows: * CATS uses CTLI * DELT uses CTLI
.CTLI EQUS ".:0" \ The "0" part of the string is overwritten with the EQUB 13 \ actual drive number by the CATS routine
Name: DELI [Show more] Type: Variable Category: Save and load Summary: The OS command string for deleting a file
Context: See this variable on its own page References: This variable is used as follows: * DELT uses DELI
.DELI EQUS "DEL.:0.E.1234567" \ Short for "*DELETE :0.E.1234567" EQUB 13
Name: CATS [Show more] Type: Subroutine Category: Save and load Summary: Ask for a disc drive number and print a catalogue of that drive
Context: See this subroutine on its own page References: This subroutine is called as follows: * CAT calls CATS * DELT calls CATS

This routine asks for a disc drive number, and if it is a valid number (0-3) it displays a catalogue of the disc in that drive. It also updates the OS command at CTLI so that when that command is run, it catalogues the correct drive.
Returns: C flag Clear if a valid drive number was entered (0-3), set otherwise
.CATS JSR GTDRV \ Get an ASCII disc drive number from the keyboard in A, \ setting the C flag if an invalid drive number was \ entered BCS DELT-1 \ If the C flag is set, then an invalid drive number was \ entered, so return from the subroutine (as DELT-1 \ contains an RTS) STA CTLI+2 \ Store the drive number in the third byte of the \ command string at CTLI, so it overwrites the "0" in \ ".0" with the drive number to catalogue STA DTW7 \ Store the drive number in DTW7, so printing extended \ token 4 will show the correct drive number (as token 4 \ contains the {drive number} jump code, which calls \ MT16 to print the character in DTW7) LDA #4 \ Print extended token 4, which clears the screen and JSR DETOK \ prints the boxed-out title "DRIVE {drive number} \ CATALOGUE" LDA #&8E \ Send command &8E to the I/O processor: JSR tube_write \ \ write_xyc(x, y, char) \ \ which will draw the text character in char at column x \ and row y, though in this case we're sending a null \ character (char = 0), so this doesn't print anything \ but just moves the text cursor in the I/O processor \ to column XC and row YC LDA XC \ Send the first parameter to the I/O processor: JSR tube_write \ \ * x = XC LDA YC \ Send the second parameter to the I/O processor: JSR tube_write \ \ * y = YC LDA #0 \ Send the third parameter to the I/O processor: JSR tube_write \ \ * char = 0 STA XC \ Move the text cursor to column 1 LDX #LO(CTLI) \ Set (Y X) to point to the OS command at CTLI, which LDY #HI(CTLI) \ contains a dot and the drive number, which is the \ DFS command for cataloguing that drive (*. being short \ for *CAT) JSR OSCLI \ Call OSCLI to execute the OS command at (Y X), which \ catalogues the disc CLC \ Clear the C flag RTS \ Return from the subroutine
Name: DELT [Show more] Type: Subroutine Category: Save and load Summary: Catalogue a disc, ask for a filename to delete, and delete the file
Context: See this subroutine on its own page References: This subroutine is called as follows: * SVE calls DELT * CATS calls via DELT-1

This routine asks for a disc drive number, and if it is a valid number (0-3) it displays a catalogue of the disc in that drive. It then asks for a filename to delete, updates the OS command at DELI so that when that command is run, it deletes the correct file, and then it does the deletion.
Other entry points: DELT-1 Contains an RTS
.DELT JSR CATS \ Call CATS to ask for a drive number (or a directory \ name on the Master Compact) and catalogue that disc \ or directory BCS SVE \ If the C flag is set then an invalid drive number was \ entered as part of the catalogue process, so jump to \ SVE to display the disc access menu LDA CTLI+2 \ The call to CATS above put the drive number into STA DELI+5 \ CTLI+2, so copy the drive number into DELI+5 so that \ the drive number in the "DEL.:0.E.1234567" string \ gets updated (i.e. the number after the colon) LDA #9 \ Print extended token 9 ("{clear bottom of screen}FILE JSR DETOK \ TO DELETE?") JSR MT26 \ Call MT26 to fetch a line of text from the keyboard \ to INWK+5, with the text length in Y TYA \ If no text was entered (Y = 0) then jump to SVE to BEQ SVE \ display the disc access menu \ We now copy the entered filename from INWK to DELI, so \ that it overwrites the filename part of the string, \ i.e. the "E.1234567" part of "DEL.:0.E.1234567" LDX #9 \ Set up a counter in X to count from 9 to 1, so that we \ copy the string starting at INWK+4+1 (i.e. INWK+5) to \ DELI+5+1 (i.e. DELI+6 onwards, or "E.1234567") .DELL1 LDA INWK+4,X \ Copy the X-th byte of INWK+4 to the X-th byte of STA DELI+6,X \ DELI+6 DEX \ Decrement the loop counter BNE DELL1 \ Loop back to DELL1 to copy the next character until we \ have copied the whole filename LDX #LO(DELI) \ Set (Y X) to point to the OS command at DELI, which LDY #HI(DELI) \ contains the DFS command for deleting this file JSR OSCLI \ Call OSCLI to execute the OS command at (Y X), which \ catalogues the disc JMP SVE \ Jump to SVE to display the disc access menu and return \ from the subroutine using a tail call
Name: MEBRK [Show more] Type: Subroutine Category: Save and load Summary: The BRKV handler for disc access operations
Context: See this subroutine on its own page References: This subroutine is called as follows: * SVE calls MEBRK

This routine is used to display error messages from the disc filing system while disc access operations are being performed. When called, it makes a beep and prints the system error message in the block pointed to by (&FD &FE), which is where the disc filing system will put any disc errors (such as "File not found", "Disc error" and so on). It then waits for a key press and returns to the disc access menu. BRKV is set to this routine at the start of the SVE routine, just before the disc access menu is shown, and it reverts to BRBR at the end of the SVE routine after the disc access menu has been processed. In other words, BRBR is the standard BRKV handler for the game, and it's swapped out to MEBRK for disc access operations only. When it is the BRKV handler, the routine can be triggered using a BRK instruction. The main difference between this routine and the standard BRKV handler in BRBR is that this routine returns to the disc access menu rather than restarting the game, and it doesn't decrement the brkd counter.
.MEBRK LDX #&FF \ The #&FF part of this instruction is modified by the TXS \ SVE routine so that it sets the stack pointer back to \ the value it had before we set BRKV to point to MEBRK \ in the SVE routine. Modifying this instruction means \ we don't need to use the stack variable, which saves \ us both a byte in this instruction, as well the byte \ of the stack variable LDY #0 \ Set Y to 0 to use as a loop counter below LDA #7 \ Set A = 7 to generate a beep before we print the error \ message .MEBRKL JSR OSWRCH \ Print the character in A (which contains a beep on the \ first loop iteration), and then any non-zero \ characters we fetch from the error message INY \ Increment the loop counter LDA (&FD),Y \ Fetch the Y-th byte of the block pointed to by \ (&FD &FE), so that's the Y-th character of the message \ pointed to by the MOS error message pointer BNE MEBRKL \ If the fetched character is non-zero, loop back to the \ JSR OSWRCH above to print the it, and keep looping \ until we fetch a zero (which marks the end of the \ message) BEQ retry \ Jump to retry to wait for a key press and display the \ disc access menu (this BEQ is effectively a JMP, as we \ didn't take the BNE branch above)
Name: CAT [Show more] Type: Subroutine Category: Save and load Summary: Catalogue a disc, wait for a key press and display the disc access menu
Context: See this subroutine on its own page References: This subroutine is called as follows: * SVE calls CAT
.CAT JSR CATS \ Call CATS to ask for a drive number, catalogue that \ disc and update the catalogue command at CTLI \ Fall through into retry to wait for a key press and \ display the disc access menu
Name: retry [Show more] Type: Subroutine Category: Save and load Summary: Scan the keyboard until a key is pressed and display the disc access menu
Context: See this subroutine on its own page References: This subroutine is called as follows: * MEBRK calls retry
.retry JSR t \ Scan the keyboard until a key is pressed, returning \ the ASCII code in A and X \ Fall through into SVE to display the disc access menu
Name: SVE [Show more] Type: Subroutine Category: Save and load Summary: Save the commander file Deep dive: Commander save files The competition code
Context: See this subroutine on its own page References: This subroutine is called as follows: * BR1 (Part 1 of 2) calls SVE * DELT calls SVE * TT102 calls SVE
.SVE JSR ZEBC \ Call ZEBC to zero-fill pages &B and &C TSX \ Transfer the stack pointer to X and store it in STX MEBRK+1 \ MEBRK+1, which modifies the LDX #&FF instruction at \ the start of MEBRK so that it sets X to the value of \ the stack pointer LDA #LO(MEBRK) \ Set BRKV to point to the MEBRK routine, which is the STA BRKV \ BRKV handler for disc access operations, and replaces LDA #HI(MEBRK) \ the standard BRKV handler in BRBR while disc access STA BRKV+1 \ operations are happening LDA #1 \ Print extended token 1, the disc access menu, which JSR DETOK \ presents these options: \ \ 1. Load New Commander \ 2. Save Commander {commander name} \ 3. Catalogue \ 4. Delete A File \ 5. Exit JSR t \ Scan the keyboard until a key is pressed, returning \ the ASCII code in A and X CMP #'1' \ If A < ASCII "1", jump to SVEX to exit as the key BCC SVEX \ press doesn't match a menu option CMP #'4' \ If "4" was pressed, jump to DELT to process option 4 BEQ DELT \ (delete a file) BCS SVEX \ If A >= ASCII "4", jump to SVEX to exit as the key \ press is either option 5 (exit), or it doesn't match a \ menu option (as we already checked for "4" above) CMP #'2' \ If A >= ASCII "2" (i.e. save or catalogue), skip to BCS SV1 \ SV1 \ If we get here then option 1 (load) was chosen LDA #0 \ If save_lock = &FF, then there are unsaved changes, so JSR confirm \ ask for confirmation before proceeding with the load, BNE SVEX \ jumping to SVEX to exit if confirmation is not given JSR GTNMEW \ Call GTNMEW to fetch the name of the commander file \ to load (including drive number and directory) into \ INWK JSR LOD \ Call LOD to load the commander file JSR TRNME \ Transfer the commander filename from INWK to NA% SEC \ Set the C flag to indicate we loaded a new commander BCS SVEX+1 \ file, and return from the subroutine (as SVEX+1 \ contains an RTS) .SV1 BNE CAT \ We get here following the CMP #'2' above, so this \ jumps to CAT if option 2 was not chosen - in other \ words, if option 3 (catalogue) was chosen \ If we get here then option 2 (save) was chosen LDA #&FF \ If save_lock = 0, then there are no unsaved changes, JSR confirm \ so ask for confirmation before proceeding with the BNE SVEX \ save, jumping to SVEX to exit if confirmation is not \ given JSR GTNMEW \ Call GTNMEW to fetch the name of the commander file \ to save (including drive number and directory) into \ INWK JSR TRNME \ Transfer the commander filename from INWK to NA% LDX #NT% \ We now want to copy the current commander data block \ from location TP to the last saved commander block at \ NA%+8, so set a counter in X to copy the NT% bytes in \ the commander data block \ \ We also want to copy the data block to another \ location &0B00, which is normally used for the ship \ lines heap .SVL1 LDA TP,X \ Copy the X-th byte of TP to the X-th byte of &0B00 STA &0B00,X \ and NA%+8 STA NA%+8,X DEX \ Decrement the loop counter BPL SVL1 \ Loop back until we have copied all the bytes in the \ commander data block JSR CHECK \ Call CHECK to calculate the checksum for the last \ saved commander and return it in A STA CHK \ Store the checksum in CHK, which is at the end of the \ last saved commander block STA &0B00+NT% \ Store the checksum in the last byte of the save file \ at &0B00 (the equivalent of CHK in the last saved \ block) EOR #&A9 \ Store the checksum EOR &A9 in CHK2, the penultimate STA CHK2 \ byte of the last saved commander block STA &0AFF+NT% \ Store the checksum EOR &A9 in the penultimate byte of \ the save file at &0B00 (the equivalent of CHK2 in the \ last saved block) LDY #&B \ Set up an OSFILE block at &0C00, containing: STY &0C0B \ INY \ Start address for save = &00000B00 in &0C0A to &0C0D STY &0C0F \ \ End address for save = &00000C00 in &0C0E to &0C11 \ \ Y is left containing &C which we use below LDA #0 \ Call QUS1 with A = 0, Y = &C to save the commander JSR QUS1 \ file with the filename we copied to INWK at the start \ of this routine .SVEX CLC \ Clear the C flag to indicate we didn't just load a new \ commander file JMP BRKBK \ Jump to BRKBK to set BRKV back to the standard BRKV \ handler for the game, and return from the subroutine \ using a tail call
Name: confirm [Show more] Type: Subroutine Category: Save and load Summary: Print "ARE YOU SURE?" and wait for a response
Context: See this subroutine on its own page References: This subroutine is called as follows: * SVE calls confirm

Arguments: A If save_lock matches this value, then we do not ask for confirmation and instead assume the answer was "Y"
Returns: Z flag If "Y" is pressed, then BEQ will branch (Z flag is set), otherwise BNE will branch (Z flag is clear)
.confirm CMP save_lock \ If A = save_lock, jump to confirmed to return from the BEQ confirmed \ subroutine without asking for confirmation, but \ assuming a positive response LDA #3 \ Print extended token 3 ("ARE YOU SURE?") JSR DETOK JSR t \ Scan the keyboard until a key is pressed, returning \ the ASCII code in A and X JSR CHPR \ Print the character in A ORA #%00100000 \ Set bit 5 in the value of the key pressed, which \ converts it to lower case PHA \ Store A on the stack so we can retrieve it after the \ call to FEED JSR TT67 \ Print a newline JSR FEED \ Print a newline PLA \ Restore A from the stack CMP #'y' \ Set the C flag if A >= ASCII y' (i.e. if "Y" was \ pressed and not "N"), otherwise clear it .confirmed RTS \ Return from the subroutine
Name: QUS1 [Show more] Type: Subroutine Category: Save and load Summary: Save or load the commander file Deep dive: Commander save files
Context: See this subroutine on its own page References: This subroutine is called as follows: * LOD calls QUS1 * SVE calls QUS1

The filename should be stored at INWK, terminated with a carriage return (13). The routine should be called with Y set to &C.
Arguments: A File operation to be performed. Can be one of the following: * 0 (save file) * &FF (load file) Y Points to the page number containing the OSFILE block, which must be &C because that's where the pointer to the filename in INWK is stored below (by the STX &0C00 instruction)
.QUS1 PHA \ Store A on the stack so we can restore it after the \ call to GTDRV JSR GTDRV \ Get an ASCII disc drive number from the keyboard in A, \ setting the C flag if an invalid drive number was \ entered STA INWK+1 \ Store the ASCII drive number in INWK+1, which is the \ drive character of the filename string ":0.E." PLA \ Restore A from the stack BCS QUR \ If the C flag is set, then an invalid drive number was \ entered, so jump to QUR to return from the subroutine STA save_lock \ Set save_lock to 0 (when we save a file) or &FF (when \ we load a file) to indicate: \ \ * 0 = last file operation was a save \ \ * &FF = last file operation was a load LDX #INWK \ Store a pointer to INWK at the start of the block at STX &0C00 \ &0C00, storing #INWK in the low byte because INWK is \ in zero page LDX #0 \ Set (Y X) = &0C00 LDY #&C JSR OSFILE \ Call OSFILE to do the file operation specified in \ &0C00 (i.e. save or load a file depending on the value \ of A) CLC \ Clear the C flag .QUR RTS \ Return from the subroutine
Name: GTDRV [Show more] Type: Subroutine Category: Save and load Summary: Get an ASCII disc drive number from the keyboard
Context: See this subroutine on its own page References: This subroutine is called as follows: * CATS calls GTDRV * QUS1 calls GTDRV

Returns: A The ASCII value of the entered drive number ("0" to "3") C flag Clear if a valid drive number was entered (0-3), set otherwise
.GTDRV LDA #2 \ Print extended token 2 ("{cr}WHICH DRIVE?") JSR DETOK JSR t \ Scan the keyboard until a key is pressed, returning \ the ASCII code in A and X ORA #%00010000 \ Set bit 4 of A, perhaps to avoid printing any control \ characters in the next instruction JSR CHPR \ Print the character in A PHA \ Store A on the stack so we can retrieve it after the \ call to FEED JSR FEED \ Print a newline PLA \ Restore A from the stack CMP #'0' \ If A < ASCII "0", then it is not a valid drive number, BCC LOR \ so jump to LOR to set the C flag and return from the \ subroutine CMP #'4' \ If A >= ASCII "4", then it is not a valid drive \ number, and this CMP sets the C flag, otherwise it is \ a valid drive number in the range 0-3, so clear it RTS \ Return from the subroutine
Name: LOD [Show more] Type: Subroutine Category: Save and load Summary: Load a commander file
Context: See this subroutine on its own page References: This subroutine is called as follows: * SVE calls LOD * GTDRV calls via LOR

The filename should be stored at INWK, terminated with a carriage return (13).
Other entry points: LOR Set the C flag and return from the subroutine
.LOD JSR ZEBC \ Call ZEBC to zero-fill pages &B and &C LDY #&B \ Set up an OSFILE block at &0C00, containing: STY &0C03 \ INC &0C0B \ Load address = &00000B00 in &0C02 to &0C05 \ \ Length of file = &00000100 in &0C0A to &0C0D LDA #&FF \ Call QUS1 with A = &FF, Y = &C to load the commander JSR QUS1 \ file to address &0B00 BCS LOR \ If the C flag is set then an invalid drive number was \ entered during the call to QUS1 and the file wasn't \ loaded, so jump to LOR to return from the subroutine LDA &0B00 \ If the first byte of the loaded file has bit 7 set, BMI ELT2F \ jump to ELT2F, as this is an invalid commander file \ \ ELT2F contains a BRK instruction, which will force an \ interrupt to call the address in BRKV, which will \ print out the system error at ELT2F LDX #NT% \ We have successfully loaded the commander file at \ &0B00, so now we want to copy it to the last saved \ commander data block at NA%+8, so we set up a counter \ in X to copy NT% bytes .LOL1 LDA &0B00,X \ Copy the X-th byte of &0B00 to the X-th byte of NA%+8 STA NA%+8,X DEX \ Decrement the loop counter BPL LOL1 \ Loop back until we have copied all NT% bytes .LOR SEC \ Set the C flag RTS \ Return from the subroutine .ELT2F BRK \ The error that is printed if we try to load an EQUB &49 \ invalid commander file with bit 7 of byte #0 set EQUS "Bad ELITE III " \ (&49 is the error number) EQUS "file" BRK
Name: FX200 [Show more] Type: Subroutine Category: Utility routines Summary: Set the behaviour of the ESCAPE and BREAK keys
Context: See this subroutine on its own page References: This subroutine is called as follows: * BR1 (Part 1 of 2) calls FX200

This is the equivalent of a *FX 200 command, which controls the behaviour of the ESCAPE and BREAK keys.
Arguments: X Controls the behaviour as follows: * 0 = Enable ESCAPE key Normal BREAK key action * 1 = Disable ESCAPE key Normal BREAK key action * 2 = Enable ESCAPE key Clear memory if the BREAK key is pressed * 3 = Disable ESCAPE key Clear memory if the BREAK key is pressed
.FX200 LDY #0 \ Call OSBYTE 200 with Y = 0, so the new value is set to LDA #200 \ X, and return from the subroutine using a tail call JMP OSBYTE
Name: NORM [Show more] Type: Subroutine Category: Maths (Geometry) Summary: Normalise the three-coordinate vector in XX15 Deep dive: Tidying orthonormal vectors Orientation vectors
Context: See this subroutine on its own page References: This subroutine is called as follows: * TAS2 calls NORM * TIDY calls NORM * NO3 calls via NO1

We do this by dividing each of the three coordinates by the length of the vector, which we can calculate using Pythagoras. Once normalised, 96 (&60) is used to represent a value of 1, and 96 with bit 7 set (&E0) is used to represent -1. This enables us to represent fractional values of less than 1 using integers.
Arguments: XX15 The vector to normalise, with: * The x-coordinate in XX15 * The y-coordinate in XX15+1 * The z-coordinate in XX15+2
Returns: XX15 The normalised vector Q The length of the original XX15 vector
Other entry points: NO1 Contains an RTS
.NORM LDA XX15 \ Fetch the x-coordinate into A JSR SQUA \ Set (A P) = A * A = x^2 STA R \ Set (R Q) = (A P) = x^2 LDA P STA Q LDA XX15+1 \ Fetch the y-coordinate into A JSR SQUA \ Set (A P) = A * A = y^2 STA T \ Set (T P) = (A P) = y^2 LDA P \ Set (R Q) = (R Q) + (T P) = x^2 + y^2 ADC Q \ STA Q \ First, doing the low bytes, Q = Q + P LDA T \ And then the high bytes, R = R + T ADC R STA R LDA XX15+2 \ Fetch the z-coordinate into A JSR SQUA \ Set (A P) = A * A = z^2 STA T \ Set (T P) = (A P) = z^2 LDA P \ Set (R Q) = (R Q) + (T P) = x^2 + y^2 + z^2 ADC Q \ STA Q \ First, doing the low bytes, Q = Q + P LDA T \ And then the high bytes, R = R + T ADC R STA R JSR LL5 \ We now have the following: \ \ (R Q) = x^2 + y^2 + z^2 \ \ so we can call LL5 to use Pythagoras to get: \ \ Q = SQRT(R Q) \ = SQRT(x^2 + y^2 + z^2) \ \ So Q now contains the length of the vector (x, y, z), \ and we can normalise the vector by dividing each of \ the coordinates by this value, which we do by calling \ routine TIS2. TIS2 returns the divided figure, using \ 96 to represent 1 and 96 with bit 7 set for -1 LDA XX15 \ Call TIS2 to divide the x-coordinate in XX15 by Q, JSR TIS2 \ with 1 being represented by 96 STA XX15 LDA XX15+1 \ Call TIS2 to divide the y-coordinate in XX15+1 by Q, JSR TIS2 \ with 1 being represented by 96 STA XX15+1 LDA XX15+2 \ Call TIS2 to divide the z-coordinate in XX15+2 by Q, JSR TIS2 \ with 1 being represented by 96 STA XX15+2 .NO1 RTS \ Return from the subroutine
Name: scan_fire [Show more] Type: Subroutine Category: Keyboard Summary: Check whether the joystick's fire button is being pressed by sending a scan_fire command to the I/O processor
Context: See this subroutine on its own page References: This subroutine is called as follows: * DKJ1 calls scan_fire * TITLE calls scan_fire
.scan_fire LDA #&89 \ Send command &89 to the I/O processor: JSR tube_write \ \ =scan_fire() \ \ which will check whether the fire button is being \ pressed and return the result in bit 4 of the returned \ value JMP tube_read \ Set A to the response from the I/O processor, which \ will have bit 4 clear if the fire button is being \ pressed, or set if it isn't, and return from the \ subroutine using a tail call
Name: RDKEY [Show more] Type: Subroutine Category: Keyboard Summary: Scan the keyboard for key presses by sending a scan_10in command to the I/O processor
Context: See this subroutine on its own page References: This subroutine is called as follows: * check_keys calls RDKEY * DK4 calls RDKEY * DK4_FLIGHT calls RDKEY * PAS1 calls RDKEY * PAUSE2 calls RDKEY * TITLE calls RDKEY

Returns: X If a key is being pressed, X contains the internal key number, otherwise it contains 0 A Contains the same as X
.RDKEY LDA #&8C \ Send command &8C to the I/O processor: JSR tube_write \ \ =scan_10in() \ \ which will scan the keyboard JSR tube_read \ Set A to the response from the I/O processor, which \ will either be the internal key number of the key \ being pressed, or 0 if no key is being pressed TAX \ Copy the response into X RTS \ Return from the subroutine
Name: ECMOF [Show more] Type: Subroutine Category: Sound Summary: Switch off the E.C.M.
Context: See this subroutine on its own page References: This subroutine is called as follows: * Main flight loop (Part 16 of 16) calls ECMOF * RES2 calls ECMOF

Switch the E.C.M. off, turn off the dashboard bulb and make the sound of the E.C.M. switching off).
.ECMOF LDA #0 \ Set ECMA and ECMP to 0 to indicate that no E.C.M. is STA ECMA \ currently running STA ECMP JSR ECBLB \ Update the E.C.M. indicator bulb on the dashboard LDA #72 \ Call the NOISE routine with A = 72 to make the sound BNE NOISE \ of the E.C.M. being turned off and return from the \ subroutine using a tail call (this BNE is effectively \ a JMP as A will never be zero)
Name: BEEP [Show more] Type: Subroutine Category: Sound Summary: Make a short, high beep
Context: See this subroutine on its own page References: This subroutine is called as follows: * CHPR calls BEEP * dn2 calls BEEP * Main flight loop (Part 11 of 16) calls BEEP
.BEEP LDA #32 \ Set A = 32 to denote a short, high beep, and fall \ through into the NOISE routine to make the sound
Name: NOISE [Show more] Type: Subroutine Category: Sound Summary: Make the sound whose number is in A
Context: See this subroutine on its own page References: This subroutine is called as follows: * ECBLB2 calls NOISE * ECMOF calls NOISE * EXNO calls NOISE * EXNO3 calls NOISE * HME2 calls NOISE * LL164 calls NOISE * Main flight loop (Part 3 of 16) calls NOISE * SFRMIS calls NOISE * TACTICS (Part 6 of 7) calls NOISE * WARP calls NOISE

Arguments: A The number of the sound to be made. See the documentation for variable SFX for a list of sound numbers
.NOISE JSR NOS1 \ Set up the sound block in XX16 for the sound in A and \ fall through into NO3 to make the sound
Name: NO3 [Show more] Type: Subroutine Category: Sound Summary: Make a sound from a prepared sound block
Context: See this subroutine on its own page References: This subroutine is called as follows: * EXNO calls NO3

Make a sound from a prepared sound block in XX16 (if sound is enabled). See routine NOS1 for details of preparing the XX16 sound block.
.NO3 LDX DNOIZ \ Set X to the DNOIZ configuration setting BNE NO1 \ If DNOIZ is non-zero, then sound is disabled, so \ return from the subroutine (as NO1 contains an RTS) LDX #LO(XX16) \ Otherwise set (Y X) to point to the sound block in LDY #HI(XX16) \ XX16 LDA #7 \ Call OSWORD 7 to makes the sound, as described in the JMP OSWORD \ documentation for variable SFX, and return from the \ subroutine using a tail call
Name: NOS1 [Show more] Type: Subroutine Category: Sound Summary: Prepare a sound block
Context: See this subroutine on its own page References: This subroutine is called as follows: * EXNO calls NOS1 * NOISE calls NOS1

Copy four sound bytes from SFX into XX16, interspersing them with null bytes, with Y indicating the sound number to copy (from the values in the sound table at SFX). So, for example, if we call this routine with A = 40 (long, low beep), the following bytes will be set in XX16 to XX16+7: &13 &00 &F4 &00 &0C &00 &08 &00 This block will be passed to OSWORD 7 to make the sound, which expects the four sound attributes as 16-bit big-endian values - in other words, with the low byte first. So the above block would pass the values &0013, &00F4, &000C and &0008 to the SOUND statement when used with OSWORD 7, or: SOUND &13, &F4, &0C, &08 as the high bytes are always zero.
Arguments: A The sound number to copy from SFX to XX16, which is always a multiple of 8
.NOS1 LSR A \ Divide A by 2, and also clear the C flag, as bit 0 of \ A is always zero (as A is a multiple of 8) ADC #3 \ Set Y = A + 3, so Y now points to the last byte of TAY \ four within the block of four-byte values LDX #7 \ We want to copy four bytes, spread out into an 8-byte \ block, so set a counter in Y to cover 8 bytes .NOL1 LDA #0 \ Set the X-th byte of XX16 to 0 STA XX16,X DEX \ Decrement the destination byte pointer LDA SFX,Y \ Set the X-th byte of XX16 to the value from SFX+Y STA XX16,X DEY \ Decrement the source byte pointer again DEX \ Decrement the destination byte pointer again BPL NOL1 \ Loop back for the next source byte RTS \ Return from the subroutine
Name: CTRL [Show more] Type: Subroutine Category: Keyboard Summary: Scan the keyboard to see if CTRL is currently pressed
Context: See this subroutine on its own page References: This subroutine is called as follows: * EQSHP calls CTRL * hyp calls CTRL * TT102 calls CTRL * TT208 calls CTRL * TT219 calls CTRL * TT25 calls CTRL

Returns: X X = %10000001 (i.e. 129 or -127) if CTRL is being pressed X = 1 if CTRL is not being pressed A Contains the same as X
.CTRL LDX #1 \ Set X to the internal key number for CTRL and fall \ through to DKS4 to scan the keyboard
Name: DKS4 [Show more] Type: Subroutine Category: Keyboard Summary: Scan for a particular key press by sending a scan_xin command to the I/O processor
Context: See this subroutine on its own page References: This subroutine is called as follows: * TT17 calls DKS4

Arguments: X The internal number of the key to check
Returns: A If the key is being pressed, A contains the original key number in X but with bit 7 set (i.e. key number + 128). If the key is not being pressed, A contains the unchanged key number
.DKS4 LDA #&8B \ Send command &8B to the I/O processor: JSR tube_write \ \ =scan_xin(key_number) \ \ which will scan the keyboard for the specified \ internal key number TXA \ Send the parameter to the I/O processor: JSR tube_write \ \ * key_number = X JSR tube_read \ Set A to the response from the I/O processor, which \ will contain the key number with bit 7 set if the key \ is being pressed, or bit 7 clear if it isn't TAX \ Copy the response into X RTS \ Return from the subroutine
Name: DKS2 [Show more] Type: Subroutine Category: Keyboard Summary: Read the joystick position
Context: See this subroutine on its own page References: This subroutine is called as follows: * DKJ1 calls DKS2 * DOKEY calls DKS2

Return the value of ADC channel in X (used to read the joystick). The value will be inverted if the game has been configured to reverse both joystick channels (which can be done by pausing the game and pressing J).
Arguments: X The ADC channel to read: * 1 = joystick X * 2 = joystick Y
Returns: (A X) The 16-bit value read from channel X, with the value inverted if the game has been configured to reverse the joystick
Other entry points: DKS2-1 Contains an RTS
.DKS2 LDA #128 \ Call OSBYTE with A = 128 to fetch the 16-bit value JSR OSBYTE \ from ADC channel X, returning (Y X), i.e. the high \ byte in Y and the low byte in X \ \ * Channel 1 is the x-axis: 0 = right, 65520 = left \ \ * Channel 2 is the y-axis: 0 = down, 65520 = up TYA \ Copy Y to A, so the result is now in (A X) EOR JSTE \ The high byte A is now EOR'd with the value in \ location JSTE, which contains &FF if both joystick \ channels are reversed and 0 otherwise (so A now \ contains the high byte but inverted, if that's what \ the current settings say) RTS \ Return from the subroutine
Name: DKS3 [Show more] Type: Subroutine Category: Keyboard Summary: Toggle a configuration setting and emit a beep
Context: See this subroutine on its own page References: This subroutine is called as follows: * DK4 calls DKS3 * DK4_FLIGHT calls DKS3

This is called when the game is paused and a key is pressed that changes the game's configuration. Specifically, this routine toggles the configuration settings for the following keys: * CAPS LOCK toggles keyboard flight damping (&40) * A toggles keyboard auto-recentre (&41) * X toggles author names on start-up screen (&42) * F toggles flashing console bars (&43) * Y toggles reverse joystick Y channel (&44) * J toggles reverse both joystick channels (&45) * K toggles keyboard and joystick (&46) * @ toggles keyboard and Delta 14B joystick (&47) The numbers in brackets are the internal key numbers (see p.142 of the Advanced User Guide for a list of internal key numbers). We pass the key that has been pressed in X, and the configuration option to check it against in Y, so this routine is typically called in a loop that loops through the various configuration options.
Arguments: X The internal number of the key that's been pressed Y The internal number of the configuration key to check against, from the list above (i.e. Y must be from &40 to &46)
.DKS3 STY T \ Store the configuration key argument in T CPX T \ If X <> Y, jump to Dk3 to return from the subroutine BNE Dk3 \ We have a match between X and Y, so now to toggle \ the relevant configuration byte. CAPS LOCK has a key \ value of &40 and has its configuration byte at \ location DAMP, A has a value of &41 and has its byte \ at location DJD, which is DAMP+1, and so on. So we \ can toggle the configuration byte by changing the \ byte at DAMP + (X - &40), or to put it in indexing \ terms, DAMP-&40,X. It's no coincidence that the \ game's configuration bytes are set up in this order \ and with these keys (and this is also why the sound \ on/off keys are dealt with elsewhere, as the internal \ key for S and Q are &51 and &10, which don't fit \ nicely into this approach) LDA DAMP-&40,X \ Fetch the byte from DAMP + (X - &40), invert it and EOR #&FF \ put it back (0 means no and &FF means yes in the STA DAMP-&40,X \ configuration bytes, so this toggles the setting) JSR BELL \ Make a beep sound so we know something has happened JSR DELAY \ Wait for Y vertical syncs (Y is between 64 and 70, so \ this is always a bit longer than a second) LDY T \ Restore the configuration key argument into Y .Dk3 RTS \ Return from the subroutine
Name: DOKEY [Show more] Type: Subroutine Category: Keyboard Summary: Scan for the seven primary flight controls and apply the docking computer manoeuvring code Deep dive: The key logger The docking computer
Context: See this subroutine on its own page References: This subroutine is called as follows: * TT17 calls DOKEY
.DOKEY LDA JSTK \ If JSTK is zero, then we are configured to use the BEQ DK4 \ keyboard rather than the joystick, so jump to DK4 to \ scan for pause, configuration and secondary flight \ keys LDX #1 \ Call DKS2 to fetch the value of ADC channel 1 (the JSR DKS2 \ joystick X value) into (A X), and OR A with 1. This ORA #1 \ ensures that the high byte is at least 1, and then we STA JSTX \ store the result in JSTX LDX #2 \ Call DKS2 to fetch the value of ADC channel 2 (the JSR DKS2 \ joystick Y value) into (A X), and EOR A with JSTGY. EOR JSTGY \ JSTGY will be &FF if the game is configured to STA JSTY \ reverse the joystick Y channel, so this EOR does \ exactly that, and then we store the result in JSTY \ Fall through into DK4 to scan for other keys
Name: DK4 [Show more] Type: Subroutine Category: Keyboard Summary: Scan for pause, configuration and secondary flight keys Deep dive: The key logger
Context: See this subroutine on its own page References: This subroutine is called as follows: * DOKEY calls DK4

Scan for pause and configuration keys, and if this is a space view, also scan for secondary flight controls. Specifically: * Scan for the pause button (COPY) and if it's pressed, pause the game and process any configuration key presses until the game is unpaused (DELETE) * If this is a space view, scan for secondary flight keys and update the relevant bytes in the key logger
.DK4 JSR RDKEY \ Scan the keyboard for a key press and return the \ internal key number in X (or 0 for no key press) STX KL \ Store X in KL, byte #0 of the key logger CPX #&69 \ If COPY is not being pressed, jump to DK2 below, BNE DK2 \ otherwise let's process the configuration keys .FREEZE \ COPY is being pressed, so we enter a loop that \ listens for configuration keys, and we keep looping \ until we detect a DELETE key press. This effectively \ pauses the game when COPY is pressed, and unpauses \ it when DELETE is pressed JSR WSCAN \ Call WSCAN to wait for the vertical sync, so the whole \ screen gets drawn JSR RDKEY \ Scan the keyboard for a key press and return the \ internal key number in X (or 0 for no key press) CPX #&51 \ If "S" is not being pressed, skip to DK6 BNE DK6 LDA #0 \ "S" is being pressed, so set DNOIZ to 0 to turn the STA DNOIZ \ sound on .DK6 LDY #&40 \ We now want to loop through the keys that toggle \ various settings. These have internal key numbers \ between &40 (CAPS LOCK) and &46 ("K"), so we set up \ the first key number in Y to act as a loop counter. \ See subroutine DKS3 for more details on this .DKL4 JSR DKS3 \ Call DKS3 to scan for the key given in Y, and toggle \ the relevant setting if it is pressed INY \ Increment Y to point to the next toggle key CPY #&48 \ The last toggle key is &47 (@), so check whether we \ have just done that one BNE DKL4 \ If not, loop back to check for the next toggle key .DK55 CPX #&10 \ If "Q" is not being pressed, skip to DK7 BNE DK7 STX DNOIZ \ "Q" is being pressed, so set DNOIZ to X, which is \ non-zero (&10), so this will turn the sound off .DK7 CPX #&70 \ If ESCAPE is not being pressed, skip over the next BNE P%+5 \ instruction JMP BR1 \ ESCAPE is being pressed, so jump to BR1 to end the \ game CPX #&59 \ If DELETE is not being pressed, we are still paused, BNE FREEZE \ so loop back up to keep listening for configuration \ keys, otherwise fall through into the rest of the \ key detection code, which unpauses the game .DK2 LDA QQ11 \ If the current view is non-zero (i.e. not a space BNE out \ view), return from the subroutine (as out contains \ an RTS) LDY #16 \ This is a space view, so now we want to check for all \ the secondary flight keys. The internal key numbers \ are in the keyboard table KYTB from KYTB+8 to \ KYTB+16, and their key logger locations are from KL+8 \ to KL+16. So set a decreasing counter in Y for the \ index, starting at 16, so we can loop through them LDA #&FF \ Set A to &FF so we can store this in the keyboard \ logger for keys that are being pressed RTS \ Return from the subroutine
Name: TT217 [Show more] Type: Subroutine Category: Keyboard Summary: Scan the keyboard until a key is pressed by sending a get_key command to the I/O processor
Context: See this subroutine on its own page References: This subroutine is called as follows: * gnum calls TT217 * qv calls TT217 * DK4 calls via out

Scan the keyboard until a key is pressed, and return the key's ASCII code. If, on entry, a key is already being held down, then wait until that key is released first (so this routine detects the first key down event following the subroutine call).
Returns: X The ASCII code of the key that was pressed A Contains the same as X
Other entry points: out Contains an RTS
.TT217 .t LDA #&8D \ Send command &8D to the I/O processor: JSR tube_write \ \ =get_key() \ \ which waits for any current key presses to be released \ (if any), and then waits for a key to be pressed \ before returning the result as an ASCII value JSR tube_read \ Set A to the response from the I/O processor, which \ will contain the ASCII value of the key press TAX \ Copy the response into X .out RTS \ Return from the subroutine
Name: ITEM [Show more] Type: Macro Category: Market Summary: Macro definition for the market prices table Deep dive: Market item prices and availability
Context: See this macro on its own page References: This macro is used as follows: * QQ23 uses ITEM

The following macro is used to build the market prices table: ITEM price, factor, units, quantity, mask It inserts an item into the market prices table at QQ23. See the deep dive on "Market item prices and availability" for more information on how the market system works.
Arguments: price Base price factor Economic factor units Units: "t", "g" or "k" quantity Base quantity mask Fluctuations mask
MACRO ITEM price, factor, units, quantity, mask IF factor < 0 s = 1 << 7 ELSE s = 0 ENDIF IF units = 't' u = 0 ELIF units = 'k' u = 1 << 5 ELSE u = 1 << 6 ENDIF e = ABS(factor) EQUB price EQUB s + u + e EQUB quantity EQUB mask ENDMACRO
Name: QQ23 [Show more] Type: Variable Category: Market Summary: Market prices table
Context: See this variable on its own page References: This variable is used as follows: * GVL uses QQ23 * TT151 uses QQ23 * TT210 uses QQ23

Each item has four bytes of data, like this: Byte #0 = Base price Byte #1 = Economic factor in bits 0-4, with the sign in bit 7 Unit in bits 5-6 Byte #2 = Base quantity Byte #3 = Mask to control price fluctuations To make it easier for humans to follow, we've defined a macro called ITEM that takes the following arguments and builds the four bytes for us: ITEM base price, economic factor, units, base quantity, mask So for food, we have the following: * Base price = 19 * Economic factor = -2 * Unit = tonnes * Base quantity = 6 * Mask = %00000001
.QQ23 ITEM 19, -2, 't', 6, %00000001 \ 0 = Food ITEM 20, -1, 't', 10, %00000011 \ 1 = Textiles ITEM 65, -3, 't', 2, %00000111 \ 2 = Radioactives ITEM 40, -5, 't', 226, %00011111 \ 3 = Slaves ITEM 83, -5, 't', 251, %00001111 \ 4 = Liquor/Wines ITEM 196, 8, 't', 54, %00000011 \ 5 = Luxuries ITEM 235, 29, 't', 8, %01111000 \ 6 = Narcotics ITEM 154, 14, 't', 56, %00000011 \ 7 = Computers ITEM 117, 6, 't', 40, %00000111 \ 8 = Machinery ITEM 78, 1, 't', 17, %00011111 \ 9 = Alloys ITEM 124, 13, 't', 29, %00000111 \ 10 = Firearms ITEM 176, -9, 't', 220, %00111111 \ 11 = Furs ITEM 32, -1, 't', 53, %00000011 \ 12 = Minerals ITEM 97, -1, 'k', 66, %00000111 \ 13 = Gold ITEM 171, -2, 'k', 55, %00011111 \ 14 = Platinum ITEM 45, -1, 'g', 250, %00001111 \ 15 = Gem-Stones ITEM 53, 15, 't', 192, %00000111 \ 16 = Alien items
Name: TIDY [Show more] Type: Subroutine Category: Maths (Geometry) Summary: Orthonormalise the orientation vectors for a ship Deep dive: Tidying orthonormal vectors Orientation vectors
Context: See this subroutine on its own page References: This subroutine is called as follows: * HAS1 calls TIDY * MVEIT (Part 1 of 9) calls TIDY * MVEIT_FLIGHT (Part 1 of 6) calls TIDY

This routine orthonormalises the orientation vectors for a ship. This means making the three orientation vectors orthogonal (perpendicular to each other), and normal (so each of the vectors has length 1). We do this because we use the small angle approximation to rotate these vectors in space. It is not completely accurate, so the three vectors tend to get stretched over time, so periodically we tidy the vectors with this routine to ensure they remain as orthonormal as possible.
.TI2 \ Called from below with A = 0, X = 0, Y = 4 when \ nosev_x and nosev_y are small, so we assume that \ nosev_z is big TYA \ A = Y = 4 LDY #2 JSR TIS3 \ Call TIS3 with X = 0, Y = 2, A = 4, to set roofv_z = STA INWK+20 \ -(nosev_x * roofv_x + nosev_y * roofv_y) / nosev_z JMP TI3 \ Jump to TI3 to keep tidying .TI1 \ Called from below with A = 0, Y = 4 when nosev_x is \ small TAX \ Set X = A = 0 LDA XX15+1 \ Set A = nosev_y, and if the top two magnitude bits AND #%01100000 \ are both clear, jump to TI2 with A = 0, X = 0, Y = 4 BEQ TI2 LDA #2 \ Otherwise nosev_y is big, so set up the index values \ to pass to TIS3 JSR TIS3 \ Call TIS3 with X = 0, Y = 4, A = 2, to set roofv_y = STA INWK+18 \ -(nosev_x * roofv_x + nosev_z * roofv_z) / nosev_y JMP TI3 \ Jump to TI3 to keep tidying .TIDY LDA INWK+10 \ Set (XX15, XX15+1, XX15+2) = nosev STA XX15 LDA INWK+12 STA XX15+1 LDA INWK+14 STA XX15+2 JSR NORM \ Call NORM to normalise the vector in XX15, i.e. nosev LDA XX15 \ Set nosev = (XX15, XX15+1, XX15+2) STA INWK+10 LDA XX15+1 STA INWK+12 LDA XX15+2 STA INWK+14 LDY #4 \ Set Y = 4 LDA XX15 \ Set A = nosev_x, and if the top two magnitude bits AND #%01100000 \ are both clear, jump to TI1 with A = 0, Y = 4 BEQ TI1 LDX #2 \ Otherwise nosev_x is big, so set up the index values LDA #0 \ to pass to TIS3 JSR TIS3 \ Call TIS3 with X = 2, Y = 4, A = 0, to set roofv_x = STA INWK+16 \ -(nosev_y * roofv_y + nosev_z * roofv_z) / nosev_x .TI3 LDA INWK+16 \ Set (XX15, XX15+1, XX15+2) = roofv STA XX15 LDA INWK+18 STA XX15+1 LDA INWK+20 STA XX15+2 JSR NORM \ Call NORM to normalise the vector in XX15, i.e. roofv LDA XX15 \ Set roofv = (XX15, XX15+1, XX15+2) STA INWK+16 LDA XX15+1 STA INWK+18 LDA XX15+2 STA INWK+20 LDA INWK+12 \ Set Q = nosev_y STA Q LDA INWK+20 \ Set A = roofv_z JSR MULT12 \ Set (S R) = Q * A = nosev_y * roofv_z LDX INWK+14 \ Set X = nosev_z LDA INWK+18 \ Set A = roofv_y JSR TIS1 \ Set (A ?) = (-X * A + (S R)) / 96 \ = (-nosev_z * roofv_y + nosev_y * roofv_z) / 96 \ \ This also sets Q = nosev_z EOR #%10000000 \ Set sidev_x = -A STA INWK+22 \ = (nosev_z * roofv_y - nosev_y * roofv_z) / 96 LDA INWK+16 \ Set A = roofv_x JSR MULT12 \ Set (S R) = Q * A = nosev_z * roofv_x LDX INWK+10 \ Set X = nosev_x LDA INWK+20 \ Set A = roofv_z JSR TIS1 \ Set (A ?) = (-X * A + (S R)) / 96 \ = (-nosev_x * roofv_z + nosev_z * roofv_x) / 96 \ \ This also sets Q = nosev_x EOR #%10000000 \ Set sidev_y = -A STA INWK+24 \ = (nosev_x * roofv_z - nosev_z * roofv_x) / 96 LDA INWK+18 \ Set A = roofv_y JSR MULT12 \ Set (S R) = Q * A = nosev_x * roofv_y LDX INWK+12 \ Set X = nosev_y LDA INWK+16 \ Set A = roofv_x JSR TIS1 \ Set (A ?) = (-X * A + (S R)) / 96 \ = (-nosev_y * roofv_x + nosev_x * roofv_y) / 96 EOR #%10000000 \ Set sidev_z = -A STA INWK+26 \ = (nosev_y * roofv_x - nosev_x * roofv_y) / 96 LDA #0 \ Set A = 0 so we can clear the low bytes of the \ orientation vectors LDX #14 \ We want to clear the low bytes, so start from sidev_y \ at byte #9+14 (we clear all except sidev_z_lo, though \ I suspect this is in error and that X should be 16) .TIL1 STA INWK+9,X \ Set the low byte in byte #9+X to zero DEX \ Set X = X - 2 to jump down to the next low byte DEX BPL TIL1 \ Loop back until we have zeroed all the low bytes RTS \ Return from the subroutine
Name: TIS2 [Show more] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate A = A / Q Deep dive: Shift-and-subtract division
Context: See this subroutine on its own page References: This subroutine is called as follows: * NORM calls TIS2

Calculate the following division, where A is a sign-magnitude number and Q is a positive integer: A = A / Q The value of A is returned as a sign-magnitude number with 96 representing 1, and the maximum value returned is 1 (i.e. 96). This routine is used when normalising vectors, where we represent fractions using integers, so this gives us an approximation to two decimal places.
.TIS2 TAY \ Store the argument A in Y AND #%01111111 \ Strip the sign bit from the argument, so A = |A| CMP Q \ If A >= Q then jump to TI4 to return a 1 with the BCS TI4 \ correct sign LDX #%11111110 \ Set T to have bits 1-7 set, so we can rotate through 7 STX T \ loop iterations, getting a 1 each time, and then \ getting a 0 on the 8th iteration... and we can also \ use T to catch our result bits into bit 0 each time .TIL2 ASL A \ Shift A to the left CMP Q \ If A < Q skip the following subtraction BCC P%+4 SBC Q \ A >= Q, so set A = A - Q \ \ Going into this subtraction we know the C flag is \ set as we passed through the BCC above, and we also \ know that A >= Q, so the C flag will still be set once \ we are done ROL T \ Rotate the counter in T 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 TIL2 \ If we still have set bits in T, loop back to TIL2 to \ do the next iteration of 7 \ We've done the division and now have a result in the \ range 0-255 here, which we need to reduce to the range \ 0-96. We can do that by multiplying the result by 3/8, \ as 256 * 3/8 = 96 LDA T \ Set T = T / 4 LSR A LSR A STA T LSR A \ Set T = T / 8 + T / 4 ADC T \ = 3T / 8 STA T TYA \ Fetch the sign bit of the original argument A AND #%10000000 ORA T \ Apply the sign bit to T RTS \ Return from the subroutine .TI4 TYA \ Fetch the sign bit of the original argument A AND #%10000000 ORA #96 \ Apply the sign bit to 96 (which represents 1) RTS \ Return from the subroutine
Name: TIS3 [Show more] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate -(nosev_1 * roofv_1 + nosev_2 * roofv_2) / nosev_3
Context: See this subroutine on its own page References: This subroutine is called as follows: * TIDY calls TIS3

Calculate the following expression: A = -(nosev_1 * roofv_1 + nosev_2 * roofv_2) / nosev_3 where 1, 2 and 3 are x, y, or z, depending on the values of X, Y and A. This routine is called with the following values: X = 0, Y = 2, A = 4 -> A = -(nosev_x * roofv_x + nosev_y * roofv_y) / nosev_z X = 0, Y = 4, A = 2 -> A = -(nosev_x * roofv_x + nosev_z * roofv_z) / nosev_y X = 2, Y = 4, A = 0 -> A = -(nosev_y * roofv_y + nosev_z * roofv_z) / nosev_x
Arguments: X Index 1 (0 = x, 2 = y, 4 = z) Y Index 2 (0 = x, 2 = y, 4 = z) A Index 3 (0 = x, 2 = y, 4 = z)
.TIS3 STA P+2 \ Store P+2 in A for later LDA INWK+10,X \ Set Q = nosev_x_hi (plus X) STA Q LDA INWK+16,X \ Set A = roofv_x_hi (plus X) JSR MULT12 \ Set (S R) = Q * A \ = nosev_x_hi * roofv_x_hi LDX INWK+10,Y \ Set Q = nosev_x_hi (plus Y) STX Q LDA INWK+16,Y \ Set A = roofv_x_hi (plus Y) JSR MAD \ Set (A X) = Q * A + (S R) \ = (nosev_x,X * roofv_x,X) + \ (nosev_x,Y * roofv_x,Y) STX P \ Store low byte of result in P, so result is now in \ (A P) LDY P+2 \ Set Q = roofv_x_hi (plus argument A) LDX INWK+10,Y STX Q EOR #%10000000 \ Flip the sign of A \ Fall through into DIVDT to do: \ \ (P+1 A) = (A P) / Q \ \ = -((nosev_x,X * roofv_x,X) + \ (nosev_x,Y * roofv_x,Y)) \ / nosev_x,A
Name: DVIDT [Show more] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate (P+1 A) = (A P) / Q
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

Calculate the following integer division between sign-magnitude numbers: (P+1 A) = (A P) / Q This uses the same shift-and-subtract algorithm as TIS2.
.DVIDT STA P+1 \ Set P+1 = A, so P(1 0) = (A P) EOR Q \ Set T = the sign bit of A EOR Q, so it's 1 if A and Q AND #%10000000 \ have different signs, i.e. it's the sign of the result STA T \ of A / Q LDA #0 \ Set A = 0 for us to build a result LDX #16 \ Set a counter in X to count the 16 bits in P(1 0) ASL P \ Shift P(1 0) left ROL P+1 ASL Q \ Clear the sign bit of Q the C flag at the same time LSR Q .DVL2 ROL A \ Shift A to the left CMP Q \ If A < Q skip the following subtraction BCC P%+4 SBC Q \ Set A = A - Q \ \ Going into this subtraction we know the C flag is \ set as we passed through the BCC above, and we also \ know that A >= Q, so the C flag will still be set once \ we are done ROL P \ Rotate P(1 0) to the left, and catch the result bit ROL P+1 \ into the C flag (which will be a 0 if we didn't \ do the subtraction, or 1 if we did) DEX \ Decrement the loop counter BNE DVL2 \ Loop back for the next bit until we have done all 16 \ bits of P(1 0) LDA P \ Set A = P so the low byte is in the result in A ORA T \ Set A to the correct sign bit that we set in T above RTS \ Return from the subroutine
Save ELTF.bin
PRINT "ELITE F" PRINT "Assembled at ", ~CODE_F% PRINT "Ends at ", ~P% PRINT "Code size is ", ~(P% - CODE_F%) PRINT "Execute at ", ~LOAD% PRINT "Reload at ", ~LOAD_F% PRINT "S.2.ELTF ", ~CODE_F%, " ", ~P%, " ", ~LOAD%, " ", ~LOAD_F% \SAVE "3-assembled-output/2.ELTF.bin", CODE_F%, P%, LOAD%