Elite on the BBC Micro

# Elite J parasite source [Elite-A]

``` ELITE J FILE

CODE_J% = P%

Type: Subroutine
Category: Main loop
Summary: Seed the random number generator
Deep dive: Program flow of the main game loop
Generating random numbers
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* DEATH calls entry point M%
* Main game loop for flight (Part 2 of 6) calls entry point M%

The main flight loop covers most of the flight-specific aspects of Elite. This
section covers the following:

* Seed the random number generator

Other entry points:

M%                   The entry point for the main flight loop

.M%

LDA K%                 \ We want to seed the random number generator with a
\ pretty random number, so fetch the contents of K%,
\ which is the x_lo coordinate of the planet. This value
\ will be fairly unpredictable, so it's a pretty good
\ candidate

STA RAND               \ Store the seed in the first byte of the four-byte
\ random number seed that's stored in RAND

Type: Subroutine
Category: Main loop
Summary: Calculate the alpha and beta angles from the current pitch and
roll of our ship
Deep dive: Program flow of the main game loop
Pitching and rolling
Context: See this subroutine on its own page
References: No direct references to this subroutine in this source file

The main flight loop covers most of the flight-specific aspects of Elite. This
section covers the following:

* Calculate the alpha and beta angles from the current pitch and roll

Here we take the current rate of pitch and roll, as set by the joystick or
keyboard, and convert them into alpha and beta angles that we can use in the
matrix functions to rotate space around our ship. The alpha angle covers
roll, while the beta angle covers pitch (there is no yaw in this version of
Elite). The angles are in radians, which allows us to use the small angle
approximation when moving objects in the sky (see the MVEIT routine for more
on this). Also, the signs of the two angles are stored separately, in both
the sign and the flipped sign, as this makes calculations easier.

LDX JSTX               \ Set X to the current rate of roll in JSTX

CPX new_max            \ If X < new_max (where new_max is our current ship's
BCC n_highx            \ maximum roll rate), then jump to n_highx to skip the
\ following instruction

LDX new_max            \ X is at least new_max, so set X to new_max so it is
\ never higher than our current ship's maximum roll rate

.n_highx

CPX new_min            \ If X >= new_min (where new_min is our current ship's
BCS n_lowx             \ minimum roll rate), then jump to n_lowx to skip the
\ following instruction

LDX new_min            \ X is less than new_min, so set X to new_min so it is
\ never lower than our current ship's minimum roll rate

.n_lowx

JSR cntr               \ Apply keyboard damping twice (if enabled) so the roll
JSR cntr               \ rate in X creeps towards the centre by 2

\ The roll rate in JSTX increases if we press ">" (and
\ the RL indicator on the dashboard goes to the right).
\ This rolls our ship to the right (clockwise), but we
\ actually implement this by rolling everything else
\ to the left (anticlockwise), so a positive roll rate
\ in JSTX translates to a negative roll angle alpha

TXA                    \ Set A and Y to the roll rate but with the sign bit
EOR #%10000000         \ flipped (i.e. set them to the sign we want for alpha)
TAY

AND #%10000000         \ Extract the flipped sign of the roll rate and store
STA ALP2               \ in ALP2 (so ALP2 contains the sign of the roll angle
\ alpha)

STX JSTX               \ Update JSTX with the damped value that's still in X

EOR #%10000000         \ Extract the correct sign of the roll rate and store
STA ALP2+1             \ in ALP2+1 (so ALP2+1 contains the flipped sign of the
\ roll angle alpha)

TYA                    \ Set A to the roll rate but with the sign bit flipped

BPL P%+7               \ If the value of A is positive, skip the following
\ three instructions

EOR #%11111111         \ A is negative, so change the sign of A using two's
CLC                    \ complement so that A is now positive and contains
ADC #1                 \ the absolute value of the roll rate, i.e. |JSTX|

LSR A                  \ Divide the (positive) roll rate in A by 4
LSR A

CMP #8                 \ If A >= 8, skip the following instruction
BCS P%+3

LSR A                  \ A < 8, so halve A again

STA ALP1               \ Store A in ALP1, so we now have:
\
\   ALP1 = |JSTX| / 8    if |JSTX| < 32
\
\   ALP1 = |JSTX| / 4    if |JSTX| >= 32
\
\ This means that at lower roll rates, the roll angle is
\ reduced closer to zero than at higher roll rates,
\ which gives us finer control over the ship's roll at
\ lower roll rates
\
\ Because JSTX is in the range -127 to +127, ALP1 is
\ in the range 0 to 31

ORA ALP2               \ Store A in ALPHA, but with the sign set to ALP2 (so
STA ALPHA              \ ALPHA has a different sign to the actual roll rate)

LDX JSTY               \ Set X to the current rate of pitch in JSTY

CPX new_max            \ If X < new_max (where new_max is our current ship's
BCC n_highy            \ maximum pitch rate), then jump to n_highy to skip the
\ following instruction

LDX new_max            \ X is at least new_max, so set X to new_max so it is
\ never higher than our current ship's maximum pitch
\ rate

.n_highy

CPX new_min            \ If X >= new_min (where new_min is our current ship's
BCS n_lowy             \ minimum pitch rate), then jump to n_lowy to skip the
\ following instruction

LDX new_min            \ X is less than new_min, so set X to new_min so it is
\ never lower than our current ship's minimum pitch rate

.n_lowy

JSR cntr               \ Apply keyboard damping so the pitch rate in X creeps
\ towards the centre by 1

TXA                    \ Set A and Y to the pitch rate but with the sign bit
EOR #%10000000         \ flipped
TAY

AND #%10000000         \ Extract the flipped sign of the pitch rate into A

STX JSTY               \ Update JSTY with the damped value that's still in X

STA BET2+1             \ Store the flipped sign of the pitch rate in BET2+1

EOR #%10000000         \ Extract the correct sign of the pitch rate and store
STA BET2               \ it in BET2

TYA                    \ Set A to the pitch rate but with the sign bit flipped

BPL P%+4               \ If the value of A is positive, skip the following
\ instruction

EOR #%11111111         \ A is negative, so flip the bits

ADC #4                 \ Add 4 to the (positive) pitch rate, so the maximum
\ value is now up to 131 (rather than 127)

LSR A                  \ Divide the (positive) pitch rate in A by 16
LSR A
LSR A
LSR A

CMP #3                 \ If A >= 3, skip the following instruction
BCS P%+3

LSR A                  \ A < 3, so halve A again

STA BET1               \ Store A in BET1, so we now have:
\
\   BET1 = |JSTY| / 32    if |JSTY| < 48
\
\   BET1 = |JSTY| / 16    if |JSTY| >= 48
\
\ This means that at lower pitch rates, the pitch angle
\ is reduced closer to zero than at higher pitch rates,
\ which gives us finer control over the ship's pitch at
\ lower pitch rates
\
\ Because JSTY is in the range -131 to +131, BET1 is in
\ the range 0 to 8

ORA BET2               \ Store A in BETA, but with the sign set to BET2 (so
STA BETA               \ BETA has the same sign as the actual pitch rate)

Type: Subroutine
Category: Main loop
Summary: Scan for flight keys and process the results
Deep dive: Program flow of the main game loop
The key logger
Context: See this subroutine on its own page
References: No direct references to this subroutine in this source file

The main flight loop covers most of the flight-specific aspects of Elite. This
section covers the following:

* Scan for flight keys and process the results

Flight keys are logged in the key logger at location KY1 onwards, with a
non-zero value in the relevant location indicating a key press. See the deep
dive on "The key logger" for more details.

The key presses that are processed are as follows:

* Space and "?" to speed up and slow down
* "U", "T" and "M" to disarm, arm and fire missiles
* TAB to activate the hyperspace unit
* ESCAPE to launch an escape pod
* "J" to initiate an in-system jump
* "E" to deploy E.C.M. anti-missile countermeasures
* "C" to use the docking computer
* "A" to fire lasers

LDA KY2                \ If Space is being pressed, keep going, otherwise jump
BEQ MA17               \ down to MA17 to skip the following

LDA DELTA              \ The "go faster" key is being pressed, so first we
CMP new_speed          \ fetch the current speed from DELTA into A, and if
BCC speed_up           \ A < new_speed (the maximum speed of our current ship),
\ then we can go a bit faster, so jump to speed_up to
\ accelerate

.MA17

LDA KY1                \ If "?" is being pressed, keep going, otherwise jump
BEQ MA4                \ down to MA4 to skip the following

DEC DELTA              \ The "slow down" key is being pressed, so we decrement
\ the current ship speed in DELTA

BNE MA4                \ If the speed is still greater than zero, jump to MA4

.speed_up

INC DELTA              \ Otherwise we just braked a little too hard, so bump
\ the speed back up to the minimum value of 1

.MA4

LDA KY15               \ If "U" is being pressed and the number of missiles
AND NOMSL              \ in NOMSL is non-zero, keep going, otherwise jump down
BEQ MA20               \ to MA20 to skip the following

LDY #&EE               \ The "disarm missiles" key is being pressed, so call
JSR ABORT              \ ABORT to disarm the missile and update the missile
\ indicators on the dashboard to green/cyan (Y = &EE)

JSR WA1                \ Call the WA1 routine to make a low, long beep to
\ indicate the missile is now disarmed

LDA #0                 \ Set MSAR to 0 to indicate that no missiles are
STA MSAR               \ currently armed

.MA20

LDA MSTG               \ If MSTG is positive (i.e. it does not have bit 7 set),
BPL MA25               \ then it indicates we already have a missile locked on
\ a target (in which case MSTG contains the ship number
\ of the target), so jump to MA25 to skip targeting. Or
\ to put it another way, if MSTG = &FF, which means
\ there is no current target lock, keep going

LDA KY14               \ If "T" is being pressed, keep going, otherwise jump
BEQ MA25               \ down to MA25 to skip the following

LDX NOMSL              \ If the number of missiles in NOMSL is zero, jump down
BEQ MA25               \ to MA25 to skip the following

STA MSAR               \ The "target missile" key is being pressed and we have
\ at least one missile, so set MSAR = &FF to denote that
\ our missile is currently armed (we know A has the
\ value &FF, as we just loaded it from MSTG and checked
\ that it was negative)

LDY #&E0               \ Change the leftmost missile indicator to yellow/white
DEX                    \ on the missile bar (this call changes the leftmost
JSR MSBARS             \ indicator because we set X to the number of missiles
\ in NOMSL above, and the indicators are numbered from
\ right to left, starting at 0, so X - 1 is the number
\ of the leftmost indicator)

.MA25

LDA KY16               \ If "M" is being pressed, keep going, otherwise jump
BEQ MA24               \ down to MA24 to skip the following

LDA MSTG               \ If MSTG = &FF then there is no target lock, so jump to
BMI MA64               \ MA64 to skip the following (also skipping the checks
\ for TAB, ESCAPE, "J" and "E")

JSR FRMIS              \ The "fire missile" key is being pressed and we have
\ a missile lock, so call the FRMIS routine to fire
\ the missile

.MA24

LDA KY12               \ If TAB is not being pressed (i.e. KY12 = 0) and we do
AND BOMB               \ not have a hyperspace unit fitted (i.e. BOMB = 0),
BEQ MA76               \ jump down to MA76 to skip the following

INC BOMB               \ The "hyperspace unit" key is being pressed and we have
\ a hyperspace unit fitted, so increment BOMB from &FF
\ (hyperspace unit fitted) to 0 (hyperspace unit not
\ fitted), as it is a single-use item and we are now
\ using it

INC new_hold           \ Free up one tonne of space in the hold, as we have
\ just used up the hyperspace unit

JSR DORND              \ Set A and X to random numbers

STA QQ9                \ Set (QQ9, QQ10) to (A, X), so we jump to a random
STX QQ10               \ point in the galaxy

JSR TT111              \ Select the system closest to galactic coordinates
\ (QQ9, QQ10)

JSR hyper_snap         \ Call hyper_snap to perform a hyperspace, but without
\ using up any fuel

.MA76

LDA KY19               \ If "C" is being pressed, and we have a docking
AND DKCMP              \ computer fitted, then KY19 and DKCMP will both be &FF,
BNE dock_toggle        \ so jump down to dock_toggle with A set to &FF

LDA KY20               \ If "P" is being pressed, keep going, otherwise skip
BEQ MA78               \ the next two instructions

LDA #0                 \ The "cancel docking computer" key is bring pressed,
\ so turn it off by setting A to 0, so we set auto to 0
\ in the next instruction

.dock_toggle

STA auto               \ Set auto to the value in A, which will be &FF if we
\ just turned on the docking computer, or 0 if we just
\ turned it off

.MA78

LDA KY13               \ If ESCAPE is being pressed and we have an escape pod
AND ESCP               \ fitted, keep going, otherwise jump to noescp to skip
BEQ noescp             \ the following instructions

JMP ESCAPE             \ The "launch escape pod" button is being pressed and
\ we have an escape pod fitted, so jump to ESCAPE to
\ launch it, and exit the main flight loop using a tail
\ call

.noescp

LDA KY18               \ If "J" is being pressed, keep going, otherwise skip
BEQ P%+5               \ the next instruction

JSR WARP               \ Call the WARP routine to do an in-system jump

LDA KY17               \ If "E" is being pressed and we have an E.C.M. fitted,
AND ECM                \ keep going, otherwise jump down to MA64 to skip the
BEQ MA64               \ following

LDA ECMA               \ If ECMA is non-zero, that means an E.C.M. is already
BNE MA64               \ operating and is counting down (this can be either
\ our E.C.M. or an opponent's), so jump down to MA64 to
\ skip the following (as we can't have two E.C.M.
\ systems operating at the same time)

DEC ECMP               \ The "E.C.M." button is being pressed and nobody else
\ is operating their E.C.M., so decrease the value of
\ ECMP to make it non-zero, to denote that our E.C.M.
\ is now on

JSR ECBLB2             \ Call ECBLB2 to light up the E.C.M. indicator bulb on
\ the dashboard, set the E.C.M. countdown timer to 32,
\ and start making the E.C.M. sound

.MA64

.MA68

LDA #0                 \ Set LAS = 0, to switch the laser off while we do the
STA LAS                \ following logic

STA DELT4              \ Take the 16-bit value (DELTA 0) - i.e. a two-byte
LDA DELTA              \ number with DELTA as the high byte and 0 as the low
LSR A                  \ byte - and divide it by 4, storing the 16-bit result
ROR DELT4              \ in DELT4(1 0). This has the effect of storing the
LSR A                  \ current speed * 64 in the 16-bit location DELT4(1 0)
ROR DELT4
STA DELT4+1

JSR read_0346          \ Get the value of the I/O processor's copy of LASCT

BNE MA3                \ If LASCT is zero, keep going, otherwise the laser is
\ a pulse laser that is between pulses, so jump down to
\ MA3 to skip the following

LDA KY7                \ If "A" is being pressed, keep going, otherwise jump
BEQ MA3                \ down to MA3 to skip the following

LDA GNTMP              \ If the laser temperature >= 242 then the laser has
CMP #242               \ overheated, so jump down to MA3 to skip the following
BCS MA3

LDX VIEW               \ If the current space view has a laser fitted (i.e. the
LDA LASER,X            \ laser power for this view is greater than zero), then
BEQ MA3                \ keep going, otherwise jump down to MA3 to skip the
\ following

\ If we get here, then the "fire" button is being
\ pressed, our laser hasn't overheated and isn't already
\ being fired, and we actually have a laser fitted to
\ the current space view, so it's time to hit me with
\ those laser beams

PHA                    \ Store the current view's laser power on the stack

AND #%01111111         \ Set LAS and LAS2 to bits 0-6 of the laser power
STA LAS2
STA LAS

LDA #0                 \ Call the NOISE routine with A = 0 to make the sound
JSR NOISE              \ of our laser firing

JSR LASLI              \ Call LASLI to draw the laser lines

PLA                    \ Restore the current view's laser power into A

BPL ma1                \ If the laser power has bit 7 set, then it's an "always
\ on" laser rather than a pulsing laser, so keep going,
\ otherwise jump down to ma1 to skip the following
\ instruction

LDA #0                 \ This is an "always on" laser (i.e. a beam laser,
\ as the cassette version of Elite doesn't have military
\ lasers), so set A = 0, which will be stored in LASCT
\ to denote that this is not a pulsing laser

.ma1

JSR write_0346         \ Tell the I/O processor to set its copy of LASCT to A,
\ which will be 0 for beam lasers, or the laser power
\ (15) for pulse lasers. See MS23 below for more on
\ laser pulsing and LASCT

Type: Subroutine
Category: Main loop
Summary: For each nearby ship: Copy the ship's data block from K% to the
zero-page workspace at INWK
Deep dive: Program flow of the main game loop
Ship data blocks
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* KS1 calls entry point MAL1
* Main flight loop (Part 12 of 16) calls entry point MAL1

The main flight loop covers most of the flight-specific aspects of Elite. This
section covers the following:

* Start looping through all the ships in the local bubble, and for each
one:

* Copy the ship's data block from K% to INWK

* Set XX0 to point to the ship's blueprint (if this is a ship)

Other entry points:

MAL1                 Marks the beginning of the ship analysis loop, so we
can jump back here from part 12 of the main flight loop
to work our way through each ship in the local bubble.
We also jump back here when a ship is removed from the
bubble, so we can continue processing from the next ship

.MA3

LDX #0                 \ We're about to work our way through all the ships in
\ our local bubble of universe, so set a counter in X,
\ starting from 0, to refer to each ship slot in turn

.MAL1

STX XSAV               \ Store the current slot number in XSAV

LDA FRIN,X             \ Fetch the contents of this slot into A. If it is 0
BNE P%+5               \ then this slot is empty and we have no more ships to
JMP MA18               \ process, so jump to MA18 below, otherwise A contains
\ the type of ship that's in this slot, so skip over the
\ JMP MA18 instruction and keep going

STA TYPE               \ Store the ship type in TYPE

JSR GINF               \ Call GINF to fetch the address of the ship data block
\ for the ship in slot X and store it in INF. The data
\ block is in the K% workspace, which is where all the
\ ship data blocks are stored

\ Next we want to copy the ship data block from INF to
\ the zero-page workspace at INWK, so we can process it
\ more efficiently

LDY #NI%-1             \ There are NI% bytes in each ship data block (and in
\ the INWK workspace, so we set a counter in Y so we can
\ loop through them

.MAL2

LDA (INF),Y            \ Load the Y-th byte of INF and store it in the Y-th
STA INWK,Y             \ byte of INWK

DEY                    \ Decrement the loop counter

BPL MAL2               \ Loop back for the next byte until we have copied the
\ last byte from INF to INWK

LDA TYPE               \ If the ship type is negative then this indicates a
BMI MA21               \ planet or sun, so jump down to MA21, as the next bit
\ sets up a pointer to the ship blueprint, which doesn't
\ apply to planets and suns

ASL A                  \ Set Y = ship type * 2
TAY

LDA XX21-2,Y           \ The ship blueprints at XX21 start with a lookup
STA XX0                \ table that points to the individual ship blueprints,
\ so this fetches the low byte of this particular ship
\ type's blueprint and stores it in XX0

LDA XX21-1,Y           \ Fetch the high byte of this particular ship type's
STA XX0+1              \ blueprint and store it in XX0+1

\ We now go straight to part 6, omitting part 5 from the
\ original disc version, as part 5 implements the energy
\ bomb, and Elite-A replaces the energy bomb with the
\ hyperspace unit

Type: Subroutine
Category: Main loop
Summary: For each nearby ship: Move the ship in space and copy the updated
INWK data block back to K%
Deep dive: Program flow of the main game loop
Program flow of the ship-moving routine
Ship data blocks
Context: See this subroutine on its own page
References: No direct references to this subroutine in this source file

The main flight loop covers most of the flight-specific aspects of Elite. This
section covers the following:

* Continue looping through all the ships in the local bubble, and for each
one:

* Move the ship in space

* Copy the updated ship's data block from INWK back to K%

.MA21

JSR MVEIT_FLIGHT       \ Call MVEIT_FLIGHT to move the ship we are processing
\ in space

\ Now that we are done processing this ship, we need to
\ copy the ship data back from INWK to the correct place
\ in the K% workspace. We already set INF in part 4 to
\ point to the ship's data block in K%, so we can simply
\ do the reverse of the copy we did before, this time
\ copying from INWK to INF

LDY #(NI%-1)           \ Set a counter in Y so we can loop through the NI%
\ bytes in the ship data block

.MAL3

LDA INWK,Y             \ Load the Y-th byte of INWK and store it in the Y-th
STA (INF),Y            \ byte of INF

DEY                    \ Decrement the loop counter

BPL MAL3               \ Loop back for the next byte, until we have copied the
\ last byte from INWK back to INF

Type: Subroutine
Category: Main loop
Summary: For each nearby ship: Check whether we are docking, scooping or
colliding with it
Deep dive: Program flow of the main game loop
Context: See this subroutine on its own page
References: No direct references to this subroutine in this source file

The main flight loop covers most of the flight-specific aspects of Elite. This
section covers the following:

* Continue looping through all the ships in the local bubble, and for each
one:

* Check how close we are to this ship and work out if we are docking,
scooping or colliding with it

LDA INWK+31            \ Fetch the status of this ship from bits 5 (is ship
AND #%10100000         \ exploding?) and bit 7 (has ship been killed?) from
\ ship byte #31 into A

JSR MAS4               \ Or this value with x_hi, y_hi and z_hi

BNE MA65               \ If this value is non-zero, then either the ship is
\ far away (i.e. has a non-zero high byte in at least
\ one of the three axes), or it is already exploding,
\ or has been flagged as being killed - in which case
\ jump to MA65 to skip the following, as we can't dock
\ scoop or collide with it

LDA INWK               \ Set A = (x_lo OR y_lo OR z_lo), and if bit 7 of the
ORA INWK+3             \ result is set, the ship is still a fair distance
ORA INWK+6             \ away (further than 127 in at least one axis), so jump
BMI MA65               \ to MA65 to skip the following, as it's too far away to
\ dock, scoop or collide with

LDX TYPE               \ If the current ship type is negative then it's either
BMI MA65               \ a planet or a sun, so jump down to MA65 to skip the
\ following, as we can't dock with it or scoop it

CPX #SST               \ If this ship is the space station, jump to ISDK to
BEQ ISDK               \ check whether we are docking with it

AND #%11000000         \ If bit 6 of (x_lo OR y_lo OR z_lo) is set, then the
BNE MA65               \ ship is still a reasonable distance away (further than
\ 63 in at least one axis), so jump to MA65 to skip the
\ following, as it's too far away to dock, scoop or
\ collide with

CPX #MSL               \ If this ship is a missile, jump down to MA65 to skip
BEQ MA65               \ the following, as we can't scoop or dock with a
\ missile, and it has its own dedicated collision
\ checks in the TACTICS routine

LDA BST                \ If we have fuel scoops fitted then BST will be &FF,
\ otherwise it will be 0

AND INWK+5             \ Ship byte #5 contains the y_sign of this ship, so a
\ negative value here means the canister is below us,
\ which means the result of the AND will be negative if
\ the canister is below us and we have a fuel scoop
\ fitted

BPL MA58               \ If the result is positive, then we either have no
\ scoop or the canister is above us, and in both cases
\ this means we can't scoop the item, so jump to MA58
\ to process a collision

Type: Subroutine
Category: Main loop
Summary: For each nearby ship: Process us potentially scooping this item
Deep dive: Program flow of the main game loop
Context: See this subroutine on its own page
References: No direct references to this subroutine in this source file

The main flight loop covers most of the flight-specific aspects of Elite. This
section covers the following:

* Continue looping through all the ships in the local bubble, and for each
one:

* Process us potentially scooping this item

CPX #OIL               \ If this is a cargo canister, jump to oily to randomly
BEQ oily               \ decide the canister's contents

LDY #0                 \ Fetch byte #0 of the ship's blueprint
LDA (XX0),Y

LSR A                  \ Shift it right four times, so A now contains the high
LSR A                  \ nibble (i.e. bits 4-7)
LSR A
LSR A

BEQ MA58               \ If A = 0, jump to MA58 to skip all the docking and
\ scooping checks

\ Only the Thargon, alloy plate, splinter and escape pod
\ have non-zero upper nibbles in their blueprint byte #0
\ so if we get here, our ship is one of those, and the
\ upper nibble gives the market item number of the item
\ when scooped, less 1

ADC #1                 \ Add 1 to the upper nibble to get the market item
\ number

BNE slvy2              \ Skip to slvy2 so we scoop the ship as a market item

.oily

JSR DORND              \ Set A and X to random numbers and reduce A to a
AND #15                \ random number in the range 0-15

.slvy2

\ By the time we get here, we are scooping, and A
\ contains the type of item we are scooping (a random
\ number 0-15 if we are scooping a cargo canister, 3 if
\ we are scooping an escape pod, or 16 if we are
\ scooping a Thargon). These numbers correspond to the
\ relevant market items (see QQ23 for a list), so a
\ cargo canister can contain anything from food to
\ gem-stones, while escape pods contain slaves, and
\ Thargons become alien items when scooped

TAX                    \ Copy the type of cargo we are scooping into X

JSR tnpr1              \ Call tnpr1 to work out whether we have room in the
\ hold for the scooped item (the C flag contains the
\ result)

BCS MA58               \ If the C flag is set then we have no room in the hold
\ for the scooped item, so jump down to MA58 to skip all
\ the docking and scooping checks

INC QQ20,X             \ Scooping was successful, so increment the number of
\ items of type X that we have in the hold

TXA                    \ Print recursive token 48 + X as an in-flight token,
ADC #208               \ which will be in the range 48 ("FOOD") to 64 ("ALIEN
JSR MESS               \ ITEMS"), so this prints the scooped item's name

JSR top_6a             \ The item has now been scooped, so call top_6a to set
\ bit 7 of its NEWB flags to indicate this

.MA65

JMP MA26               \ If we get here, then the ship we are processing was
\ too far away to be scooped, docked or collided with,
\ and move on to missile targeting

Type: Subroutine
Category: Main loop
Summary: For each nearby ship: If it is a space station, check whether we
are successfully docking with it
Deep dive: Program flow of the main game loop
Docking checks
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* ESCAPE calls entry point GOIN

The main flight loop covers most of the flight-specific aspects of Elite. This
section covers the following:

* Process docking with a space station

For details on the various docking checks in this routine, see the deep dive
on "Docking checks".

Other entry points:

GOIN                 We jump here from part 3 of the main flight loop if the
docking computer is activated by pressing "C"

.ISDK

LDA K%+NI%+36          \ 1. Fetch the NEWB flags (byte #36) of the second ship
AND #%00000100         \ in the ship data workspace at K%, which is reserved
BNE MA62               \ for the sun or the space station (in this case it's
\ the latter), and if bit 2 is set, meaning the station
\ is hostile, jump down to MA62 to fail docking (so
\ trying to dock at a station that we have annoyed does
\ not end well)

LDA INWK+14            \ 2. If nosev_z_hi < 214, jump down to MA62 to fail
CMP #214               \ docking, as the angle of approach is greater than 26
BCC MA62               \ degrees

LDY #NI%               \ Set Y = NI% so the following call to SPS1 calculates
\ the vector to the space station rather than the planet

JSR SPS1               \ Call SPS1 to calculate the vector to the space station
\ and store it in XX15

LDA XX15+2             \ Set A to the z-axis of the vector

CMP #86                \ 4. If z-axis < 86, jump to MA62 to fail docking, as
BCC MA62               \ we are not in the 26.3 degree safe cone of approach

LDA INWK+16            \ 5. If |roofv_x_hi| < 80, jump to MA62 to fail docking,
AND #%01111111         \ as the slot is more than 36.6 degrees from horizontal
CMP #80
BCC MA62

.GOIN

\ If we arrive here, either the docking computer has
\ been activated, or we just docked successfully

JSR RES2               \ Reset a number of flight variables and workspaces

LDA #8                 \ Set the step size for the launch tunnel rings to 8, so
\ there are fewer sections in the rings and they are
\ quite polygonal (compared to the step size of 4 used
\ in the much rounder hyperspace rings)

JSR HFS2               \ Call HFS2 to draw the launch tunnel rings

JMP DOENTRYS           \ Go to the docking bay (i.e. show the ship hanger)

.MA62

\ If we arrive here, docking has just failed

LDA DELTA              \ If the ship's speed is >= 5, jump to n_crunch to
CMP #5                 \ register a fair emount of damage to our shields (128)
BCS n_crunch

\ Otherwise we have just crashed gently into the
\ station, so we need to check whether it's our fault or
\ the docking computer

Type: Subroutine
Category: Main loop
Summary: For each nearby ship: Remove if scooped, or process collisions
Deep dive: Program flow of the main game loop
Context: See this subroutine on its own page
References: No direct references to this subroutine in this source file

The main flight loop covers most of the flight-specific aspects of Elite. This
section covers the following:

* Continue looping through all the ships in the local bubble, and for each
one:

* Remove scooped item after both successful and failed scoopings

* Process collisions

LDA auto               \ If the docking computer is on, then auto will be &FF,
AND #%00000100         \ so this will set A = 1, a tiny amount of damage
EOR #%00000101         \
\ If the docking computer is off, then auto will be 0,
\ so this will set A = 5, a small amount of damage

BNE MA63               \ Jump to MA63 to process the damage in A (this BNE is
\ effectively a JMP as A will never be zero)

.MA58

\ If we get here, we have collided with something in a
\ potentially fatal way

LDA #64                \ Call n_hit to apply a hit of strength 64 to the ship
JSR n_hit              \ we just collided with

JSR anger_8c           \ Call anger_8c to make the ship angry

.n_crunch

\ If we get here, we have collided with something, so we
\ need to take a hit to our shields

LDA #128               \ Set A = 128 to indicate a fairly large amount of
\ damage

.MA63

JSR OOPS               \ The amount of damage is in A, so call OOPS to reduce
\ our shields, and if the shields are gone, there's a
\ a chance of cargo loss or even death

JSR EXNO3              \ Make the sound of colliding with the other ship and
\ fall through into MA26 to try targeting a missile

Type: Subroutine
Category: Main loop
Summary: For each nearby ship: Process missile lock and firing our laser
Deep dive: Program flow of the main game loop
Flipping axes between space views
Context: See this subroutine on its own page
References: No direct references to this subroutine in this source file

The main flight loop covers most of the flight-specific aspects of Elite. This
section covers the following:

* Continue looping through all the ships in the local bubble, and for each
one:

* If this is not the front space view, flip the axes of the ship's
coordinates in INWK

* Process missile lock

* Process our laser firing

.MA26

LDA NEWB               \ If bit 7 of the ship's NEWB flags is clear, skip the
BPL P%+5               \ following instruction

JSR SCAN               \ Bit 7 of the ship's NEWB flags is set, which means the
\ ship has docked or been scooped, so we draw the ship
\ on the scanner, which has the effect of removing it

LDA QQ11               \ If this is not a space view, jump to MA15 to skip
BNE MA15               \ missile and laser locking

LDX VIEW               \ Load the current view into X

BEQ P%+5               \ If the current view is the front view, skip the
\ following instruction, as the geometry in INWK is

JSR PU1                \ Call PU1 to update the geometric axes in INWK to
\ match the view (front, rear, left, right)

JSR HITCH              \ Call HITCH to see if this ship is in the crosshairs,
BCC MA8                \ in which case the C flag will be set (so if there is
\ no missile or laser lock, we jump to MA8 to skip the
\ following)

LDA MSAR               \ We have missile lock, so check whether the leftmost
BEQ MA47               \ missile is currently armed, and if not, jump to MA47
\ to process laser fire, as we can't lock an unarmed
\ missile

JSR BEEP               \ We have missile lock and an armed missile, so call
\ the BEEP subroutine to make a short, high beep

LDX XSAV               \ Call ABORT2 to store the details of this missile
LDY #&0E               \ lock, with the targeted ship's slot number in X
JSR ABORT2             \ (which we stored in XSAV at the start of this ship's
\ loop at MAL1), and set the colour of the missile
\ indicator to the colour in Y (red = &0E)

.MA47

\ If we get here then the ship is in our sights, but
\ we didn't lock a missile, so let's see if we're
\ firing the laser

LDA LAS                \ If we are firing the laser then LAS will contain the
BEQ MA8                \ laser power (which we set in MA68 above), so if this
\ is zero, jump down to MA8 to skip the following

LDX #15                \ We are firing our laser and the ship in INWK is in
JSR EXNO               \ the crosshairs, so call EXNO to make the sound of
\ us making a laser strike on another ship

LDA LAS                \ Set A to the power of the laser we just used to hit
\ the ship (i.e. the laser in the current view)

LDY TYPE               \ Did we just hit the space station? If so, jump to
CPY #SST               \ MA14 to make it angry
BEQ MA14

CPY #CON               \ If the ship we hit is not a Constrictor, jump to BURN
BNE BURN               \ to skip the following

LSR A                  \ Divide the laser power of the current view by 2, so
\ the damage inflicted on the Constrictor is half of the
\ damage our military lasers would inflict on a normal
\ ship

.BURN

LSR A                  \ Divide the laser power of the current view by 2

JSR n_hit              \ Call n_hit to apply a laser strike of strength A to
\ the enemy ship

BCS MA14               \ If the C flag is set then the enemy ship survived the
\ hit, so jump down to MA14 to make it angry

LDA TYPE               \ Did we just kill an asteroid? If not, jump to nosp,
CMP #AST               \ otherwise keep going
BNE nosp

LDA LAS                \ Did we kill the asteroid using mining lasers? If so,
CMP new_mining         \ then our current laser strength in LAS will match the
BNE nosp               \ strength of mining lasers when fitted to our current
\ ship type, which is stored in new_mining. If they
\ don't match, which means we didn't use minig lasers,

JSR DORND              \ Set A and X to random numbers

LDX #SPL               \ Set X to the ship type for a splinter

AND #3                 \ Reduce the random number in A to the range 0-3

JSR SPIN2              \ Call SPIN2 to spawn A items of type X (i.e. spawn
\ 0-3 spliters)

.nosp

LDY #PLT               \ Randomly spawn some alloy plates
JSR SPIN

LDY #OIL               \ Randomly spawn some cargo canisters
JSR SPIN

JSR EXNO2              \ Call EXNO2 to process the fact that we have killed a
\ ship (so increase the kill tally, make an explosion
\ sound and so on)

.MA14

JSR anger_8c           \ Call anger_8c to make this ship hostile angry, now
\ that we have hit it

Type: Subroutine
Category: Main loop
Summary: For each nearby ship: Draw the ship, remove if killed, loop back
Deep dive: Program flow of the main game loop
Drawing ships
Context: See this subroutine on its own page
References: No direct references to this subroutine in this source file

The main flight loop covers most of the flight-specific aspects of Elite. This
section covers the following:

* Continue looping through all the ships in the local bubble, and for each
one:

* Draw the ship

* Process removal of killed ships

* Loop back up to MAL1 to move onto the next ship in the local bubble

.MA8

JSR LL9_FLIGHT         \ Call LL9 to draw the ship we're processing on-screen

.MA15

LDY #35                \ Fetch the ship's energy from byte #35 and copy it to
LDA INWK+35            \ byte #35 in INF (so the ship's data in K% gets
STA (INF),Y            \ updated)

LDA NEWB               \ If bit 7 of the ship's NEWB flags is set, which means
BMI KS1S               \ the ship has docked or been scooped, jump to KS1S to
\ skip the following, as we can't get a bounty for a
\ ship that's no longer around

LDA INWK+31            \ If bit 7 of the ship's byte #31 is clear, then the
BPL MAC1               \ ship hasn't been killed by collision or laser fire,

AND #%00100000         \ If bit 5 of the ship's byte #31 is clear then the
BEQ MAC1               \ ship is no longer exploding, so jump to MAC1 to skip
\ the following

\ We now update our FIST flag ("fugitive/innocent
\ status") by 1 if we didn't kill a cop, or by a large
\ amount if we did - specifically, if we killed a cop,
\ then the most significant bit in FIST that is
\ currently clear will be set, which means we increase
\ FIST by the highest multiple of 2 that we can add and
\ still fit the result in a byte
\
\ Also, at this point, we know that A = %00100000 (as we
\ didn't take the BEQ branch)

BIT NEWB               \ If bit 6 of the ship's NEWB flags is set, then this
\ policeman

BEQ n_goodboy          \ The BIT NEWB instruction sets the Z flag according to
\ the result of:
\
\   A AND NEWB = %00100000 AND NEWB
\
\ so this jumps to n_goodboy if bit 5 of NEWB is clear,
\ so in other words, if the ship is no longer exploding,
\ we don't update FIST

LDA #%10000000         \ Set A so that the shift and rotate instructions we're
\ about to do set A = %00000001, so we increase our FIST
\ status by just 1

\ We get here with two possible values of A:
\
\   * A = %00100000 if we just killed a cop
\   * A = %10000000 otherwise

ASL A                  \ Shift and rotate A so that we get:
ROL A                  \
\   * A = %10000000 if we just killed a cop
\   * A = %00000001 otherwise

.n_bitlegal

LSR A                  \ We now shift A to the right and AND it with FIST,
BIT FIST               \ repeating the process until the single set bit in A
BNE n_bitlegal         \ matches a clear bit in FIST, so this shifts A right
\ so that the set bit matches the highest clear bit in
\ FIST (if we just killed a cop), or it sets A to 0 and
\ sets the C flag (if we didn't)

ADC FIST               \ Set A = A + C + FIST, so:
\
\   * A = A + 0 + FIST if we just killed a cop
\   * A = 0 + 1 + FIST otherwise
\
\ so if we just killed a cop, this will effectively set
\ the highest clear bit in FIST, otherwise we just add 1
\ to FIST

\ showing an on-screen bounty for this kill, and without
\ updating FIST first (as we are too bad to get any
\ worse)

STA FIST               \ Otherwise update the value of FIST to the new value

BCC KS1S               \ Jump to KS1S to skip showing an on-screen bounty for
\ this kill (the BCC is effectively a JMP as we just
\ passed through a BCS)

.n_goodboy

LDA DLY                \ If we already have an in-flight message on-screen (in
ORA MJ                 \ which case DLY > 0), or we are in witchspace (in
BNE KS1S               \ which case MJ > 0), jump to KS1S to skip showing an
\ on-screen bounty for this kill

LDY #10                \ Fetch byte #10 of the ship's blueprint, which is the
LDA (XX0),Y            \ low byte of the bounty awarded when this ship is
\ killed (in Cr * 10)

TAX                    \ Put the low byte of the bounty into X

INY                    \ Fetch byte #11 of the ship's blueprint, which is the
LDA (XX0),Y            \ high byte of the bounty awarded (in Cr * 10), and put
TAY                    \ it into Y

JSR MCASH              \ Call MCASH to add (Y X) to the cash pot

LDA #0                 \ Print control code 0 (current cash, right-aligned to
JSR MESS               \ width 9, then " CR", newline) as an in-flight message

.KS1S

JMP KS1                \ Process the killing of this ship (which removes this
\ ship from its slot and shuffles all the other ships
\ down to close up the gap)

.n_hit

\ If we get here then we need to apply a hit of strength
\ A to the enemy ship

STA T                  \ Store the strength of the hit in T

SEC                    \ Set the C flag so we can do some subtraction

LDY #14                \ Fetch byte #14 of the enemy ship's blueprint into A,
LDA (XX0),Y            \ which gives the ship's maximum energy/shields

AND #7                 \ Reduce the maximum energy/shields figure to the range
\ 0-7

SBC T                  \ Subtract the hit strength from the maximum shields, so
\ A = ship energy - hit strength

BCS n_kill             \ If the subtraction didn't underflow, then the hit was
\ with the C flag set to indicate that the ship has
\ survived the attack

\BCC n_defense          \ These instructions are commented out in the original
\LDA #&FF               \ source
\.n_defense

CLC                    \ Otherwise the hit was stronger than the enemy shields,
ADC INWK+35            \ so the ship's energy level needs to register some
STA INWK+35            \ damage. A contains a negative number whose magnitude
\ is the amount by which the attack is greater than the
\ shield defence, so we can simply add this figure to
\ the ship's energy levels in the ship's byte #35 to
\ reduce the energy by the amount that the attack was
\ stronger than the defence (i.e. the shields absorb the
\ amount of energy that is defined in the blueprint, and
\ the rest of the hit makes it through to damage the
\ energy levels)

BCS n_kill             \ Adding this negative number is the same as subtracting
\ a positive number, so having the C flag set indicates
\ that the subtraction didn't underflow - in other words
\ the damage isn't greater than the energy levels, and
\ the ship has survuved the hit. In this case we jump to
\ n_kill with the C flag set to indicate that the ship
\ has survived the attack

JSR TA87+3             \ If we get here then the ship has not survived the
\ attack, so call TA87+3 to set bit 7 of the ship's byte
\ #31, which marks the ship as being killed

.n_kill

RTS                    \ Return from the subroutine with the C flag set if the
\ ship has survived the onslaught, or clear if it has
\ been destroyed

.MAC1

LDA TYPE               \ If the ship we are processing is a planet or sun,
BMI MA27               \ jump to MA27 to skip the following two instructions

JSR FAROF              \ If the ship we are processing is a long way away (its
BCC KS1S               \ distance in any one direction is > 224, jump to KS1S
\ to remove the ship from our local bubble, as it's just
\ left the building

.MA27

LDY #31                \ Fetch the ship's explosion/killed state from byte #31
LDA INWK+31            \ and copy it to byte #31 in INF (so the ship's data in
STA (INF),Y            \ K% gets updated)

LDX XSAV               \ We're done processing this ship, so fetch the ship's
\ slot number, which we saved in XSAV back at the start
\ of the loop

INX                    \ Increment the slot number to move on to the next slot

JMP MAL1               \ And jump back up to the beginning of the loop to get
\ the next ship in the local bubble for processing

Type: Subroutine
Category: Main loop
Summary: Charge shields and energy banks
Deep dive: Program flow of the main game loop
Scheduling tasks with the main loop counter
Context: See this subroutine on its own page
References: No direct references to this subroutine in this source file

The main flight loop covers most of the flight-specific aspects of Elite. This
section covers the following:

* Charge shields and energy banks (every 7 iterations of the main loop)

.MA18

.MA77

LDA MCNT               \ Fetch the main loop counter and calculate MCNT mod 7,
AND #7                 \ jumping to MA22 if it is non-zero (so the following
BNE MA22               \ code only runs every 8 iterations of the main loop)

LDX ENERGY             \ Fetch our ship's energy levels and skip to b if bit 7
BPL b                  \ is not set, i.e. only charge the shields from the
\ energy banks if they are at more than 50% charge

LDX ASH                \ Call SHD to recharge our aft shield and update the
JSR SHD                \ shield status in ASH
STX ASH

LDX FSH                \ Call SHD to recharge our forward shield and update
JSR SHD                \ the shield status in FSH
STX FSH

.b

SEC                    \ Set A = ENERGY + ENGY + 1, so our ship's energy
LDA ENGY               \ level goes up by the correct amount for our current
ADC ENERGY             \ ship, depending on whether we have an energy unit
\ fitted

BCS P%+5               \ If the value of A did not overflow (the maximum
STA ENERGY             \ energy level is &FF), then store A in ENERGY

Type: Subroutine
Category: Main loop
Summary: Spawn a space station if we are close enough to the planet
Deep dive: Program flow of the main game loop
Scheduling tasks with the main loop counter
Ship data blocks
Context: See this subroutine on its own page
References: No direct references to this subroutine in this source file

The main flight loop covers most of the flight-specific aspects of Elite. This
section covers the following:

* Spawn a space station if we are close enough to the planet (every 32
iterations of the main loop)

LDA MJ                 \ If we are in witchspace, jump down to MA23S to skip
BNE MA23S              \ the following, as there are no space stations in
\ witchspace

LDA MCNT               \ Fetch the main loop counter and calculate MCNT mod 32,
AND #31                \ jumping to MA93 if it is on-zero (so the following
BNE MA93               \ code only runs every 32 iterations of the main loop

LDA SSPR               \ If we are inside the space station safe zone, jump to
BNE MA23S              \ MA23S to skip the following, as we already have a
\ space station and don't need another

TAY                    \ Set Y = A = 0 (A is 0 as we didn't branch with the
\ previous BNE instruction)

JSR MAS2               \ Call MAS2 to calculate the largest distance to the
BNE MA23S              \ planet in any of the three axes, and if it's
\ are too far from the planet to bump into a space
\ station

\ We now want to spawn a space station, so first we
\ need to set up a ship data block for the station in
\ INWK that we can then pass to NWSPS to add a new
\ station to our bubble of universe. We do this by
\ copying the planet data block from K% to INWK so we
\ can work on it, but we only need the first 29 bytes,
\ as we don't need to worry about bytes #29 to #35
\ for planets (as they don't have rotation counters,
\ AI, explosions, missiles, a ship line heap or energy
\ levels)

LDX #28                \ So we set a counter in X to copy 29 bytes from K%+0
\ to K%+28

.MAL4

LDA K%,X               \ Load the X-th byte of K% and store in the X-th byte
STA INWK,X             \ of the INWK workspace

DEX                    \ Decrement the loop counter

BPL MAL4               \ Loop back for the next byte until we have copied the
\ first 28 bytes of K% to INWK

\ We now check the distance from our ship (at the
\ origin) towards the planet's surface, by adding the
\ planet's nosev vector to the planet's centre at
\ (x, y, z) and checking our distance to the end
\ point along the relevant axis

INX                    \ Set X = 0 (as we ended the above loop with X as &FF)

LDY #9                 \ Call MAS1 with X = 0, Y = 9 to do the following:
JSR MAS1               \
\   (x_sign x_hi x_lo) += (nosev_x_hi nosev_x_lo) * 2
\
\   A = |x_hi|

BNE MA23S              \ If A > 0, jump to MA23S to skip the following, as we
\ are too far from the planet in the x-direction to
\ bump into a space station

LDX #3                 \ Call MAS1 with X = 3, Y = 11 to do the following:
LDY #11                \
JSR MAS1               \   (y_sign y_hi y_lo) += (nosev_y_hi nosev_y_lo) * 2
\
\   A = |y_hi|

BNE MA23S              \ If A > 0, jump to MA23S to skip the following, as we
\ are too far from the planet in the y-direction to
\ bump into a space station

LDX #6                 \ Call MAS1 with X = 6, Y = 13 to do the following:
LDY #13                \
JSR MAS1               \   (z_sign z_hi z_lo) += (nosev_z_hi nosev_z_lo) * 2
\
\   A = |z_hi|

BNE MA23S              \ If A > 0, jump to MA23S to skip the following, as we
\ are too far from the planet in the z-direction to
\ bump into a space station

LDA #192               \ Call FAROF2 to compare x_hi, y_hi and z_hi with 192,
JSR FAROF2             \ which will set the C flag if all three are < 192, or
\ clear the C flag if any of them are >= 192

BCC MA23S              \ Jump to MA23S if any one of x_hi, y_hi or z_hi are
\ >= 192 (i.e. they must all be < 192 for us to be near
\ enough to the planet to bump into a space station)

JSR WPLS               \ Call WPLS to remove the sun from the screen, as we
\ can't have both the sun and the space station at the
\ same time

JSR NWSPS              \ Add a new space station to our local bubble of
\ universe

.MA23S

JMP MA23               \ Jump to MA23 to skip the following planet and sun
\ altitude checks

Type: Subroutine
Category: Main loop
Summary: Perform altitude checks with the planet and sun and process fuel
scooping if appropriate
Deep dive: Program flow of the main game loop
Scheduling tasks with the main loop counter
Context: See this subroutine on its own page
References: No direct references to this subroutine in this source file

The main flight loop covers most of the flight-specific aspects of Elite. This
section covers the following:

* Perform an altitude check with the planet (every 32 iterations of the main
loop, on iteration 10 of each 32)

* Perform an an altitude check with the sun and process fuel scooping (every
32 iterations of the main loop, on iteration 20 of each 32)

.MA22

LDA MJ                 \ If we are in witchspace, jump down to MA23S to skip
BNE MA23S              \ the following, as there are no planets or suns to
\ bump into in witchspace

LDA MCNT               \ Fetch the main loop counter and calculate MCNT mod 32,
AND #31                \ which tells us the position of this loop in each block
\ of 32 iterations

.MA93

CMP #10                \ If this is the tenth iteration in this block of 32,
BNE MA29               \ do the following, otherwise jump to MA29 to skip the
\ planet altitude check and move on to the sun distance
\ check

LDA #50                \ If our energy bank status in ENERGY is >= 50, skip
CMP ENERGY             \ printing the following message (so the message is
BCC P%+6               \ only shown if our energy is low)

ASL A                  \ Print recursive token 100 ("ENERGY LOW{beep}") as an
JSR MESS               \ in-flight message

LDY #&FF               \ Set our altitude in ALTIT to &FF, the maximum
STY ALTIT

INY                    \ Set Y = 0

JSR m                  \ Call m to calculate the maximum distance to the
\ planet in any of the three axes, returned in A

BNE MA23               \ If A > 0 then we are a fair distance away from the
\ planet in at least one axis, so jump to MA23 to skip
\ the rest of the altitude check

JSR MAS3               \ Set A = x_hi^2 + y_hi^2 + z_hi^2, so using Pythagoras
\ we now know that A now contains the square of the
\ distance between our ship (at the origin) and the
\ centre of the planet at (x_hi, y_hi, z_hi)

BCS MA23               \ If the C flag was set by MAS3, then the result
\ overflowed (was greater than &FF) and we are still a
\ fair distance from the planet, so jump to MA23 as we
\ haven't crashed into the planet

SBC #36                \ Subtract 36 from x_hi^2 + y_hi^2 + z_hi^2. The radius
\ of the planet is defined as 6 units and 6^2 = 36, so
\ A now contains the high byte of our altitude above
\ the planet surface, squared

BCC MA28               \ If A < 0 then jump to MA28 as we have crashed into
\ the planet

STA R                  \ We are getting close to the planet, so we need to
JSR LL5                \ work out how close. We know from the above that A
\ contains our altitude squared, so we store A in R
\ and call LL5 to calculate:
\
\   Q = SQRT(R Q) = SQRT(A Q)
\
\ Interestingly, Q doesn't appear to be set to 0 for
\ this calculation, so presumably this doesn't make a
\ difference

LDA Q                  \ Store the result in ALTIT, our altitude
STA ALTIT

BNE MA23               \ If our altitude is non-zero then we haven't crashed,

.MA28

JMP DEATH              \ If we get here then we just crashed into the planet
\ or got too close to the sun, so jump to DEATH to start
\ the funeral preparations and return from the main
\ flight loop using a tail call

.MA29

CMP #15                \ If this is the 15th iteration in this block of 32,
BNE MA33               \ do the following, otherwise jump to MA33 to skip the
\ docking computer manoeuvring

LDA auto               \ If auto is zero, then the docking computer is not
\ docking computer manoeuvring

LDA #123               \ Set A = 123 and jump down to MA34 to print token 123
BNE MA34               \ ("DOCKING COMPUTERS ON") as an in-flight message

.MA33

CMP #20                \ If this is the 20th iteration in this block of 32,
BNE MA23               \ do the following, otherwise jump to MA23 to skip the
\ sun altitude check

LDA #30                \ Set CABTMP to 30, the cabin temperature in deep space
STA CABTMP             \ (i.e. one notch on the dashboard bar)

LDA SSPR               \ If we are inside the space station safe zone, jump to
BNE MA23               \ MA23 to skip the following, as we can't have both the
\ sun and space station at the same time, so we clearly
\ can't be flying near the sun

LDY #NI%               \ Set Y to NI%, which is the offset in K% for the sun's
\ data block, as the second block at K% is reserved for
\ the sun (or space station)

JSR MAS2               \ Call MAS2 to calculate the largest distance to the
BNE MA23               \ sun in any of the three axes, and if it's non-zero,
\ jump to MA23 to skip the following, as we are too far
\ from the sun for scooping or temperature changes

JSR MAS3               \ Set A = x_hi^2 + y_hi^2 + z_hi^2, so using Pythagoras
\ we now know that A now contains the square of the
\ distance between our ship (at the origin) and the
\ heart of the sun at (x_hi, y_hi, z_hi)

EOR #%11111111         \ Invert A, so A is now small if we are far from the
\ sun and large if we are close to the sun, in the
\ range 0 = far away to &FF = extremely close, ouch,
\ hot, hot, hot!

ADC #30                \ Add the minimum cabin temperature of 30, so we get
\ one of the following:
\
\   * If the C flag is clear, A contains the cabin
\     temperature, ranging from 30 to 255, that's hotter
\     the closer we are to the sun
\
\   * If the C flag is set, the addition has rolled over
\     and the cabin temperature is over 255

STA CABTMP             \ Store the updated cabin temperature

BCS MA28               \ If the C flag is set then jump to MA28 to die, as
\ our temperature is off the scale

CMP #&E0               \ If the cabin temperature < 224 then jump to MA23 to
BCC MA23               \ to skip fuel scooping, as we aren't close enough

LDA BST                \ If we don't have fuel scoops fitted, jump to BA23 to
BEQ MA23               \ skip fuel scooping, as we can't scoop without fuel
\ scoops

LDA DELT4+1            \ We are now successfully fuel scooping, so it's time
LSR A                  \ to work out how much fuel we're scooping. Fetch the
\ high byte of DELT4, which contains our current speed
\ divided by 4, and halve it to get our current speed
\ divided by 8 (so it's now a value between 1 and 5, as
\ our speed is normally between 1 and 40). This gives
\ us the amount of fuel that's being scooped in A, so
\ the faster we go, the more fuel we scoop, and because
\ the fuel levels are stored as 10 * the fuel in light
\ years, that means we just scooped between 0.1 and 0.5
\ light years of free fuel

ADC QQ14               \ Set A = A + the current fuel level * 10 (from QQ14)

CMP new_range          \ If A > new_range then set A = new_range (as new_range
BCC P%+5               \ is the maximum fuel level for our current ship
LDA new_range

STA QQ14               \ Store the updated fuel level in QQ14

LDA #160               \ Set A to token 160 ("FUEL SCOOPS ON")

.MA34

JSR MESS               \ Print the token in A as an in-flight message

Type: Subroutine
Category: Main loop
Summary: Process laser pulsing, E.C.M. energy drain, call stardust routine
Deep dive: Program flow of the main game loop
Context: See this subroutine on its own page
References: No direct references to this subroutine in this source file

The main flight loop covers most of the flight-specific aspects of Elite. This
section covers the following:

* Process laser pulsing

* Process E.C.M. energy drain

* Jump to the stardust routine if we are in a space view

* Return from the main flight loop

.MA23

LDA LAS2               \ If the current view has no laser, jump to MA16 to skip
BEQ MA16               \ the following

JSR read_0346          \ Get the value of the I/O processor's copy of LASCT

CMP #8                 \ If LASCT >= 8, jump to MA16 to skip the following, so
BCS MA16               \ for a pulse laser with a LASCT between 8 and 10, the
\ the laser stays on, but for a LASCT of 7 or less it
\ gets turned off and stays off until LASCT reaches zero
\ and the next pulse can start (if the fire button is
\ still being pressed)

\
\ For pulse lasers, LASCT gets set to 10 in ma1 above,
\ and it decrements every vertical sync (50 times a
\ second), so this means it pulses five times a second,
\ with the laser being on for the first 3/10 of each
\ pulse and off for the rest of the pulse
\
\ If this is a beam laser, LASCT is 0 so we always keep
\ going here. This means the laser doesn't pulse, but it
\ does get drawn and removed every cycle, in a slightly
\ different place each time, so the beams still flicker
\ around the screen

JSR LASLI2             \ Redraw the existing laser lines, which has the effect
\ of removing them from the screen

LDA #0                 \ Set LAS2 to 0 so if this is a pulse laser, it will
STA LAS2               \ skip over the above until the next pulse (this has no
\ effect if this is a beam laser)

.MA16

LDA ECMP               \ If our E.C.M is not on, skip to MA69, otherwise keep
BEQ MA69               \ going to drain some energy

JSR DENGY              \ Call DENGY to deplete our energy banks by 1

BEQ MA70               \ If we have no energy left, jump to MA70 to turn our
\ E.C.M. off

.MA69

LDA ECMA               \ If an E.C.M is going off (our's or an opponent's) then

DEC ECMA               \ Decrement the E.C.M. countdown timer, and if it has

.MA70

JSR ECMOF              \ If we get here then either we have either run out of
\ energy, or the E.C.M. timer has run down, so switch
\ off the E.C.M.

.MA66

LDA QQ11               \ If this is not a space view (i.e. QQ11 is non-zero)
BNE oh                 \ then jump to oh to return from the main flight loop
\ (as oh is an RTS)

JMP STARS              \ This is a space view, so jump to the STARS routine to
\ process the stardust, and return from the main flight
\ loop using a tail call

Type: Subroutine
Category: Universe
Summary: Randomly spawn cargo from a destroyed ship
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* Main flight loop (Part 11 of 16) calls SPIN
* Main flight loop (Part 11 of 16) calls entry point SPIN2
* Main flight loop (Part 16 of 16) calls entry point oh

Arguments:

Y                    The type of cargo to consider spawning (typically #PLT
or #OIL)

Other entry points:

oh                   Contains an RTS

SPIN2                Remove any randomness: spawn cargo of a specific type
(given in X), and always spawn the number given in A

.SPIN

JSR DORND              \ Fetch a random number, and jump to oh if it is
BPL oh                 \ positive (50% chance)

PHA                    \ Store A on the stack so we can restore it after the
\ following transfers

TYA                    \ Copy the cargo type from Y into A and X
TAX

PLA                    \ Restore A from the stack

LDY #0                 \ Fetch the first byte of the hit ship's blueprint,
AND (XX0),Y            \ which determines the maximum number of bits of
\ debris shown when the ship is destroyed, and AND
\ with the random number we just fetched

AND #15                \ Reduce the random number in A to the range 0-15

.SPIN2

STA CNT                \ Store the result in CNT, so CNT contains a random
\ number between 0 and the maximum number of bits of
\ debris that this ship will release when destroyed
\ (to a maximum of 15 bits of debris)

.spl

BEQ oh                 \ We're going to go round a loop using CNT as a counter
\ so this checks whether the counter is zero and jumps
\ to oh when it gets there (which might be straight
\ away)

LDA #0                 \ Call SFS1 to spawn the specified cargo from the now
JSR SFS1               \ deceased parent ship, giving the spawned canister an
\ AI flag of 0 (no AI, no E.C.M., non-hostile)

DEC CNT                \ Decrease the loop counter

BNE spl+2              \ Jump back up to the LDA &0 instruction above (this BPL
\ is effectively a JMP as CNT will never be negative)

.oh

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Maths (Arithmetic)
Summary: Calculate (YY+1 SYL+Y) = (A P) + (S R) and draw stardust particle
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* STARS1 calls PIX1
* STARS2 calls PIX1
* STARS6 calls PIX1

Calculate the following:

(YY+1 SYL+Y) = (A P) + (S R)

and draw a stardust particle at (X1,Y1) with distance ZZ.

Arguments:

(A P)                A is the angle ALPHA or BETA, P is always 0

(S R)                YY(1 0) or YY(1 0) + Q * A

Y                    Stardust particle number

X1                   The x-coordinate offset

Y1                   The y-coordinate offset

ZZ                   The distance of the point (further away = smaller point)

.PIX1

JSR ADD                \ Set (A X) = (A P) + (S R)

STA YY+1               \ Set YY+1 to A, the high byte of the result

TXA                    \ Set SYL+Y to X, the low byte of the result
STA SYL,Y

\ Fall through into PIX1 to draw the stardust particle
\ at (X1,Y1)

Type: Subroutine
Category: Drawing pixels
Summary: Draw a stardust particle relative to the screen centre
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* FLIP calls PIXEL2
* STARS1 calls PIXEL2
* STARS2 calls PIXEL2
* STARS6 calls PIXEL2
* nWq calls PIXEL2

Draw a point (X1, Y1) from the middle of the screen with a size determined by
a distance value. Used to draw stardust particles.

Arguments:

X1                   The x-coordinate offset

Y1                   The y-coordinate offset (positive means up the screen
from the centre, negative means down the screen)

ZZ                   The distance of the point (further away = smaller point)

.PIXEL2

LDA X1                 \ Fetch the x-coordinate offset into A

BPL PX1                \ If the x-coordinate offset is positive, jump to PX1
\ to skip the following negation

EOR #%01111111         \ The x-coordinate offset is negative, so flip all the
CLC                    \ bits apart from the sign bit and add 1, to negate
ADC #1                 \ it to a positive number, i.e. A is now |X1|

.PX1

EOR #%10000000         \ Set X = -|A|
TAX                    \       = -|X1|

LDA Y1                 \ Fetch the y-coordinate offset into A and clear the
AND #%01111111         \ sign bit, so A = |Y1|

CMP #96                \ If |Y1| >= 96 then it's off the screen (as 96 is half
BCS PX4                \ the screen height), so return from the subroutine (as
\ PX4 contains an RTS)

LDA Y1                 \ Fetch the y-coordinate offset into A

BPL PX2                \ If the y-coordinate offset is positive, jump to PX2
\ to skip the following negation

EOR #%01111111         \ The y-coordinate offset is negative, so flip all the
ADC #1                 \ bits apart from the sign bit and subtract 1, to negate
\ it to a positive number, i.e. A is now |Y1|

.PX2

STA T                  \ Set A = 97 - A
LDA #97                \       = 97 - |Y1|
SBC T                  \
\ so if Y is positive we display the point up from the
\ centre, while a negative Y means down from the centre

JMP PIXEL              \ Jump to PIXEL to draw the stardust at the screen
\ coordinates in (X, A), returning from the subroutine
\ using a tail call

.PX4

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Stardust
Summary: Reflect the stardust particles in the screen diagonal
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* LOOK1 calls FLIP

Swap the x- and y-coordinates of all the stardust particles and draw the new
set of particles. Called by LOOK1 when we switch views.

This is a quick way of making the stardust field in the new view feel
different without having to generate a whole new field. If you look carefully
at the stardust field when you switch views, you can just about see that the
new field is a reflection of the previous field in the screen diagonal, i.e.
in the line from bottom left to top right. This is the line where x = y when
the origin is in the middle of the screen, and positive x and y are right and
up, which is the coordinate system we use for stardust).

.FLIP

LDY NOSTM              \ Set Y to the current number of stardust particles, so
\ we can use it as a counter through all the stardust

.FLL1

LDX SY,Y               \ Copy the Y-th particle's y-coordinate from SY+Y into X

LDA SX,Y               \ Copy the Y-th particle's x-coordinate from SX+Y into
STA Y1                 \ both Y1 and the particle's y-coordinate
STA SY,Y

TXA                    \ Copy the Y-th particle's original y-coordinate into
STA X1                 \ both X1 and the particle's x-coordinate, so the x- and
STA SX,Y               \ y-coordinates are now swapped and (X1, Y1) contains
\ the particle's new coordinates

LDA SZ,Y               \ Fetch the Y-th particle's distance from SZ+Y into ZZ
STA ZZ

JSR PIXEL2             \ Draw a stardust particle at (X1,Y1) with distance ZZ

DEY                    \ Decrement the counter to point to the next particle of
\ stardust

BNE FLL1               \ Loop back to FLL1 until we have moved all the stardust
\ particles

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Stardust
Summary: The main routine for processing the stardust
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* Main flight loop (Part 16 of 16) calls STARS

Called at the very end of the main flight loop.

.STARS

LDX VIEW               \ Load the current view into X:
\
\   0 = front
\   1 = rear
\   2 = left
\   3 = right

BEQ STARS1             \ If this 0, jump to STARS1 to process the stardust for
\ the front view

DEX                    \ If this is view 2 or 3, jump to STARS2 (via ST11) to
BNE ST11               \ process the stardust for the left or right views

JMP STARS6             \ Otherwise this is the rear view, so jump to STARS6 to
\ process the stardust for the rear view

.ST11

JMP STARS2             \ Jump to STARS2 for the left or right views, as it's
\ too far for the branch instruction above

Type: Subroutine
Category: Stardust
Summary: Process the stardust for the front view
Deep dive: Stardust in the front view
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* STARS calls STARS1

This moves the stardust towards us according to our speed (so the dust rushes
past us), and applies our current pitch and roll to each particle of dust, so
the stardust moves correctly when we steer our ship.

When a stardust particle rushes past us and falls off the side of the screen,
its memory is recycled as a new particle that's positioned randomly on-screen.

.STARS1

LDY NOSTM              \ Set Y to the current number of stardust particles, so
\ we can use it as a counter through all the stardust

\ In the following, we're going to refer to the 16-bit
\ space coordinates of the current particle of stardust
\ (i.e. the Y-th particle) like this:
\
\   x = (x_hi x_lo)
\   y = (y_hi y_lo)
\   z = (z_hi z_lo)
\
\ These values are stored in (SX+Y SXL+Y), (SY+Y SYL+Y)
\ and (SZ+Y SZL+Y) respectively

.STL1

JSR DV42               \ Call DV42 to set the following:
\
\   (P R) = 256 * DELTA / z_hi
\         = 256 * speed / z_hi
\
\ The maximum value returned is P = 2 and R = 128 (see
\ DV42 for an explanation)

LDA R                  \ Set A = R, so now:
\
\   (P A) = 256 * speed / z_hi

LSR P                  \ Rotate (P A) right by 2 places, which sets P = 0 (as P
ROR A                  \ has a maximum value of 2) and leaves:
LSR P                  \
ROR A                  \   A = 64 * speed / z_hi

ORA #1                 \ Make sure A is at least 1, and store it in Q, so we
STA Q                  \ now have result 1 above:
\
\   Q = 64 * speed / z_hi

LDA SZL,Y              \ We now calculate the following:
SBC DELT4              \
STA SZL,Y              \  (z_hi z_lo) = (z_hi z_lo) - DELT4(1 0)
\
\ starting with the low bytes

LDA SZ,Y               \ And then we do the high bytes
STA ZZ                 \
SBC DELT4+1            \ We also set ZZ to the original value of z_hi, which we
STA SZ,Y               \ use below to remove the existing particle
\
\ So now we have result 2 above:
\
\   z = z - DELT4(1 0)
\     = z - speed * 64

JSR MLU1               \ Call MLU1 to set:
\
\   Y1 = y_hi
\
\   (A P) = |y_hi| * Q
\
\ So Y1 contains the original value of y_hi, which we
\ use below to remove the existing particle

\ We now calculate:
\
\   (S R) = YY(1 0) = (A P) + y

STA YY+1               \ First we do the low bytes with:
LDA P                  \
ADC SYL,Y              \   YY+1 = A
STA YY                 \   R = YY = P + y_lo
STA R                  \
\ so we get this:
\
\   (? R) = YY(1 0) = (A P) + y_lo

LDA Y1                 \ And then we do the high bytes with:
STA YY+1               \   S = YY+1 = y_hi + YY+1
STA S                  \
\ so we get our result:
\
\   (S R) = YY(1 0) = (A P) + (y_hi y_lo)
\                   = |y_hi| * Q + y
\
\ which is result 3 above, and (S R) is set to the new
\ value of y

LDA SX,Y               \ Set X1 = A = x_hi
STA X1                 \
\ So X1 contains the original value of x_hi, which we
\ use below to remove the existing particle

JSR MLU2               \ Set (A P) = |x_hi| * Q

\ We now calculate:
\
\   XX(1 0) = (A P) + x

STA XX+1               \ First we do the low bytes:
LDA P                  \
ADC SXL,Y              \   XX(1 0) = (A P) + x_lo
STA XX

LDA X1                 \ And then we do the high bytes:
STA XX+1               \   XX(1 0) = XX(1 0) + (x_hi 0)
\
\ so we get our result:
\
\   XX(1 0) = (A P) + x
\           = |x_hi| * Q + x
\
\ which is result 4 above, and we also have:
\
\   A = XX+1 = (|x_hi| * Q + x) / 256
\
\ i.e. A is the new value of x, divided by 256

EOR ALP2+1             \ EOR with the flipped sign of the roll angle alpha, so
\ A has the opposite sign to the flipped roll angle
\ alpha, i.e. it gets the same sign as alpha

JSR MLS1               \ Call MLS1 to calculate:
\
\   (A P) = A * ALP1
\         = (x / 256) * alpha

\
\   (A X) = (A P) + (S R)
\         = (x / 256) * alpha + y
\         = y + alpha * x / 256

STA YY+1               \ Set YY(1 0) = (A X) to give:
STX YY                 \
\   YY(1 0) = y + alpha * x / 256
\
\ which is result 5 above, and we also have:
\
\   A = YY+1 = y + alpha * x / 256
\
\ i.e. A is the new value of y, divided by 256

EOR ALP2               \ EOR A with the correct sign of the roll angle alpha,
\ so A has the opposite sign to the roll angle alpha

JSR MLS2               \ Call MLS2 to calculate:
\
\   (S R) = XX(1 0)
\         = x
\
\   (A P) = A * ALP1
\         = -y / 256 * alpha

\
\   (A X) = (A P) + (S R)
\         = -y / 256 * alpha + x

STA XX+1               \ Set XX(1 0) = (A X), which gives us result 6 above:
STX XX                 \
\   x = x - alpha * y / 256

LDX BET1               \ Fetch the pitch magnitude into X

LDA YY+1               \ Set A to y_hi and set it to the flipped sign of beta
EOR BET2+1

JSR MULTS-2            \ Call MULTS-2 to calculate:
\
\   (A P) = X * A
\         = -beta * y_hi

STA Q                  \ Store the high byte of the result in Q, so:
\
\   Q = -beta * y_hi / 256

JSR MUT2               \ Call MUT2 to calculate:
\
\   (S R) = XX(1 0) = x
\
\   (A P) = Q * A
\         = (-beta * y_hi / 256) * (-beta * y_hi / 256)
\         = (beta * y / 256) ^ 2

ASL P                  \ Double (A P), store the top byte in A and set the C
ROL A                  \ flag to bit 7 of the original A, so this does:
STA T                  \
\   (T P) = (A P) << 1
\         = 2 * (beta * y / 256) ^ 2

LDA #0                 \ Set bit 7 in A to the sign bit from the A in the
ROR A                  \ calculation above and apply it to T, so we now have:
ORA T                  \
\   (A P) = (A P) * 2
\         = 2 * (beta * y / 256) ^ 2
\
\ with the doubling retaining the sign of (A P)

\
\   (A X) = (A P) + (S R)
\         = 2 * (beta * y / 256) ^ 2 + x

STA XX+1               \ Store the high byte A in XX+1

TXA
STA SXL,Y              \ Store the low byte X in x_lo

\ So (XX+1 x_lo) now contains:
\
\   x = x + 2 * (beta * y / 256) ^ 2
\
\ which is result 7 above

LDA YY                 \ Set (S R) = YY(1 0) = y
STA R
LDA YY+1
STA S

LDA #0                 \ Set P = 0
STA P

LDA BETA               \ Set A = -beta, so:
EOR #%10000000         \
\   (A P) = (-beta 0)
\         = -beta * 256

JSR PIX1               \ Call PIX1 to calculate the following:
\
\   (YY+1 y_lo) = (A P) + (S R)
\               = -beta * 256 + y
\
\ i.e. y = y - beta * 256, which is result 8 above
\
\ PIX1 also draws a particle at (X1, Y1) with distance
\ ZZ, which will remove the old stardust particle, as we
\ set X1, Y1 and ZZ to the original values for this
\ particle during the calculations above

\ We now have our newly moved stardust particle at
\ x-coordinate (XX+1 x_lo) and y-coordinate (YY+1 y_lo)
\ and distance z_hi, so we draw it if it's still on
\ screen, otherwise we recycle it as a new bit of
\ stardust and draw that

LDA XX+1               \ Set X1 and x_hi to the high byte of XX in XX+1, so
STA X1                 \ the new x-coordinate is in (x_hi x_lo) and the high
STA SX,Y               \ byte is in X1

AND #%01111111         \ If |x_hi| >= 120 then jump to KILL1 to recycle this
CMP #120               \ particle, as it's gone off the side of the screen,
BCS KILL1              \ and re-join at STC1 with the new particle

LDA YY+1               \ Set Y1 and y_hi to the high byte of YY in YY+1, so
STA SY,Y               \ the new x-coordinate is in (y_hi y_lo) and the high
STA Y1                 \ byte is in Y1

AND #%01111111         \ If |y_hi| >= 120 then jump to KILL1 to recycle this
CMP #120               \ particle, as it's gone off the top or bottom of the
BCS KILL1              \ screen, and re-join at STC1 with the new particle

LDA SZ,Y               \ If z_hi < 16 then jump to KILL1 to recycle this
CMP #16                \ particle, as it's so close that it's effectively gone
BCC KILL1              \ past us, and re-join at STC1 with the new particle

STA ZZ                 \ Set ZZ to the z-coordinate in z_hi

.STC1

JSR PIXEL2             \ Draw a stardust particle at (X1,Y1) with distance ZZ,
\ i.e. draw the newly moved particle at (x_hi, y_hi)
\ with distance z_hi

DEY                    \ Decrement the loop counter to point to the next
\ stardust particle

BEQ P%+5               \ If we have just done the last particle, skip the next
\ instruction to return from the subroutine

JMP STL1               \ We have more stardust to process, so jump back up to
\ STL1 for the next particle

RTS                    \ Return from the subroutine

.KILL1

\ Our particle of stardust just flew past us, so let's
\ recycle that particle, starting it at a random
\ position that isn't too close to the centre point

JSR DORND              \ Set A and X to random numbers

ORA #4                 \ Make sure A is at least 4 and store it in Y1 and y_hi,
STA Y1                 \ so the new particle starts at least 4 pixels above or
STA SY,Y               \ below the centre of the screen

JSR DORND              \ Set A and X to random numbers

ORA #8                 \ Make sure A is at least 8 and store it in X1 and x_hi,
STA X1                 \ so the new particle starts at least 8 pixels either
STA SX,Y               \ side of the centre of the screen

JSR DORND              \ Set A and X to random numbers

ORA #144               \ Make sure A is at least 144 and store it in ZZ and
STA SZ,Y               \ z_hi so the new particle starts in the far distance
STA ZZ

LDA Y1                 \ Set A to the new value of y_hi. This has no effect as
\ STC1 starts with a jump to PIXEL2, which starts with a
\ LDA instruction

JMP STC1               \ Jump up to STC1 to draw this new particle

Type: Subroutine
Category: Stardust
Summary: Process the stardust for the rear view
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* STARS calls STARS6

This routine is very similar to STARS1, which processes stardust for the front
view. The main difference is that the direction of travel is reversed, so the
signs in the calculations are different, as well as the order of the first
batch of calculations.

When a stardust particle falls away into the far distance, it is removed from
the screen and its memory is recycled as a new particle, positioned randomly
along one of the four edges of the screen.

See STARS1 for an explanation of the maths used in this routine. The
calculations are as follows:

1. q = 64 * speed / z_hi
2. x = x - |x_hi| * q
3. y = y - |y_hi| * q
4. z = z + speed * 64

5. y = y - alpha * x / 256
6. x = x + alpha * y / 256

7. x = x - 2 * (beta * y / 256) ^ 2
8. y = y + beta * 256

.STARS6

LDY NOSTM              \ Set Y to the current number of stardust particles, so
\ we can use it as a counter through all the stardust

.STL6

JSR DV42               \ Call DV42 to set the following:
\
\   (P R) = 256 * DELTA / z_hi
\         = 256 * speed / z_hi
\
\ The maximum value returned is P = 2 and R = 128 (see
\ DV42 for an explanation)

LDA R                  \ Set A = R, so now:
\
\   (P A) = 256 * speed / z_hi

LSR P                  \ Rotate (P A) right by 2 places, which sets P = 0 (as P
ROR A                  \ has a maximum value of 2) and leaves:
LSR P                  \
ROR A                  \   A = 64 * speed / z_hi

ORA #1                 \ Make sure A is at least 1, and store it in Q, so we
STA Q                  \ now have result 1 above:
\
\   Q = 64 * speed / z_hi

LDA SX,Y               \ Set X1 = A = x_hi
STA X1                 \
\ So X1 contains the original value of x_hi, which we
\ use below to remove the existing particle

JSR MLU2               \ Set (A P) = |x_hi| * Q

\ We now calculate:
\
\   XX(1 0) = x - (A P)

STA XX+1               \ First we do the low bytes:
LDA SXL,Y              \
SBC P                  \   XX(1 0) = x_lo - (A P)
STA XX

LDA X1                 \ And then we do the high bytes:
SBC XX+1               \
STA XX+1               \   XX(1 0) = (x_hi 0) - XX(1 0)
\
\ so we get our result:
\
\   XX(1 0) = x - (A P)
\           = x - |x_hi| * Q
\
\ which is result 2 above, and we also have:

JSR MLU1               \ Call MLU1 to set:
\
\   Y1 = y_hi
\
\   (A P) = |y_hi| * Q
\
\ So Y1 contains the original value of y_hi, which we
\ use below to remove the existing particle

\ We now calculate:
\
\   (S R) = YY(1 0) = y - (A P)

STA YY+1               \ First we do the low bytes with:
LDA SYL,Y              \
SBC P                  \   YY+1 = A
STA YY                 \   R = YY = y_lo - P
STA R                  \
\ so we get this:
\
\   (? R) = YY(1 0) = y_lo - (A P)

LDA Y1                 \ And then we do the high bytes with:
SBC YY+1               \
STA YY+1               \   S = YY+1 = y_hi - YY+1
STA S                  \
\ so we get our result:
\
\   (S R) = YY(1 0) = (y_hi y_lo) - (A P)
\                   = y - |y_hi| * Q
\
\ which is result 3 above, and (S R) is set to the new
\ value of y

LDA SZL,Y              \ We now calculate the following:
STA SZL,Y              \  (z_hi z_lo) = (z_hi z_lo) + DELT4(1 0)
\
\ starting with the low bytes

LDA SZ,Y               \ And then we do the high bytes
STA ZZ                 \
ADC DELT4+1            \ We also set ZZ to the original value of z_hi, which we
STA SZ,Y               \ use below to remove the existing particle
\
\ So now we have result 4 above:
\
\   z = z + DELT4(1 0)
\     = z + speed * 64

LDA XX+1               \ EOR x with the correct sign of the roll angle alpha,
EOR ALP2               \ so A has the opposite sign to the roll angle alpha

JSR MLS1               \ Call MLS1 to calculate:
\
\   (A P) = A * ALP1
\         = (-x / 256) * alpha

\
\   (A X) = (A P) + (S R)
\         = (-x / 256) * alpha + y
\         = y - alpha * x / 256

STA YY+1               \ Set YY(1 0) = (A X) to give:
STX YY                 \
\   YY(1 0) = y - alpha * x / 256
\
\ which is result 5 above, and we also have:
\
\   A = YY+1 = y - alpha * x / 256
\
\ i.e. A is the new value of y, divided by 256

EOR ALP2+1             \ EOR with the flipped sign of the roll angle alpha, so
\ A has the opposite sign to the flipped roll angle
\ alpha, i.e. it gets the same sign as alpha

JSR MLS2               \ Call MLS2 to calculate:
\
\   (S R) = XX(1 0)
\         = x
\
\   (A P) = A * ALP1
\         = y / 256 * alpha

\
\   (A X) = (A P) + (S R)
\         = y / 256 * alpha + x

STA XX+1               \ Set XX(1 0) = (A X), which gives us result 6 above:
STX XX                 \
\   x = x + alpha * y / 256

LDA YY+1               \ Set A to y_hi and set it to the flipped sign of beta
EOR BET2+1

LDX BET1               \ Fetch the pitch magnitude into X

JSR MULTS-2            \ Call MULTS-2 to calculate:
\
\   (A P) = X * A
\         = beta * y_hi

STA Q                  \ Store the high byte of the result in Q, so:
\
\   Q = beta * y_hi / 256

LDA XX+1               \ Set S = x_hi
STA S

EOR #%10000000         \ Flip the sign of A, so A now contains -x

JSR MUT1               \ Call MUT1 to calculate:
\
\   R = XX = x_lo
\
\   (A P) = Q * A
\         = (beta * y_hi / 256) * (-beta * y_hi / 256)
\         = (-beta * y / 256) ^ 2

ASL P                  \ Double (A P), store the top byte in A and set the C
ROL A                  \ flag to bit 7 of the original A, so this does:
STA T                  \
\   (T P) = (A P) << 1
\         = 2 * (-beta * y / 256) ^ 2

LDA #0                 \ Set bit 7 in A to the sign bit from the A in the
ROR A                  \ calculation above and apply it to T, so we now have:
ORA T                  \
\   (A P) = -2 * (beta * y / 256) ^ 2
\
\ with the doubling retaining the sign of (A P)

\
\   (A X) = (A P) + (S R)
\         = -2 * (beta * y / 256) ^ 2 + x

STA XX+1               \ Store the high byte A in XX+1

TXA
STA SXL,Y              \ Store the low byte X in x_lo

\ So (XX+1 x_lo) now contains:
\
\   x = x - 2 * (beta * y / 256) ^ 2
\
\ which is result 7 above

LDA YY                 \ Set (S R) = YY(1 0) = y
STA R
LDA YY+1
STA S

LDA #0                 \ Set P = 0
STA P

LDA BETA               \ Set A = beta, so (A P) = (beta 0) = beta * 256

JSR PIX1               \ Call PIX1 to calculate the following:
\
\   (YY+1 y_lo) = (A P) + (S R)
\               = beta * 256 + y
\
\ i.e. y = y + beta * 256, which is result 8 above
\
\ PIX1 also draws a particle at (X1, Y1) with distance
\ ZZ, which will remove the old stardust particle, as we
\ set X1, Y1 and ZZ to the original values for this
\ particle during the calculations above

\ We now have our newly moved stardust particle at
\ x-coordinate (XX+1 x_lo) and y-coordinate (YY+1 y_lo)
\ and distance z_hi, so we draw it if it's still on
\ screen, otherwise we recycle it as a new bit of
\ stardust and draw that

LDA XX+1               \ Set X1 and x_hi to the high byte of XX in XX+1, so
STA X1                 \ the new x-coordinate is in (x_hi x_lo) and the high
STA SX,Y               \ byte is in X1

LDA YY+1               \ Set Y1 and y_hi to the high byte of YY in YY+1, so
STA SY,Y               \ the new x-coordinate is in (y_hi y_lo) and the high
STA Y1                 \ byte is in Y1

AND #%01111111         \ If |y_hi| >= 110 then jump to KILL6 to recycle this
CMP #110               \ particle, as it's gone off the top or bottom of the
BCS KILL6              \ screen, and re-join at STC6 with the new particle

LDA SZ,Y               \ If z_hi >= 160 then jump to KILL6 to recycle this
CMP #160               \ particle, as it's so far away that it's too far to
BCS KILL6              \ see, and re-join at STC1 with the new particle

STA ZZ                 \ Set ZZ to the z-coordinate in z_hi

.STC6

JSR PIXEL2             \ Draw a stardust particle at (X1,Y1) with distance ZZ,
\ i.e. draw the newly moved particle at (x_hi, y_hi)
\ with distance z_hi

DEY                    \ Decrement the loop counter to point to the next
\ stardust particle

BEQ MA9                \ If we have just done the last particle, return from
\ the subroutine (as MA9 contains an RTS)

JMP STL6               \ We have more stardust to process, so jump back up to
\ STL6 for the next particle

.KILL6

JSR DORND              \ Set A and X to random numbers

AND #%01111111         \ Clear the sign bit of A to get |A|

ADC #10                \ Make sure A is at least 10 and store it in z_hi and
STA SZ,Y               \ ZZ, so the new particle starts close to us
STA ZZ

LSR A                  \ Divide A by 2 and randomly set the C flag

LSR A                  \ Randomly set the C flag again

LDA #252               \ Set A to either +126 or -126 (252 >> 1) depending on
ROR A                  \ the C flag, as this is a sign-magnitude number with
\ the C flag rotated into its sign bit

STA X1                 \ Set x_hi and X1 to A, so this particle starts on
STA SX,Y               \ either the left or right edge of the screen

JSR DORND              \ Set A and X to random numbers

STA Y1                 \ Set y_hi and Y1 to random numbers, so the particle
STA SY,Y               \ starts anywhere along either the left or right edge

JMP STC6               \ Jump up to STC6 to draw this new particle

.ST4

JSR DORND              \ Set A and X to random numbers

STA X1                 \ Set x_hi and X1 to random numbers, so the particle
STA SX,Y               \ starts anywhere along the x-axis

LSR A                  \ Randomly set the C flag

LDA #230               \ Set A to either +115 or -115 (230 >> 1) depending on
ROR A                  \ the C flag, as this is a sign-magnitude number with
\ the C flag rotated into its sign bit

STA Y1                 \ Set y_hi and Y1 to A, so the particle starts anywhere
STA SY,Y               \ along either the top or bottom edge of the screen

BNE STC6               \ Jump up to STC6 to draw this new particle (this BNE is
\ effectively a JMP as A will never be zero)

Type: Subroutine
Category: Maths (Geometry)
Summary: Add an orientation vector coordinate to an INWK coordinate
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* Main flight loop (Part 14 of 16) calls MAS1
* STARS6 calls entry point MA9

Add a doubled nosev vector coordinate, e.g. (nosev_y_hi nosev_y_lo) * 2, to
an INWK coordinate, e.g. (x_sign x_hi x_lo), storing the result in the INWK
coordinate. The axes used in each side of the addition are specified by the
arguments X and Y.

In the comments below, we document the routine as if we are doing the
following, i.e. if X = 0 and Y = 11:

(x_sign x_hi x_lo) = (x_sign x_hi x_lo) + (nosev_y_hi nosev_y_lo) * 2

as that way the variable names in the comments contain "x" and "y" to match
the registers that specify the vector axis to use.

Arguments:

X                    The coordinate to add, as follows:

* If X = 0, add (x_sign x_hi x_lo)
* If X = 3, add (y_sign y_hi y_lo)
* If X = 6, add (z_sign z_hi z_lo)

Y                    The vector to add, as follows:

* If Y = 9,  add (nosev_x_hi nosev_x_lo)
* If Y = 11, add (nosev_y_hi nosev_y_lo)
* If Y = 13, add (nosev_z_hi nosev_z_lo)

Returns:

A                    The high byte of the result with the sign cleared (e.g.
|x_hi| if X = 0, etc.)

Other entry points:

MA9                  Contains an RTS

.MAS1

LDA INWK,Y             \ Set K(2 1) = (nosev_y_hi nosev_y_lo) * 2
ASL A
STA K+1
LDA INWK+1,Y
ROL A
STA K+2

LDA #0                 \ Set K+3 bit 7 to the C flag, so the sign bit of the
ROR A                  \ above result goes into K+3
STA K+3

JSR MVT3               \ Add (x_sign x_hi x_lo) to K(3 2 1)

STA INWK+2,X           \ Store the sign of the result in x_sign

LDY K+1                \ Store K(2 1) in (x_hi x_lo)
STY INWK,X
LDY K+2
STY INWK+1,X

AND #%01111111         \ Set A to the sign byte with the sign cleared

.MA9

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Maths (Geometry)
Summary: Calculate a cap on the maximum distance to the planet or sun
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* Main flight loop (Part 14 of 16) calls MAS2
* Main flight loop (Part 15 of 16) calls MAS2
* WARP calls MAS2
* Main flight loop (Part 15 of 16) calls entry point m
* WARP calls entry point m

Given a value in Y that points to the start of a ship data block as an offset
from K%, calculate the following:

A = A OR x_sign OR y_sign OR z_sign

and clear the sign bit of the result. The K% workspace contains the ship data
blocks, so the offset in Y must be 0 or a multiple of NI% (as each block in
K% contains NI% bytes).

The result effectively contains a maximum cap of the three values (though it
might not be one of the three input values - it's just guaranteed to be
larger than all of them).

If Y = 0 and A = 0, then this calculates the maximum cap of the highest byte
containing the distance to the planet, as K%+2 = x_sign, K%+5 = y_sign and
K%+8 = z_sign (the first slot in the K% workspace represents the planet).

Arguments:

Y                    The offset from K% for the start of the ship data block
to use

Returns:

A                    A OR K%+2+Y OR K%+5+Y OR K%+8+Y, with bit 7 cleared

Other entry points:

m                    Do not include A in the calculation

.m

LDA #0                 \ Set A = 0 and fall through into MAS2 to calculate the
\ OR of the three bytes at K%+2+Y, K%+5+Y and K%+8+Y

.MAS2

ORA K%+2,Y             \ Set A = A OR x_sign OR y_sign OR z_sign
ORA K%+5,Y
ORA K%+8,Y

AND #%01111111         \ Clear bit 7 in A

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Maths (Arithmetic)
Summary: Calculate A = x_hi^2 + y_hi^2 + z_hi^2 in the K% block
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* Main flight loop (Part 15 of 16) calls MAS3

Given a value in Y that points to the start of a ship data block as an offset
from K%, calculate the following:

A = x_hi^2 + y_hi^2 + z_hi^2

returning A = &FF if the calculation overflows a one-byte result. The K%
workspace contains the ship data blocks, so the offset in Y must be 0 or a
multiple of NI% (as each block in K% contains NI% bytes).

Arguments:

Y                    The offset from K% for the start of the ship data block
to use

Returns

A                    A = x_hi^2 + y_hi^2 + z_hi^2

A = &FF if the calculation overflows a one-byte result

.MAS3

LDA K%+1,Y             \ Set (A P) = x_hi * x_hi
JSR SQUA2

STA R                  \ Store A (high byte of result) in R

LDA K%+4,Y             \ Set (A P) = y_hi * y_hi
JSR SQUA2

ADC R                  \ Add A (high byte of second result) to R

BCS MA30               \ If the addition of the two high bytes caused a carry
\ (i.e. they overflowed), jump to MA30 to return A = &FF

STA R                  \ Store A (sum of the two high bytes) in R

LDA K%+7,Y             \ Set (A P) = z_hi * z_hi
JSR SQUA2

ADC R                  \ Add A (high byte of third result) to R, so R now
\ contains the sum of x_hi^2 + y_hi^2 + z_hi^2

BCC P%+4               \ If there is no carry, skip the following instruction
\ to return straight from the subroutine

.MA30

LDA #&FF               \ The calculation has overflowed, so set A = &FF

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Moving
Summary: Calculate K(3 2 1) = (x_sign x_hi x_lo) + K(3 2 1)
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* MAS1 calls MVT3
* MV40 calls MVT3
* TAS1 calls MVT3

Add an INWK position coordinate - i.e. x, y or z - to K(3 2 1), like this:

K(3 2 1) = (x_sign x_hi x_lo) + K(3 2 1)

The INWK coordinate to add to K(3 2 1) is specified by X.

Arguments:

X                    The coordinate to add to K(3 2 1), as follows:

* If X = 0, add (x_sign x_hi x_lo)

* If X = 3, add (y_sign y_hi y_lo)

* If X = 6, add (z_sign z_hi z_lo)

Returns:

A                    Contains a copy of the high byte of the result, K+3

X                    X is preserved

.MVT3

LDA K+3                \ Set S = K+3
STA S

AND #%10000000         \ Set T = sign bit of K(3 2 1)
STA T

EOR INWK+2,X           \ If x_sign has a different sign to K(3 2 1), jump to
BMI MV13               \ MV13 to process the addition as a subtraction

LDA K+1                \ Set K(3 2 1) = K(3 2 1) + (x_sign x_hi x_lo)
CLC                    \ starting with the low bytes
STA K+1

LDA K+2                \ Then the middle bytes
STA K+2

LDA K+3                \ And finally the high bytes

AND #%01111111         \ Setting the sign bit of K+3 to T, the original sign
ORA T                  \ of K(3 2 1)
STA K+3

RTS                    \ Return from the subroutine

.MV13

LDA S                  \ Set S = |K+3| (i.e. K+3 with the sign bit cleared)
AND #%01111111
STA S

LDA INWK,X             \ Set K(3 2 1) = (x_sign x_hi x_lo) - K(3 2 1)
SEC                    \ starting with the low bytes
SBC K+1
STA K+1

LDA INWK+1,X           \ Then the middle bytes
SBC K+2
STA K+2

LDA INWK+2,X           \ And finally the high bytes, doing A = |x_sign| - |K+3|
AND #%01111111         \ and setting the C flag for testing below
SBC S

ORA #%10000000         \ Set the sign bit of K+3 to the opposite sign of T,
EOR T                  \ i.e. the opposite sign to the original K(3 2 1)
STA K+3

BCS MV14               \ If the C flag is set, i.e. |x_sign| >= |K+3|, then
\ the sign of K(3 2 1). In this case, we want the
\ result to have the same sign as the largest argument,
\ which is (x_sign x_hi x_lo), which we know has the
\ opposite sign to K(3 2 1), and that's what we just set
\ the sign of K(3 2 1) to... so we can jump to MV14 to
\ return from the subroutine

LDA #1                 \ We need to swap the sign of the result in K(3 2 1),
SBC K+1                \ which we do by calculating 0 - K(3 2 1), which we can
STA K+1                \ do with 1 - C - K(3 2 1), as we know the C flag is

LDA #0                 \ Then the middle bytes
SBC K+2
STA K+2

LDA #0                 \ And finally the high bytes
SBC K+3

AND #%01111111         \ Set the sign bit of K+3 to the same sign as T,
ORA T                  \ i.e. the same sign as the original K(3 2 1), as
STA K+3                \ that's the largest argument

.MV14

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Flight
Summary: Launch our escape pod
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* Main flight loop (Part 3 of 16) calls ESCAPE

This routine displays our doomed Cobra Mk III disappearing off into the ether
before arranging our replacement ship. Called when we press ESCAPE during
flight and have an escape pod fitted.

.ESCAPE

JSR RES2               \ Reset a number of flight variables and workspaces

LDX #ESC               \ Set the current ship type to an escape pod, so we can
STX TYPE               \ show it disappearing into the distance when we eject
\ in our pod

JSR FRS1               \ Call FRS1 to launch the escape pod straight ahead,
\ like a missile launch, but with our ship instead

LDA #16                \ Set the escape pod's byte #27 (speed) to 8
STA INWK+27

LDA #194               \ Set the escape pod's byte #30 (pitch counter) to 194,
STA INWK+30            \ so it pitches as we pull away

LSR A                  \ Set the escape pod's byte #32 (AI flag) to %01100001,
STA INWK+32            \ so it has no AI, and we can use this value as a
\ counter to do the following loop 97 times

.ESL1

JSR MVEIT_FLIGHT       \ Call MVEIT_FLIGHT to move the escape pod in space

JSR LL9_FLIGHT         \ Call LL9 to draw the Cobra on-screen

DEC INWK+32            \ Decrement the counter in byte #32

BNE ESL1               \ Loop back to keep moving the Cobra until the AI flag
\ is 0, which gives it time to drift away from our pod

JSR SCAN               \ Call SCAN to remove the Cobra from the scanner (by
\ redrawing it)

LDA #0                 \ Set A = 0 so we can use it to zero the contents of
\ the cargo hold

STA QQ20+16            \ We lose any alien items when using our escape pod, so
\ set QQ20+16 to 0 (which is where they are stored)

LDX #12                \ We lose all our cargo canisters when using our escape
\ pod (i.e. all the cargo except gold, platinum and gem
\ stones), so up a counter in X so we can zero cargo
\ slots 0-12 in QQ20

.ESL2

STA QQ20,X             \ Set the X-th byte of QQ20 to zero, so we no longer
\ have any of item type X in the cargo hold

DEX                    \ Decrement the counter

BPL ESL2               \ Loop back to ESL2 until we have emptied the entire
\ cargo hold

STA FIST               \ Launching an escape pod also clears our criminal
\ record, so set our legal status in FIST to 0 ("clean")

STA ESCP               \ The escape pod is a one-use item, so set ESCP to 0 so
\ we no longer have one fitted

INC new_hold           \ We just used our escape pod, and as it's a single-use
\ item, we no longer have an escape pod, so increment
\ the free space in our ship's hold, as the pod is no
\ longer taking up space

LDA new_range          \ Our replacement ship is delivered with a full tank of
STA QQ14               \ fuel, so fetch our current ship's hyperspace range
\ from new_range and set the current fuel level in QQ14
\ to this value

JSR update_pod         \ Update the dashboard colours as we no longer have an
\ escape pod

JSR ping               \ Set the target system to the current system (which
\ will move the location in (QQ9, QQ10) to the current
\ home system

JSR TT111              \ Select the system closest to galactic coordinates
\ (QQ9, QQ10)

JSR jmp                \ Set the current system to the selected system

JMP GOIN               \ Go to the docking bay (i.e. show the ship hanger
\ screen) and return from the subroutine with a tail
\ call

Save ELTJ.bin

PRINT "ELITE J"
PRINT "Assembled at ", ~CODE_J%
PRINT "Ends at ", ~P%
PRINT "Code size is ", ~(P% - CODE_J%)