Skip to navigation

Aviator on the BBC Micro

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 ADC U 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
Name: DrawOrthoLine [Show more] 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 entry point 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 ADC 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
Name: ScaleSigned [Show more] 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 ADC #1 .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
Name: UpdateDashboard [Show more] 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 entry point 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_block26_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 ADC #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 maxiumum 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
Name: ProcessOtherKeys [Show more] 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
Name: IndicatorU [Show more] 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 ADC #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_block32_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
Name: IndicatorF [Show more] Type: Subroutine Category: Dashboard Summary: Update the flaps indicator ("F") and 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 BNE indf1 \ jump to indf1 \ 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 ADC #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_block35_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
Name: FireGuns [Show more] 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 ADC #200 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 \ AddPointToObject .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 JSR AddPointToObject 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 \ AddPointToObject 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
Name: IndicatorB [Show more] 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 BNE indb1 \ jump to indb1 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_block37_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
Name: IndicatorT [Show more] 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_block0_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
Name: ScanKeyboard [Show more] Type: Subroutine Category: Keyboard Summary: Scan the keyboard for a specific key Deep dive: The key logger
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
Name: UpdateKeyLogger [Show more] 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
Name: AddPointToObject [Show more] 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
.AddPointToObject 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
Name: SetObjectToOrigin [Show more] 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
Name: ReadADCChannel [Show more] 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: * ReadJoystick calls ReadADCChannel

Arguments: X The ADC channel to read: * 1 = joystick X * 2 = joystick Y Returns: A The joystick position, inverted and clipped to the range -118 to +116
.ReadADCChannel 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 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 BCC radc1 LDA #246 \ Set A = 246, so A now has a maximum value of 246 .radc1 CMP #12 \ If A >= 12, jump to radc2 to skip the next instruction BCS radc2 LDA #12 \ Set A = 12, so A now has a minimum value of 12 .radc2 \ 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 ADC #1 RTS \ Return from the subroutine
Name: ResetVariables [Show more] 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 resen 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
Name: ResetRadar [Show more] 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
.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
Name: StartGame [Show more] 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
Name: NewGame [Show more] 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, update the radar 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 JSR UpdateRadarBlip 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-screeen
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 visibility checked already.
.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
Name: UpdateLinesToShow [Show more] 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 ADC #0 \ 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
Name: ProcessLinesToShow [Show more] 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 ADC #0 \ 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
Name: ProcessLinesToHide [Show more] 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
Name: ShowOrHideLine [Show more] 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 entry point 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 \ 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 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 visibilty, \ 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 ADC #1 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
Name: CheckObjDistance [Show more] 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
Name: ResetLineLists [Show more] 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
Name: FlipColours [Show more] 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
Name: SetColourToWhite [Show more] 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
Name: SetColourToBlack [Show more] 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
Name: SetLogicalColour [Show more] 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
Name: DrawCanopyView [Show more] 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
Name: ProcessHorizonLine [Show more] 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 \ are set), jump to phor2 .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
Name: UpdateRadarBlip [Show more] 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
.UpdateRadarBlip 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 ADC #nextAxis 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)
Name: DrawRadarBlip [Show more] 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: * ResetRadar calls DrawRadarBlip

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
.DrawRadarBlip 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_block35_6 \ oddly, not the very top pixel) STA row26_block35_0 \ Redraw the bottom two pixels of the cross STA row26_block35_1 LDA #%00010001 \ Redraw the left pixel of the cross STA row25_block34_7 LDA #%11001100 \ Redraw the centre and right pixels of the cross STA row25_block35_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| ADC #1 .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| ADC #1 .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 \ the radar 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