Elite on the BBC Micro

# Elite H flight source [Elite-A]

``` ELITE H FILE

CODE_H% = P%

Type: Subroutine
Category: Moving
Summary: Move current ship: Tidy the orientation vectors
Deep dive: Program flow of the ship-moving routine
Scheduling tasks with the main loop counter
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* ESCAPE calls MVEIT
* Main flight loop (Part 6 of 16) calls MVEIT

This routine has multiple stages. This stage does the following:

* Tidy the orientation vectors for one of the ship slots

Arguments:

INWK                 The current ship/planet/sun's data block

XSAV                 The slot number of the current ship/planet/sun

TYPE                 The type of the current ship/planet/sun

.MVEIT

LDA INWK+31            \ If bits 5 or 7 of ship byte #31 are set, jump to MV30
AND #%10100000         \ as the ship is either exploding or has been killed, so
BNE MV30               \ we don't need to tidy its orientation vectors or apply
\ tactics

LDA MCNT               \ Fetch the main loop counter

EOR XSAV               \ Fetch the slot number of the ship we are moving, EOR
AND #15                \ with the loop counter and apply mod 15 to the result.
BNE MV3                \ The result will be zero when "counter mod 15" matches
\ the slot number, so this makes sure we call TIDY 12
\ times every 16 main loop iterations, like this:
\
\   Iteration 0, tidy the ship in slot 0
\   Iteration 1, tidy the ship in slot 1
\   Iteration 2, tidy the ship in slot 2
\     ...
\   Iteration 11, tidy the ship in slot 11
\   Iteration 12, do nothing
\   Iteration 13, do nothing
\   Iteration 14, do nothing
\   Iteration 15, do nothing
\   Iteration 16, tidy the ship in slot 0
\     ...
\
\ and so on

JSR TIDY               \ Call TIDY to tidy up the orientation vectors, to
\ prevent the ship from getting elongated and out of
\ shape due to the imprecise nature of trigonometry
\ in assembly language

Type: Subroutine
Category: Moving
Summary: Move current ship: Call tactics routine, remove ship from scanner
Deep dive: 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

This routine has multiple stages. This stage does the following:

* Apply tactics to ships with AI enabled (by calling the TACTICS routine)

* Remove the ship from the scanner, so we can move it

.MV3

LDX TYPE               \ If the type of the ship we are moving is positive,
BPL P%+5               \ i.e. it is not a planet (types 128 and 130) or sun
\ (type 129), then skip the following instruction

JMP MV40               \ This item is the planet or sun, so jump to MV40 to
\ move it, which ends by jumping back into this routine
\ at MV45 (after all the rotation, tactics and scanner
\ code, which we don't need to apply to planets or suns)

LDA INWK+32            \ Fetch the ship's byte #32 (AI flag) into A

BPL MV30               \ If bit 7 of the AI flag is clear, then if this is a
\ ship or missile it is dumb and has no AI, and if this
\ is the space station it is not hostile, so in both
\ cases skip the following as it has no tactics

CPX #MSL               \ If the ship is a missile, skip straight to MV26 to
BEQ MV26               \ call the TACTICS routine, as we do this every
\ iteration of the main loop for missiles only

LDA MCNT               \ Fetch the main loop counter

EOR XSAV               \ Fetch the slot number of the ship we are moving, EOR
AND #7                 \ with the loop counter and apply mod 8 to the result.
BNE MV30               \ The result will be zero when "counter mod 8" matches
\ the slot number mod 8, so this makes sure we call
\ TACTICS 12 times every 8 main loop iterations, like
\ this:
\
\   Iteration 0, apply tactics to slots 0 and 8
\   Iteration 1, apply tactics to slots 1 and 9
\   Iteration 2, apply tactics to slots 2 and 10
\   Iteration 3, apply tactics to slots 3 and 11
\   Iteration 4, apply tactics to slot 4
\   Iteration 5, apply tactics to slot 5
\   Iteration 6, apply tactics to slot 6
\   Iteration 7, apply tactics to slot 7
\   Iteration 8, apply tactics to slots 0 and 8
\     ...
\
\ and so on

.MV26

JSR TACTICS            \ Call TACTICS to apply AI tactics to this ship

.MV30

JSR SCAN               \ Draw the ship on the scanner, which has the effect of
\ removing it, as it's already at this point and hasn't
\ yet moved

Type: Subroutine
Category: Moving
Summary: Move current ship: Move ship forward according to its speed
Context: See this subroutine on its own page
References: No direct references to this subroutine in this source file

This routine has multiple stages. This stage does the following:

* Move the ship forward (along the vector pointing in the direction of
travel) according to its speed:

(x, y, z) += nosev_hi * speed / 64

LDA INWK+27            \ Set Q = the ship's speed byte #27 * 4
ASL A
ASL A
STA Q

LDA INWK+10            \ Set A = |nosev_x_hi|
AND #%01111111

JSR FMLTU              \ Set R = A * Q / 256
STA R                  \       = |nosev_x_hi| * speed / 64

LDA INWK+10            \ If nosev_x_hi is positive, then:
LDX #0                 \
JSR MVT1-2             \   (x_sign x_hi x_lo) = (x_sign x_hi x_lo) + R
\
\ If nosev_x_hi is negative, then:
\
\   (x_sign x_hi x_lo) = (x_sign x_hi x_lo) - R
\
\ So in effect, this does:
\
\   (x_sign x_hi x_lo) += nosev_x_hi * speed / 64

LDA INWK+12            \ Set A = |nosev_y_hi|
AND #%01111111

JSR FMLTU              \ Set R = A * Q / 256
STA R                  \       = |nosev_y_hi| * speed / 64

LDA INWK+12            \ If nosev_y_hi is positive, then:
LDX #3                 \
JSR MVT1-2             \   (y_sign y_hi y_lo) = (y_sign y_hi y_lo) + R
\
\ If nosev_y_hi is negative, then:
\
\   (y_sign y_hi y_lo) = (y_sign y_hi y_lo) - R
\
\ So in effect, this does:
\
\   (y_sign y_hi y_lo) += nosev_y_hi * speed / 64

LDA INWK+14            \ Set A = |nosev_z_hi|
AND #%01111111

JSR FMLTU              \ Set R = A * Q / 256
STA R                  \       = |nosev_z_hi| * speed / 64

LDA INWK+14            \ If nosev_y_hi is positive, then:
LDX #6                 \
JSR MVT1-2             \   (z_sign z_hi z_lo) = (z_sign z_hi z_lo) + R
\
\ If nosev_z_hi is negative, then:
\
\   (z_sign z_hi z_lo) = (z_sign z_hi z_lo) - R
\
\ So in effect, this does:
\
\   (z_sign z_hi z_lo) += nosev_z_hi * speed / 64

Type: Subroutine
Category: Moving
Summary: Move current ship: Apply acceleration to ship's speed as a one-off
Context: See this subroutine on its own page
References: No direct references to this subroutine in this source file

This routine has multiple stages. This stage does the following:

* Apply acceleration to the ship's speed (if acceleration is non-zero),
and then zero the acceleration as it's a one-off change

LDA INWK+27            \ Set A = the ship's speed in byte #24 + the ship's
CLC                    \ acceleration in byte #28

BPL P%+4               \ If the result is positive, skip the following
\ instruction

LDA #0                 \ Set A to 0 to stop the speed from going negative

LDY #15                \ Fetch byte #15 from the ship's blueprint, which
\ contains the ship's maximum speed

CMP (XX0),Y            \ If A < the ship's maximum speed, skip the following
BCC P%+4               \ instruction

LDA (XX0),Y            \ Set A to the ship's maximum speed

STA INWK+27            \ We have now calculated the new ship's speed after
\ accelerating and keeping the speed within the ship's
\ limits, so store the updated speed in byte #27

LDA #0                 \ We have added the ship's acceleration, so we now set
STA INWK+28            \ it back to 0 in byte #28, as it's a one-off change

Type: Subroutine
Category: Moving
Summary: Move current ship: Rotate ship's location by our pitch and roll
Deep dive: Rotating the universe
Context: See this subroutine on its own page
References: No direct references to this subroutine in this source file

This routine has multiple stages. This stage does the following:

* Rotate the ship's location in space by the amount of pitch and roll of
our ship. See below for a deeper explanation of this routine

LDX ALP1               \ Fetch the magnitude of the current roll into X, so
\ if the roll angle is alpha, X contains |alpha|

LDA INWK               \ Set P = ~x_lo (i.e. with all its bits flipped) so that
EOR #%11111111         \ we can pass x_lo to MLTU2 below)
STA P

LDA INWK+1             \ Set A = x_hi

JSR MLTU2-2            \ Set (A P+1 P) = (A ~P) * X
\               = (x_hi x_lo) * alpha

STA P+2                \ Store the high byte of the result in P+2, so we now
\ have:
\
\ P(2 1 0) = (x_hi x_lo) * alpha

LDA ALP2+1             \ Fetch the flipped sign of the current roll angle alpha
EOR INWK+2             \ from ALP2+1 and EOR with byte #2 (x_sign), so if the
\ flipped roll angle and x_sign have the same sign, A
\ will be positive, else it will be negative. So A will
\ contain the sign bit of x_sign * flipped alpha sign,
\ which is the opposite to the sign of the above result,
\ so we now have:
\
\ (A P+2 P+1) = - (x_sign x_hi x_lo) * alpha / 256

LDX #3                 \ Set (A P+2 P+1) = (y_sign y_hi y_lo) + (A P+2 P+1)
JSR MVT6               \                 = y - x * alpha / 256

STA K2+3               \ Set K2(3) = A = the sign of the result

LDA P+1                \ Set K2(1) = P+1, the low byte of the result
STA K2+1

EOR #%11111111         \ Set P = ~K2+1 (i.e. with all its bits flipped) so
STA P                  \ that we can pass K2+1 to MLTU2 below)

LDA P+2                \ Set K2(2) = A = P+2
STA K2+2

\ So we now have result 1 above:
\
\ K2(3 2 1) = (A P+2 P+1)
\           = y - x * alpha / 256

LDX BET1               \ Fetch the magnitude of the current pitch into X, so
\ if the pitch angle is beta, X contains |beta|

JSR MLTU2-2            \ Set (A P+1 P) = (A ~P) * X
\               = K2(2 1) * beta

STA P+2                \ Store the high byte of the result in P+2, so we now
\ have:
\
\ P(2 1 0) = K2(2 1) * beta

LDA K2+3               \ Fetch the sign of the above result in K(3 2 1) from
EOR BET2               \ K2+3 and EOR with BET2, the sign of the current pitch
\ rate, so if the pitch and K(3 2 1) have the same sign,
\ A will be positive, else it will be negative. So A
\ will contain the sign bit of K(3 2 1) * beta, which is
\ the same as the sign of the above result, so we now
\ have:
\
\ (A P+2 P+1) = K2(3 2 1) * beta / 256

LDX #6                 \ Set (A P+2 P+1) = (z_sign z_hi z_lo) + (A P+2 P+1)
JSR MVT6               \                 = z + K2 * beta / 256

STA INWK+8             \ Set z_sign = A = the sign of the result

LDA P+1                \ Set z_lo = P+1, the low byte of the result
STA INWK+6

EOR #%11111111         \ Set P = ~z_lo (i.e. with all its bits flipped) so that
STA P                  \ we can pass z_lo to MLTU2 below)

LDA P+2                \ Set z_hi = P+2
STA INWK+7

\ So we now have result 2 above:
\
\ (z_sign z_hi z_lo) = (A P+2 P+1)
\                    = z + K2 * beta / 256

JSR MLTU2              \ MLTU2 doesn't change Q, and Q was set to beta in
\ the previous call to MLTU2, so this call does:
\
\ (A P+1 P) = (A ~P) * Q
\           = (z_hi z_lo) * beta

STA P+2                \ Set P+2 = A = the high byte of the result, so we
\ now have:
\
\ P(2 1 0) = (z_hi z_lo) * beta

LDA K2+3               \ Set y_sign = K2+3
STA INWK+5

EOR BET2               \ EOR y_sign with BET2, the sign of the current pitch
EOR INWK+8             \ rate, and z_sign. If the result is positive jump to
BPL MV43               \ MV43, otherwise this means beta * z and y have
\ different signs, i.e. P(2 1) and K2(3 2 1) have
\ different signs, so we need to add them in order to
\ calculate K2(2 1) - P(2 1)

LDA P+1                \ Set (y_hi y_lo) = K2(2 1) + P(2 1)
STA INWK+3
LDA P+2
STA INWK+4

.MV43

LDA K2+1               \ Reversing the logic above, we need to subtract P(2 1)
SBC P+1                \ and K2(3 2 1) to calculate K2(2 1) - P(2 1), so this
STA INWK+3             \ sets (y_hi y_lo) = K2(2 1) - P(2 1)
LDA K2+2
SBC P+2
STA INWK+4

BCS MV44               \ If the above subtraction did not underflow, then

LDA #1                 \ Negate (y_sign y_hi y_lo) using two's complement,
SBC INWK+3             \ first doing the low bytes:
STA INWK+3             \
\ y_lo = 1 - y_lo

LDA #0                 \ Then the high bytes:
SBC INWK+4             \
STA INWK+4             \ y_hi = 0 - y_hi

EOR #%10000000
STA INWK+5

.MV44

\ So we now have result 3 above:
\
\ (y_sign y_hi y_lo) = K2(2 1) - P(2 1)
\                    = K2 - beta * z

LDX ALP1               \ Fetch the magnitude of the current roll into X, so
\ if the roll angle is alpha, X contains |alpha|

LDA INWK+3             \ Set P = ~y_lo (i.e. with all its bits flipped) so that
EOR #&FF               \ we can pass y_lo to MLTU2 below)
STA P

LDA INWK+4             \ Set A = y_hi

JSR MLTU2-2            \ Set (A P+1 P) = (A ~P) * X
\               = (y_hi y_lo) * alpha

STA P+2                \ Store the high byte of the result in P+2, so we now
\ have:
\
\ P(2 1 0) = (y_hi y_lo) * alpha

LDA ALP2               \ Fetch the correct sign of the current roll angle alpha
EOR INWK+5             \ from ALP2 and EOR with byte #5 (y_sign), so if the
\ correct roll angle and y_sign have the same sign, A
\ will be positive, else it will be negative. So A will
\ contain the sign bit of x_sign * correct alpha sign,
\ which is the same as the sign of the above result,
\ so we now have:
\
\ (A P+2 P+1) = (y_sign y_hi y_lo) * alpha / 256

LDX #0                 \ Set (A P+2 P+1) = (x_sign x_hi x_lo) + (A P+2 P+1)
JSR MVT6               \                 = x + y * alpha / 256

STA INWK+2             \ Set x_sign = A = the sign of the result

LDA P+2                \ Set x_hi = P+2, the high byte of the result
STA INWK+1

LDA P+1                \ Set x_lo = P+1, the low byte of the result
STA INWK

\ So we now have result 4 above:
\
\ x = x + alpha * y
\
\ and the rotation of (x, y, z) is done

Type: Subroutine
Category: Moving
Summary: Move current ship: Move the ship in space according to our speed
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* MV40 calls entry point MV45

This routine has multiple stages. This stage does the following:

* Move the ship in space according to our speed (we already moved it
according to its own speed in part 3).

We do this by subtracting our speed (i.e. the distance we travel in this
iteration of the loop) from the other ship's z-coordinate. We subtract because
they appear to be "moving" in the opposite direction to us, and the whole
MVEIT routine is about moving the other ships rather than us (even though we
are the one doing the moving).

Other entry points:

MV45                 Rejoin the MVEIT routine after the rotation, tactics and
scanner code

.MV45

LDA DELTA              \ Set R to our speed in DELTA
STA R

LDA #%10000000         \ Set A to zeroes but with bit 7 set, so that (A R) is
\ a 16-bit number containing -R, or -speed

LDX #6                 \ Set X to the z-axis so the call to MVT1 does this:
JSR MVT1               \
\ (z_sign z_hi z_lo) = (z_sign z_hi z_lo) + (A R)
\                    = (z_sign z_hi z_lo) - speed

LDA TYPE               \ If the ship type is not the sun (129) then skip the
AND #%10000001         \ next instruction, otherwise return from the subroutine
CMP #129               \ as we don't need to rotate the sun around its origin.
BNE P%+3               \ Having both the AND and the CMP is a little odd, as
\ the sun is the only ship type with bits 0 and 7 set,
\ so the AND has no effect and could be removed

RTS                    \ Return from the subroutine, as the ship we are moving
\ is the sun and doesn't need any of the following

Type: Subroutine
Category: Moving
Summary: Move current ship: Rotate ship's orientation vectors by pitch/roll
Deep dive: Orientation vectors
Pitching and rolling
Context: See this subroutine on its own page
References: No direct references to this subroutine in this source file

This routine has multiple stages. This stage does the following:

* Rotate the ship's orientation vectors according to our pitch and roll

As with the previous step, this is all about moving the other ships rather
than us (even though we are the one doing the moving). So we rotate the
current ship's orientation vectors (which defines its orientation in space),
by the angles we are "moving" the rest of the sky through (alpha and beta, our
roll and pitch), so the ship appears to us to be stationary while we rotate.

LDY #9                 \ Apply our pitch and roll rotations to the current
JSR MVS4               \ ship's nosev vector

LDY #15                \ Apply our pitch and roll rotations to the current
JSR MVS4               \ ship's roofv vector

LDY #21                \ Apply our pitch and roll rotations to the current
JSR MVS4               \ ship's sidev vector

Type: Subroutine
Category: Moving
Summary: Move current ship: Rotate ship about itself by its own pitch/roll
Deep dive: Orientation vectors
Pitching and rolling by a fixed angle
Context: See this subroutine on its own page
References: No direct references to this subroutine in this source file

This routine has multiple stages. This stage does the following:

* If the ship we are processing is rolling or pitching itself, rotate it and
apply damping if required

LDA INWK+30            \ Fetch the ship's pitch counter and extract the sign
AND #%10000000         \ into RAT2
STA RAT2

LDA INWK+30            \ Fetch the ship's pitch counter and extract the value
AND #%01111111         \ without the sign bit into A

BEQ MV8                \ If the pitch counter is 0, then jump to MV8 to skip
\ the following, as the ship is not pitching

CMP #%01111111         \ If bits 0-6 are set in the pitch counter (i.e. the
\ ship's pitch is not damping down), then the C flag
\ will be set by this instruction

SBC #0                 \ Set A = A - 0 - (1 - C), so if we are damping then we
\ reduce A by 1, otherwise it is unchanged

ORA RAT2               \ Change bit 7 of A to the sign we saved in RAT2, so
\ the updated pitch counter in A retains its sign

STA INWK+30            \ Store the updated pitch counter in byte #30

LDX #15                \ Rotate (roofv_x, nosev_x) by a small angle (pitch)
LDY #9
JSR MVS5

LDX #17                \ Rotate (roofv_y, nosev_y) by a small angle (pitch)
LDY #11
JSR MVS5

LDX #19                \ Rotate (roofv_z, nosev_z) by a small angle (pitch)
LDY #13
JSR MVS5

.MV8

LDA INWK+29            \ Fetch the ship's roll counter and extract the sign
AND #%10000000         \ into RAT2
STA RAT2

LDA INWK+29            \ Fetch the ship's roll counter and extract the value
AND #%01111111         \ without the sign bit into A

BEQ MV5                \ If the roll counter is 0, then jump to MV5 to skip the
\ following, as the ship is not rolling

CMP #%01111111         \ If bits 0-6 are set in the roll counter (i.e. the
\ ship's roll is not damping down), then the C flag
\ will be set by this instruction

SBC #0                 \ Set A = A - 0 - (1 - C), so if we are damping then we
\ reduce A by 1, otherwise it is unchanged

ORA RAT2               \ Change bit 7 of A to the sign we saved in RAT2, so
\ the updated roll counter in A retains its sign

STA INWK+29            \ Store the updated pitch counter in byte #29

LDX #15                \ Rotate (roofv_x, sidev_x) by a small angle (roll)
LDY #21
JSR MVS5

LDX #17                \ Rotate (roofv_y, sidev_y) by a small angle (roll)
LDY #23
JSR MVS5

LDX #19                \ Rotate (roofv_z, sidev_z) by a small angle (roll)
LDY #25
JSR MVS5

Type: Subroutine
Category: Moving
Summary: Move current ship: Redraw on scanner, if it hasn't been destroyed
Context: See this subroutine on its own page
References: No direct references to this subroutine in this source file

This routine has multiple stages. This stage does the following:

* If the ship is exploding or being removed, hide it on the scanner

* Otherwise redraw the ship on the scanner, now that it's been moved

.MV5

LDA INWK+31            \ Fetch the ship's exploding/killed state from byte #31

AND #%10100000         \ If we are exploding or removing this ship then jump to
BNE MVD1               \ MVD1 to remove it from the scanner permanently

LDA INWK+31            \ Set bit 4 to keep the ship visible on the scanner
ORA #%00010000
STA INWK+31

JMP SCAN               \ Display the ship on the scanner, returning from the
\ subroutine using a tail call

.MVD1

LDA INWK+31            \ Clear bit 4 to hide the ship on the scanner
AND #%11101111
STA INWK+31

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Moving
Summary: Calculate (x_sign x_hi x_lo) = (x_sign x_hi x_lo) + (A R)
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* MVEIT (Part 6 of 9) calls MVT1
* SFS2 calls MVT1
* MVEIT (Part 3 of 9) calls entry point MVT1-2

Add the signed delta (A R) to a ship's coordinate, along the axis given in X.
Mathematically speaking, this routine translates the ship along a single axis
by a signed delta. Taking the example of X = 0, the x-axis, it does the
following:

(x_sign x_hi x_lo) = (x_sign x_hi x_lo) + (A R)

(In practice, MVT1 is only ever called directly with A = 0 or 128, otherwise
it is always called via MVT-2, which clears A apart from the sign bit. The
routine is written to cope with a non-zero delta_hi, so it supports a full
16-bit delta, but it appears that delta_hi is only ever used to hold the
sign of the delta.)

The comments below assume we are adding delta to the x-axis, though the axis
is determined by the value of X.

Arguments:

(A R)                The signed delta, so A = delta_hi and R = delta_lo

X                    Determines which coordinate axis of INWK to change:

* X = 0 adds the delta to (x_lo, x_hi, x_sign)

* X = 3 adds the delta to (y_lo, y_hi, y_sign)

* X = 6 adds the delta to (z_lo, z_hi, z_sign)

Other entry points:

MVT1-2               Clear bits 0-6 of A before entering MVT1

AND #%10000000         \ Clear bits 0-6 of A

.MVT1

ASL A                  \ Set the C flag to the sign bit of the delta, leaving
\ delta_hi << 1 in A

STA S                  \ Set S = delta_hi << 1
\
\ This also clears bit 0 of S

LDA #0                 \ Set T = just the sign bit of delta (in bit 7)
ROR A
STA T

LSR S                  \ Set S = delta_hi >> 1
\       = |delta_hi|
\
\ This also clear the C flag, as we know that bit 0 of
\ S was clear before the LSR

EOR INWK+2,X           \ If T EOR x_sign has bit 7 set, then x_sign and delta

\ At this point, we know x_sign and delta have the same
\ sign, that sign is in T, and S contains |delta_hi|,
\ so now we want to do:
\
\   (x_sign x_hi x_lo) = (x_sign x_hi x_lo) + (S R)
\
\ and then set the sign of the result to the same sign
\ as x_sign and delta

LDA R                  \ First we add the low bytes, so:
STA INWK,X             \   x_lo = x_lo + R

LDA S                  \ Then we add the high bytes:
STA INWK+1,X           \   x_hi = x_hi + S

LDA INWK+2,X           \ And finally we add any carry into x_sign, and if the
ADC #0                 \ sign of x_sign and delta in T is negative, make sure
ORA T                  \ the result is negative (by OR'ing with T)
STA INWK+2,X

RTS                    \ Return from the subroutine

.MV10

\ If we get here, we know x_sign and delta have
\ |delta_hi| in S, so now we want to do:
\
\   (x_sign x_hi x_lo) = (x_sign x_hi x_lo) - (S R)
\
\ and then set the sign of the result according to
\ the signs of x_sign and delta

LDA INWK,X             \ First we subtract the low bytes, so:
SEC                    \
SBC R                  \   x_lo = x_lo - R
STA INWK,X

LDA INWK+1,X           \ Then we subtract the high bytes:
SBC S                  \
STA INWK+1,X           \   x_hi = x_hi - S

LDA INWK+2,X           \ And finally we subtract any borrow from bits 0-6 of
AND #%01111111         \ x_sign, and give the result the opposite sign bit to T
SBC #0                 \ (i.e. give it the sign of the original x_sign)
ORA #%10000000
EOR T
STA INWK+2,X

BCS MV11               \ If the C flag is set by the above SBC, then our sum
\ above didn't underflow and is correct - to put it
\ another way, (x_sign x_hi x_lo) >= (S R) so the result
\ should indeed have the same sign as x_sign, so jump to
\ MV11 to return from the subroutine

\ Otherwise our subtraction underflowed because
\ (x_sign x_hi x_lo) < (S R), so we now need to flip the
\ subtraction around by using two's complement to this:
\
\   (S R) - (x_sign x_hi x_lo)
\
\ and then we need to give the result the same sign as
\ (S R), the delta, as that's the dominant figure in the
\ sum

LDA #1                 \ First we subtract the low bytes, so:
SBC INWK,X             \
STA INWK,X             \   x_lo = 1 - x_lo

LDA #0                 \ Then we subtract the high bytes:
SBC INWK+1,X           \
STA INWK+1,X           \   x_hi = 0 - x_hi

LDA #0                 \ And then we subtract the sign bytes:
SBC INWK+2,X           \
\   x_sign = 0 - x_sign

AND #%01111111         \ Finally, we set the sign bit to the sign in T, the
ORA T                  \ sign of the original delta, as the delta is the
STA INWK+2,X           \ dominant figure in the sum

.MV11

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Moving
Summary: Apply pitch and roll to an orientation vector
Deep dive: Orientation vectors
Pitching and rolling
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* MVEIT (Part 7 of 9) calls MVS4

Apply pitch and roll angles alpha and beta to the orientation vector in Y.

Specifically, this routine rotates a point (x, y, z) around the origin by
pitch alpha and roll beta, using the small angle approximation to make the
maths easier, and incorporating the Minsky circle algorithm to make the
rotation more stable (though more elliptic).

If that paragraph makes sense to you, then you should probably be writing
this commentary! For the rest of us, there's a detailed explanation of all
this in the deep dive on "Pitching and rolling".

Arguments:

Y                    Determines which of the INWK orientation vectors to
transform:

* Y = 9 rotates nosev: (nosev_x, nosev_y, nosev_z)

* Y = 15 rotates roofv: (roofv_x, roofv_y, roofv_z)

* Y = 21 rotates sidev: (sidev_x, sidev_y, sidev_z)

.MVS4

LDA ALPHA              \ Set Q = alpha (the roll angle to rotate through)
STA Q

LDX INWK+2,Y           \ Set (S R) = nosev_y
STX R
LDX INWK+3,Y
STX S

LDX INWK,Y             \ These instructions have no effect as MAD overwrites
STX P                  \ X and P when called, but they set X = P = nosev_x_lo

LDA INWK+1,Y           \ Set A = -nosev_x_hi
EOR #%10000000

JSR MAD                \ Set (A X) = Q * A + (S R)
STA INWK+3,Y           \           = alpha * -nosev_x_hi + nosev_y
STX INWK+2,Y           \
\ and store (A X) in nosev_y, so this does:
\
\ nosev_y = nosev_y - alpha * nosev_x_hi

STX P                  \ This instruction has no effect as MAD overwrites P,
\ but it sets P = nosev_y_lo

LDX INWK,Y             \ Set (S R) = nosev_x
STX R
LDX INWK+1,Y
STX S

LDA INWK+3,Y           \ Set A = nosev_y_hi

JSR MAD                \ Set (A X) = Q * A + (S R)
STA INWK+1,Y           \           = alpha * nosev_y_hi + nosev_x
STX INWK,Y             \
\ and store (A X) in nosev_x, so this does:
\
\ nosev_x = nosev_x + alpha * nosev_y_hi

STX P                  \ This instruction has no effect as MAD overwrites P,
\ but it sets P = nosev_x_lo

LDA BETA               \ Set Q = beta (the pitch angle to rotate through)
STA Q

LDX INWK+2,Y           \ Set (S R) = nosev_y
STX R
LDX INWK+3,Y
STX S
LDX INWK+4,Y

STX P                  \ This instruction has no effect as MAD overwrites P,
\ but it sets P = nosev_y

LDA INWK+5,Y           \ Set A = -nosev_z_hi
EOR #%10000000

JSR MAD                \ Set (A X) = Q * A + (S R)
STA INWK+3,Y           \           = beta * -nosev_z_hi + nosev_y
STX INWK+2,Y           \
\ and store (A X) in nosev_y, so this does:
\
\ nosev_y = nosev_y - beta * nosev_z_hi

STX P                  \ This instruction has no effect as MAD overwrites P,
\ but it sets P = nosev_y_lo

LDX INWK+4,Y           \ Set (S R) = nosev_z
STX R
LDX INWK+5,Y
STX S

LDA INWK+3,Y           \ Set A = nosev_y_hi

JSR MAD                \ Set (A X) = Q * A + (S R)
STA INWK+5,Y           \           = beta * nosev_y_hi + nosev_z
STX INWK+4,Y           \
\ and store (A X) in nosev_z, so this does:
\
\ nosev_z = nosev_z + beta * nosev_y_hi

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Moving
Summary: Calculate (A P+2 P+1) = (x_sign x_hi x_lo) + (A P+2 P+1)
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* MVEIT (Part 5 of 9) calls MVT6

Do the following calculation, for the coordinate given by X (so this is what
it does for the x-coordinate):

(A P+2 P+1) = (x_sign x_hi x_lo) + (A P+2 P+1)

A is a sign bit and is not included in the calculation, but bits 0-6 of A are
preserved. Bit 7 is set to the sign of the result.

Arguments:

A                    The sign of P(2 1) in bit 7

P(2 1)               The 16-bit value we want to add the coordinate to

X                    The coordinate to add, as follows:

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

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

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

Returns:

A                    The sign of the result (in bit 7)

.MVT6

TAY                    \ Store argument A into Y, for later use

EOR INWK+2,X           \ Set A = A EOR x_sign

BMI MV50               \ If the sign is negative, i.e. A and x_sign have

\ The signs are the same, so we can add the two
\ arguments and keep the sign to get the result

LDA P+1                \ First we add the low bytes:
CLC                    \
ADC INWK,X             \   P+1 = P+1 + x_lo
STA P+1

LDA P+2                \ And then the high bytes:
STA P+2                \   P+2 = P+2 + x_hi

TYA                    \ Restore the original A argument that we stored earlier
\ so that we keep the original sign

RTS                    \ Return from the subroutine

.MV50

LDA INWK,X             \ First we subtract the low bytes:
SEC                    \
SBC P+1                \   P+1 = x_lo - P+1
STA P+1

LDA INWK+1,X           \ And then the high bytes:
SBC P+2                \
STA P+2                \   P+2 = x_hi - P+2

BCC MV51               \ If the last subtraction underflowed, then the C flag
\ will be clear and x_hi < P+2, so jump to MV51 to
\ negate the result

TYA                    \ Restore the original A argument that we stored earlier
EOR #%10000000         \ but flip bit 7, which flips the sign. We do this
\ because x_hi >= P+2 so we want the result to have the
\ same sign as x_hi (as it's the dominant side in this
\ calculation). The sign of x_hi is x_sign, and x_sign
\ has the opposite sign to A, so we flip the sign in A
\ to return the correct result

RTS                    \ Return from the subroutine

.MV51

LDA #1                 \ Our subtraction underflowed, so we negate the result
SBC P+1                \ using two's complement, first with the low byte:
STA P+1                \
\   P+1 = 1 - P+1

LDA #0                 \ And then the high byte:
SBC P+2                \
STA P+2                \   P+2 = 0 - P+2

TYA                    \ Restore the original A argument that we stored earlier
\ as this is the correct sign for the result. This is
\ because x_hi < P+2, so we want to return the same sign
\ as P+2, the dominant side

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Moving
Summary: Rotate the planet or sun's location in space by the amount of
pitch and roll of our ship
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* MVEIT (Part 2 of 9) calls MV40

We implement this using the same equations as in part 5 of MVEIT, where we
rotated the current ship's location by our pitch and roll. Specifically, the
calculation is as follows:

1. K2 = y - alpha * x
2. z = z + beta * K2
3. y = K2 - beta * z
4. x = x + alpha * y

See the deep dive on "Rotating the universe" for more details on the above.

.MV40

LDA ALPHA              \ Set Q = -ALPHA, so Q contains the angle we want to
EOR #%10000000         \ roll the planet through (i.e. in the opposite
STA Q                  \ direction to our ship's roll angle alpha)

LDA INWK               \ Set P(1 0) = (x_hi x_lo)
STA P
LDA INWK+1
STA P+1

LDA INWK+2             \ Set A = x_sign

JSR MULT3              \ Set K(3 2 1 0) = (A P+1 P) * Q
\
\ which also means:
\
\   K(3 2 1) = (A P+1 P) * Q / 256
\            = x * -alpha / 256
\            = - alpha * x / 256

LDX #3                 \ Set K(3 2 1) = (y_sign y_hi y_lo) + K(3 2 1)
JSR MVT3               \              = y - alpha * x / 256

LDA K+1                \ Set K2(2 1) = P(1 0) = K(2 1)
STA K2+1
STA P

LDA K+2                \ Set K2+2 = K+2
STA K2+2

STA P+1                \ Set P+1 = K+2

LDA BETA               \ Set Q = beta, the pitch angle of our ship
STA Q

LDA K+3                \ Set K+3 to K2+3, so now we have result 1 above:
STA K2+3               \
\   K2(3 2 1) = K(3 2 1)
\             = y - alpha * x / 256

\ We also have:
\
\   A = K+3
\
\   P(1 0) = K(2 1)
\
\ so combined, these mean:
\
\   (A P+1 P) = K(3 2 1)
\             = K2(3 2 1)

JSR MULT3              \ Set K(3 2 1 0) = (A P+1 P) * Q
\
\ which also means:
\
\   K(3 2 1) = (A P+1 P) * Q / 256
\            = K2(3 2 1) * beta / 256
\            = beta * K2 / 256

LDX #6                 \ K(3 2 1) = (z_sign z_hi z_lo) + K(3 2 1)
JSR MVT3               \          = z + beta * K2 / 256

LDA K+1                \ Set P = K+1
STA P

STA INWK+6             \ Set z_lo = K+1

LDA K+2                \ Set P+1 = K+2
STA P+1

STA INWK+7             \ Set z_hi = K+2

LDA K+3                \ Set A = z_sign = K+3, so now we have:
STA INWK+8             \
\   (z_sign z_hi z_lo) = K(3 2 1)
\                      = z + beta * K2 / 256

\ So we now have result 2 above:
\
\   z = z + beta * K2

EOR #%10000000         \ Flip the sign bit of A to give A = -z_sign

JSR MULT3              \ Set K(3 2 1 0) = (A P+1 P) * Q
\                = (-z_sign z_hi z_lo) * beta
\                = -z * beta

LDA K+3                \ Set T to the sign bit of K(3 2 1 0), i.e. to the sign
AND #%10000000         \ bit of -z * beta
STA T

EOR K2+3               \ If K2(3 2 1 0) has a different sign to K(3 2 1 0),
BMI MV1                \ then EOR'ing them will produce a 1 in bit 7, so jump
\ to MV1 to take this into account

\ If we get here, K and K2 have the same sign, so we can
\ add them together to get the result we're after, and
\ then set the sign afterwards

LDA K                  \ We now do the following sum:
CLC                    \
ADC K2                 \   (A y_hi y_lo -) = K(3 2 1 0) + K2(3 2 1 0)
\
\ starting with the low bytes (which we don't keep)
\
\ The CLC has no effect because MULT3 clears the C
\ flag, so this instruction could be removed (as it is
\ in the cassette version, for example)

LDA K+1                \ We then do the middle bytes, which go into y_lo
STA INWK+3

LDA K+2                \ And then the high bytes, which go into y_hi
STA INWK+4

LDA K+3                \ And then the sign bytes into A, so overall we have the
ADC K2+3               \ following, if we drop the low bytes from the result:
\
\   (A y_hi y_lo) = (K + K2) / 256

JMP MV2                \ Jump to MV2 to skip the calculation for when K and K2
\ have different signs

.MV1

LDA K                  \ If we get here then K2 and K have different signs, so
SEC                    \ instead of adding, we need to subtract to get the
SBC K2                 \ result we want, like this:
\
\   (A y_hi y_lo -) = K(3 2 1 0) - K2(3 2 1 0)
\
\ starting with the low bytes (which we don't keep)

LDA K+1                \ We then do the middle bytes, which go into y_lo
SBC K2+1
STA INWK+3

LDA K+2                \ And then the high bytes, which go into y_hi
SBC K2+2
STA INWK+4

LDA K2+3               \ Now for the sign bytes, so first we extract the sign
AND #%01111111         \ byte from K2 without the sign bit, so P = |K2+3|
STA P

LDA K+3                \ And then we extract the sign byte from K without the
AND #%01111111         \ sign bit, so A = |K+3|

SBC P                  \ And finally we subtract the sign bytes, so P = A - P
STA P

\ By now we have the following, if we drop the low bytes
\ from the result:
\
\   (A y_hi y_lo) = (K - K2) / 256
\
\ so now we just need to make sure the sign of the
\ result is correct

BCS MV2                \ If the C flag is set, then the last subtraction above
\ MV2 as we are done with this particular stage

LDA #1                 \ Otherwise the subtraction above underflowed, as K2 is
SBC INWK+3             \ the dominant part of the subtraction, so we need to
STA INWK+3             \ negate the result using two's complement, starting
\ with the low bytes:
\
\   y_lo = 1 - y_lo

LDA #0                 \ And then the high bytes:
SBC INWK+4             \
STA INWK+4             \   y_hi = 0 - y_hi

LDA #0                 \ And finally the sign bytes:
SBC P                  \
\   A = 0 - P

ORA #%10000000         \ We now force the sign bit to be negative, so that the
\ final result below gets the opposite sign to K, which
\ we want as K2 is the dominant part of the sum

.MV2

EOR T                  \ T contains the sign bit of K, so if K is negative,
\ this flips the sign of A

STA INWK+5             \ Store A in y_sign

\ So we now have result 3 above:
\
\   y = K2 + K
\     = K2 - beta * z

LDA ALPHA              \ Set A = alpha
STA Q

LDA INWK+3             \ Set P(1 0) = (y_hi y_lo)
STA P
LDA INWK+4
STA P+1

LDA INWK+5             \ Set A = y_sign

JSR MULT3              \ Set K(3 2 1 0) = (A P+1 P) * Q
\                = (y_sign y_hi y_lo) * alpha
\                = y * alpha

LDX #0                 \ Set K(3 2 1) = (x_sign x_hi x_lo) + K(3 2 1)
JSR MVT3               \              = x + y * alpha / 256

LDA K+1                \ Set (x_sign x_hi x_lo) = K(3 2 1)
STA INWK               \                        = x + y * alpha / 256
LDA K+2
STA INWK+1
LDA K+3
STA INWK+2

\ So we now have result 4 above:
\
\   x = x + y * alpha

JMP MV45               \ We have now finished rotating the planet or sun by
\ our pitch and roll, so jump back into the MVEIT
\ routine at MV45 to apply all the other movements

Type: Subroutine
Category: Flight
Summary: Flip the coordinate axes for the four different views
Deep dive: Flipping axes between space views
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* Main flight loop (Part 11 of 16) calls PU1

This routine flips the relevant geometric axes in INWK depending on which
view we are looking through (front, rear, left, right).

.PU1

DEX                    \ Decrement the view, so now:
\
\   0 = rear
\   1 = left
\   2 = right

BNE PU2                \ If the current view is left or right, jump to PU2,
\ otherwise this is the rear view, so continue on

LDA INWK+2             \ Flip the sign of x_sign
EOR #%10000000
STA INWK+2

LDA INWK+8             \ Flip the sign of z_sign
EOR #%10000000
STA INWK+8

LDA INWK+10            \ Flip the sign of nosev_x_hi
EOR #%10000000
STA INWK+10

LDA INWK+14            \ Flip the sign of nosev_z_hi
EOR #%10000000
STA INWK+14

LDA INWK+16            \ Flip the sign of roofv_x_hi
EOR #%10000000
STA INWK+16

LDA INWK+20            \ Flip the sign of roofv_z_hi
EOR #%10000000
STA INWK+20

LDA INWK+22            \ Flip the sign of sidev_x_hi
EOR #%10000000
STA INWK+22

LDA INWK+26            \ Flip the sign of roofv_z_hi
EOR #%10000000
STA INWK+26

RTS                    \ Return from the subroutine

.PU2

\ We enter this with X set to the view, as follows:
\
\   1 = left
\   2 = right

LDA #0                 \ Set RAT2 = 0 (left view) or -1 (right view)
CPX #2
ROR A
STA RAT2

EOR #%10000000         \ Set RAT = -1 (left view) or 0 (right view)
STA RAT

LDA INWK               \ Swap x_lo and z_lo
LDX INWK+6
STA INWK+6
STX INWK

LDA INWK+1             \ Swap x_hi and z_hi
LDX INWK+7
STA INWK+7
STX INWK+1

LDA INWK+2             \ Swap x_sign and z_sign
EOR RAT                \ If left view, flip sign of new z_sign
TAX                    \ If right view, flip sign of new x_sign
LDA INWK+8
EOR RAT2
STA INWK+2
STX INWK+8

LDY #9                 \ Swap nosev_x_lo and nosev_z_lo
JSR PUS1               \ Swap nosev_x_hi and nosev_z_hi
\ If left view, flip sign of new nosev_z_hi
\ If right view, flip sign of new nosev_x_hi

LDY #15                \ Swap roofv_x_lo and roofv_z_lo
JSR PUS1               \ Swap roofv_x_hi and roofv_z_hi
\ If left view, flip sign of new roofv_z_hi
\ If right view, flip sign of new roofv_x_hi

LDY #21                \ Swap sidev_x_lo and sidev_z_lo
\ Swap sidev_x_hi and sidev_z_hi
\ If left view, flip sign of new sidev_z_hi
\ If right view, flip sign of new sidev_x_hi

.PUS1

LDA INWK,Y             \ Swap the low x and z bytes for the vector in Y:
LDX INWK+4,Y           \
STA INWK+4,Y           \   * For Y =  9 swap nosev_x_lo and nosev_z_lo
STX INWK,Y             \   * For Y = 15 swap roofv_x_lo and roofv_z_lo
\   * For Y = 21 swap sidev_x_lo and sidev_z_lo

LDA INWK+1,Y           \ Swap the high x and z bytes for the offset in Y:
EOR RAT                \
TAX                    \   * If left view, flip sign of new z-coordinate
LDA INWK+5,Y           \   * If right view, flip sign of new x-coordinate
EOR RAT2
STA INWK+1,Y
STX INWK+5,Y

\ Fall through into LOOK1 to return from the subroutine

Type: Subroutine
Category: Flight
Summary: Initialise the space view
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* MJP calls LOOK1
* TT102 calls LOOK1
* TT110 calls LOOK1
* WARP calls LOOK1
* SIGHT calls entry point LO2

Initialise the space view, with the direction of view given in X. This clears
the upper screen and draws the laser crosshairs, if the view in X has lasers
fitted. It also wipes all the ships from the scanner, so we can recalculate
ship positions for the new view (they get put back in the main flight loop).

Arguments:

X                    The space view to set:

* 0 = front

* 1 = rear

* 2 = left

* 3 = right

Other entry points:

LO2                  Contains an RTS

.LO2

RTS                    \ Return from the subroutine

.LQ

STX VIEW               \ Set the current space view to X

JSR TT66               \ Clear the top part of the screen, draw a white border,
\ and set the current view type in QQ11 to 0 (space
\ view)

JSR SIGHT              \ Draw the laser crosshairs

JMP NWSTARS            \ Set up a new stardust field and return from the
\ subroutine using a tail call

.LOOK1

LDA #0                 \ Set A = 0, the type number of a space view

LDY QQ11               \ If the current view is not a space view, jump up to LQ
BNE LQ                 \ to set up a new space view

BEQ LO2                \ to return from the subroutine (as LO2 contains an RTS)

STX VIEW               \ Change the current space view to X

JSR TT66               \ Clear the top part of the screen, draw a white border,
\ and set the current view type in QQ11 to 0 (space
\ view)

JSR FLIP               \ Swap the x- and y-coordinates of all the stardust
\ particles

JSR WPSHPS             \ Wipe all the ships from the scanner

\ And fall through into SIGHT to draw the laser
\ crosshairs

Type: Subroutine
Category: Flight
Summary: Draw the laser crosshairs
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* LOOK1 calls SIGHT

.SIGHT

LDY VIEW               \ Fetch the laser power for our new view
LDA LASER,Y

BEQ LO2                \ If it is zero (i.e. there is no laser fitted to this
\ LO2 contains an RTS)

LDA #128               \ Set QQ19 to the x-coordinate of the centre of the
STA QQ19               \ screen

LDA #Y-24              \ Set QQ19+1 to the y-coordinate of the centre of the
STA QQ19+1             \ screen, minus 24 (because TT15 will add 24 to the
\ coordinate when it draws the crosshairs)

LDA #20                \ Set QQ19+2 to size 20 for the crosshairs size
STA QQ19+2

JSR TT15               \ Call TT15 to draw crosshairs of size 20 just to the
\ left of the middle of the screen

LDA #10                \ Set QQ19+2 to size 10 for the crosshairs size
STA QQ19+2

JMP TT15               \ Call TT15 to draw crosshairs of size 10 at the same
\ location, which will remove the centre part from the
\ laser crosshairs, leaving a gap in the middle, and
\ return from the subroutine using a tail call

Type: Subroutine
Category: Utility routines
Summary: Clear the screen and set the current view type
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* DEATH calls TT66
* LOOK1 calls TT66
* MJP calls TT66
* STATUS calls TT66
* TT167 calls TT66
* TT18 calls TT66
* TT213 calls TT66
* TT22 calls TT66
* TT23 calls TT66
* TT25 calls TT66
* TT26 calls TT66

Clear the top part of the screen, draw a white border, and set the current
view type in QQ11 to A.

Arguments:

A                    The type of the new current view (see QQ11 for a list of
view types)

.TT66

STA QQ11               \ Set the current view type in QQ11 to A

\ Fall through into TTX66 to clear the screen and draw a
\ white border

Type: Subroutine
Category: Utility routines
Summary: Clear the top part of the screen and draw a white border
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* HFS2 calls TTX66
* TT18 calls TTX66
* DEATH calls entry point BOX

Clear the top part of the screen (the space view) and draw a white border
along the top and sides.

Other entry points:

BOX                  Just draw the border and (if this is a space view) the
view name. This can be used to remove the border and
view name, as it is drawn using EOR logic

Other entry points:

BOL1-1               Contains an RTS

.TTX66

\ --- Mod: Original Acornsoft code removed: ----------->

\ LDA #%10000000        \ Set bit 7 of QQ17 to switch to Sentence Case
\ STA QQ17

\ --- And replaced by: -------------------------------->

JSR vdu_80             \ Call vdu_80 to switch to Sentence Case

\ --- End of replacement ------------------------------>

JSR FLFLLS             \ Call FLFLLS to reset the LSO block

STA LAS2               \ Set LAS2 = 0 to stop any laser pulsing (the call to
\ FLFLLS sets A = 0)

STA DLY                \ Set the delay in DLY to 0, to indicate that we are
\ no longer showing an in-flight message, so any new
\ in-flight messages will be shown instantly

STA de                 \ Clear de, the flag that appends " DESTROYED" to the
\ end of the next text token, so that it doesn't

LDX #&60               \ Set X to the screen memory page for the top row of the
\ screen (as screen memory starts at &6000)

.BOL1

JSR ZES1               \ Call ZES1 to zero-fill the page in X, which clears
\ that character row on the screen

INX                    \ Increment X to point to the next page, i.e. the next
\ character row

CPX #&78               \ Loop back to BOL1 until we have cleared page &7700,
BNE BOL1               \ the last character row in the space view part of the
\ screen (the top part)

LDX QQ22+1             \ Fetch into X the number that's shown on-screen during
\ the hyperspace countdown

BEQ BOX                \ If the counter is zero then we are not counting down
\ instruction

JSR ee3                \ Print the 8-bit number in X at text location (0, 1),
\ i.e. print the hyperspace countdown in the top-left
\ corner

.BOX

LDY #1                 \ Move the text cursor to row 1
STY YC

LDA QQ11               \ If this is not a space view, jump to tt66 to skip
BNE tt66               \ displaying the view name

LDY #11                \ Move the text cursor to row 11
STY XC

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

ORA #&60               \ OR with &60 so we get a value of &60 to &63 (96 to 99)

JSR TT27               \ Print recursive token 96 to 99, which will be in the
\ range "FRONT" to "RIGHT"

JSR TT162              \ Print a space

LDA #175               \ Print recursive token 15 ("VIEW ")
JSR TT27

.tt66

LDX #0                 \ Set (X1, Y1) to (0, 0)
STX X1
STX Y1

STX QQ17               \ Set QQ17 = 0 to switch to ALL CAPS

DEX                    \ Set X2 = 255
STX X2

JSR HLOIN              \ Draw a horizontal line from (X1, Y1) to (X2, Y1), so
\ that's (0, 0) to (255, 0), along the very top of the
\ screen

LDA #2                 \ Set X1 = X2 = 2
STA X1
STA X2

JSR BOS2               \ Call BOS2 below, which will call BOS1 twice, and then
\ fall through into BOS2 again, so we effectively do
\ BOS1 four times, decrementing X1 and X2 each time
\ before calling LOIN, so this whole loop-within-a-loop
\ mind-bender ends up drawing these four lines:
\
\   (1, 0)   to (1, 191)
\   (0, 0)   to (0, 191)
\   (255, 0) to (255, 191)
\   (254, 0) to (254, 191)
\
\ So that's a 2-pixel wide vertical border along the
\ left edge of the upper part of the screen, and a
\ 2-pixel wide vertical border along the right edge

.BOS2

JSR BOS1               \ Call BOS1 below and then fall through into it, which
\ ends up running BOS1 twice. This is all part of the
\ loop-the-loop border-drawing mind-bender explained
\ above

.BOS1

LDA #0                 \ Set Y1 = 0
STA Y1

LDA #2*Y-1             \ Set Y2 = 2 * #Y - 1. The constant #Y is 96, the
STA Y2                 \ y-coordinate of the mid-point of the space view, so
\ this sets Y2 to 191, the y-coordinate of the bottom
\ pixel row of the space view

DEC X1                 \ Decrement X1 and X2
DEC X2

JMP LOIN               \ Draw a line from (X1, Y1) to (X2, Y2), and return from
\ the subroutine using a tail call

Type: Subroutine
Category: Utility routines
Summary: Wait for a specified time, in 1/50s of a second
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* DKS3 calls DELAY
* Main game loop (Part 5 of 6) calls DELAY

Wait for the number of vertical syncs given in Y, so this effectively waits
for Y/50 of a second (as the vertical sync occurs 50 times a second).

Arguments:

Y                    The number of vertical sync events to wait for

.DELAY

JSR WSCAN              \ Call WSCAN to wait for the vertical sync, so the whole
\ screen gets drawn

DEY                    \ Decrement the counter in Y

BNE DELAY              \ If Y isn't yet at zero, jump back to DELAY to wait
\ for another vertical sync

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Utility routines
Summary: Clear the bottom three text rows of the mode 4 screen
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* hm calls CLYNS

This routine clears some space at the bottom of the screen and moves the text
cursor to column 1, row 21.

Specifically, it zeroes the following screen locations:

&7507 to &75F0
&7607 to &76F0
&7707 to &77F0

which clears the three bottom text rows of the mode 4 screen (rows 21 to 23),
clearing each row from text column 1 to 30 (so it doesn't overwrite the box
border in columns 0 and 32, or the last usable column in column 31).

Returns:

A                    A is set to 0

Y                    Y is set to 0

.CLYNS

LDA #20                \ Move the text cursor to row 20, near the bottom of
STA YC                 \ the screen

LDA #&75               \ Set the two-byte value in SC to &7507
STA SC+1
LDA #7
STA SC

JSR TT67               \ Print a newline, which will move the text cursor down
\ a line (to row 21) and back to column 1

LDA #0                 \ Call LYN to clear the pixels from &7507 to &75F0
JSR LYN

INC SC+1               \ Increment SC+1 so SC points to &7607

INY                    \ Move the text cursor to column 1 (as LYN sets Y to 0)
STY XC

\ Fall through into LYN to clear the pixels from &7707
\ to &77F0

Type: Subroutine
Category: Utility routines
Summary: Clear most of a row of pixels
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* CLYNS calls LYN
* SCAN calls entry point SC5

Set pixels 0-233 to the value in A, starting at the pixel pointed to by SC.

Arguments:

A                    The value to store in pixels 1-233 (the only value that
is actually used is A = 0, which clears those pixels)

Returns:

Y                    Y is set to 0

Other entry points:

SC5                  Contains an RTS

.LYN

LDY #233               \ Set up a counter in Y to count down from pixel 233

.EE2

STA (SC),Y             \ Store A in the Y-th byte after the address pointed to
\ by SC

DEY                    \ Decrement Y

BNE EE2                \ Loop back until Y is zero

.SC5

RTS                    \ Return from the subroutine

Type: Variable
Category: Dashboard
Summary: The EOR value for different types of ship in the the I.F.F. system
for creating striped sticks in the scanner
Deep dive: The I.F.F. system
Context: See this variable on its own page
References: This variable is used as follows:
* SCAN calls iff_xor

These are the EOR values for the I.F.F. system, which shows ships on the
scanner in the following colours, depending on the type index for this ship
(as returned by the iff_index routine). The EOR values determine whether the
stick is striped.

The colours for the normal dashboard palette are:

Index     Dot colour  Stick colour(s)     Ship types
-----     ----------  ---------------     ----------
0         Green       Green               Clean
1         Yellow      Yellow              Station tracked
2         Green       Green and yellow    Debris
3         Yellow      Yellow and red      Missile
4         Green       Green and red       Offender/fugitive

The colours for the escape pod dashboard palette are:

Index     Dot colour  Stick colour(s)     Ship types
-----     ----------  ---------------     ----------
0         Cyan        Cyan                Clean
1         White       White               Station tracked
2         Cyan        Cyan and white      Debris
3         White       White and red       Missile
4         Cyan        Cyan and red        Offender/fugitive

The EOR values have the following effect on the colour of the stick:

%00000000       Stick is a solid colour, in the base colour
%00001111       Stick is striped, in the base colour and base colour EOR %01
%11110000       Stick is striped, in the base colour and base colour EOR %10
%11111111       Stick is striped, in the base colour and base colour EOR %11

Taking the example of debris, the base colour from iff_base+2 is &FF, which is
%11111111, or a four-pixel byte of colour %11, or colour 3 in mode 5, or
green/cyan (green for the normal palette, cyan in the escape pod palette).

The EOR value from iff_xor+2 is &0F, which is %00001111, or a four-pixel byte
of %01 values. Applying this EOR to the base colour (%11) gives:

%11 EOR %01 = %10 = 2

and colour 2 in mode 5 is yellow/white (yellow for the normal palette, white
in the escape pod palette). So the stick colour for debris when we have an
I.F.F. system fitted is:

Green/cyan (the base colour) striped with yellow/white (the colour after
applying the EOR value)

If there is no I.F.F. system fitted, the index is 0 and the EOR value is 0,
which doesn't affect the default colour.

The last two entries are the same as the first two entries in iff_base, which
is the next variable, so they are commented out here to save two bytes.

\ --- Mod: Whole section added for Elite-A: ----------->

.iff_xor

EQUB &00               \ Index 0: Clean = %00000000

EQUB &00               \ Index 1: Station tracked = %00000000

EQUB &0F               \ Index 2: Debris = %00001111

\EQUB &FF               \ Index 3: Missile = %11111111

\EQUB &F0               \ Index 4: Offender/fugitive = %11110000

\ --- End of added section ---------------------------->

Type: Variable
Category: Dashboard
Summary: Base colours for different types of ship in the the I.F.F. system
Deep dive: The I.F.F. system
Context: See this variable on its own page
References: This variable is used as follows:
* SCAN calls iff_base

These are the base colours for the I.F.F. system, which shows ships on the
scanner in the following colours, depending on the type index for this ship
(as returned by the iff_index routine). The base colours determine the colour
of the dot, as well as the underlying colour of the stick (which can be
striped, depending on the corresponding EOR colour from iff_xor).

The colours for the normal dashboard palette are:

Index     Dot colour  Stick colour(s)     Ship types
0         Green       Green               Clean
1         Yellow      Yellow              Station tracked
2         Green       Green and yellow    Debris
3         Yellow      Yellow and red      Missile
4         Green       Green and red       Offender/fugitive

The colours for the escape pod dashboard palette are:

Index     Dot colour  Stick colour(s)     Ship types
0         Cyan        Cyan                Clean
1         White       White               Station tracked
2         Cyan        Cyan and white      Debris
3         White       White and red       Missile
4         Cyan        Cyan and red        Offender/fugitive

\ --- Mod: Whole section added for Elite-A: ----------->

.iff_base

EQUB &FF               \ Index 0: Clean = green/cyan

EQUB &F0               \ Index 1: Station tracked = yellow/white

EQUB &FF               \ Index 2: Debris = green/cyan

EQUB &F0               \ Index 3: Missile = yellow/white

EQUB &FF               \ Index 4: Offender/fugitive = green/cyan

\ --- End of added section ---------------------------->

Type: Subroutine
Category: Dashboard
Summary: Display the current ship on the scanner
Deep dive: The 3D scanner
The I.F.F. system
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* ESCAPE calls SCAN
* MVEIT (Part 2 of 9) calls SCAN
* MVEIT (Part 9 of 9) calls SCAN
* Main flight loop (Part 11 of 16) calls SCAN
* WPSHPS calls SCAN

This is used both to display a ship on the scanner, and to erase it again.

Arguments:

INWK                 The ship's data block

.SCAN

LDA INWK+31            \ Fetch the ship's scanner flag from byte #31

AND #%00010000         \ If bit 4 is clear then the ship should not be shown
BEQ SC5                \ on the scanner, so return from the subroutine (as SC5
\ contains an RTS)

LDA TYPE               \ Fetch the ship's type from TYPE into A

BMI SC5                \ If this is the planet or the sun, then the type will
\ have bit 7 set and we don't want to display it on the
\ scanner, so return from the subroutine (as SC5
\ contains an RTS)

\ --- Mod: Original Acornsoft code removed: ----------->

\ LDX #&FF              \ Set X to the default scanner colour of green/cyan
\                       \ (a 4-pixel mode 5 byte in colour 3)

\ CMP #MSL              \ If this is not a missile, skip the following
\ BNE P%+4              \ instruction

\ LDX #&F0              \ This is a missile, so set X to colour 2 (yellow/white)

\ STX COL               \ Store X, the colour of this ship on the scanner, in
\                       \ COL

\ --- And replaced by: -------------------------------->

JSR iff_index          \ Call iff_index to work out the index of the colour we
\ should use for the dot and stick for this ship,
\ according to whether we have an I.F.F. system fitted

LDA iff_base,X         \ Set COL to the base colour for this ship, given the
STA COL                \ I.F.F. colour index in X (this colour is used for both
\ the dot and stick)

LDA iff_xor,X          \ Set Y2 to the alternating colour for this ship, given
STA Y2                 \ the I.F.F. colour index in X (this colour is used for
\ making the stick striped, where appropriate)

\ --- End of replacement ------------------------------>

LDA INWK+1             \ If any of x_hi, y_hi and z_hi have a 1 in bit 6 or 7,
ORA INWK+4             \ then the ship is too far away to be shown on the
ORA INWK+7             \ scanner, so return from the subroutine (as SC5
AND #%11000000         \ contains an RTS)
BNE SC5

\ If we get here, we know x_hi, y_hi and z_hi are all
\ 63 (%00111111) or less

\ Now, we convert the x_hi coordinate of the ship into
\ the screen x-coordinate of the dot on the scanner,
\ using the following (see the deep dive on "The 3D
\ scanner" for an explanation):
\
\   X1 = 123 + (x_sign x_hi)

LDA INWK+1             \ Set x_hi

CLC                    \ Clear the C flag so we can do addition below

LDX INWK+2             \ Set X = x_sign

BPL SC2                \ If x_sign is positive, skip the following

EOR #%11111111         \ x_sign is negative, so flip the bits in A and subtract
ADC #1                 \ 1 to make it a negative number (bit 7 will now be set
\ as we confirmed above that bits 6 and 7 are clear). So
\ this gives A the sign of x_sign and gives it a value
\ range of -63 (%11000001) to 0

.SC2

ADC #123               \ Set X1 = 123 + x_hi
STA X1

\ Next, we convert the z_hi coordinate of the ship into
\ the y-coordinate of the base of the ship's stick,
\ like this (see the deep dive on "The 3D scanner" for
\ an explanation):
\
\   SC = 220 - (z_sign z_hi) / 4
\
\ though the following code actually does it like this:
\
\   SC = 255 - (35 + z_hi / 4)

LDA INWK+7             \ Set A = z_hi / 4
LSR A                  \
LSR A                  \ So A is in the range 0-15

CLC                    \ Clear the C flag

LDX INWK+8             \ Set X = z_sign

BPL SC3                \ If z_sign is positive, skip the following

EOR #%11111111         \ z_sign is negative, so flip the bits in A and set the
SEC                    \ C flag. As above, this makes A negative, this time
\ with a range of -16 (%11110000) to -1 (%11111111). And
\ another 1 to that value, giving a range of -15 to 0

.SC3

ADC #35                \ Set A = 35 + A to give a number in the range 20 to 50

EOR #%11111111         \ Flip all the bits and store in SC, so SC is in the
STA SC                 \ range 205 to 235, with a higher z_hi giving a lower SC

\ Now for the stick height, which we calculate using the
\ following (see the deep dive on "The 3D scanner" for
\ an explanation):
\
\ A = - (y_sign y_hi) / 2

LDA INWK+4             \ Set A = y_hi / 2
LSR A

CLC                    \ Clear the C flag

LDX INWK+5             \ Set X = y_sign

BMI SCD6               \ If y_sign is negative, skip the following, as we
\ already have a positive value in A

EOR #%11111111         \ y_sign is positive, so flip the bits in A and set the
SEC                    \ C flag. This makes A negative, and as we are about to
\ do an ADC below, the SEC effectively adds another 1 to
\ that value to implement two's complement negation, so
\ we don't need to add another 1 here

.SCD6

\ We now have all the information we need to draw this
\ ship on the scanner, namely:
\
\   X1 = the screen x-coordinate of the ship's dot
\
\   SC = the screen y-coordinate of the base of the
\        stick
\
\   A = the screen height of the ship's stick, with the
\       correct sign for adding to the base of the stick
\       to get the dot's y-coordinate
\
\ First, though, we have to make sure the dot is inside
\ the dashboard, by moving it if necessary

ADC SC                 \ Set A = SC + A, so A now contains the y-coordinate of
\ the end of the stick, plus the length of the stick, to
\ give us the screen y-coordinate of the dot

BPL ld246              \ If the result has bit 0 clear, then the result has
\ overflowed and is bigger than 256, so jump to ld246 to
\ set A to the maximum allowed value of 246 (this
\ instruction isn't required as we test both the maximum
\ and minimum below, but it might save a few cycles)

CMP #194               \ If A >= 194, skip the following instruction, as 194 is
BCS P%+4               \ the minimum allowed value of A

LDA #194               \ A < 194, so set A to 194, the minimum allowed value
\ for the y-coordinate of our ship's dot

CMP #247               \ If A < 247, skip the following instruction, as 246 is
BCC P%+4               \ the maximum allowed value of A

.ld246

LDA #246               \ A >= 247, so set A to 246, the maximum allowed value
\ for the y-coordinate of our ship's dot

STA Y1                 \ Store A in Y1, as it now contains the screen
\ y-coordinate for the ship's dot, clipped so that it
\ fits within the dashboard

SEC                    \ Set A = A - SC to get the stick length, by reversing
SBC SC                 \ the ADC SC we did above. This clears the C flag if the
\ result is negative (i.e. the stick length is negative)
\ and sets it if the result is positive (i.e. the stick
\ length is negative)

\ So now we have the following:
\
\   X1 = the screen x-coordinate of the ship's dot,
\        clipped to fit into the dashboard
\
\   Y1 = the screen y-coordinate of the ship's dot,
\        clipped to fit into the dashboard
\
\   SC = the screen y-coordinate of the base of the
\        stick
\
\   A = the screen height of the ship's stick, with the
\       correct sign for adding to the base of the stick
\       to get the dot's y-coordinate
\
\   C = 0 if A is negative, 1 if A is positive
\
\ and we can get on with drawing the dot and stick

\ --- Mod: Original Acornsoft code removed: ----------->

\ PHP                   \ Store the flags (specifically the C flag) from the
\                       \ above subtraction

\ --- End of removed code ----------------------------->

.SC48

PHA                    \ Store the stick height in A on the stack

JSR CPIX4              \ Draw a double-height dot at (X1, Y1). This also leaves
\ the following variables set up for the dot's top-right
\ pixel, the last pixel to be drawn (as the dot gets
\ drawn from the bottom up):
\
\   SC(1 0) = screen address of the pixel's character
\             block
\
\   Y = number of the character row containing the pixel
\
\   X = the pixel's number (0-3) in that row
\
\ We can use there as the starting point for drawing the
\ stick, if there is one

\ --- Mod: Original Acornsoft code removed: ----------->

\ LDA CTWOS+1,X         \ Load the same mode 5 1-pixel byte that we just used
\ AND COL               \ for the top-right pixel, and mask it with the same
\ STA X1                \ colour, storing the result in X1, so we can use it as
\                       \ the character row byte for the stick

\ PLA                   \ Restore the stick height from the stack into A

\ PLP                   \ Restore the flags from above, so the C flag once again
\                       \ reflects the sign of the stick height

\ TAX                   \ Copy the stick height into X

\ BEQ RTS               \ If the stick height is zero, then there is no stick to
\                       \ draw, so return from the subroutine (as RTS contains
\                       \ an RTS)

\ BCC RTS+1             \ If the C flag is clear then the stick height in A is
\                       \ negative, so jump down to RTS+1

\ --- And replaced by: -------------------------------->

LDA CTWOS+1,X          \ Load the same mode 5 1-pixel byte that we just used
TAX                    \ and make a copy in X

AND COL                \ Mask the 1-pixel byte with the same colour, storing
STA X1                 \ the result in X1, so we can use it as the character
\ rowbyte for the stick

TXA                    \ Mask the 1-pixel byte with the colour in Y2, which
AND Y2                 \ we set to the alternating colour for the stick, and
STA Y1                 \ store the result in Y1

PLA                    \ Restore the stick height from the stack into A

TAX                    \ Copy the stick height into X

BEQ RTS                \ If the stick height is zero, then there is no stick to
\ draw, so return from the subroutine (as RTS contains
\ an RTS)

BMI RTS+1              \ If the stick height in A is negative, jump down to
\ RTS+1

\ --- End of replacement ------------------------------>

.VLL1

\ If we get here then the stick length is positive (so
\ the dot is below the ellipse and the stick is above
\ the dot, and we need to draw the stick upwards from
\ the dot)

DEY                    \ We want to draw the stick upwards, so decrement the
\ pixel row in Y

BPL VL1                \ If Y is still positive then it correctly points at the

LDY #7                 \ We just decremented Y up through the top of the
\ character block, so we need to move it to the last row
\ in the character above, so set Y to 7, the number of
\ the last row

DEC SC+1               \ Decrement the high byte of the screen address to move
\ to the character block above

.VL1

LDA X1                 \ Set A to the character row byte for the stick, which
\ we stored in X1 above, and which has the same pixel
\ pattern as the bottom-right pixel of the dot (so the
\ stick comes out of the right side of the dot)

\ --- Mod: Code added for Elite-A: -------------------->

EOR Y1                 \ Apply the alternating colour in Y1 to the stick

STA X1                 \ Update the value in X1 so the alternating colour is
\ applied every other row (as doing an EOR twice
\ reverses it)

\ --- End of added code ------------------------------->

EOR (SC),Y             \ Draw the stick on row Y of the character block using
STA (SC),Y             \ EOR logic

DEX                    \ Decrement the (positive) stick height in X

BNE VLL1               \ If we still have more stick to draw, jump up to VLL1
\ to draw the next pixel

.RTS

RTS                    \ Return from the subroutine

\ If we get here then the stick length is negative (so
\ the dot is above the ellipse and the stick is below
\ the dot, and we need to draw the stick downwards from
\ the dot)

INY                    \ We want to draw the stick downwards, so we first
\ increment the row counter so that it's pointing to the
\ bottom-right pixel in the dot (as opposed to the top-
\ right pixel that the call to CPIX4 finished on)

CPY #8                 \ If the row number in Y is less than 8, then it
BNE P%+6               \ correctly points at the next line down, so jump to
\ VLL2 to skip the following

LDY #0                 \ We just incremented Y down through the bottom of the
\ character block, so we need to move it to the first
\ row in the character below, so set Y to 0, the number
\ of the first row

INC SC+1               \ Increment the high byte of the screen address to move
\ to the character block above

.VLL2

INY                    \ We want to draw the stick itself, heading downwards,
\ so increment the pixel row in Y

CPY #8                 \ If the row number in Y is less than 8, then it
BNE VL2                \ correctly points at the next line down, so jump to
\ VL2 to skip the following

LDY #0                 \ We just incremented Y down through the bottom of the
\ character block, so we need to move it to the first
\ row in the character below, so set Y to 0, the number
\ of the first row

INC SC+1               \ Increment the high byte of the screen address to move
\ to the character block above

.VL2

LDA X1                 \ Set A to the character row byte for the stick, which
\ we stored in X1 above, and which has the same pixel
\ pattern as the bottom-right pixel of the dot (so the
\ stick comes out of the right side of the dot)

\ --- Mod: Code added for Elite-A: -------------------->

EOR Y1                 \ Apply the alternating colour in Y1 to the stick

STA X1                 \ Update the value in X1 so the alternating colour is
\ applied every other row (as doing an EOR twice
\ reverses it)

\ --- End of added code ------------------------------->

EOR (SC),Y             \ Draw the stick on row Y of the character block using
STA (SC),Y             \ EOR logic

INX                    \ Increment the (negative) stick height in X

BNE VLL2               \ If we still have more stick to draw, jump up to VLL2
\ to draw the next pixel

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Screen mode
Summary: Wait for the vertical sync
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* DELAY calls WSCAN
* DK4 calls WSCAN
* TT102 calls WSCAN

Wait for vertical sync to occur on the video system - in other words, wait
for the screen to start its refresh cycle, which it does 50 times a second
(50Hz).

.WSCAN

LDA #0                 \ Set DL to 0
STA DL

LDA DL                 \ Loop round these two instructions until DL is no
BEQ P%-2               \ longer 0 (DL gets set to 30 in the LINSCN routine,
\ which is run when vertical sync has occurred on the
\ video system, so DL will change to a non-zero value
\ at the start of each screen refresh)

RTS                    \ Return from the subroutine

Save 1.F.bin

PRINT "S.1.F ", ~CODE%, " ", ~P%, " ", ~LOAD%, " ", ~LOAD%