# Aviator C source

```       Name: ArtificialHorizon                                       [Show more]
Type: Subroutine
Category: Dashboard
Summary: Vector line calculation for the artificial horizon on indicator 7
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* UpdateIndicator (Part 11 of 15) calls ArtificialHorizon

The commentary in this routine is a work in progress.

matrix4Lo, matrix4Lo+2, matrix4Lo+3, matrix4Lo+4 are only used to provide
signs in bit 0:
Negative if bit 0 is set, positive if clear

matrix4Hi, matrix4Hi+3 provide values for T calculations
matrix4Hi+2, matrix4Hi+4 provide values for U calculations

matrix4Hi+3 = current roll orientation, 0-&FF for 0 to 45 degrees
matrix4Lo+3 = direction of roll

Y = 0, K = 0:
T = (matrix4Lo matrix4Hi) / 4
U = -(matrix4Lo+2 matrix4Hi+2) / 4
Return (T + U) / 8 with the sign bits retained = x-coord of start

Y = 0, K = 1:
T = -(matrix4Lo matrix4Hi) / 4
U = -(matrix4Lo+2 matrix4Hi+2) / 4
Return (T + U) / 8 with the sign bits retained = y-coord of start

Y = 3, K = 0:
T = (matrix4Lo+3 matrix4Hi+3) / 4
U = -(matrix4Lo+4 matrix4Hi+4) / 4
Return (T + U) / 8 with the sign bits retained = x-delta

Y = 3, K = 1:
T = -(matrix4Lo+3 matrix4Hi+3) / 4
U = -(matrix4Lo+4 matrix4Hi+4) / 4
Return (T + U) / 8 with the sign bits retained = y-delta

The line is returned relative to the origin (0, 0), so that's as if the centre
of the artificial horizon indicator were at (0, 0). This means that the deltas
that are calculated are the equivalent to the end point of the line. The line
itself gets moved to the location of the on-screen indicator in part 11 of
UpdateIndicator.

Arguments:

K                    The axis to calculate:

* 0 = x-axis

* 1 = y-axis

Y                    The value to calculate:

* 0 = coordinate of starting point

* 3 = deltas (i.e. coordinates of end point)

Returns:

A                    Depending on the values of K and Y:

* K = 0, Y = 0: returns the x-coordinate of the
artificial horizon's starting point

* K = 0, Y = 3: returns the y-coordinate of the
artificial horizon's starting point

* K = 1, Y = 0: returns the x-delta of the artificial
horizon

* K = 1, Y = 3: returns the y-delta of the artificial
horizon

.ArtificialHorizon

LDA matrix4Hi,Y        \ Set A = matrix4Hi (Y = 0) or matrix4Hi+3 (Y = 3)

LSR A                  \ Set A = A / 4
LSR A

CPY #0                 \ If Y = 3, halve A again, so A = A / 8
BNE arhi1
LSR A

.arhi1

STA T                  \ Set T = A, so T = A / 4 or A / 8

LDA matrix4Lo,Y        \ Set A = matrix4Lo (Y = 0) or matrix4Lo+3 (Y = 3)

EOR K                  \ If K = 1, flip bit 0 of A

AND #1                 \ If bit 0 of A is zero, jump to arhi2 to skip the
BEQ arhi2              \ following

LDA #0                 \ Set T = 0 - T
SEC
SBC T
STA T

.arhi2

LDA matrix4Hi+2,Y      \ Set A = matrix4Hi+2 (Y = 0) or matrix4Hi+4 (Y = 3)

LSR A                  \ Set A = A / 4
LSR A

CPY #0                 \ If Y = 3, halve A again, so A = A / 8
BNE arhi3
LSR A

.arhi3

STA U                  \ Set U = A, so U = A / 4 or A / 8

LDA matrix4Lo+2,Y      \ Set A = matrix4Lo+2 (Y = 0) or matrix4Lo+4 (Y = 3)

CPY #0                 \ If Y = 0, flip bit 0 of A
BNE arhi4
EOR #1

.arhi4

AND #1                 \ If bit 0 of A is zero, jump to arhi5 to skip the
BEQ arhi5              \ following

LDA #0                 \ Set U = 0 - U
SEC
SBC U
STA U

.arhi5

CLC                    \ A = T + U
LDA T

BMI arhi6

LSR A                  \ A = A / 8
LSR A
LSR A

ADC #0                 \ Round up the A/8 division

RTS                    \ Return from the subroutine

.arhi6

SEC                    \ A = A / 8 + with bits 5-7 set
ROR A
SEC
ROR A
SEC
ROR A

ADC #0                 \ Round up the A / 8 division

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Drawing lines
Summary: Draw an orthogonal line (i.e. vertical or horizontal)
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* DrawIndicatorBar calls DrawOrthoLine
* DrawJoystickCross calls DrawOrthoLine
* DrawIndicatorBar calls via EraseOrthoLine

Arguments:

S                    Defines the starting coordinate for the line:

* 0 = (joyCoord, H + W)

* 128 = (H + W, joyCoord)

H + W                Coordinate of the start of the line (it doesn't matter
how this value is split between H and W as only the sum
is used)

joyCoord             Coordinate of the start of the line

T                    Horizontal width/length of line

U                    Vertical width/length of line

N                    Drawing mode:

* 0 = Draw (using OR logic)

* 128 = Erase (using EOR logic)

Other entry points:

EraseOrthoLine       Use the value of G instead of H (so the coordinate is
G + W) and always use EOR Logic to draw the line (which
will erase it if it is already on-screen)

.DrawOrthoLine

LDA H                  \ Set A = H

JMP dort1              \ Jump to dort1 to draw the orthogonal line and skip the
\ code for the EraseOrthoLine entry point

.EraseOrthoLine

LDA #128               \ Set N = 128 so the line is drawn with EOR logic, which
STA N                  \ erases the line if it is already on-screen

LDA G                  \ Set A = G

.dort1

CLC                    \ Set A = A + W

BIT S                  \ If bit 7 of S is set, jump down to dort2
BMI dort2

STA J                  \ Set J = A

LDA joyCoord           \ Set I = joyCoord
STA I

\ We now have (I, J) = (joyCoord, A + W)

JMP dort3              \ Jump down to dort3

.dort2

STA I                  \ Set I = A

LDA joyCoord           \ Set J = joyCoord
STA J

\ We now have (I, J) = (A + W, joyCoord)

.dort3

LDA #0                 \ Set V = 0 so the line is drawn in a positive direction
STA V                  \ for both axes

JSR DrawVectorLine     \ Draw/erase a line from (I, J) as a vector (T, U) with
\ direction V

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Maths
Summary: Scale an indicator value by 4 or 16, retaining the sign and adding
sensitivity for smaller values
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* UpdateIndicator (Part 13 of 15) calls ScaleSigned
* UpdateIndicator (Part 14 of 15) calls ScaleSigned

This routine is used to scale the values for the following indicators:

* The joystick position display (indicator 8 or 10), where the x-coordinate
is scaled by 16 and the y-coordinate by 4, as the display is taller than
it is wide

* Rudder (indicator 9), which is divided by 16

When scaling down by a factor of 16, some smaller values scale to 1 instead of
0 (specifically, 4 to 7), and in all cases the final scaling is rounded up, so
this routine shows small deviations on the rudder and joystick indicators that
otherwise wouldn't register.

Arguments:

A                    The value to scale

C flag               Determines the scale factor:

* C flag set = divide A by 16

* C flag clear = divide A by 4

.ScaleSigned

PHP                    \ Store the flags on the stack, so we can check later
\ what their values were on entry

BPL scsi1              \ If A is positive, jump to scsi1 to skip the following
\ three instructions

EOR #&FF               \ Set A = -A using two's complement, so A is positive
CLC

.scsi1

\ By this point, A = |A|

LSR A                  \ Set A = |A| / 2

PLP                    \ Restore the flags from the stack, leaving them on the
PHP                    \ stack for later

BCC scsi3              \ If the C flag is clear, jump to scsi3 so we only
\ divide the original value by 4

\ If we get here then the C flag was set on entry, so we
\ want to divide A by 16 using four shifts in total

LSR A                  \ Set A = A / 2
\       = |A| / 4

CMP #1                 \ If A <> 1, skip the following instruction
BNE scsi2

LDA #2                 \ A = 1 (so the original |A| was in the range 4 to 7),
\ so set A = 2, which will give us an end result of 1
\
\ In other words, this scales smaller values to 1 that
\ would otherwise scale to 0, like this:
\
\   * 0 to 3 scale down to 0
\   * 4 to 23 scale down to 1
\   * 24 to 39 scale down to 2
\   * 40 to 55 scale down to 3
\
\ and so on

.scsi2

LSR A                  \ Set A = A / 2
\       = |A| / 8

.scsi3

LSR A                  \ Set A = A / 2
\
\ so this is:
\
\   * |A| / 16 if the C flag was set on entry
\   * |A| / 4  if the C flag was clear on entry

ADC #0                 \ Increment A if the value of A before the LSR was odd
\ (so the result of the last division gets rounded up)
\
\ This works because the LSR will set the C flag if bit
\ 0 of A was set before the shift, so A gets bumped up
\ by 1 by the ADC

PLP                    \ Restore the flags from the stack

BPL scsi4              \ If the N flag is clear, then the result already has
\ the correct sign (positive), so jump to scsi4 to
\ return from the subroutine

EOR #&FF               \ Set A = -A using two's complement, so A is now
CLC                    \ negative and the sign matches the original value of A
ADC #1                 \ on entry into the subroutine

.scsi4

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Dashboard
Summary: Update two indicators on the dashboard, one from 0-6, one from
7-11, cycling through them with each subsequent call
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* UpdateFlightModel (Part 4 of 4) calls UpdateDashboard
* UpdateFlightModel (Part 4 of 4) calls via UpdateDash7To11

Each call to this routine updates two indicators, one from the range 0 to 6,
and the other from 7 to 11, with subsequent calls working their way through
the ranges in order (so the first call updates indicators 0 and 7, the second
call updates indicators 1 and 8, and so on). When we reach the end of a range,
we wrap round to the start again.

Note that the joystick position display has two numbers, 8 and 10, so it gets
updated at twice the rate of the other indicators.

Also, because the first group covers seven indicators (0 to 6) and the second
group covers five (7 to 11), the indicators in the second group are updated
more frequently than those in the first group.

Other entry points:

UpdateDash7To11      Update the next indicator in the range 7 to 11

.UpdateDashboard

LDX indicator0To6      \ Increment the indicator number to point to the next
INX                    \ indicator within the range 0 to 6

CPX #7                 \ If X < 7, skip the following instruction
BCC udas1

LDX #0                 \ Set X = 0, so X steps through the range 0 to 6 with
\ each subsequent call to UpdateDashboard

.udas1

STX indicator0To6      \ Store the updated indicator number

JSR UpdateIndicator    \ Update indicator X (0 to 6)

.UpdateDash7To11

LDX indicator7To11     \ Increment the indicator number to point to the next
INX                    \ indicator within the range 7 to 11

CPX #11                \ If X < 11, skip the following instruction
BCC udas2

LDX #7                 \ Set X = 7, so X steps through the range 7 to 11 with
\ each subsequent call to UpdateDashboard

.udas2

STX indicator7To11     \ Store the updated indicator number

JSR UpdateIndicator    \ Update indicator X (7 to 11)

LDA #%01110111         \ Redraw the small horizontal line in the centre of the
STA row28_char26_5     \ slip-and-turn indicator

RTS                    \ Return from the subroutine

Name: UpdateFlightModel (Part 1 of 4)                         [Show more]
Type: Subroutine
Category: Flight model
Summary: Apply any axis control key presses to the current axis values
Deep dive: The key logger
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* MainLoop (Part 1 of 15) calls UpdateFlightModel
* NewGame calls UpdateFlightModel

.UpdateFlightModel

LDX #2                 \ We start with the aileron, rudder and elevator key
\ pairs, whose values are stored in these key logger
\ offsets in (keyLoggerHi keyLoggerLo):
\
\   * 2 = aileron
\   * 1 = rudder
\   * 0 = elevator
\
\ So we set a counter in X to count down through these
\ index values, from 2 to 1 to 0

.umod1

CLC                    \ Clear the C flag for the addition below

LDA keyLoggerLo,X      \ Fetch the low byte for this key pair, which will be 1
\ if a key is being pressed, or 0 if no key is pressed

BEQ umod4              \ If A = 0 then neither key in this key pair is being
\ pressed, so jump down to umod4

ADC axisKeyUsage,X     \ Add the low value to the corresponding variable for
STA axisKeyUsage,X     \ this key pair in axisKeyUsage, so we increment the
\ value every time a key from this pair is used

LDA keyLoggerHi,X      \ Fetch the high byte for this key pair, which will be
\ +1 or -1 if a key is being pressed, or 0 if no key is
\ pressed (so it contains the sign of the key logger
\ value)

STA P                  \ Store the value in P so we can check its sign below

ADC elevatorPosition,X \ Set A = A + one of the following axis values:
\
\   * aileronPosition if this is the aileron key pair
\   * rudderPosition if this is the rudder key pair
\   * elevatorPosition if this is the elevator key pair
\
\ so the relevant value is increased or decreased by 1
\ according to the key press

LDY axisChangeRate,X   \ If this key pair's axisChangeRate value is already
BEQ umod2              \ zero, skip the following instruction

DEC axisChangeRate,X   \ Decrement this key pair's axisChangeRate value

.umod2

BNE umod3              \ If this key pair's axisChangeRate value is non-zero,
\ skip the following

\ If we get here, then this key pair's axisChangeRate
\ value has been reduced down to zero by repeated calls
\ to UpdateFlightModel with the key being held down, so
\ the relevant control is now fully engaged and we bump
\ up the rate of change by another 3 in the relevant
\ direction

CLC                    \ Set A = A + 3

BIT P                  \ If P is positive (so we are increasing the relevant
BPL umod3              \ axis value), skip the following instruction

ADC #250               \ P is negative (so we are decreasing the relevant
\ axis value), so set A = A - 6, giving a net change of
\ setting A = A - 3

.umod3

TAY                    \ If the adjusted axis value is positive, jump down to
BPL umod5              \ umod5 to check the maximum allowed value

\ If we get here then the adjusted axis value is
\ negative, so now we check against the minimum allowed
\ value

CMP #140               \ If A >= -116, the value is within limits, so jump to
BCS umod6              \ umod6 to store it

LDA #140               \ Set A = -116 as the minimum allowed value for the axis
BNE umod6              \ value and jump to umod6 to store it

.umod4

\ If we get here then neither key was pressed from this
\ key pair

LDA #6                 \ Set the axisChangeRate value for this key pair to 6,
STA axisChangeRate,X   \ so the rate of change goes back to 1 until we fully
\ engage the control once again

BNE umod7              \ Jump down to umod7 to move on to the next key pair
\ (this BNE is effectively a JMP as A is never zero)

.umod5

\ If we get here then the adjusted axis value is
\ positive, so now we check against the maximum allowed
\ value

CMP #119               \ If A < 119, the value is within limits, so jump to
BCC umod6              \ umod6 to store it

LDA #118               \ Set A = 118 as the maximum allowed value for the axis
\ value

.umod6

STA elevatorPosition,X \ Store the adjusted axis value in the relevant axis
\ variable:
\
\   * aileronPosition if this is the aileron key pair
\   * rudderPosition if this is the rudder key pair
\   * elevatorPosition if this is the elevator key pair

.umod7

DEX                    \ Decrement the key pair index

BPL umod1              \ Loop back to process the next key pair until we have
\ done all three axes of movement (aileron roll, rudder
\ yaw and elevator pitch)

JSR ReadJoystick       \ Read the joystick axes and fire button and update the
\ aileron, elevator and fire key values accordingly

Name: UpdateFlightModel (Part 2 of 4)                         [Show more]
Type: Subroutine
Category: Flight model
Summary: Apply any throttle key presses to the current thrust value
Deep dive: The key logger
Context: See this subroutine on its own page
References: No direct references to this subroutine in this source file

\ Now we process the thrust keys

CLC                    \ Clear the C flag for the addition below

LDA keyLoggerLo+3      \ Fetch the low byte for the throttle key

BEQ umod11             \ If A = 0 then neither key in this key pair is being
\ pressed, so jump down to umod11 to skip the throttle
\ calculations below

\ We now want to add the key logger value to the current
\ thrust value, which we do like this:
\
\   (Y X) = (keyLoggerHi+3 keyLoggerLo+3)
\           +  (thrustHi thrustLo)

ADC thrustLo           \ We start by adding the low bytes
TAX

LDA keyLoggerHi+3      \ And then add the high bytes, so now (Y X) contains the
ADC thrustHi           \ updated thrust value
TAY

BMI umod8              \ If the result is negative, jump to umod8 to set both X
\ and Y to zero, so the minimum thrust value is zero

CPY #5                 \ If the high byte in Y < 5, then the result is within
BCC umod10             \ bounds, so jump to umod10 to store the new thrust
\ value

LDY #5                 \ Set Y = 5 and jump to umod9 to set X to 0, so the
BNE umod9              \ maximum thrust value is (5 0), or 1280 (this BNE is
\ effectively a JMP, as Y is never zero)

.umod8

LDY #0                 \ If we get here then the new thrust in (Y X) turned out
\ to be negative, so we set Y = 0 and then X = 0 to zero
\ the thrust

.umod9

LDX #0                 \ Zero the low byte of the new thrust as we are either
\ at the maximum value of (5 0) or the minimum value of
\ (0 0)

.umod10

STX thrustLo           \ Store the thrust value in (thrustHi thrustLo) to the
STY thrustHi           \ updated thrust value in (Y X)

LDX #11                \ Update the thrust indicator
JSR UpdateIndicator

Name: UpdateFlightModel (Part 3 of 4)                         [Show more]
Type: Subroutine
Category: Flight model
Summary: Process the undercarriage, brake, flaps and fire keys
Deep dive: The key logger
Context: See this subroutine on its own page
References: No direct references to this subroutine in this source file

.umod11

LDX #4                 \ Process the undercarriage or brake keys, if pressed
JSR ProcessOtherKeys

BEQ umod13             \ If neither are being pressed, jump to umod13 to check
\ for the next set of key presses

BMI umod12             \ If the brake key is being pressed, then the returned
\ value is 128, so jump to umod12

JSR IndicatorU         \ The undercarriage key is being pressed, so update the
\ undercarriage indicator

JMP umod13             \ Jump to umod13 to check for the next set of key
\ presses

.umod12

JSR IndicatorB         \ The brake key is being pressed, so update the brakes
\ indicator

.umod13

LDX #5                 \ Process the flaps or fire keys, if pressed
JSR ProcessOtherKeys

BEQ umod15             \ If neither are being pressed, jump to umod15 to check
\ for the next set of key presses

BMI umod14             \ If the fire key is being pressed, then the returned
\ value is 128, so jump to umod14

JSR IndicatorF         \ The flaps key is being pressed, so update the flaps
\ indicator

JMP umod15             \ Jump to umod15 to check for the next set of key
\ presses

.umod14

JSR FireGuns           \ The fire key is being pressed, so call FireGuns

Name: UpdateFlightModel (Part 4 of 4)                         [Show more]
Type: Subroutine
Category: Flight model
Summary: Set up matrices, apply the flight model and update the dashboard
Context: See this subroutine on its own page
References: No direct references to this subroutine in this source file

.umod15

LDY #2                 \ We now set up the matrices, starting with the
\ projected rotation angles, which we populate one axis
\ at a time, so set a counter in Y to work through the
\ three axes

.umod16

STY matrixAxis         \ Set matrixAxis to the current axis in Y, to pass to
\ ProjectAxisAngle

JSR ProjectAxisAngle   \ Convert the rotation angles of the plane in axis Y to
\ coordinates that we can use to populate the matrices
\ in the call to SetMatrices

LDY matrixAxis         \ Restore the axis counter that we stored above

DEY                    \ Decrement the axis counter in Y

BPL umod16             \ Loop back until we have processed all three axes

LDA #0                 \ Set matrixNumber = 0 to pass to SetMatrices, so we
STA matrixNumber       \ set the values for matrices 1 to 4

STA matrixAxis         \ Set matrixAxis = 0 to pass to SetMatrices, so we set
\ the three standard axes in matrices 1 to 4

JSR SetMatrices        \ Set up the four rotation matrices

JSR ApplyFlightModel   \ Apply the flight model to our plane

JSR UpdateDashboard    \ Update the next two indicators in the ranges 0 to 6
\ and 7 to 11

JSR UpdateDash7To11    \ Update the next indicator in the range 7 to 11

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Keyboard
Summary: Apply the undercarriage, brakes, flaps and fire keys
Deep dive: The key logger
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* UpdateFlightModel (Part 3 of 4) calls ProcessOtherKeys

Arguments:

X                    The offset within the key logger for the keys to check:

* 4 = "U" or "B" (undercarriage, brakes)

* 5 = "F" or SHIFT (flaps, fire)

Returns:

A                    Returns the status of the relevant key presses:

* 0 = neither key in the pair is being pressed, or a
key is still being held down from a previous
call to the routine, so it has already been
processed

* 1 = One of "U" and "F" (undercarriage, flaps) is
being pressed

* 128 = One of "B" and SHIFT (brakes, fire) is being
pressed

.ProcessOtherKeys

LDA keyLoggerLo,X      \ Fetch the low byte for this key pair

BNE poth2              \ If A is non-zero then a key from this key pair is
\ being pressed, so jump down to poth2 to process the
\ key press

STA pressingUFBS-4,X   \ Zero the relevant value in pressingUFBS (for U) or
\ pressingUFBS+1 (for F) to indicate that the first key
\ from this pair is not being held down

STA pressingUFBS-4+3,X \ Zero the relevant value in pressingUFBS+3 (for B) or
\ pressingUFBS+4 (for SHIFT) to indicate that the second
\ key from this pair is not being held down

.poth1

LDA #0                 \ Set A = 0 as the return value

RTS                    \ Return from the subroutine

.poth2

TAY                    \ Copy the low value into Y, so we have:
\
\   * Y = 4 if "U" is being pressed (undercarriage)
\   * Y = 5 if "F" is being pressed (flaps)
\   * Y = 7 if "B" is being pressed (brakes)
\   * Y = 8 if SHIFT is being pressed (fire)

LDA pressingUFBS-4,Y   \ Fetch the relevant value from pressingUFBS to see if
BNE poth1              \ the key press has already been processed, and if it is
\ non-zero, this indicates that it is still being held
\ down from a previous visit to this routine, so jump to
\ poth1 to return a value of 0, so we can ignore the key
\ press

LDA ucStatus-4,Y       \ Flip the value of the relevant status byte, as
EOR #1                 \ follows:
STA ucStatus-4,Y       \
\   * Flip ucStatus if "U" is being pressed
\   * Flip flapsStatus if "F" is being pressed
\   * Flip brakesStatus if "B" is being pressed
\   * Flip brakesStatus+1 if SHIFT is being pressed
\      (which has no effect)

LDA #1                 \ Set the relevant value in pressingUFBS to denote that
STA pressingUFBS-4,Y   \ this key is being pressed, so if we revisit this
\ routine before the key is released, we don't keep on
\ flipping the status byte

CPY #7                 \ If Y < 7, i.e. U or F are being pressed, jump to poth3
BCC poth3              \ to return a result of 1

LDA #128               \ Y >= 7, i.e. B or SHIFT are being pressed, so set
\ A = 128 as the return value

RTS                    \ Return from the subroutine

.poth3

LDA #1                 \ Set A = 1 as the return value

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Dashboard
Summary: Update the undercarriage indicator ("U") and related variables
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* ResetVariables calls IndicatorU
* UpdateFlightModel (Part 3 of 4) calls IndicatorU

.IndicatorU

LDA forceFactor+5      \ Set A to the force factor for zLiftDrag

LDY ucStatus           \ If ucStatus is non-zero then the undercarriage is
BNE indu1              \ down, so jump to indu1

\ If we get here then the undercarriage is up

SEC                    \ Set A = A - 10
SBC #10                \
\ so having the undercarriage up reduces drag

LDX #5                 \ Set X = 5 to store in yLandingGear below, as the
\ vertical distance between the cockpit and the bottom
\ of the plane

LDY #%01010101         \ Set Y to a four-pixel block with pixels 0 and 2 in
\ white, to act as the centre of the undercarriage
\ indicator when turned off

BNE indu2              \ Jump to indu2 to update the indicator (this BNE is
\ effectively a JMP as Y is never zero)

.indu1

\ If we get here then the undercarriage is down

LDY onGround           \ If onGround is non-zero, then we are on the ground, so
BNE indu4              \ jump to indu4 to set the undercarriage to up (because
\ once we have landed with the undercarriage up, we
\ can't put it down again)

CLC                    \ Set A = A + 10
\ so having the undercarriage down increases drag

LDX #10                \ Set X = 10 to store in in yLandingGear below, as the
\ vertical distance between the cockpit and the bottom
\ of the plane

LDY #%01110111         \ Set Y to a four-pixel block with pixels 0, 1 and 2 in
\ white, to act as the centre of the undercarriage
\ indicator when turned on

.indu2

STA forceFactor+5      \ Store A in the force factor for zLiftDrag, so it is
\ incremented by 10 when the undercarriage is down, and
\ reduced by 10 when the undercarriage is up, i.e.
\ having the undercarriage down increases drag

STX yLandingGear       \ Store X in yLandingGear, so the vertical distance
\ between the cockpit and the bottom of the plane is 5
\ if the undercarriage is up, or 10 if it is down

TYA                    \ Set A to the pixel pattern in Y

LDX #2                 \ Set X = 2 to use as a pixel row counter for the three
\ pixel rows in the undercarriage indicator

.indu3

STA row30_char32_2,X   \ Update pixel row X of the undercarriage indicator to
\ the pixel pattern in A

DEX                    \ Decrement the byte counter to the pixel row above

BPL indu3              \ Loop back to update the next row of the indicator

RTS                    \ Return from the subroutine

.indu4

LDA #0                 \ Set ucStatus = 0 to set the undercarriage to up,
STA ucStatus           \ because once we have landed with the undercarriage up,
\ the belly of the plane is on the ground and we can't
\ put the undercarriage down again

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Dashboard
Summary: Update the flaps indicator ("F") and related variables
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* NewGame calls IndicatorF
* RetractFlapsIfFast calls IndicatorF
* UpdateFlightModel (Part 3 of 4) calls IndicatorF

.IndicatorF

LDA forceFactor+5      \ Set A to the force factor for zLiftDrag

LDY flapsStatus        \ If flapsStatus is non-zero then the flaps are on, so

\ If we get here then the flaps are off

SEC                    \ Set A = A - 200
SBC #200               \
\ so having the flaps off reduces drag

LDX #0                 \ Set X = 0 to use as the force factor for yFlapsLift
\ below

LDY #%01000100         \ Set Y to a four-pixel block with pixel 2 in white, to
\ act as the centre of the flaps indicator when turned
\ off

BNE indf2              \ Jump to indf2 to update the indicator (this BNE is
\ effectively a JMP as Y is never zero)

.indf1

\ If we get here then the flaps are on

CLC                    \ Set A = A + 200
\ so having the flaps on increases drag

LDX #152               \ Set X = 152 to use as the force factor for yFlapsLift
\ below

LDY #%11001100         \ Set Y to a four-pixel block with pixels 1 and 2 in
\ white, to act as the centre of the flaps indicator
\ when turned on

.indf2

STA forceFactor+5      \ Store A in the force factor for zLiftDrag, so it is
\ incremented by 200 when the flaps are on, and reduced
\ by 200 when the flaps are off, i.e. having the flaps
\ on increases drag

STX forceFactor+7      \ Store X in the force factor for yFlapsLift, so it is 0
\ if the flaps are off and 152 if they are on, i.e.
\ having the flaps on increases the vertical lift

TYA                    \ Set A to the pixel pattern in Y

LDX #2                 \ Set X = 2 to use as a pixel row counter for the three
\ pixel rows in the flaps indicator

.indf3

STA row30_char35_2,X   \ Update pixel row X of the flaps indicator to the pixel
\ pattern in A

DEX                    \ Decrement the byte counter to the pixel row above

BPL indf3              \ Loop back to update the next row of the indicator

RTS                    \ Return from the subroutine

Type: Subroutine
Category: The Theme
Summary: Create the bullet objects and send them on their way
Deep dive: Adding bullets to the world
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* UpdateFlightModel (Part 3 of 4) calls FireGuns

Other entry points:

FireGuns-1           Contains an RTS

.FireGuns

LDA firingStatus       \ If either firingStatus or hitTimer are non-zero, then
ORA hitTimer           \ there are either bullets still in the air, or we only
BNE FireGuns-1         \ just hit an alien. In either case we can't fire the
\ guns, so return from the subroutine (as FireGuns-1
\ contains an RTS)

LDX #228               \ Set point 228 to (0, 0, zVelocityPHi + 200)
JSR SetPointToOrigin   \
\ starting with the zeroes

LDA zVelocityPHi       \ And then setting the low byte of the z-coordinate
CLC
STA zPointLo+228

LDA #&FF               \ Set the point with ID 95 to (&FFF6, &FFFC, &FF14)
LDX #95                \                           = (-10, -4, -236)
JSR SetPoint           \
\ which is the vector from the plane back to object 12
\ at the trailing end of the left bullet's trail
\
\ We start with the high bytes

LDA #&14               \ And then set the low bytes
STA zPointLo+95
LDA #&F6
STA xPointLo+95
LDA #&FC
STA yPointLo+95

LDA #228               \ Set GG to point ID 228, to pass to the call to
STA GG                 \ SetPointCoords

LDA #9                 \ Set the matrix number so the call to SetPointCoords
STA matrixNumber       \ uses matrix 2 in the calculation, so it rotates the
\ point from the plane's frame of reference to the
\ outside world's frame of reference (so we spawn the
\ bullets using coordinates that are relative to the
\ plane, but calculate them in the following as
\ world-relative coordinates, so they are then
\ independent of the plane's future movement)

STA firingStatus       \ Set firingStatus = 9, which is a non-zero value, to
\ indicate that there are bullets are in the air (the
\ value 9 isn't significant beyond the fact that it is
\ non-zero)

JSR SetPointCoords     \ Calculate the coordinates for point 228, which also
\ returns the coordinates in (xTemp1, yTemp1, zTemp1)

LDX #229               \ We now copy the coordinates from (xTemp1, yTemp1,
\ zTemp1) to points 229, 230 and 231, so we set a
\ counter in X for the point IDs

.fire1

JSR CopyTempToPoint    \ Copy from (xTemp1, yTemp1, zTemp1) into the
\ coordinates for point X

INX                    \ Increment the point ID

CPX #232               \ Loop back until we have copied (xTemp1, yTemp1,
BNE fire1              \ zTemp1) into points 229, 230 and 231

LDA #95                \ Set GG to point ID 95, to pass to the call to
STA GG                 \ SetPointCoords

JSR SetPointCoords     \ Calculate the coordinates for point 95

LDX #LO(xPlaneLo)      \ Set X so the call to CopyWorkToPoint copies the
\ coordinates from (xPlane, yPlane, zPlane)

LDY #96                \ Set Y so the call to CopyPointToWork copies the
\ coordinates to point 96

JSR CopyWorkToPoint    \ Copy the coordinates from (xPlane, yPlane, zPlane)
\ to point 96

LDY #12                \ We now set the object coordinates for objects 12 to 15
\ to the coordinates in point 96, so set Y as the object
\ ID, starting with 12

LDX #96                \ And set X as the point ID to add in the call to

.fire2

JSR SetObjectToOrigin  \ Set the object coordinates for object Y to (0, 0, 0)

JSR AddPointToObject   \ Add the vector in point 96 to the object coordinates,
\ so this sets the location of the object to that of
\ point 96, i.e. the coordinates of the plane

INY                    \ Increment the object ID

CPY #16                \ Loop back until we have set objects 12 to 15 to the
BNE fire2              \ coordinates in point 96, so they are all now at the
\ plane's coordinates

LDY #12                \ Add the vector in point 95 to the object coordinates
LDX #95                \ for object 12

STX objectAnchorPoint  \ Store the vector in point 95 as the object anchor
\ point, so the following calculations use this as the
\ anchor point
\
\ This means that the following coordinate calculations
\ will return the vector from the plane's location to
\ each point, as we are telling SetObjPointCoords to
\ take the anchor point vector (from the plane to point
\ 95) and add on the vector of the object point (which
\ is the interior vector of the point within the object)

LDA #96                \ Calculate the coordinates for object point 96 with
STA GG                 \ anchor point 95
JSR SetObjPointCoords

LDA #97                \ Calculate the coordinates for object point 97 with
STA GG                 \ anchor point 95, which also sets (xTemp1 yTemp1
JSR SetObjPointCoords  \ zTemp1) to the rotated vector from the anchor point
\ (point 95) to point 97

LDX #98                \ Set point 98's coordinates to point 96's coordinates +
LDY #96                \ (xTemp1 yTemp1 zTemp1), the latter containing the
JSR AddTempToPoint     \ vector from point 95 to point 97

\ Finally, we add the following vectors to the object
\ points for objects 13 to 15:
\
\   * Move object 13 by the vector in point 96
\
\   * Move object 14 by the vector in point 97
\
\   * Move object 15 by the vector in point 98

LDY #15                \ Set Y as the object ID that gets moved in the call to

LDX #98                \ Set X as the point ID to add to the object coordinates
\ in the call to AddPointToObject

.fire3

JSR AddPointToObject   \ Add the vector in point X to the object coordinates
\ for point Y

DEX                    \ Decrement the point ID

DEY                    \ Decrement the object ID

CPY #12                \ Loop back until we have added points 96 to 98 to
BNE fire3              \ objects 13 to 15

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Dashboard
Summary: Update the brakes indicator ("B")
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* NewGame calls IndicatorB
* UpdateFlightModel (Part 3 of 4) calls IndicatorB

.IndicatorB

LDA #%01110111         \ Set A to a four-pixel block with pixels 0, 1 and 2 in
\ white, to act as the centre of the brakes indicator
\ when turned on

LDX brakesStatus       \ If brakesStatus is non-zero then the brakes are on, so

LDA #%01010101         \ Set A to a four-pixel block with pixels 0 and 2 in
\ white, to act as the centre of the brakes indicator
\ when turned off

.indb1

LDX #2                 \ Set X = 2 to use as a pixel row counter for the three
\ pixel rows in the brakes indicator

.indb2

STA row30_char37_2,X   \ Update pixel row X of the brakes indicator to the
\ pixel pattern in A

DEX                    \ Decrement the byte counter to the pixel row above

BPL indb2              \ Loop back to update the next row of the indicator

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Dashboard
Summary: Update the Theme indicator ("T")
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* MainLoop (Part 8 of 15) calls IndicatorT
* ResetVariables calls IndicatorT

.IndicatorT

LDA #%01110111         \ Set A to a four-pixel block with pixels 0, 1 and 2 in
\ white, to act as the centre of the Theme indicator
\ when turned on

LDX themeStatus        \ If themeStatus is positive then the Theme is enabled,
BPL indt1              \ so jump to indt1

LDA #%01010101         \ Set A to a four-pixel block with pixels 0 and 2 in
\ white, to act as the centre of the Theme indicator
\ when turned off

.indt1

LDX #2                 \ Set X = 2 to use as a pixel row counter for the three
\ pixel rows in the Theme indicator

.indt2

STA row30_char0_2,X    \ Update pixel row X of the Theme indicator to the pixel
\ pattern in A

DEX                    \ Decrement the byte counter to the pixel row above

BPL indt2              \ Loop back to update the next row of the indicator

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Keyboard
Summary: Scan the keyboard for a specific key
Deep dive: The key logger
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* DrawGunSights calls ScanKeyboard
* MainLoop (Part 7 of 15) calls ScanKeyboard
* MainLoop (Part 11 of 15) calls ScanKeyboard
* MainLoop (Part 14 of 15) calls ScanKeyboard
* ProcessVolumeKeys calls ScanKeyboard
* TerminateGame calls ScanKeyboard
* ToggleJoystick calls ScanKeyboard
* UpdateKeyLogger calls ScanKeyboard

Arguments:

X                    The internal key number of the key to scan for

Returns:

Z flag               If set (BEQ) then the key is being pressed, if clear
(BNE) then it is not being pressed

.ScanKeyboard

LDA #129               \ Call OSBYTE with A = 129, X = key number and Y = &FF
LDY #&FF               \ to scan the keyboard for the key in X, returning the
JSR OSBYTE             \ following in both X and Y:
\
\   * 0 = the key is not being pressed
\
\   * &FF = the key is being pressed

CPX #&FF               \ Set the Z flag depending on whether the key is being
\ pressed

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Keyboard
Summary: Scan the keyboard for keys in the two key tables and update the
key logger
Deep dive: The key logger
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* MainLoop (Part 1 of 15) calls UpdateKeyLogger
* NewGame calls UpdateKeyLogger

This routine updates the value in the key logger, which is stored in
(keyLoggerHi keyLoggerLo). If a key is pressed, then the corresponding 16-bit
value in the key logger is set to the corresponding value from the KeyTable
tables, which are stored at (keyTable1Hi keyTable1Lo) and (keyTable2Hi
keyTable2Lo).

.UpdateKeyLogger

LDA #5                 \ Set V = 5 to act as an offset as we work our way
STA V                  \ through the six keys in keyTable1

.klog1

LDY V                  \ Set Y = the offset of the key we are processing

LDX keyTable1,Y        \ Scan the keyboard to see if the Y-th key in keyTable1
JSR ScanKeyboard       \ is being pressed

BNE klog2              \ If the key is not being pressed, jump down to klog2 to
\ check the Y-th key in keyTable1

LDX V                  \ Set X = the offset of the key we are processing

LDY keyTable1Lo,X      \ Fetch the key logger value for this key press into
LDA keyTable1Hi,X      \ (A Y)

JMP klog4              \ Jump down to klog4 to store (A Y) in the key logger

.klog2

LDY V                  \ Set Y = the offset of the key we are processing

LDX keyTable2,Y        \ Scan the keyboard to see if the Y-th key in keyTable2
JSR ScanKeyboard       \ is being pressed

BNE klog3              \ If the key is not being pressed, jump down to klog3 to
\ store 0 in the key logger

LDX V                  \ Set X = the offset of the key we are processing

LDY keyTable2Lo,X      \ Fetch the key logger value for this key press into
LDA keyTable2Hi,X      \ (A Y)

JMP klog4              \ Jump down to klog4 to store (A Y) in the key logger

.klog3

LDA #0                 \ Set A = 0

LDX V                  \ Set X = the offset of the key we are processing

TAY                    \ Set Y = 0, so the key logger value in (A Y) is 0

.klog4

STA keyLoggerHi,X      \ Store the high byte of the key logger value in (A Y)
\ in the X-th byte of keyLoggerHi

TYA                    \ Store the low byte of the key logger value in (A Y)
STA keyLoggerLo,X      \ in the X-th byte of keyLoggerLo

DEC V                  \ Decrement the offset to point to the next key to
\ process

BPL klog1              \ Loop back until we have processed all six key pairs

RTS                    \ Return from the subroutine

Type: Subroutine
Category: 3D geometry
Summary: Add a point vector to an object's coordinates
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* CheckIfAlienIsHit (Part 2 of 2) calls AddPointToObject
* FireGuns calls AddPointToObject
* UpdateBullets calls AddPointToObject

This routine adds a point vector in (xPoint, yPoint, zPoint) to the object
coordinates in (xObject, yObject, zObject), storing the result in the object
coordinates. In other words, this moves an object by the xPoint vector.

This is called MOBJ or UOBJ in the original source code.

Arguments:

X                    The ID of the point vector to add to the object

Y                    The ID of the object to update

LDA xObjectLo,Y        \ Set object Y's x-coordinate to the following:
CLC                    \
ADC xPointLo,X         \  (xObjectHi+Y xObjectLo+Y) + (xPointHi+X xPointLo+X)
STA xObjectLo,Y        \
LDA xObjectHi,Y        \ i.e. we add object Y's x-coordinate and point X's
ADC xPointHi,X         \ x-coordinate
STA xObjectHi,Y

LDA yObjectLo,Y        \ Set object Y's y-coordinate to the following:
CLC                    \
ADC yPointLo,X         \  (yObjectHi+Y yObjectLo+Y) + (yPointHi+X yPointLo+X)
STA yObjectLo,Y        \
LDA yObjectHi,Y        \ i.e. we add object Y's y-coordinate and point X's
ADC yPointHi,X         \ y-coordinate
STA yObjectHi,Y

LDA zObjectLo,Y        \ Set object Y's z-coordinate to the following:
CLC                    \
ADC zPointLo,X         \  (zObjectHi+Y zObjectLo+Y) + (zPointHi+X zPointLo+X)
STA zObjectLo,Y        \
LDA zObjectHi,Y        \ i.e. we add object Y's z-coordinate and point X's
ADC zPointHi,X         \ z-coordinate
STA zObjectHi,Y

RTS                    \ Return from the subroutine

Type: Subroutine
Category: 3D geometry
Summary: Set an object's coordinates to (0, 0, 0)
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* FireGuns calls SetObjectToOrigin
* ResetRadar calls SetObjectToOrigin

Arguments:

Y                    The ID of the object to set to (0, 0, 0)

.SetObjectToOrigin

LDA #0                 \ Zero the object's x-coordinate
STA xObjectLo,Y
STA xObjectHi,Y

STA yObjectLo,Y        \ Zero the object's y-coordinate
STA yObjectHi,Y

STA zObjectLo,Y        \ Zero the object's z-coordinate
STA zObjectHi,Y

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Keyboard
Summary: Read the joystick position from one of the ADC channels
Context: See this subroutine on its own page
References: This subroutine is called as follows:

Arguments:

* 1 = joystick X

* 2 = joystick Y

Returns:

A                    The joystick position, inverted and clipped to the range
-118 to +116

LDA #128               \ Call OSBYTE with A = 128 to fetch the 16-bit value
JSR OSBYTE             \ from ADC channel X, returning (Y X), i.e. the high
\ byte in Y and the low byte in X
\
\   * Channel 1 is the x-axis: 0 = right, 65520 = left
\
\   * Channel 2 is the y-axis: 0 = down,  65520 = up

TYA                    \ Copy Y to A, so the result is now in (A X)

CMP #247               \ If A < 247, jump to radc1 to skip the next instruction

LDA #246               \ Set A = 246, so A now has a maximum value of 246

CMP #12                \ If A >= 12, jump to radc2 to skip the next instruction

LDA #12                \ Set A = 12, so A now has a minimum value of 12

\ By the time we get here, A is in the range 12 to 246

SEC                    \ Set A = A - 128, so A is now in the range -116 to +118
SBC #128

EOR #&FF               \ Negate A using two's complement, so A is now in the
CLC                    \ range -118 to +116

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Setup
Summary: Reset most variables to prepare for a new flight
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* NewGame calls ResetVariables

.ResetVariables

LDX #0                 \ Set A = 0 to use as our zero value

TXA                    \ Set X = 0 to use as a counter for zeroing 256 bytes in
\ the rset1 loop

STA alienSlot          \ Set alienSlot = 0 to clear out the first alien slot
\ (we clear out the other three slots below)

STA forceFactor+7      \ Set the force factor for yFlapsLift = 0

STA hitTimer           \ Set hitTimer = 0 to cancel any alien explosions

STA randomNumbers      \ Set randomNumbers = 0 to reset the pointer for the
\ list of random numbers

STA scoreLo            \ Set (scoreHi scoreLo) = 0 to reset the current score
STA scoreHi

.rset1

\ This loop zeroes the whole page at pointStatus, which
\ zeroes both pointStatus and objectStatus, resetting
\ all the point and object statuses

STA pointStatus,X      \ Zero the X-th byte of pointStatus

DEX                    \ Decrement the byte counter

BNE rset1              \ Loop back until we have zeroed the whole page

\ We now zero all the workspace variables from xTurnHi
\ to yPlaneHi, so their default values are all zero
\ unless they are explicitly set below

LDX #255               \ Set X = 255 to use as a counter for zeroing 255 bytes
\ in the rset2 loop

STA relatedPoints      \ Set relatedPoints = 0 to reset the relatedPoints list

STA mainLoopCounter    \ Set mainLoopCounter = 0 to reset the main loop counter

.rset2

STA xTurnHi-1,X        \ Zero the X-1-th byte of the workspace variables

DEX                    \ Decrement the byte counter

BNE rset2              \ Loop back until we have zeroed from xTurnHi to
\ yPlaneHi

\ We now zero the eight bytes at alienState to reset the
\ state of the aliens

LDX #7                 \ Set X = 7 to use as a counter for zeroing eight bytes
\ in the following loop

.rset3

STA alienState,X       \ Zero the X-th byte of alienState

DEX                    \ Decrement the byte counter

BPL rset3              \ Loop back until we have zeroed alienState to
\ alienState+7

LDA #&48               \ Set (zPlaneHi zPlaneLo) = &485C
STA zPlaneHi
LDA #&5C
STA zPlaneLo

LDA #&C6               \ Set (xPlaneHi xPlaneLo) = &C6E5
STA xPlaneHi
LDA #&E5
STA xPlaneLo

LDA #&0A               \ Set (yPlaneHi yPlaneLo) = &000A
STA yPlaneLo

STA alienSpeed         \ Set alienSpeed = 10, the movement speed for the first
\ wave of aliens

LDA #242               \ Set the force factor for zLiftDrag = 242, which is
STA forceFactor+5      \ quickly adjusted by +10 for the undercarriage being
\ down and -200 for the flaps being off, giving a
\ starting value of 52 when we are sitting on the runway

LDA #1                 \ Set ucStatus = 1, so the undercarriage is down
STA ucStatus

STA brakesStatus       \ Set brakesStatus = 1, so the brakes are on

STA landingStatus      \ Set landingStatus = 1, so we do all landing tasks

JSR IndicatorU         \ Update the undercarriage indicator

LDA #1                 \ Set onGround = 1, so we start on the ground
STA onGround

LDA #47                \ Set lineBuffer2Count = 47, so line buffer 2 is empty
STA lineBuffer2Count

LDA #255               \ Set themeStatus = 255, so the Theme is disabled
STA themeStatus

STA lineBuffer1Count   \ Set lineBuffer1Count = 255, so line buffer 1 is empty

\ We now zero the eight bytes at alienObjectId, so no
\ objects are associated with any aliens

LDX #7                 \ Set X = 7 to use as a counter for zeroing eight bytes
\ in the following loop

STX xRotationHi        \ Set xRotationHi = 7, so the plane tilts backwards by
\ 7/256 = 9.84 degrees when its wheels are on the ground

.rset4

STA alienObjectId,X    \ Zero the X-th byte of alienObjectId

DEX                    \ Decrement the byte counter

BPL rset4              \ Loop back until we have zeroed all 8 alienObjectId
\ bytes

\ We now zero alienSlot+1 to alienSlot+3 to clear out
\ the rest of the alien slots (we already zeroed
\ alienSlot above)

LDX #2                 \ Set X = 2 to use as a counter for zeroing three bytes
\ in the following loop

.rset5

STA alienSlot+1,X      \ Zero the X-th byte of alienSlot+1

DEX                    \ Decrement the byte counter

BPL rset5              \ Loop back until we have zeroed all three bytes

JSR IndicatorT         \ Update the Theme indicator

LDX #11                \ Update the thrust indicator
JSR UpdateIndicator

LDA #65                \ Set fuelLevel = 65, to indicate a full tank
STA fuelLevel

\ We now drain the fuel tank one point at a time,
\ updating the fuel gauge as we go so the fuel gauge
\ gets cleared down to empty at the same time as
\ the value in fuelLevel

.rset6

DEC fuelLevel          \ Decrement the counter in fuelLevel

JSR UpdateFuelGauge    \ Update the fuel gauge

LDA fuelLevel          \ Loop back until fuelLevel = 0, by which point we have
BNE rset6              \ reset the fuel tanks and cleared the fuel gauge

\ Fall through into ResetRadar to reset the radar
\ display

Type: Subroutine
Category: Dashboard
Summary: Reset the radar display
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* ScoreHitPoints calls ResetRadar

LDA #80                \ Set xPointLo = 80, so we don't draw a new alien on the
STA xPointLo           \ radar (as this coordinate is off the radar)

LDA #1                 \ Set alien = 1, so we remove the alien from the radar
STA alien              \ rather than the runway when we call DrawRadarBlip

STA xPointHi           \ Set xPointHi = 1, so the value in xPointLo is treated
\ as positive

JSR DrawRadarBlip      \ Remove the current dot from the radar, but don't draw
\ a new one, as xPointLo is off-radar

LDY #33                \ Reset object 33's coordinates (the flying alien) to
JSR SetObjectToOrigin  \ (0, 0, 0)

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Setup
Summary: Reset the high score, set up the gunfire sound envelope and start
a new game
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* DrawCanopy calls StartGame

.StartGame

LDA #0                 \ Set the high score in (highScoreHi highScoreLo) to 0
STA highScoreHi
STA highScoreLo

LDA #14                \ Call DefineEnvelope with A = 14 to set up the second
JSR DefineEnvelope     \ sound envelope

\ Fall through into NewGame to start a new game

Type: Subroutine
Category: Setup
Summary: Start a new game
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* AlienInAcornsville calls NewGame
* Crash calls NewGame
* MainLoop (Part 7 of 15) calls NewGame

.NewGame

JSR ClearCanopy        \ Clear the canopy to black, leaving the canopy edges
\ alone

JSR ResetVariables     \ Reset most variables to prepare for a new flight

JSR UpdateKeyLogger    \ Scan for key presses and update the key logger

JSR UpdateFlightModel  \ Process any key presses in the key logger and update
\ the matrices and flight model

JSR ResetLineLists     \ Reset the line lists at linesToShow and linesToHide,
\ which will populate them with the correct lines to
\ show for the starting point on the runway

JSR IndicatorF         \ Update the flaps indicator

JSR IndicatorB         \ Update the brakes indicator

LDA #%01000000         \ Set the 6522 User VIA auxiliary control register
STA VIA+&6B            \ (SHEILA &6B) to %01000000 to disable the shift
\ register

LDA #234               \ Set 6522 User VIA T1C-L timer 1 high-order counter
STA VIA+&65            \ (SHEILA &65) to 234 to start the T1 counter
\ counting down at a rate of 1 MHz

Name: MainLoop (Part 1 of 15)                                 [Show more]
Type: Subroutine
Category: Main loop
Summary: Start the main loop by processing gunfire and bullets
Deep dive: Program flow of the main game loop
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* MainLoop (Part 15 of 15) calls MainLoop

.MainLoop

LDA linesToHideEnd     \ Store the index of the end of the linesToHide list in
STA previousListEnd    \ previousListEnd so we can check it at the end of the
\ main loop

JSR SpawnAlien         \ If the Theme is enabled and the current wave does not
\ yet have eight aliens in it, spawn a new alien

JSR UpdateKeyLogger    \ Scan for key presses and update the key logger

LDA firingStatus       \ If firingStatus is non-zero, then we have already
BNE main2              \ fired our gun and the bullets are still in the air, so
\ jump to main2 to skip firing bullets, as we can't fire
\ any more bullets until the current ones expire

JSR UpdateFlightModel  \ Process any key presses in the key logger and update
\ the matrices and flight model

LDA firingStatus       \ If the call to UpdateFlightModel has left firingStatus
BEQ main3              \ set to zero, then the fire key is not being pressed,
\ so jump to main3 to skip firing bullets

\ The call to UpdateFlightModel had changed firingStatus
\ from zero to non-zero, which means the fire key is
\ being pressed, so we now need to add two bullets to
\ the scene

LDA #2                 \ Set gunSoundCounter = 2, so we make two firing sounds
STA gunSoundCounter    \ below, one for each bullet

LDY #33                \ We now copy the status bytes for objects 30 to 33 (the
\ four alien objects), copying the four bytes between
\ objectStatus+30 and objectStatus+33 into the four
\ bytes at alienStatus, so set up a counter in Y that
\ can also act as the offset

.main1

LDA objectStatus,Y     \ Copy the Y-th byte of objectStatus to alienStatus-30,
STA alienStatus-30,Y   \ to give this:
\
\   objectStatus+30 -> alienStatus
\   objectStatus+31 -> alienStatus+1
\   objectStatus+32 -> alienStatus+2
\   objectStatus+33 -> alienStatus+3

DEY                    \ Decrement the loop counter

CPY #30                \ Loop back until we have copied all four bytes
BCS main1

\ We now add the bullet lines (line IDs 60 and 61) to
\ the linesToShow list, so they get displayed

LDY linesToShowEnd     \ Set Y to the first free entry at the end of the
\ linesToShow list

LDA #60                \ Append line 60 to the end of the linesToShow list
STA linesToShow,Y

INY                    \ Increment Y to point to the next free entry in the
\ list

LDA #61                \ Append line 61 to the end of the linesToShow list
STA linesToShow,Y

INY                    \ Increment Y to point to the next free entry in the
\ list

STY linesToShowEnd     \ Update linesToShowEnd with the updated index of the
\ next free entry, which is two more than it was before
\ we added the bullet lines

JMP main3              \ Skip the following instruction, as we have already
\ processed the key logger

.main2

JSR UpdateFlightModel  \ Process any key presses in the key logger and update
\ the matrices and flight model

Name: MainLoop (Part 2 of 15)                                 [Show more]
Type: Subroutine
Category: Main loop
Summary: Reset object statuses and related points
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

.main3

\ We now want to zero the 40 bytes in objectStatus, so
\ that all objects are marked as unprocessed, ready to
\ be processed again in this iteration of the main loop

LDX #19                \ We do this as two blocks of 20 bytes, so set a counter
\ in X to use in the loop below

LDA #0                 \ Set showRunwayDashes = 0 to reset the dashes down the
STA showRunwayDashes   \ middle of the runway to be visible

STA relatedPoints      \ Set relatedPoints = 0 to reset the relatedPoints list,
\ so we can build a new list of related object points
\ in this iteration of the main loop

.main4

STA objectStatus,X     \ Zero the X-th byte of objectStatus

STA objectStatus+20,X  \ Zero the X-th byte of objectStatus+20

DEX                    \ Decrement the loop counter

BPL main4              \ Loop back until we have zeroed all 40 bytes

Name: MainLoop (Part 3 of 15)                                 [Show more]
Type: Subroutine
Category: Main loop
Summary: Make the sound of firing, if appropriate
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

LDA firingStatus       \ If firingStatus is zero then there are no bullets in
BEQ main5              \ the air, so jump to main5 to skip updating the bullet
\ positions

JSR UpdateBullets      \ Update the bullet positions

LDA gunSoundCounter    \ If gunSoundCounter = 0 then we don't have any gun
BEQ main5              \ firing sounds to make, so jump to main5 to skip the
\ gun sounds code

DEC gunSoundCounter    \ Decrement the sound counter in gunSoundCounter

LDA #6                 \ Make sound #6, the sound of our guns firing
JSR MakeSound

Name: MainLoop (Part 4 of 15)                                 [Show more]
Type: Subroutine
Category: Main loop
Summary: Check whether aliens have invaded Acornsville
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

.main5

LDA themeStatus        \ If bit 7 of themeStatus is set, then the Theme is not
BMI main6              \ enabled, so jump to main6

JSR AlienInAcornsville \ Check to see whether an alien has reached Acornsville
\ and terminate the main loop if it has

Name: MainLoop (Part 5 of 15)                                 [Show more]
Type: Subroutine
Category: Main loop
Summary: Update lines, check flying skills, increment main loop counter,
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

.main6

JSR UpdateLinesToShow  \ Update the linesToShow list, moving any lines that
\ aren't visible into the linesToHide list

JSR ExplodeAlien       \ If an alien has been hit, process its explosion, with
\ all the convulsions that entails

LDY #2                 \ Check to see if we are flying under the suspension
JSR CheckFlyingSkills  \ bridge and award points if we are

LDY #34                \ Check to see if we are flying down the main street of
JSR CheckFlyingSkills  \ Acornsville and award points if we are

INC mainLoopCounter    \ Increment the main loop counter

LDA mainLoopCounter    \ If (loop counter + 4) mod 8 <> 0, jump to main7, so
CLC                    \ we only do the following once every 8 iterations of
ADC #4                 \ the main loop, when the loop counter is 4, 12, 20 and
AND #7                 \ so on
BNE main7

LDY #1                 \ Update the runway on the radar

LDX alienToMove        \ Set X to the number of the alien whose turn it is to
\ move towards Acornsville in this iteration of the main
\ loop, which we set in UpdateAliens

BMI main7              \ If X is negative, then there is no alien to move, so
\ jump to main7 to skip the following

LDY #33                \ Set Y = 33 so the call to UpdateRadarBlip updates the
\ alien on the radar

LDA alienState,X       \ If the moving alien's state is < 27, skip the
CMP #27                \ following instruction as the alien is not flying
BCC main7

JSR UpdateRadarBlip    \ The moving alien's state is >= 27, which means it is
\ either flying to Acornsville or is in the final
\ descent stage, so update the alien on the radar

Name: MainLoop (Part 6 of 15)                                 [Show more]
Type: Subroutine
Category: Main loop
Summary: Check whether any aliens have been hit
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

.main7

LDA themeStatus        \ If themeStatus is non-zero then either the Theme is
BNE main12             \ not enabled, or it is enabled but we haven't yet added
\ all eight aliens to the current wave, so jump to
\ main12 as we only move aliens when the whole wave has
\ arrived

LDA firingStatus       \ If firingStatus is zero then there are no bullets in
BEQ main11             \ the air, so jump to main11, as we only need to check
\ whether an alien is hit when there are bullets around

LDA #33                \ We now loop through objects 33 down to 30, which are
STA objectId           \ all the alien objects, so we can check whether any of
\ them have been hit, so set a loop counter in objectId

.main8

LDY objectId           \ Set Y to the object ID of the alien to check

LDA alienStatus-30,Y   \ We copied the object status bytes for all four alien
BPL main9              \ objects into alienStatus in part 1, so this checks
\ whether bit 7 of the alien's object status byte is
\ clear
\
\ If it is clear, then this object is not visible, so
\ we skip the following three instructions and move on
\ to the next alien, as we can't hit an alien that we
\ can't see

JSR CheckIfAlienIsHit  \ This alien is visible, so check to see whether it has
\ been hit, and if so, initiate the explosion

LDA hitTimer           \ If hitTimer is non-zero, then we just hit an alien, so
BNE main10             \ jump to main10 to skip checking the rest of the aliens

.main9

DEC objectId           \ Decrement the loop counter to the next alien

LDA objectId           \ Loop back to check the next alien, until we have
CMP #30                \ done all of them (from object 33 down to object 30)
BCS main8

BCC main11             \ Jump to main11 (this BCC is effectively a JMP as we
\ just passed through a BCS)

.main10

STA distanceFromHit    \ Store the value of hitTimer (which will be 27 as we
\ just hit an alien) in distanceFromHit, so we are far
\ enough away to avoid any turbulence, for now (as
\ turbulence only kicks in when distanceFromHit < 16)

LDA #0                 \ Set firingStatus = 0 to indicate that there are no
STA firingStatus       \ longer any bullets are in the air, as they are now
\ embedded in an unfortunate alien

.main11

JSR UpdateAliens       \ Update the aliens' statuses

Name: MainLoop (Part 7 of 15)                                 [Show more]
Type: Subroutine
Category: Main loop
Summary: Process the terminate key
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

.main12

LDA onGround           \ If onGround is zero, we are in the air and can't
BEQ main13             \ terminate the game with the right arrow key, so jump
\ to main13 to skip the following

LDX #&86               \ Scan the keyboard to see if the right arrow is being
JSR ScanKeyboard       \ pressed

BNE main13             \ If the right arrow is not being pressed, jump to
\ main13

JSR TerminateGame      \ The right arrow is being pressed, which is the key to
\ terminate the game, so call TerminateGame to do
\ exactly that

JMP NewGame            \ Jump to NewGame to start a new game

Name: MainLoop (Part 8 of 15)                                 [Show more]
Type: Subroutine
Category: Main loop
Summary: If we fire the guns on the runway, enable the Theme
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

.main13

LDA landingStatus      \ If landingStatus = 0, jump to main17 to skip the
BEQ main17             \ following (enabling the Theme, filling up with fuel,
\ awarding points for landing)

BMI main16             \ If bit 7 of landingStatus is set, jump to main16 to
\ skip the following (enabling the Theme, filling up
\ with fuel) as we are on the ground following an
\ emergency landing

LDA firingStatus       \ If firingStatus is zero then there are no bullets in
BEQ main14             \ the air, so jump to main14

LDA themeStatus        \ If themeStatus is positive then the Theme is already
BPL main14             \ enabled, so jump to main14

\ If we get here then there are bullets in the air and
\ the Theme is not enabled, so we now need to enable the
\ Theme (as it is triggered by us firing bullets when
\ we are stationary and on the runway with the brakes
\ on)

LDA #8                 \ Set themeStatus = 8 to enable the Theme and initiate
STA themeStatus        \ a new wave of aliens

JSR IndicatorT         \ Update the Theme indicator

Name: MainLoop (Part 9 of 15)                                 [Show more]
Type: Subroutine
Category: Main loop
Summary: Fill up the tank if the engine is switched off, and process the
volume keys
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

.main14

LDA engineStatus       \ If the engine is on, jump to main15 to skip the
BNE main15             \ following instruction

JSR FillUpFuelTank     \ Fill up the fuel tank by one unit

.main15

JSR ProcessVolumeKeys  \ Check the volume keys and adjust the sound volume
\ accordingly

Name: MainLoop (Part 10 of 15)                                [Show more]
Type: Subroutine
Category: Main loop
Summary: Award points for a successful landing
Deep dive: Program flow of the main game loop
Take-offs and landings
Context: See this subroutine on its own page
References: No direct references to this subroutine in this source file

.main16

LDA reached512ft       \ If we have not yet reached an altitude of 512 feet
BEQ main17             \ since taking off, reached512ft will be zero, so jump
\ to main17 to skip the following, as we are not
\ eligible for the landing points

LDX #0                 \ Set reached512ft = 0 to reset the 512 feet counter,
STX reached512ft       \ ready for the next landing attempt

LDA #&15               \ We have successfully landed the plane without
JSR ScorePoints        \ crashing, so add 150 points to the score and make a
\ beep by calling ScorePoints with (X A) = &0015

Name: MainLoop (Part 11 of 15)                                [Show more]
Type: Subroutine
Category: Main loop
Summary: Process engine start and stop
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

.main17

JSR UpdateFuelGauge    \ Update the fuel gauge

LDX #&DC               \ Scan the keyboard to see if "T" is being pressed
JSR ScanKeyboard

BNE main18             \ If "T" is not being pressed, jump to main18

LDA pressingT          \ If pressingT is non-zero, then we are still pressing
BNE main19             \ "T" having already toggled the engine, so jump down
\ to main19 so we don't keep switching the engine on and
\ off by accident

LDA propellorStatus    \ If propellorStatus is non-zero, then the propellor is
BNE main20             \ broken and we can't turn on the engine, so jump to
\ main20 to skip the following

\ At this point, we know that "T" is being pressed,
\ pressingT is zero (so we haven't yet acted on the key
\ press), and propellorStatus is zero (so the propellor
\ is working), so now we toggle the engine status to
\ switch it on or off

LDA engineStatus       \ Fetch the value of engineStatus and invert bit 0 so it
EOR #1                 \ changes to the opposite state

JSR SetEngine          \ Set the engine status to the value in A

LDA #1                 \ Set A = 1 to use as the new value of pressingT below,
BNE main19             \ so that holding down "T" won't keep toggling the
\ engine status

.main18

LDA #0                 \ "T" is not being pressed, so set A = 0 to use as the
\ new value of pressingT

.main19

STA pressingT          \ Set pressingT = A, so we don't try toggling the engine
\ again until we release the "T" key

Name: MainLoop (Part 12 of 15)                                [Show more]
Type: Subroutine
Category: Main loop
Summary: Spend at least 9 centiseconds processing lines from the
linesToHide list
Deep dive: Program flow of the main game loop
Scheduling tasks in the main loop
Context: See this subroutine on its own page
References: No direct references to this subroutine in this source file

.main20

LDA relatedPoints      \ If the relatedPoints list contains 35 or more entries,
CMP #35                \ jump to main21 to skip the following three
BCS main21             \ instructions

JSR ProcessLinesToHide \ Process three lines from the linesToHide list, if
JSR ProcessLinesToHide \ there are any unprocessed lines there
JSR ProcessLinesToHide

.main21

LDX #&70               \ Call OSWORD with A = 1 and (Y X) = &0070, which reads
LDY #&00               \ the system clock and writes the result into the five
LDA #1                 \ bytes from &0070 to &0074 (P, Q, R, S and T). For the
JSR OSWORD             \ purposes of the call to CheckTimePassed, P is set to
\ the least significant byte of the time, which
\ increments 100 times a second

JSR CheckTimePassed    \ If fewer than 9 centiseconds have passed since the
BCC main20             \ first time we were here on this iteration of the main
\ loop, then we haven't yet spent enough time processing
\ lines from the linesToHide list, so jump back to
\ main20 to do a few more

Name: MainLoop (Part 13 of 15)                                [Show more]
Type: Subroutine
Category: Main loop
Summary: Process more lines and update the view out of the canopy
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

JSR ProcessLinesToShow \ Process the lines in the linesToShow list

JSR DrawCanopyView     \ Update the main view out of the canopy

JSR SetRandomNumber    \ Add a new random number to the end of the
\ randomNumbers list

Name: MainLoop (Part 14 of 15)                                [Show more]
Type: Subroutine
Category: Main loop
Summary: Handle the score display
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

LDA scoreDisplayTimer  \ If scoreDisplayTimer = 0, jump to main22 to skip the
BEQ main22             \ following three instructions, as we are not currently
\ displaying the score

CMP #220               \ If scoreDisplayTimer <> 220, jump down to main23 to
BNE main23             \ decrement the timer and keep displaying the score

BEQ main24             \ If scoreDisplayTimer = 220, it's time to remove the
\ score from the screen, so jump down to main24

.main22

LDX #&C8               \ Scan the keyboard to see if "P" is being pressed
JSR ScanKeyboard

BNE main25             \ If "P" is not being pressed, jump to main25 to
\ continue with the main loop

.main23

\ If we get here then either we just pressed "P" or the
\ score is already being displayed, so in either case we
\ should update the timer and display the score

DEC scoreDisplayTimer  \ Decrement the score timer

JSR DisplayScore       \ Display the score, so we show it for the first time if
\ we just pressed "P", or update it if it changes while
\ being displayed

JMP main25             \ Jump down to main25 to continue with the main loop

.main24

JSR RemoveScore        \ Remove the score from the screen

LDA #0                 \ Set scoreDisplayTimer = 0 as we are no longer showing
STA scoreDisplayTimer  \ the score on-screen

Name: MainLoop (Part 15 of 15)                                [Show more]
Type: Subroutine
Category: Main loop
Summary: Update the status of any new line points
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

If we have added any lines to the linesToHide list during the main loop (and
therefore incremented linesToHideEnd), we reset the point status bytes for the
line's start and end points, so they are no longer flagged as having had their

.main25

LDX previousListEnd    \ If the value of linesToHideEnd has changed since the
CPX linesToHideEnd     \ start of the main loop - in other words, it is not the
BNE main26             \ same as the value we stored in previousListEnd - then
\ this means the value of linesToHideEnd has changed
\ during this iteration of the main loop, so jump down
\ to main26 to process the new additions to the
\ linesToHide list

JMP MainLoop           \ Otherwise we are done, so jump back up to the top of
\ the main loop for the next iteration

.main26

\ The first time we get here, X contains the size of the
\ linesToHide list as it was when we started this
\ iteration of the main loop, and we have added new
\ lines to the list, so now we need to process those
\ new lines

INX                    \ Increment X to point to the next line, which will be
\ the first new one to process

LDY linesToHide,X      \ Set Y to this line's ID from linesToHide

STX previousListEnd    \ Update the value of previousListEnd, so if we revisit
\ the main26 loop, we will move on to the next line

\ Now to process the line

LDX lineStartPointId,Y \ Set X to the ID of this line's start point

LDA #0                 \ Zero the start point's status byte, so it is no longer
STA pointStatus,X      \ marked as having had its coordinates and visibility
\ calculated

LDX lineEndPointId,Y   \ Set X to the ID of this line's end point

STA pointStatus,X      \ Zero the end point's status byte, so it is no longer
\ marked as having had its coordinates and visibility
\ calculated

JMP main25             \ Jump back to main25 to repeat this process until we
\ have zeroed all the new additions to linesToHide

Type: Subroutine
Category: Visibility
Summary: Update the linesToShow list, moving any lines that aren't visible
into the linesToHide list
Deep dive: Visibility checks
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* MainLoop (Part 5 of 15) calls UpdateLinesToShow

.UpdateLinesToShow

LDX linesToShowEnd     \ If the linesToShow list is empty, jump to upll5 to
BEQ upll5              \ return from the subroutine, as there is nothing to
\ process

LDA #255               \ Set linesToShowPointer = 255, to use as a pointer
STA linesToShowPointer \ to the end of the new linesToShow list as we build it
\ up in-situ

LDA #0                 \ Set lineCounter = 0, to use as a line counter as we
STA lineCounter        \ loop through the linesToShow list

.upll1

LDX lineCounter        \ Set lineId to the ID of the next line in the
LDA linesToShow,X      \ linesToShow list
STA lineId

LDA #1                 \ Set HH = 1, so in the following call to ProcessLine
STA HH                 \ we do not check the line's distance against
\ maxLineDistance in the visibility checks

JSR ProcessLine        \ Check whether this line is visible, returning the
\ result in showLine

LDA lineId             \ If lineId = 0, then this is the runway, so skip the
BEQ upll2              \ following instruction to keep the line in the list

LDX showLine           \ If showLine is non-zero then this line is not visible,
BNE upll3              \ so jump to upll3

.upll2

\ If we get here then we want to keep this line in the
\ linesToShow list

INC linesToShowPointer \ Increment the linesToShowPointer pointer to point to
\ the next free slot in the new list we are creating

LDX linesToShowPointer \ Store the line ID at the end of the new list we are
STA linesToShow,X      \ creating

JMP upll4              \ Jump down to upll4 to move on to the next line in the
\ list

.upll3

\ If we get here then we do not want to keep this line
\ in the linesToShow list, so we need to move it to the
\ linesToHide list

INC linesToHideEnd     \ Increment linesToHideEnd to point to the next free
\ entry at the end of the linesToHide list

LDX linesToHideEnd     \ Add the line ID to the end of the linesToHide list
STA linesToHide,X

.upll4

INC lineCounter        \ Increment the loop counter to point to the next line
\ in the linesToShow list

LDA lineCounter        \ Loop back to process the next line from linesToShow
CMP linesToShowEnd     \ until we have worked our way to the end
BCC upll1

LDA linesToShowPointer \ Set linesToShowEnd = linesToShowPointer + 1
STA linesToShowEnd     \ so it points to the index of the first empty entry in
\ the newly processed linesToShow list

.upll5

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Visibility
Summary: Process the linesToShow list, projecting all the lines onto the
screen and moving any that aren't visible to the linesToHide list
Deep dive: Visibility checks
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* MainLoop (Part 13 of 15) calls ProcessLinesToShow

.ProcessLinesToShow

LDA linesToShowEnd     \ If the linesToShow list is empty, jump to show5 to
BEQ show5              \ clear down the relatedPoints list, as there are no
\ visible lines at all

LDA #255               \ Set linesToShowPointer = 255, to use as a pointer
STA linesToShowPointer \ to the end of the new linesToShow list as we build it
\ up in-situ

LDA #0                 \ Set lineCounter = 0, to use as a line counter as we
STA lineCounter        \ loop through the linesToShow list

.show1

LDX lineCounter        \ Set lineId to the ID of the next line in the
LDY linesToShow,X      \ linesToShow list
STY lineId

LDX lineStartPointId,Y \ Set GG to the point ID of the line's start point, to
STX GG                 \ pass to the ProjectPoint routine

STX L                  \ Set L to the point ID of the line's start point

LDX lineEndPointId,Y   \ Set M to the point ID of the line's end point
STX M

JSR ProjectPoint       \ Project the line's end point onto the screen, putting
\ the screen coordinates into (xPoint, yPoint) and
\ setting the point's status byte accordingly

LDA M                  \ Set GG to the point ID of the line's start point, to
STA GG                 \ pass to the ProjectPoint routine

JSR ProjectPoint       \ Project the line's start point onto the screen,
\ putting the screen coordinates into (xPoint, yPoint)
\ and setting the point's status byte accordingly

LDX L                  \ Set startStatus to the status byte of the line's start
LDA pointStatus,X      \ point
STA startStatus

LDX M                  \ Set N to the status byte of the line's end point
LDA pointStatus,X
STA N

AND startStatus        \ Set A as follows:
AND #%00110000         \
\   * Bit 4 is set in A if bit 4 is set in both points,
\     so both the start and end points (in 3D space)
\     have |yPoint| * 2 >= |zPoint|
\
\   * Bit 5 is set in A if bit 5 is set in both points,
\     so both the start and end points (in 3D space)
\     have |xPoint| >= |zPoint|
\
\ so A is in the form %00xx0000

BEQ show2              \ If both bits 4 and 5 are clear in the above, jump to
\ show2 to keep the line in the linesToShow list

LSR A                  \ Shift bits 4 and 5 down into bits 2 and 3 and store
LSR A                  \ the result in T, so:
STA T                  \
\   * Bit 2 is set in T if bit 4 is set in both points
\   * Bit 3 is set in T if bit 5 is set in both points
\
\ and the rest of the bits will be 0, so T is in the
\ form %0000xx00

LDA N                  \ Set A =     end point status
EOR startStatus        \         EOR start point status
EOR #&FF               \         EOR %11111111
AND T                  \         AND T
\
\ Because T is in the form %0000xx00, we are only
\ interested in the calculation for bits 2 and 3, as all
\ other bits will be zeroed by the AND T. Just taking
\ bit 2 we get the following (the numbers on the right
\ show the calculation in progress):
\
\   A =     bit 2 of end point
\       EOR bit 2 of start point   <- 0 if bits are same
\       EOR 1                      <- 1 if bits are same
\       AND bit 2 of T             <- 1 if set
\
\ So bit 2 of A will be set only when both of the
\ following are true:
\
\   * Bit 2 is the same in both points
\   * Bit 2 of T is set
\
\ which expands to the following:
\
\   * Bit 2 is the same in both points
\   * Bit 4 is set in both points
\
\ and means:
\
\   * yPoint in both points has the same sign
\   * |yPoint| * 2 >= |zPoint| for both points
\
\ Similarly, bit 3 of A will be set only when both of
\ the following are true:
\
\   * Bit 3 is the same in both points
\   * Bit 5 is set in both points
\
\ which means:
\
\   * xPoint in both points has the same sign
\   * |xPoint| >= |zPoint| for both points

BNE show3              \ If A is non-zero, then this means that at least one of
\ the above is true for this line. Let's see what that
\ means. If both of the following are true:
\
\   * yPoint in both points has the same sign
\   * |yPoint| * 2 >= |zPoint| for both points
\
\ then:
\
\   * The first one means that either both ends of the
\     line are above us, or both are below us, so the
\     line isn't crossing our field of view from top to
\     bottom
\
\   * The second one means that both points are above or
\     both points are below the 2y = z line (or, more
\     accurately, the 2y = z plane). In terms of 3D
\     space, this means that for us to look at these
\     points from our plane seat, we would need to raise
\     or lower our gaze by more than 22.5 degrees
\
\ If both of these are true, then the line isn't
\ crossing our field of view, and it's too far above or
\ below us to be seen... or in other words, it isn't
\ visible
\
\ The xPoint rules are similar. If both of these are
\ true:
\
\   * xPoint in both points has the same sign
\   * |xPoint| >= |zPoint| for both points
\
\ then:
\
\   * The first one means that either both ends of the
\     line are to the left of us, or both are to the
\     right of us, so the line isn't crossing our field
\     of view from left to right
\
\   * The second one means that both points are to the
\     left of or both points are to the right of the
\     x = z line (or, more accurately, the x = z plane).
\     In terms of 3D space, this means that for us to
\     look at these points from our plane seat, we would
\     need to look left or right by more than 45 degrees
\
\ If both of these are true, then the line isn't
\ crossing our field of view, and it's too far to the
\ left or right of us to be seen... or in other words,
\ it isn't visible
\
\ So if A is non-zero, this means that the line is not
\ visible so we jump to show3 to add this line to the
\ linesToHide list

.show2

\ If we get here then we want to keep this line in the
\ linesToShow list

INC linesToShowPointer \ Increment the linesToShowPointer pointer to point to
\ the next free slot in the new list we are creating

LDX linesToShowPointer \ Store the line ID at the end of the new list we are
LDA lineId             \ creating
STA linesToShow,X

JMP show4              \ Jump to show4 to move on to the next line

.show3

LDA lineId             \ If the line Id is 0 then it's the horizon, so jump up
BEQ show2              \ to show2 add it to the linesToShow list, as we don't
\ want to hide the horizon

INC linesToHideEnd     \ Increment linesToHideEnd to point to the next free
\ entry at the end of the linesToHide list

LDX linesToHideEnd     \ Add the line ID in A to the end of the linesToHide
STA linesToHide,X      \ list

.show4

INC lineCounter        \ Increment the loop counter to point to the next line
\ in the linesToShow list

LDA lineCounter        \ Loop back to process the next line from linesToShow
CMP linesToShowEnd     \ until we have worked our way to the end
BCC show1

LDA linesToShowPointer \ Set linesToShowEnd = linesToShowPointer + 1
STA linesToShowEnd     \ so it points to the index of the first empty entry in
\ the newly processed linesToShow list

.show5

LDX relatedPoints      \ If the relatedPoints list is empty, jump to show7 to
BEQ show7              \ return from the subroutine

\ Otherwise we now reset the point status bytes for all
\ the points mentioned in the relatedPoints list, so
\ they are no longer marked as having their coordinates
\ and visibility calculated

LDA #0                 \ Set A = 0 to use for resetting the status bytes

.show6

LDY relatedPoints,X    \ Zero the status byte for the X-th entry in the list
STA pointStatus,Y

DEX                    \ Decrement the loop counter

BNE show6              \ Loop back until we have reset all the

.show7

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Visibility
Summary: Process an unprocessed line from the linesToHide list
Deep dive: Visibility checks
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* MainLoop (Part 12 of 15) calls ProcessLinesToHide

This routine is called from the main loop, but only when there are fewer than
35 related points, and only if there is enough time (the routine is called in
batches of three, until we have spent 9 centiseconds processing the lines to
hide list).

It pulls a line ID from the end of the linesToHide list, and checks the line's
visibility. If it is visible, then it moves the line to the linesToShow list
so it gets shown on-screen, but if it is not visible, it sticks the line on
the end of the linesToHide list and moves on to the next. In this way the game
is constantly checking hidden lines to see if they are visible, so when we fly
near enough to a line that it should be shown, this is the routine that shows
it. (And, if that line is part of an object, then other lines in the object
will also be shown, via the related points list.)

.ProcessLinesToHide

LDA linesToHidePointer \ Set A to the position within the linesToHide list up
\ to which we have processed lines

CMP linesToHideEnd     \ If we have processed all the way to the end of the
BEQ ProcessLine-1      \ list, then linesToHidePointer = linesToHideEnd, so
\ return from the subroutine (as ProcessLine-1
\ contains an RTS)

CLC                    \ Otherwise we have lines on the end of the linesToHide
ADC #1                 \ list that we have not yet processed, so increment
STA linesToHidePointer \ the linesToHidePointer, as we are about to process a
\ line

TAX                    \ Set lineId to the line's ID from the linesToHide list
LDA linesToHide,X
STA lineId

CMP #60                \ If lineId = 60, jump back to ProcessLinesToHide to
BEQ ProcessLinesToHide \ pick another line

CMP #61                \ If lineId = 61, jump back to ProcessLinesToHide to
BEQ ProcessLinesToHide \ pick another line

\ Fall through into ShowOrHideLine to process the line
\ and add it to the linesToShow or linesToHide list

Type: Subroutine
Category: Visibility
Summary: Process a line, working out its visibility and adding it to the
linesToShow or linesToHide list
Deep dive: Visibility checks
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* ResetLineLists calls ShowOrHideLine

Check the visibility of a line and either add it to the linesToShow list
or the linesToHide list, depending on whether it is visible.

Arguments:

lineId               The line ID to process

.ShowOrHideLine

LDA #0                 \ Set HH = 0, so in the following call to ProcessLine
STA HH                 \ we check the line's distance against maxLineDistance
\ in the visibility checks

JSR ProcessLine        \ Check whether this line is visible, returning the
\ result in showLine

LDA lineId             \ Fetch the line ID into A

LDX showLine           \ If showLine = 0 then the line is visible, so jump down
BEQ shli1              \ to shli1 to add the line to the linesToShow list

\ The value of showLine is non-zero, so the line is not
\ visible and we now add it to the linesToHide list

INC linesToHideEnd     \ Increment linesToHideEnd to point to the next free
\ entry at the end of the linesToHide list

LDX linesToHideEnd     \ Add the line's ID to the end of the linesToHide list
STA linesToHide,X

RTS                    \ Return from the subroutine

.shli1

\ The value of showLine is zero, so the line is visible
\ and we now add it to the linesToShow list

INC linesToShowEnd     \ Increment linesToShowEnd so it still points to the
\ first empty entry in the linesToShow list after we
\ add this line

INC linesToShowPointer \ Increment linesToShowPointer as we have already
\ processed the line, so we set the progress pointer to
\ be after this line

LDX linesToShowPointer \ Add the line's ID to the end of the linesToShow list
STA linesToShow,X

RTS                    \ Return from the subroutine

Name: ProcessLine (Part 1 of 7)                               [Show more]
Type: Subroutine
Category: Visibility
Summary: Process a line, rotating and transforming it to the correct
coordinates and calculating its visibility
Deep dive: Visibility checks
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* ShowOrHideLine calls ProcessLine
* UpdateLinesToShow calls ProcessLine
* ProcessLinesToHide calls via ProcessLine-1

Arguments:

lineId               The line ID of the line to process

HH                   Determines whether we check against the line's maximum
visible distance during the visibility checks:

* 0 = check the line's distance against the maximum
visible distance in maxLineDistance

* Non-zero = skip the distance check

Returns:

showLine             Whether this line is visible:

* 0 = line is visible

* Non-zero = line is not visible

Other entry points:

ProcessLine-1        Contains an RTS

.ProcessLine

LDA #0                 \ Set showLine = 0 as a starting point for the return
STA showLine           \ value (so we start out by assuming the line is
\ visible, and change this if we find that it isn't)

STA isObject           \ Set isObject = 0, which we will set to a non-zero
\ object ID below if we end up processing an object

LDX lineId             \ Set X = lineId, so X contains the ID of the line we
\ want to check for visibility

LDY lineEndPointId,X   \ Set M to the point ID of the line's end point
STY M

LDY lineStartPointId,X \ Set L to the point ID for the line's start point
STY L

CPX #12                \ If lineId >= 12, then this is not the horizon or a
BCS plin2              \ runway line, so jump to plin2 to process the line

CPX #0                 \ If lineId is not zero, then this is not the horizon,
BNE plin1              \ so it must be a runway line, so jump to plin1 to
\ process it

\ If we get here then lineId is 0, so this is the
\ horizon

JSR ProcessHorizonLine \ Process the horizon and set showLine accordingly

RTS                    \ Return from the subroutine

Name: ProcessLine (Part 2 of 7)                               [Show more]
Type: Subroutine
Category: Visibility
Summary: Process runway lines
Deep dive: Visibility checks
Context: See this subroutine on its own page
References: No direct references to this subroutine in this source file

.plin1

\ If we get here then lineId is in the range 1 to 11,
\ so this is a runway line

JSR ProcessRunwayLine  \ Process the runway line and set showLine accordingly

JMP plin19             \ Jump down to plin19 to check the line's z-coordinates
\ and return the final visibility result

Name: ProcessLine (Part 3 of 7)                               [Show more]
Type: Subroutine
Category: Visibility
Summary: If a line is part of a multi-point object, extract the other
points in the line so we can check them all
Deep dive: Visibility checks
Context: See this subroutine on its own page
References: No direct references to this subroutine in this source file

.plin2

\ If we get here, lineId >= 12 and Y contains the point
\ ID for the line's start point
\
\ We now run through the following process twice, first
\ for the line's start point, and then for the line's
\ end point

LDA #2                 \ Set pointCount = 2 to act as a counter so we can check
STA pointCount         \ the start point first, and then the end point

.plin3

LDA pointStatus,Y      \ If bit 7 of the point's status byte is clear, then the
BPL plin4              \ point has not yet had its coordinates and visibility
\ calculated, so skip the following instruction to move
\ on to those calculations

JMP plin17             \ Bit 7 of the point's status byte is set, which means
\ we have already calculated this point's coordinates
\ and visibility, so jump to plin17 to do the final
\ distance and z-coordinate tests

.plin4

\ We get here if we haven't already calculated the
\ coordinates and visibility for the point whose ID is
\ in Y, so that's what we do now

TYA                    \ Store the point ID of this point on the stack and in
PHA                    \ pointId. We are about to check whether this point is
STA pointId            \ part of a multi-point object, and if so we're going to
\ add the IDs of all the other points in the object to
\ the stack and then work our way through the stacked
\ values, processing each of them in turn
\
\ Adding the starting point ID to the stack and to
\ pointId at the same time lets us use this ID as a
\ backstop - in other words, we'll know we have
\ processed all the other point that we added to the
\ stack when we pull a value off the stack that matches
\ the value of pointId (see parts 5 and 6 to see this in
\ action)

.plin5

LDA objectPoints,Y     \ Fetch this point's entry from objectPoints, which
\ will tell us if this point is related to any other
\ points as part of a multi-point object

CMP #40                \ If object ID < 40 then this point does not link to
BCC plin8              \ another point, so it's the last point in a linked
\ object - i.e. the ID of the object itself - so jump to
\ plin8 to process the point and any others we already
\ put on the stack

\ If we get here then this point links to another point
\ in objectPoints, so we now follow the links and add
\ all of the point IDs to the stack and to the
\ relatedPoints list, looping back until we reach the
\ last point, at which point we jump to plin8 with a
\ stack full of points

SEC                    \ Subtract 40 from A to get the point ID of the new
SBC #40                \ point to check

STA objectAnchorPoint  \ Store the new point's ID as the object's anchor point,
\ so it contains the ID of the last point before the
\ object ID at the end of the linked list of points

TAY                    \ Copy the new point's ID into Y so we can use it as an
\ index into pointStatus

LDA pointStatus,Y      \ If bit 7 of the new point's status byte is set, then
BMI plin14             \ we have already calculated the coordinates and
\ visibility for this new point, which means we have
\ also done the rest of the points in the linked object,
\ so jump down to plin14 to check the new points we
\ added to the stack

TYA                    \ Set A = the new point's ID

PHA                    \ Store the new point's ID on the stack

LDX relatedPoints      \ If relatedPoints >= 49, then the relatedPoints list is
CPX #49                \ full, so jump to plin11 to set this line as not being
BCS plin11             \ visible

INC relatedPoints      \ Increment the value in relatedPoints, which contains
\ the size of the list, as we are about to add a new
\ entry to the end of the list

LDX relatedPoints      \ Add A, the new point's ID, to the end of the
STA relatedPoints,X    \ relatedPoints list

BNE plin5              \ Jump back to plin5 to recurse through the new point
\ (this BNE is effectively a JMP as A is never zero,
\ because objectPoints does not contain a value of 40)

Name: ProcessLine (Part 4 of 7)                               [Show more]
Type: Subroutine
Category: Visibility
Summary: Process bullet lines
Deep dive: Visibility checks
Context: See this subroutine on its own page
References: No direct references to this subroutine in this source file

.plin6

\ If we get here then the point is not part of a linked
\ object, and object ID is in the range 12 to 15, so
\ this is a bullet line

PLA                    \ Pull the point ID from the stack and store it in GG
STA GG

LDA objectStatus,Y     \ If bit 7 of the object's status byte is set, jump to
BMI plin7              \ plin16 via plin7

\ If we get here then bit 7 of the object's status byte
\ is clear

LDA #%10000000         \ Set showLine to say that the line is not in view
STA showLine

RTS                    \ Return from the subroutine

.plin7

JMP plin16             \ Jump down to plin16 with the bullet point ID in GG, to
\ check distance and z-coordinates and return the final
\ result

Name: ProcessLine (Part 5 of 7)                               [Show more]
Type: Subroutine
Category: Visibility
Summary: Calculate the object's coordinates and visibility
Deep dive: Visibility checks
Context: See this subroutine on its own page
References: No direct references to this subroutine in this source file

.plin8

\ By the time we get here, Y contains the last entry
\ from objectPoints for this multi-point object (so it
\ contains the object ID), and any previous points from
\ the objectPoints entry are on the stack

TAY                    \ Store the object ID in objectId
STY objectId

CMP #16                \ If the object ID >= 16, jump to plin9 to skip the
BCS plin9              \ following

CMP #12                \ If the object ID >= 12, then the object ID is in the
BCS plin6              \ range 12 to 15, which means it's a bullet line, so
\ jump up to plin6 to work out its visibility

.plin9

LDA objectStatus,Y     \ Fetch the object's status byte

AND #%01000000         \ If bit 6 of the object's status byte is set then we
BNE plin10             \ have already set this object's coordinates in this
\ iteration of the main loop, so skip the following

JSR SetObjectCoords    \ Calculate the object's coordinates and visibility,
\ updating the object's status byte with the results
\ and setting isObject to the object ID

.plin10

LDY objectId           \ Fetch the object ID from objectId

LDA objectStatus,Y     \ If bit 7 of the object's status byte is set, then the
BMI plin13             \ object is visible, so jump to plin13 to set the anchor
\ point and work our way through the points on the stack

.plin11

LDA #%10000000         \ Set showLine to say that the line is not in view
STA showLine

.plin12

PLA                    \ Pull numbers off the stack until we reach the first
CMP pointId            \ number that we put on, which will match pointId, so
BNE plin12             \ by the time we are done we have removed all the IDs
\ we added to the stack during the routine

RTS                    \ Return from the subroutine

.plin13

TYA                    \ Set objectAnchorPoint = object ID + 216
CLC                    \
ADC #216               \ so it contains the point ID associated with the anchor
STA objectAnchorPoint  \ point of this object

Name: ProcessLine (Part 6 of 7)                               [Show more]
Type: Subroutine
Category: Visibility
Summary: Check the visibility of all the object's points on the stack
Deep dive: Visibility checks
Context: See this subroutine on its own page
References: No direct references to this subroutine in this source file

.plin14

\ We jump straight here if we are working through a
\ linked object and come across a point with bit 7 set
\ in the point's status byte (which means we have
\ already processed the rest of the points in the
\ linked object), otherwise we got here after processing
\ the object in the previous part

\ We now loop through any points we have added to the
\ stack and process them all. If we find any that are
\ not visible, then we return the result that the whole
\ line is not visible

PLA                    \ Pull the next point ID from the stack

CMP pointId            \ If the point ID matches pointId, then we have no other
BEQ plin15             \ points on the stack to process, so jump down to plin15
\ to calculate its visibility

STA GG                 \ Store the point ID in GG so its coordinates get
\ calculated in the call to SetObjPointCoords

LDA #0                 \ Set matrixAxis = 0 (this has no effect, as this value
STA matrixAxis         \ is only used when setting the matrices, so this is
\ presumably left over from a version of the code that
\ supported multiple sets of matrices)

STA matrixNumber       \ Set the matrix number so the call to SetObjPointCoords
\ uses matrix 1 in the calculation, which will rotate
\ the point by the plane's pitch, roll and yaw angles,
\ transforming it from the outside world's frame of
\ reference to the plane's frame of reference

JSR SetObjPointCoords  \ Calculate the coordinates for this object point

LDA showLine           \ If showLine is non-zero, then the point is not
BNE plin12             \ visible, so jump to plin12 to clear down the stack and
\ return from the subroutine

LDY GG                 \ Set objectAnchorPoint = the point ID in GG, so it
STY objectAnchorPoint  \ contains the ID of the last point before the object ID
\ at the end of the linked list of points (as the last
\ ID, the object ID, is processed by plin15 below)

LDA #%10000000         \ Set bit 7 of the point's status byte, to indicate that
ORA pointStatus,Y      \ the point has now had its coordinates and visibility
STA pointStatus,Y      \ calculated

BNE plin14             \ Jump to plin14 (this BNE is effectively a JMP as A is
\ never zero)

.plin15

STA GG                 \ Store the point ID in GG

LDA #0                 \ Set matrixAxis = 0 (this has no effect, as this value
STA matrixAxis         \ is only used when setting the matrices, so this is
\ presumably left over from a version of the code that
\ supported multiple sets of matrices)

STA matrixNumber       \ Set the matrix number so the call to SetObjPointCoords
\ uses matrix 1 in the calculation, which will rotate
\ the point by the plane's pitch, roll and yaw angles,
\ transforming it from the outside world's frame of
\ reference to the plane's frame of reference

JSR SetObjPointCoords  \ Calculate the coordinates for this object point

LDA showLine           \ If showLine is non-zero, then the line is not visible,
BNE plin20             \ so jump to plin20 to return from the subroutine

Name: ProcessLine (Part 7 of 7)                               [Show more]
Type: Subroutine
Category: Visibility
Summary: Check distance and z-coordinates and return the final result
Deep dive: Visibility checks
Context: See this subroutine on its own page
References: No direct references to this subroutine in this source file

.plin16

LDY GG                 \ Set Y to the point ID, which we stored in GG before
\ jumping here or falling through from above

.plin17

\ We jump straight here if line ID >= 12 and bit 7 of
\ the point's status byte is set (which means we have
\ already processed this point)

LDA HH                 \ If HH is non-zero, jump to plin18 to move on to the
BNE plin18             \ end point and then check the z-coordinates

LDX lineId             \ Check whether this line ID is close enough to be
JSR CheckLineDistance  \ visible (so it gets hidden if it is further away than
\ the visibility distance for this line)

STA showLine           \ The above call returns 1 if the line is too far away,
\ or the previous value of showLine if it is close
\ enough to be shown, so store the updated response in
\ showLine

BNE plin20             \ If the response is non-zero then the line is not
\ visible and showLine contains a non-zero response, so
\ jump to plin20 to return from the subroutine

\ Otherwise the line is close enough to be visible and
\ has passed all the other visibility checks so far, so
\ now we check the z-coordinates

.plin18

LDA #%10000000         \ Set bit 7 of the point's status byte, to indicate that
ORA pointStatus,Y      \ the point has now had its coordinates and visibility
STA pointStatus,Y      \ calculated

DEC pointCount         \ Decrement pointCount so we check the end point next

BEQ plin19             \ If pointCount = 0 then we have checked both the start
\ and end point, so jump to plin19 to check the line's
\ z-coordinates

\ If we get here then we have checked the start point,
\ so we now loop back to check the end point

LDY M                  \ Set M to the point ID for the line's end point, so
\ when we run through the process above, we do it for
\ the end point instead of the start point

JMP plin3              \ Jump back to plin3 to check the visibility of the end
\ point

.plin19

LDY M                  \ If the end point's z-coordinate is positive, jump to
LDA zPointHi,Y         \ plin20 to return from the subroutine
BPL plin20

LDY L                  \ If the start point's z-coordinate is positive, jump to
LDA zPointHi,Y         \ plin20 to return from the subroutine
BPL plin20

\ If we get here then both the start and end point have
\ negative z-coordinates, so they are both behind us and
\ the line is therefore not visible

LDA showLine           \ Set bit 7 of showLine to indicate that the line is not
ORA #%10000000         \ visible
STA showLine

LDY isObject           \ If we processed an object above, then its ID will be
\ in isObject, so fetch this into Y for the following
\ call

JSR NextObjectGroup    \ If we just processed an object in an object group,
\ i.e. isObject is 6, 7, 8 or 9, then increment the
\ object's group number

.plin20

RTS                    \ Return from the subroutine

Name: SetObjectCoords (Part 1 of 11)                          [Show more]
Type: Subroutine
Category: 3D geometry
Summary: Calculate the coordinates for an object's location
Deep dive: 3D objects
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* ProcessLine (Part 5 of 7) calls SetObjectCoords
* ProcessRunwayLine (Part 2 of 5) calls SetObjectCoords
* UpdateBullets calls SetObjectCoords

Arguments:

objectId             The ID of the object to process (1 to 39)

GG                   For bullets only (12, 13, 14 or 15), the point ID for
the bullet's anchor point

Returns:

objectStatus         The object's status byte: bits 6 and 7 affected

Flags                Set according to the object's updated status byte

isObject             The object ID, i.e. a non-zero value so callers of this
routine will know we just processed an object

xObjectHi etc.       Set to the object's coordinates, relative to the plane

xPointHi etc.        The object's anchor point:

* For bullets, point GG is updated with the bullet's
anchor point

* For other objects, point objectId+216 is updated
with the object's anchor point

.SetObjectCoords

LDY objectId           \ Set Y to the object ID

STY isObject           \ Store the object ID in isObject, so callers of this
\ routine will know we just processed an object

TYA                    \ Set N = 216 + object ID
CLC                    \
ADC #216               \ so N is in the range 217 to 255 and is the point ID
STA N                  \ that we use for storing the object's anchor point

LDA #1                 \ Set K = 1, so the CheckObjDistance routine returns
STA K                  \ the correct 0 or 1 value when checking an object's
\ distance, which is the default behaviour (we set K to
\ 0 below for bullets only, as we always want them to
\ remain visible at distance)

\ We now check for some specific objects:
\
\   * Object groups, i.e. trees (6, 7), hills (8, 9)
\   * Bullets (12, 13, 14 or 15)
\   * Feeding aliens (object group 30)
\   * Flying aliens (31, 32 or 33)
\
\ so we can pre-process them before moving on to the
\ main processing routine in part 8

CPY #12                \ If the object ID < 12, jump to objc1 to check the next
BCC objc1              \ range

CPY #16                \ If the object ID >= 16, jump to objc1 to check the
BCS objc1              \ next range

Name: SetObjectCoords (Part 2 of 11)                          [Show more]
Type: Subroutine
Category: 3D geometry
Summary: Pre-process the bullets (objects 12, 13, 14 or 15)
Deep dive: 3D objects
Context: See this subroutine on its own page
References: No direct references to this subroutine in this source file

\ If we get here then the object ID is 12, 13, 14 or 15,
\ so this is a bullet object
\
\ There are two bullet trails, with objects 13 and 15 at
\ the head of the trail (i.e. the bullets), and objects
\ 12 and 14 at the back of the bullet trail

LDA #0                 \ Set K = 0, so the CheckObjDistance routine always
STA K                  \ returns 0, which means the bullet trails never get
\ too far away to be hidden

LDA yObjectHi,Y        \ If the object's y-coordinate is positive, then the
BPL objc3              \ bullet is above ground, so jump to objc3 to check the
\ next range and then process the bullet

TYA                    \ If bit 0 of the object ID is set, i.e. the object ID
AND #1                 \ is 13 or 15, jump to objc3 to check the next range
BNE objc3              \ and then process the bullet (so objects 13 and 15 can
\ be below ground while 12 and 14 are above ground, in
\ which case the bullets are still in play)

JMP objc10             \ The object ID is 12 or 14 (the back end of the bullet
\ trail) and the object is below ground level, so jump
\ to objc10 to tidy up and return from the subroutine,
\ as the bullets have hit the ground

Name: SetObjectCoords (Part 3 of 11)                          [Show more]
Type: Subroutine
Category: 3D geometry
Summary: Logic for checking which objects to pre-process
Deep dive: 3D objects
Context: See this subroutine on its own page
References: No direct references to this subroutine in this source file

.objc1

\ If we get here then this is not a bullet

LDA N                  \ Set GG = N
STA GG                 \        = 216 + object ID
\
\ so GG contains the point ID that we use for storing
\ the object's anchor point

CPY #6                 \ If the object ID < 6, jump to objc3 to check the next
BCC objc3              \ range

CPY #10                \ If the object ID >= 10, jump to objc3 to check the
BCS objc3              \ next range

\ Otherwise the object ID is 6, 7, 8 or 9, so fall
\ through into part 4 to pre-process the object

Name: SetObjectCoords (Part 4 of 11)                          [Show more]
Type: Subroutine
Category: 3D geometry
Summary: Pre-process the object groups (objects 6, 7, 8 or 9)
Deep dive: 3D objects
Context: See this subroutine on its own page
References: No direct references to this subroutine in this source file

\ If we get here then the object ID is 6, 7, 8 or 9, so
\ this is an object group (e.g. a tree)

LDA #8                 \ Set objCount = 8 to act as a counter in the loop
STA objCount           \ below, so we work through all 8 objects in this object
\ group

.objc2

LDX objectGroup-6,Y    \ Set X = 0, 8, 16 or 24, for Y = 6, 7, 8 or 9
\
\ (or 1, 9, 17, 25 etc., depending on the current group)

LDA xGroupObjectHi,X   \ Set the object's x-coordinate to the X-th entry in
STA xObjectHi,Y        \ xGroupObjectHi, which contains the high byte of the
\ group object's x-coordinate

LDA zGroupObjectHi,X   \ Set the object's z-coordinate to the X-th entry in
STA zObjectHi,Y        \ zGroupObjectHi, which contains the high byte of the
\ group object's z-coordinate

JMP objc9              \ Jump to objc9 to process this object

Name: SetObjectCoords (Part 5 of 11)                          [Show more]
Type: Subroutine
Category: 3D geometry
Summary: Logic for checking which objects to pre-process
Deep dive: 3D objects
Context: See this subroutine on its own page
References: No direct references to this subroutine in this source file

.objc3

CPY #34                \ If the object ID >= 34, jump to objc9 to process the
BCS objc9              \ object

CPY #30                \ If the object ID < 30, jump to objc9 to process the
BCC objc9              \ object

BNE objc6              \ If the object ID <> 30, i.e. 31, 32 or 33, jump to
\ objc6 to pre-process the object

\ Otherwise the object ID is 30, so fall through into
\ part 6 to pre-process object 30

Name: SetObjectCoords (Part 6 of 11)                          [Show more]
Type: Subroutine
Category: 3D geometry
Summary: Pre-process dormant aliens (object group 30)
Deep dive: 3D objects
Context: See this subroutine on its own page
References: No direct references to this subroutine in this source file

\ If we get here then the object ID is 30

LDA themeStatus        \ If themeStatus is non-zero then either the Theme is
BNE objc8              \ not enabled, or it is enabled but we haven't yet added
\ all eight aliens to the current wave, so jump to
\ objc8 to return from the subroutine

LDA #8                 \ Set objCount = 8 to act as a counter in the loop
STA objCount           \ below, so we work through all eight aliens in this
\ group

\ As we loop through the following, alienSlot gets
\ incremented so we work our way through all the aliens
\ in the slot

.objc4

LDX alienSlot          \ Fetch the contents of the slot for alien 30, so X now
\ contains the number of the alien in that slot (0 to 7)

LDA alienState,X       \ If the alien's state is non-zero, jump to objc11 via
BNE objc7              \ objc7 to move on to the next alien in the group, as
\ the alien is feeding

LDA alienObjectId,X    \ Set A to the object ID for the alien

BPL objc5              \ If A is positive, jump to objc5... which has no
\ effect, as that's the next instruction anyway

.objc5

TAX                    \ Copy the alien's x-coordinate to the current object's
LDA xObjectHi,X        \ x-coordinate
STA xObjectHi,Y

LDA zObjectHi,X        \ Copy the alien's z-coordinate to the current object's
STA zObjectHi,Y        \ x-coordinate

JMP objc9              \ Jump to objc9 to process this object, after which we
\ return to the above from part 10 to process the next
\ alien (with alienSlot incremented)

Name: SetObjectCoords (Part 7 of 11)                          [Show more]
Type: Subroutine
Category: 3D geometry
Summary: Pre-process feeding and flying aliens (objects 31, 32 and 33)
Deep dive: 3D objects
Context: See this subroutine on its own page
References: No direct references to this subroutine in this source file

.objc6

\ If we get here then the object ID is 31, 32 or 33

LDX alienSlot-30,Y     \ If the slot for alien Y contains a negative number,
BMI objc7              \ then the slot is empty, so jump to objc11 via objc7
\ to return from the subroutine

LDA alienState,X       \ If the alien's state is >= 27, then it is heading for
CMP #27                \ Acornsville, so jump to objc9 to process this object
BCS objc9

LDA alienObjectId,X    \ Set A to the object ID for this alien

BPL objc5              \ If A is positive, jump to objc5 to set the object
\ coordinates and process this object

.objc7

JMP objc11             \ Jump to objc11 to move on to the next alien in the
\ group (for object 30) or return from the subroutine
\ (for objects 31 to 33)

.objc8

JMP objc12             \ Jump to objc12 to return from the subroutine

Name: SetObjectCoords (Part 8 of 11)                          [Show more]
Type: Subroutine
Category: 3D geometry
Summary: Process the object
Deep dive: 3D objects
Context: See this subroutine on its own page
References: No direct references to this subroutine in this source file

.objc9

\ By this point we have pre-processed any special
\ objects, so now to process it

\ We now translate the base location of the object,
\ in (xObject, yObject, zObject), by subtracting the
\ current plane coordinates (xPlane, altitude, zPlane)
\ to give the vector from the plane's location to the
\ object's location

LDX GG                 \ Set X to the value of GG:
\
\   * For bullets, this is the value of GG that is
\     passed to the routine, i.e. 98
\
\   * For other objects, this is 216 + object ID, so
\     points 216 to 255 contain the calculated
\     coordinates for objects 0 to 39
\
\ So in the following, we set the coordinates of the
\ point whose ID is in GG, i.e. point GG

\ We do the following calculation with 24-bit values,
\ so we can do the visibility checks. This means we
\ calculate:
\
\   xPoint = xObject - xPlane
\   yPoint = yObject - yPlane
\   zPoint = zObject - zPlane
\
\ but in each case, the values have three bytes. To take
\ the x-axis calculation:
\
\   xPoint  is (A xPointHi xPointLo)
\   xObject is (0 xObjectHi xObjectLo)
\   xPlane  is (xPlaneTop xPlaneHi xPlaneLo)
\
\ and we pass the top byte of the result, in A, to the
\ CheckObjDistance routine, so it can be used in the
\ visibility check (a high value in A means the object
\ is a very long way away)

SEC                    \ Set the x-coordinate of point GG:
LDA xObjectLo,Y        \
SBC xPlaneLo           \   xPoint = xObject - xPlane
STA xPointLo,X         \
\ starting with the low bytes

LDA xObjectHi,Y        \ And then the high bytes
SBC xPlaneHi
STA xPointHi,X

STA T                  \ Set T = xPointHi to pass to CheckObjDistance

LDA #0                 \ And then the top bytes
SBC xPlaneTop

JSR CheckObjDistance   \ Check whether the object is close enough to be visible
\ in the direction of the x-axis

BNE objc10             \ If it is too far away to be visible, jump to objc10 to
\ either move on to the next object in the group, or
\ tidy up and return from the subroutine

SEC                    \ Set the y-coordinate of point GG:
LDA yObjectLo,Y        \
SBC yPlaneLo           \   yPoint = yObject - yPlane
STA yPointLo,X         \
\ starting with the low bytes

LDA yObjectHi,Y        \ And then the high bytes
SBC yPlaneHi
STA yPointHi,X

STA T                  \ Set T = yPointHi to pass to CheckObjDistance

LDA #0                 \ And then the top bytes
SBC yPlaneTop

JSR CheckObjDistance   \ Check whether the object is close enough to be visible
\ in the direction of the y-axis

BNE objc10             \ If it is too far away to be visible, jump to objc10 to
\ either move on to the next object in the group, or
\ tidy up and return from the subroutine

SEC                    \ Set the z-coordinate of point GG:
LDA zObjectLo,Y        \
SBC zPlaneLo           \   zPoint = zObject - zPlane
STA zPointLo,X         \
\ starting with the low bytes

LDA zObjectHi,Y        \ And then the high bytes
SBC zPlaneHi
STA zPointHi,X

STA T                  \ Set T = zPointHi to pass to CheckObjDistance

LDA #0                 \ And then the top bytes
SBC zPlaneTop

JSR CheckObjDistance   \ Check whether the object is close enough to be visible
\ in the direction of the z-axis

BNE objc10             \ If it is too far away to be visible, jump to objc10 to
\ either move on to the next object in the group, or
\ tidy up and return from the subroutine

LDA #0                 \ Set the matrix number so the call to SetPointCoords
STA matrixNumber       \ uses matrix 1 in the calculation, which will rotate
\ the point by the plane's pitch, roll and yaw angles,
\ transforming it from the outside world's frame of
\ reference to the plane's frame of reference

JSR SetPointCoords     \ Rotate the coordinates for point GG:
\
\   * For bullets, this is point 98
\
\   * For other objects, this is 216 + object ID, so
\     points 216 to 255 will contain the calculated
\     coordinates for objects 0 to 39
\
\ and update the coordinates in (xPoint, yPoint, zPoint)

LDY objectId           \ Fetch the object ID into Y

LDA showLine           \ If showLine is non-zero, which means the line is not
BNE objc12             \ visible, jump to objc12 to return from the subroutine
\ without setting bit 7 of the object's status byte

LDA #%11000000         \ Set A = %11000000 to set bits 6 and 7 of the object's
\ status byte, to denote that the object has had its
\ coordinates calculated, and that it is visible

BNE objc13             \ Jump to objc13 to set the object's status byte and
\ return from the subroutine (this BNE is effectively a
\ JMP as A is never zero)

Name: SetObjectCoords (Part 9 of 11)                          [Show more]
Type: Subroutine
Category: 3D geometry
Summary: Process the next object in the group, if applicable, or return
from the subroutine if not
Deep dive: 3D objects
Context: See this subroutine on its own page
References: No direct references to this subroutine in this source file

.objc10

\ If we get here then we are done processing this
\ object, so now we check whether there are other
\ objects to process:
\
\   * If the object is in a group (i.e. the object ID
\     is 6, 7, 8 or 9) and there are other objects to
\     process in the group, we loop back to process them
\
\   * If this is object 30, then we check whether we
\     have processed all 8 items in its group, and if
\     not, we loop back to process the next one (see
\     part 10)
\
\   * If this object is not caught by the above, then
\     we finally return from the subroutine (see part
\     11)

JSR NextObjectGroup    \ If this object is in an object group, i.e. Y is 6, 7,
\ 8 or 9, then increment the object's group number

BCC objc11             \ If the C flag is clear then this object is not part of
\ an object group, so jump to objc11 to skip the
\ following

\ This object is part of an object group, so now we move
\ onto the next object in the group

DEC objCount           \ Decrement the loop counter to move on to the next
\ object in the group

BEQ objc12             \ If we have processed all 8 items in the group, jump
\ to objc12 to return from the subroutine

JMP objc2              \ Otherwise loop back to objc2 to process the next item
\ in the group

Name: SetObjectCoords (Part 10 of 11)                         [Show more]
Type: Subroutine
Category: 3D geometry
Summary: Process the next dormant alien (object group 30)
Deep dive: 3D objects
Context: See this subroutine on its own page
References: No direct references to this subroutine in this source file

.objc11

CPY #30                \ If the object ID <> 30, jump to objc12 to return from
BNE objc12             \ the subroutine

LDA alienSlot          \ Increment alienSlot to iterate through values 0 to 7
CLC                    \ and round again
AND #7
STA alienSlot

DEC objCount           \ Decrement the loop counter to move on to the next
\ alien in the group

BEQ objc12             \ If we have processed all 8 items in the group, jump
\ to objc12 to return from the subroutine

JMP objc4              \ Otherwise loop back to objc4 to process the next item
\ in the group

Name: SetObjectCoords (Part 11 of 11)                         [Show more]
Type: Subroutine
Category: 3D geometry
Summary: Update the object status and return from the subroutine
Deep dive: 3D objects
Context: See this subroutine on its own page
References: No direct references to this subroutine in this source file

.objc12

LDA #%01000000         \ Set A = %01000000 to set bit 6 of the object's status
\ byte, to denote that the object has had its
\ coordinates calculated

.objc13

ORA objectStatus,Y     \ Set the bits of the object's status byte as per the
STA objectStatus,Y     \ value in A

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Visibility
Summary: Check whether an object is within the visible distance for that
object, along just one axis
Deep dive: Visibility checks
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* SetObjectCoords (Part 8 of 11) calls CheckObjDistance

We call this routine by passing in the distance from the plane to the object,
along the axis we are checking. We pass it as the top two bytes of a 24-bit
number, (A T x), containing the object's coordinate in the relevant axis. We
can ignore the low byte as it doesn't affect object visibility.

First we check the top byte in A to make sure it is either 0 or -1, as
otherwise the distance is definitely too far for the object to be visible.

Assuming the top byte is within range, we then check the high byte in T
against the maximum visible distance for the object in question. The object
is visible only if |T| < the object's maxObjDistance value, otherwise it is
too far away to be seen.

When called with K = 0, this routine will always return 0 to indicate that the
object is visible. This is used by the bullets, to ensure that they remain
visible, whatever their distance from the plane.

Arguments:

Y                    The object ID of the object to check

A                    The top byte of the object's coordinates in the axis we
want to check (i.e. the byte above xPointHi, yPointHi or
zPointHi)

T                    The high byte the object's coordinates in the axis we
want to check (i.e. xPointHi, yPointHi or zPointHi)

K                    The non-zero return value (see below), so setting this
to 0 will ensure that the object is always reported as
being visible

Returns:

A                    Contains the result as follows:

* 0 if the object is close enough to be visible

* K if the object is too far away to be visible

.CheckObjDistance

BPL objd1              \ If A is positive, jump to objd1

CMP #&FF               \ If A <> -1, jump to objd3 as the object is too far
BNE objd3              \ away to be visible

\ If we get here, then A = -1, so now we need to check
\ the middle byte against the object's maximum visible
\ distance, though we need to negate the middle byte
\ first, as the coordinate is negative and the values in
\ maxObjDistance are positive

LDA T                  \ Set A = ~T to negate it
EOR #&FF

JMP objd2              \ Jump to objd2 to check against the object's maximum
\ visible distance

.objd1

\ If we get here, then A is positive

BNE objd3              \ If A is non-zero, jump to objd3 as the object is too
\ far away to be visible

\ If we get here then A = 0, so now we need to check the
\ middle byte against the object's maximum visible
\ distance

LDA T                  \ Set A = T so we check the middle byte in the following

.objd2

CMP maxObjDistance,Y   \ If A >= the object's maximum visible distance, then
BCS objd3              \ the object is too far away to be visible, so jump to
\ objd3

\ If we get here then we want to return a "visible"
\ result

LDA #0                 \ Set A = 0 as the return value

RTS                    \ Return from the subroutine

.objd3

\ If we get here then we want to return a "not visible"
\ result

LDA K                  \ Set A = K as the return value

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Visibility
Summary: Reset the line lists at linesToShow and linesToHide
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* NewGame calls ResetLineLists

.ResetLineLists

LDA #%10000000         \ Set colourLogic = %10000000
STA colourLogic

LDA #%00001111         \ Set colourCycle = %00001111, so we show colour 1 and
STA colourCycle        \ hide colour 2 in the canopy view

JSR ModifyDrawRoutine  \ Modify the drawing routines so we draw canopy lines in
\ colour 1 (as colourLogic = %10000000)

LDA #0                 \ Set lineId = 0 to act as a loop counter below
STA lineId

STA linesToShowEnd     \ Set linesToShowEnd = 0 to reset the linesToShow list

LDA #255               \ Set linesToHideEnd = 255 to reset the linesToHide list
STA linesToHideEnd

STA linesToShowPointer \ Set linesToShowPointer = 255 to reset the progress
\ pointer for the linesToShow list

STA linesToHidePointer \ Set linesToHidePointer = 255 to reset the progress
\ pointer for the linesToHide list

.rell1

JSR ShowOrHideLine     \ Process the line with ID lineId, adding it to either
\ the linesToShow or the linesToHide list

INC lineId             \ Increment lineId to move on to the next line

LDA lineId             \ Loop back until we have processed all 193 lines
CMP numberOfLines
BCC rell1

LDX #3                 \ Set logical colour 3 to white so the dashboard display
JSR SetColourToWhite   \ shows up in white

\ Fall through into FlipColours to flip the values of
\ colourCycle and colourLogic to cycle to the next
\ colour state, so we end up with drawing routines that
\ draw in colour 1, while colourLogic = %01000000 and
\ colourCycle = %11110000

Type: Subroutine
Category: Graphics
Summary: Flip the values of colourCycle and colourLogic to cycle to the
next colour state
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* DrawCanopyView calls FlipColours

This routine flips the colourCycle and colourLogic variables between these two
states:

* colourLogic = %10000000
colourCycle = %00001111 = show colour 1, hide colour 2

* colourLogic = %01000000
colourCycle = %11110000 = show colour 2, hide colour 1

The routine only checks the value of colourCycle, so if colourLogic is
%00000000 on entry, it will be set to one of %10000000 and %01000000, so we
can use this routine to set the correct state after erasing lines during the
colourLogic %00000000 phase.

.FlipColours

LDX #%00001111         \ Set the values of X and Y to use if bit 7 of
LDY #%10000000         \ colourCycle is set, i.e. %11110000

LDA colourCycle        \ If bit 7 of colourCycle is set, i.e. %11110000, jump
BMI flip1              \ down to flip1

LDX #%11110000         \ Set X and Y for when bit 7 of colourCycle is clear,
LDY #%01000000         \ i.e. %00001111

.flip1

STX colourCycle        \ Store X in colourCycle, so colourCycle is now:
\
\   * %11110000 if the old colourCycle was %00001111
\   * %00001111 if the old colourCycle was %11110000

STY colourLogic        \ Store Y in colourLogic, so colourLogic is now:
\
\   * %01000000 if the old colourCycle was %00001111
\   * %10000000 if the old colourCycle was %11110000

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Graphics
Summary: Set a logical colour to white
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* DrawCanopyView calls SetColourToWhite
* ResetLineLists calls SetColourToWhite

Arguments:

X                    The logical colour to set to white

.SetColourToWhite

LDY #7                 \ Set Y = 7 so we set the logical colour to physical
\ colour 7 (white)

BNE SetLogicalColour   \ Jump to SetLogicalColour to set the logical colour in
\ X to white (this BNE is effectively a JMP as Y is
\ never zero

Type: Subroutine
Category: Graphics
Summary: Set a logical colour to black
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* DrawCanopyView calls SetColourToBlack

Arguments:

X                    The logical colour to set to black

.SetColourToBlack

LDY #0                 \ Set Y = 0 so we set the logical colour to physical
\ colour 7 (black)

\ Fall through into SetLogicalColour to set the logical
\ colour in X to black

Type: Subroutine
Category: Graphics
Summary: Set a logical colour to a physical colour
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* SetColourToWhite calls SetLogicalColour

Arguments:

X                    The logical colour to set

Y                    The physical colour to map to the logical colour

.SetLogicalColour

LDA #19                \ Start a VDU 19 command, which sets a logical colour to
JSR OSWRCH             \ a physical colour using the following format:
\
\   VDU 19, logical, physical, 0, 0, 0

TXA                    \ Write the value in X, which is the logical colour
JSR OSWRCH

TYA                    \ Copy the physical colour from Y to A

LDX #3                 \ Set a counter in X to write the next four values, so
\ the following loop writes:
\
\   physical, 0, 0, 0

.setl1

JSR OSWRCH             \ Write the value in A

LDA #0                 \ Set A to 0 to write the three zeroes

DEX                    \ Decrement the loop counter

BPL setl1              \ Loop back until we have written the whole VDU command

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Graphics
Summary: Draw the main view out of the canopy
Deep dive: Flicker-free animation through colour cycling
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* MainLoop (Part 13 of 15) calls DrawCanopyView

.DrawCanopyView

JSR ModifyDrawRoutine  \ Modify the drawing routines to use the current colour
\ cycle

LDA linesToShowEnd     \ If linesToShowEnd is non-zero, jump to view1 to skip
BNE view1              \ the following instruction, as there are lines in the
\ linesToShow list that we need to draw

BEQ view6              \ linesToShowEnd is zero, which means the linesToShow
\ list is empty, so there is nothing to draw, so we
\ jump down to view6 to flip the colours (this BEQ is
\ effectively a JMP as we know A is zero)

.view1

LDA #0                 \ Set lineCounter = 0 to act as a counter in the
STA lineCounter        \ following loop, where we loop through the linesToShow
\ list

.view2

TAX                    \ Set lineId to the next line ID from the linesToShow
LDA linesToShow,X      \ list
STA lineId

BNE view3              \ If lineId is non-zero, jump to view3, as this is not
\ the horizon

JSR DrawHalfHorizon    \ The line ID is 0, which is the horizon, so draw half
\ of the horizon line (as only half of the horizon is
\ stored in line 0)

LDA lineId             \ Retrieve the line's ID, as it will have been corrupted
\ by the call to DrawHalfHorizon, and fall through into
\ view3 to draw the other half of the horizon

.view3

TAX                    \ Set M to the point ID for the line's end point
LDY lineEndPointId,X
STY M

LDA #0                 \ Zero the end point's status byte, so it is no longer
STA pointStatus,Y      \ marked as having had its coordinates and visibility
\ calculated

LDY lineStartPointId,X \ Set L to the point ID for the line's start point
STY L

STA pointStatus,Y      \ Zero the start point's status byte, so it is no longer
\ marked as having had its coordinates and visibility
\ calculated

JSR DrawClippedLine    \ Draw the line, clipping it to the canopy view if
\ required

INC lineCounter        \ Increment the loop counter

LDA lineCounter        \ Loop back to draw the next line from the linesToShow
CMP linesToShowEnd     \ list
BCC view2

JSR DrawGunSights      \ Draw the gun sights, if shown

\ We now flip the screens between the old screen (which
\ is being shown in white) and the new one (which we
\ just drew in black)

LDA colourCycle        \ If bit 7 of colourCycle is set, i.e. %11110000, jump
BMI view4              \ down to view4 to hide colour 1 and show colour 2

\ If we get here then colourCycle is %00001111

LDX #2                 \ Set logical colour 2 to black, to hide the old canopy
JSR SetColourToBlack   \ view in colour 2

LDX #1                 \ Set logical colour 1 to white, to show the new canopy
JSR SetColourToWhite   \ view that we just drew in colour 1

JMP view5              \ Now that we have cycled the colours, jump down to
\ view5

.view4

\ If we get here then colourCycle is %11110000

LDX #1                 \ Set logical colour 1 to black, to hide the old canopy
JSR SetColourToBlack   \ view in colour 1

LDX #2                 \ Set logical colour 2 to white, to show the new
JSR SetColourToWhite   \ view that we just drew in colour 2

.view5

JSR EraseCanopyLines   \ Erase the lines that are now hidden, and which are
\ stored in the relevant line buffer

.view6

JSR FlipColours        \ Flip the values of colourCycle and colourLogic to
\ cycle to the next colour state

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Visibility
Summary: Calculate coordinates for the horizon's start and end points
Deep dive: Visibility checks
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* ProcessLine (Part 1 of 7) calls ProcessHorizonLine

Arguments:

L                    The point ID of the horizon line's start point

M                    The point ID of the horizon line's end point

.ProcessHorizonLine

LDX M                  \ Set the end point to (0, 0, 0)
JSR SetPointToOrigin

LDX L                  \ Set X = the point ID of the horizon line's start point

JSR SetPointToOrigin   \ Set the start point to (0, 0, 0)

LDA #&F0               \ Set the x-coordinate of the horizon line's start point
STA xPointHi,X         \ to &F000 by setting the high byte

STX GG                 \ Set GG = the point ID of the horizon line's start
\ point

\ We now do the following loop twice, once for the
\ horizon's start point, and again for the end point

.phor1

BIT xRotationHi        \ If bit 7 of xRotation is set, jump to phor3
BMI phor3

BVS phor4              \ If bit 6 of xRotation is set (so bit 7 is clear
\ and bit 6 is set), jump to phor4

.phor2

\ We get here if one of the following is true:
\
\   * Bit 7 of zRotationHi is clear and bit 6 is clear
\   * Bit 7 of zRotationHi is set and bit 6 is set
\
\ either of which means the plane is the correct way up

LDA #40                \ Set A = 40 to set as point's z-coordinate, so we
\ draw the horizon in front of the plane (which is the
\ direction we are looking)

BNE phor5              \ Jump to phor5 to set this as the point's z-coordinate
\ (this BNE is effectively a JMP as A is never zero)

.phor3

BVS phor2              \ If bit 6 of xRotation is set (so both bits 6 and 7

.phor4

\ We get here if one of the following is true:
\
\   * Bit 7 of zRotationHi is clear and bit 6 is set
\   * Bit 7 of zRotationHi is set and bit 6 is clear
\
\ either of which means the plane is upside down

LDA #216               \ Set A = -40 to use as the point's z-coordinate, so
\ we draw the horizon behind the plane (which is the
\ direction we are looking)

.phor5

STA zPointHi,X         \ Set the z-coordinate of the horizon line's start (or
\ end) point to A

LDA #%10000000         \ Set bit 7 of point X's status byte, to indicate that
ORA pointStatus,X      \ the point's coordinates and visibility have been
STA pointStatus,X      \ calculated

LDA #27                \ Set the matrix number so the call to SetPointCoords
STA matrixNumber       \ uses matrix 4 in the calculation, so it rotates the
\ point by the roll and pitch angles, but not the yaw,
\ as rotating a horizon by a yaw angle has no effect

JSR SetPointCoords     \ Calculate the coordinates for point GG, which will
\ be the start or end point of the horizon

CPX M                  \ If we just calculated the coordinates for the horizon
BEQ phor6              \ line's end point, then we have now done both points,
\ so jump to phor6 to return from the subroutine

LDX M                  \ Set GG and X to the point ID for the horizon line's
STX GG                 \ end point

BNE phor1              \ Loop back to phor1 to calculate the coordinates for
\ the end point (this BNE is effectively a JMP as X is
\ never zero)

.phor6

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Dashboard
Summary: Update a blip on the radar (runway or alien)
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* MainLoop (Part 5 of 15) calls UpdateRadarBlip

This routine calculates the screen coordinates for a blip on the radar, which
it then passes to DrawRadarBlip to update the blip.

Arguments:

Y                    The item to update on the radar:

* 1 = update the runway

* 33 = update the alien

LDX #0                 \ Set X = 0 act as an offset in the loop below, so we
\ iterate through the x, y and z axes

STX alien              \ Set alien = 0, to indicate that we should draw the
\ runway for when we fall through into DrawRadarBlip
\ below

CPY #33                \ If Y = 33, skip the following two instructions
BNE blip1

LDA #1                 \ Y = 33, which is the alien, so set alien = 1, to
STA alien              \ indicate that we should draw the alien for when we
\ fall through into DrawRadarBlip below

\ We now loop through the x-, y- and z-axes to do the
\ following, where the object is either the runway or
\ the alien:
\
\   xTemp2 = xObject - xPlane
\   yTemp2 = yObject - yPlane
\   zTemp2 = zObject - zPlane
\
\ so (xTemp2 yTemp2 zTemp2) contains the vector from the
\ plane to the object, which is the same as the vector
\ from the centre of the radar to the blip
\
\ Note that we only bother with the top and high bytes
\ of the calculation (where top, high and low are the
\ bytes in a 24-bit number), as the radar isn't accurate
\ enough to show the low byte, so we can just ignore it
\
\ As the object coordinates don't have a top byte, we
\ use 0 rather than the non-existent xObjectTop
\
\ The loop comments are for the xTemp2 iteration

.blip1

LDA xObjectHi,Y        \ Set (xTemp2Hi xTemp2Lo) to the following:
SEC                    \
SBC xPlaneHi,X         \   (0 xObjectHi) - (xPlaneTop xPlaneHi)
STA xTemp2Lo,X         \
\ starting with the top bytes

LDA #0                 \ and then the high bytes (we don't bother with the low
SBC xPlaneTop,X        \ bytes)
STA xTemp2Hi,X

TYA                    \ Point Y to the next axis (xObject, yObject, zObject)
CLC
TAY

INX                    \ Increment X to move on to the next axis

CPX #3                 \ Loop back until we have done all 3 axes
BNE blip1

\ We now want to calculate the coordinates for this
\ vector when rotated correctly, so we first set up
\ the coordinates, and then rotate them

LDX #LO(xTemp2Lo)      \ Set X so the call to CopyWorkToPoint copies the
\ coordinates from (xTemp2, yTemp2, zTemp2)

LDY #0                 \ Set Y so the call to CopyWorkToPoint copies the
\ coordinates to point 0

STY GG                 \ Set GG = 0

JSR CopyWorkToPoint    \ Copy the coordinates from (xTemp2, yTemp2, zTemp2)
\ to point 0

LDA #0                 \ Set the matrix number so the call to SetPointCoords
STA matrixNumber       \ uses matrix 1 in the calculation, which will rotate
\ the point by the plane's pitch, roll and yaw angles,
\ transforming it from the outside world's frame of
\ reference to the plane's frame of reference

JSR SetPointCoords     \ Calculate the coordinates for point 0

\ We now take the rotated x- and z-coordinates and
\ scale them down so they work as screen coordinates
\ within the range of the radar display (we can ignore
\ the y-coordinate, as the radar is a top-down display
\ that ignores altitude)
\
\ Specifically, we do this by dividing the z-coordinate
\ by 8, and the x-coordinate by 16, and then using the
\ low bytes of the result as the radar coordinates
\ (along the sign bit from the high byte of the result)
\
\ For the z-coordinate, his reduces the value from the
\ range -256 to +256 down to -32 to +32, which maps onto
\ the four character rows above and below the centre of
\ the radar (each of which contains eight pixels, so the
\ vertical range of the radar is -32 to +32 pixels, as
\ 8 * 4 = 32)
\
\ For the x-coordinate, we halve it again as mode 5
\ pixels are twice as wide as they are high

LDA xPointHi           \ Set R = xPointHi so we can shift xPoint below without
STA R                  \ affecting the value of xPointHi

LDA zPointHi           \ Set S = zPointHi so we can shift zPoint below without
STA S                  \ affecting the value of zPointHi

LDX #3                 \ We now want to shift the point values right by 3
\ places, so set a shift counter in X

.blip2

LSR R                  \ Set (R xPointLo) = (R xPointLo) >> 1
ROR xPointLo           \                  = xPoint / 2

LSR S                  \ Set (S zPointLo) = (S zPointLo) >> 1
ROR zPointLo           \                  = zPoint / 2

DEX                    \ Decrement the shift counter

BPL blip2              \ Loop back until we have shifted right three times

\ Because mode 5 pixels are twice as wide as they are
\ high, we need to halve the x-coordinate one more time
\ to get the correct result for the pixel x-coordinate

LSR R                  \ Set (R A) = (R xPointLo) >> 1
LDA xPointLo
ROR A

ADC #0                 \ Add bit 0 of the original value to round up the
STA xPointLo           \ division and store the result in xPointLo

\ We now fall through into DrawRadarBlip to erase and
\ redraw the blip on the radar at the coordinates in
\ (xPointLo, zPointLo)

Type: Subroutine
Category: Dashboard
Summary: Draw a blip on the radar (runway or alien)
Context: See this subroutine on its own page
References: This subroutine is called as follows:

Arguments:

xPointLo             The radar x-coordinate of the blip to update

xPointHi             The sign of the x-coordinate in bit 7

zPointLo             The radar y-coordinate of the blip to update (we use the
real-world z-coordinate, as the radar is a top-down
view)

zPointHi             The sign of the y-coordinate in bit 7

alien                The blip to update on the radar:

* 0 = update the runway

* 1 = update the alien

LDX alien              \ Set X = alien to point to the blip to update on the
\ radar (0 for the runway, 1 for the alien)

LDA xRadarBuffer,X     \ Set I = the X-th byte of xRadarBuffer, which contains
STA I                  \ the x-coordinate of the current line or dot on the
\ radar from a previous call to DrawRadarBlip

LDA yRadarBuffer,X     \ Set J = the X-th byte of yRadarBuffer, which contains
STA J                  \ the y-coordinate of the current line or dot on the
\ radar from a previous call to DrawRadarBlip

LDA #128               \ Set N = 128 so the call to DrawVectorLine erases the
STA N                  \ current line

LDA previousCompass    \ Set A = previousCompass, so it contains the value of A
\ from the last call to GetRadarVector, i.e. for the
\ current line on the radar

JSR GetRadarVector     \ Calculate the vector for the current line on the radar

JSR DrawVectorLine     \ Erase a line from (I, J) as a vector (T, U) with
\ direction V

LDX alien              \ If alien is non-zero then we just erased a dot from
BNE drbl1              \ the radar, so jump to drbl1 as we don't need to redraw
\ the cross at the centre of the radar

\ If we get here then we just erased a line from the
\ radar, which may have corrupted the cross in the
\ centre, so we redraw it

LDA #%10001000         \ Redraw the top-but-one pixel of the cross (though,
STA row25_char35_6     \ oddly, not the very top pixel)

STA row26_char35_0     \ Redraw the bottom two pixels of the cross
STA row26_char35_1

LDA #%00010001         \ Redraw the left pixel of the cross
STA row25_char34_7

LDA #%11001100         \ Redraw the centre and right pixels of the cross
STA row25_char35_7

.drbl1

\ Now to calculate the position of the new line or dot
\ to draw on the radar

LDA xPointLo           \ Set A = xPointLo, the x-coordinate of the alien or
\ runway

BIT xPointHi           \ If the high byte in xPointHi is positive, jump to
BPL drbl2              \ drbl2 to skip the following three instructions

EOR #&FF               \ Otherwise negate A using two's complement, so A is
CLC                    \ positive, i.e. A = |xPointLo|

.drbl2

CMP #13                \ If A >= 13, jump to drbl4 to return from the
BCS drbl4              \ subroutine, as the item is off the side of the radar

LDA zPointLo           \ Set A = zPointLo, the y-coordinate of the alien or
\ runway

BIT zPointHi           \ If the high byte in zPointHi is positive, jump to
BPL drbl3              \ drbl3 to skip the following three instructions

EOR #&FF               \ Otherwise negate A using two's complement, so A is
CLC                    \ positive, i.e. A = |zPointLo|

.drbl3

CMP #26                \ If A >= 26, jump to drbl4 to return from the
BCS drbl4              \ subroutine, as the item is off the top or bottom of

LDA xPointLo           \ Set I = xPointLo + 140
CLC                    \
ADC #140               \ to move the coordinate onto the radar, whose centre
STA I                  \ cross on-screen is at (140, 207)

STA xRadarBuffer,X     \ Store the x-coordinate in the relevant byte of
\ xRadarBuffer, so we can easily erase this item from
\ the radar when we want to move it again

LDA zPointLo           \ Set J = zPointLo + 208
CLC                    \
ADC #208               \ to move the coordinate onto the radar, whose centre
STA J                  \ cross on-screen is at (140, 207)

STA yRadarBuffer,X     \ Store the x-coordinate in the relevant byte of
\ yRadarBuffer, so we can easily erase this item from
\ the radar when we want to move it again

LDA #0                 \ Set N = 0 so the call to DrawVectorLine draws the
STA N                  \ new line

LDA yRotationHi        \ Set A to the current compass heading, for use in the
\ call to GetRadarVector if this is the runway (if this
\ is an alien, this value is ignored)

JSR GetRadarVector     \ Calculate the vector for the new line on the radar

JSR DrawVectorLine     \ Draw a line from (I, J) as a vector (T, U) with
\ direction V

.drbl4

RTS                    \ Return from the subroutine

```