Skip to navigation

Aviator on the BBC Micro

Aviator F source

Name: PrintTooLate [Show more] Type: Subroutine Category: The Theme Summary: Print the "TOO LATE!" message at column 6, row 7 on-screen
Context: See this subroutine on its own page References: This subroutine is called as follows: * AlienInAcornsville calls PrintTooLate
.PrintTooLate LDX #11 \ There are 11 VDU characters in the message, so set up \ a counter in X .late1 LDA tooLateText,X \ Print the X-th character from pleaseWaitText JSR OSWRCH DEX \ Decrement the loop counter, as the characters are \ stored backwards in the pleaseWaitText variable BPL late1 \ Loop back to print the next character until we have \ printed all 11 RTS \ Return from the subroutine
Name: tooLateText [Show more] Type: Variable Category: The Theme Summary: The "TOO LATE!" message shown when the aliens land in Acornsville
Context: See this variable on its own page References: This variable is used as follows: * PrintTooLate calls tooLateText

The text and VDU codes in this variable are stored backwards, perhaps to discourage hackers from working out how to cheat.
.tooLateText EQUB "!ETAL OOT" \ "TOO LATE!", stored backwards EQUB 7, 6, 31 \ VDU 31, 6, 7 moves the text cursor to column 6, row 7
Name: xLookupLo [Show more] Type: Variable Category: Graphics Summary: Lookup table for converting pixel x-coordinate to low byte of screen address
Context: See this variable on its own page References: This variable is used as follows: * DrawCanopyLine (Part 1 of 9) calls xLookupLo * DrawVectorLine (Part 3 of 3) calls xLookupLo

See xLookupHi for an explanation of this table.
.xLookupLo FOR I%, 0, 39 EQUB LO(I% * 8) NEXT
Name: xLookupHi [Show more] Type: Variable Category: Graphics Summary: Lookup table for converting pixel x-coordinate to high byte of screen address
Context: See this variable on its own page References: This variable is used as follows: * DrawCanopyLine (Part 1 of 9) calls xLookupHi * DrawVectorLine (Part 3 of 3) calls xLookupHi

Each character block contains 8 bytes, so this lookup table lets us convert a pixel x-coordinate to a 16-bit address offset from the beginning of the character row. We could achieve the same effect by simply multiplying the pixel x-coordinate by 8, but using a lookup table is quicker than doing the multiplication.
.xLookupHi FOR I%, 0, 39 EQUB HI(I% * 8) NEXT
Name: keyTable1 [Show more] Type: Variable Category: Keyboard Summary: Internal key numbers of high priority keys that are scanned first Deep dive: The key logger
Context: See this variable on its own page References: This variable is used as follows: * UpdateKeyLogger calls keyTable1

Keys in this table are scanned first. If pressed, the corresponding value in the key logger is set to the 16-bit value, with the high byte coming from keyTable1Hi and the low byte from keyTable1Lo. If the key in keyTable1 is not being pressed, we then check the corresponding key in keyTable2. This contains the other key in this key pair, which is normally the opposite key, e.g. up vs down, left vs right and so on.
.keyTable1 EQUB &A9 \ L Elevator (stick forwards, dive) EQUB &BE \ A Left rudder EQUB &AE \ S Aileron (joystick left, bank left) EQUB &DE \ W Decrease throttle EQUB &CA \ U Undercarriage up/down EQUB &BC \ F Flaps on/off
Name: keyTable2 [Show more] Type: Variable Category: Keyboard Summary: Internal key numbers of lower priority keys that are scanned second Deep dive: The key logger
Context: See this variable on its own page References: This variable is used as follows: * UpdateKeyLogger calls keyTable2

Keys in this table are scanned only if the corresponding key in keyTable1 is not being pressed. If pressed, the corresponding value in the key logger is set to the 16-bit value, with the high byte coming from keyTable2Hi and the low byte from keyTable2Lo.
.keyTable2 EQUB &99 \ < Elevator (stick backwards, ascend) EQUB &A8 \ + Right rudder EQUB &CD \ D Aileron (joystick right, bank right) EQUB &DD \ E Increase throttle EQUB &9B \ B Brakes on/off EQUB &FF \ SHIFT Fire
Name: axisChangeRate [Show more] Type: Variable Category: Flight model Summary: Stores the amount by which the three axes of movement change when the aileron, elevator or rudder are moved
Context: See this variable on its own page References: This variable is used as follows: * UpdateFlightModel (Part 1 of 4) calls axisChangeRate

If an axis control key is held down (e.g. dive, yaw left, roll right and so on), then it will change that axis value by 1 in the relevant direction (by updating elevatorPosition, rudderPosition or aileronPosition). If the key is held down, then after six calls to UpdateFlightModel without the key being released, the relevant control is fully engaged, and the rate of change increases to 4 in the relevant direction.
.axisChangeRate EQUB 0 \ The rate of change of the elevator (pitch) EQUB 0 \ The rate of change of the rudder (yaw) EQUB 0 \ The rate of change of the aileron (roll) EQUB 0 \ This byte appears to be unused
Name: keyTable2Lo [Show more] Type: Variable Category: Keyboard Summary: Key logger value (low byte) for key presses in keyTable2 Deep dive: The key logger
Context: See this variable on its own page References: This variable is used as follows: * UpdateKeyLogger calls keyTable2Lo

When a key in keyTable2 is pressed (and assuming the corresponding key in keyTable1 is not being pressed), the low byte of the key logger for this key pair is set to the value in this table.
.keyTable2Lo EQUB 1 \ < Elevator (stick backwards, ascend) EQUB 1 \ + Right rudder EQUB 1 \ D Aileron (joystick right, bank right) EQUB 15 \ E Increase throttle EQUB 7 \ B Brakes on/off EQUB 8 \ SHIFT Fire
Name: keyTable2Hi [Show more] Type: Variable Category: Keyboard Summary: Key logger value (high byte) for key presses in keyTable2 Deep dive: The key logger
Context: See this variable on its own page References: This variable is used as follows: * UpdateKeyLogger calls keyTable2Hi

When a key in keyTable2 is pressed (and assuming the corresponding key in keyTable1 is not being pressed), the high byte of the key logger for this key pair is set to the value in this table.
.keyTable2Hi EQUB 1 \ < Elevator (stick backwards, ascend) EQUB 1 \ + Right rudder EQUB 1 \ D Aileron (joystick right, bank right) EQUB 0 \ E Increase throttle EQUB 0 \ B Brakes on/off EQUB 0 \ SHIFT Fire
Name: keyTable1Lo [Show more] Type: Variable Category: Keyboard Summary: Key logger value (low byte) for key presses in keyTable1 Deep dive: The key logger
Context: See this variable on its own page References: This variable is used as follows: * UpdateKeyLogger calls keyTable1Lo

When a key in keyTable1 is pressed (and assuming the corresponding key in keyTable1 is not being pressed), the low byte of the key logger for this key pair is set to the value in this table.
.keyTable1Lo EQUB 1 \ L Elevator (stick forwards, dive) EQUB 1 \ A Left rudder EQUB 1 \ S Aileron (joystick left, bank left) EQUB 241 \ W Decrease throttle EQUB 4 \ U Undercarriage up/down EQUB 5 \ F Flaps on/off
Name: keyTable1Hi [Show more] Type: Variable Category: Keyboard Summary: Key logger value (high byte) for key presses in keyTable1 Deep dive: The key logger
Context: See this variable on its own page References: This variable is used as follows: * UpdateKeyLogger calls keyTable1Hi

When a key in keyTable1 is pressed (and assuming the corresponding key in keyTable1 is not being pressed), the high byte of the key logger for this key pair is set to the value in this table.
.keyTable1Hi EQUB &FF \ L Elevator (stick forwards, dive) EQUB &FF \ A Left rudder EQUB &FF \ S Aileron (joystick left, bank left) EQUB &FF \ W Decrease throttle EQUB 0 \ U Undercarriage up/down EQUB 0 \ F Flaps on/off
Name: soundData26 [Show more] Type: Variable Category: Sound Summary: The sound of us making contact with the ground while landing
Context: See this variable on its own page References: No direct references to this variable in this source file

This location is at soundData + (26 * 8), so this block is contains the details for sound #26.
.soundData26 EQUB &10, &00 \ Sound #26: Ground contact (SOUND &10, -13, 6, 3) EQUB &F3, &FF EQUB &06, &00 EQUB &03, &00
Name: forceFactor [Show more] Type: Variable Category: Flight model Summary: The factors by which the flight forces are multiplied as part of the scaling process Deep dive: The flight model Stalling and recovery
Context: See this variable on its own page References: This variable is used as follows: * ApplyAerodynamics (Part 2 of 3) calls forceFactor * IndicatorF calls forceFactor * IndicatorU calls forceFactor * ResetVariables calls forceFactor * ScaleFlightForces calls forceFactor * SetEngine calls forceFactor
.forceFactor EQUB 212 \ xMoments is scaled by 212 * 1 EQUB 201 \ yMoments is scaled by 201 / 4 EQUB 204 \ zMoments is scaled by 204 / 2 EQUB 176 \ xLiftDrag is scaled by 176 * 2 EQUB 156 \ yLiftDrag is scaled as follows: \ \ * Scaled by 156 * 16 in normal flight \ \ * Scaled by 39 * 16 when the plane is stalling EQUB 22 \ zLiftDrag is scaled as follows: \ \ * Default scaling is by 52 \ (undercarriage down, flaps off, engine off) \ \ * Goes up by 10 if undercarriage is down \ * Goes down by 10 if undercarriage is up \ \ * Goes up by 200 if flaps are on \ * Goes down by 200 if flaps are off \ \ * Goes up by 20 if engine is on \ * Goes down by 20 if engine is switched off \ \ This value is set to 242 in ResetVariables, which is \ 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 EQUB 40 \ zSlipMoment is scaled by 40 / 32 EQUB 152 \ yFlapsLift is scaled as follows: \ \ * Scaled to 0 if flaps are off \ \ * Scaled by 152 * 4 if flaps are on \ \ This value is zeroed in ResetVariables EQUB 0 \ Unused EQUB 0 \ Unused EQUB 255 \ xControls is scaled by 255 / 2 EQUB 141 \ yControls is scaled by 141 / 4 EQUB 190 \ zControls is scaled by 190 / 4 EQUB &00, &05 \ These bytes appear to be unused EQUB &7D, &FF EQUB &50
Name: dialQuadrant [Show more] Type: Variable Category: Dashboard Summary: The value range of a quadrant in each indicator
Context: See this variable on its own page References: This variable is used as follows: * GetHandVector calls dialQuadrant
.dialQuadrant EQUB 18 \ Centre value for indicator 0 (compass) EQUB 22 \ Centre value for indicator 1 (airspeed) EQUB 16 \ Centre value for indicator 2 (altimeter small) EQUB 26 \ Centre value for indicator 3 (altimeter large) EQUB 22 \ Centre value for indicator 4 (vertical speed) EQUB 26 \ Centre value for indicator 5 (turn) EQUB 26 \ Centre value for indicator 6 (slip) EQUB &41 \ This byte appears to be unused
Name: xDeltaMax [Show more] Type: Variable Category: Dashboard Summary: The maximum x-delta for the hand line in each indicator
Context: See this variable on its own page References: This variable is used as follows: * GetHandVector calls xDeltaMax
.xDeltaMax EQUB 7 \ Maximum x-delta for indicator 0 (compass) EQUB 9 \ Maximum x-delta for indicator 1 (airspeed) EQUB 5 \ Maximum x-delta for indicator 2 (altimeter small) EQUB 10 \ Maximum x-delta for indicator 3 (altimeter large) EQUB 8 \ Maximum x-delta for indicator 4 (vertical speed) EQUB 9 \ Maximum x-delta for indicator 5 (turn) EQUB 9 \ Maximum x-delta for indicator 6 (slip) EQUB &0D \ This byte appears to be unused
Name: yDeltaMax [Show more] Type: Variable Category: Dashboard Summary: The maximum y-delta for the hand line in each indicator
Context: See this variable on its own page References: This variable is used as follows: * GetHandVector calls yDeltaMax
.yDeltaMax EQUB 12 \ Maximum y-delta for indicator 0 (compass) EQUB 10 \ Maximum y-delta for indicator 1 (airspeed) EQUB 10 \ Maximum y-delta for indicator 2 (altimeter small) EQUB 14 \ Maximum y-delta for indicator 3 (altimeter large) EQUB 12 \ Maximum y-delta for indicator 4 (vertical speed) EQUB 14 \ Maximum y-delta for indicator 5 (turn) EQUB 14 \ Maximum y-delta for indicator 6 (slip) EQUB &20 \ This byte appears to be unused
Name: indicatorLineI [Show more] Type: Variable Category: Dashboard Summary: Line buffer storage for the start x-coordinate for each indicator line (I) Deep dive: Line buffers
Context: See this variable on its own page References: This variable is used as follows: * DrawIndicatorLine calls indicatorLineI
.indicatorLineI \ Storage for the x-coordinate of the starting point of \ of the current hand on indicators 0-7, so we can erase \ it again (this value matches the value of I passed to \ DrawVectorLine) EQUB 54 \ Start x-coordinate for indicator 0 (compass) EQUB 21 \ Start x-coordinate for indicator 1 (airspeed) EQUB 22 \ Start x-coordinate for indicator 2 (altimeter small) EQUB 22 \ Start x-coordinate for indicator 3 (altimeter large) EQUB 106 \ Start x-coordinate for indicator 4 (vertical speed) EQUB 106 \ Start x-coordinate for indicator 5 (turn) EQUB 106 \ Start x-coordinate for indicator 6 (slip) EQUB 84 \ Start x-coordinate for indicator 7 (horizon)
Name: indicatorLineJ [Show more] Type: Variable Category: Dashboard Summary: Line buffer storage for the start y-coordinate for each indicator line (J) Deep dive: Line buffers
Context: See this variable on its own page References: This variable is used as follows: * DrawIndicatorLine calls indicatorLineJ
.indicatorLineJ \ Storage for the y-coordinate of the starting point of \ of the current hand on indicators 0-6, so we can erase \ it again (this value matches the value of J passed to \ DrawVectorLine and is relative to the top of the \ dashboard at screen y-coordinate 160) EQUB -72 \ Start y-coordinate for indicator 0 (compass) EQUB -23 \ Start y-coordinate for indicator 1 (airspeed) EQUB -68 \ Start y-coordinate for indicator 2 (altimeter small) EQUB -68 \ Start y-coordinate for indicator 3 (altimeter large) EQUB -24 \ Start y-coordinate for indicator 4 (vertical speed) EQUB -70 \ Start y-coordinate for indicator 5 (turn) EQUB -70 \ Start y-coordinate for indicator 6 (slip) \ Storage for the y-coordinate of the starting point of \ of the current artificial horizon on indicator 7, so \ we can erase it again (this value matches the value of \ H passed to DrawVectorLine) EQUB 88 \ Start y-coordinate for indicator 7 (horizon)
Name: indicatorBase [Show more] Type: Variable Category: Dashboard Summary: The base value for each indicator
Context: See this variable on its own page References: This variable is used as follows: * DrawIndicatorHand calls indicatorBase
.indicatorBase EQUB 0 \ Base value for indicator 0 (compass) EQUB 48 \ Base value for indicator 1 (airspeed) EQUB 0 \ Base value for indicator 2 (altimeter small) EQUB 0 \ Base value for indicator 3 (altimeter large) EQUB 67 \ Base value for indicator 4 (vertical speed) EQUB 53 \ Base value for indicator 5 (turn) EQUB 106 \ Base value for indicator 6 (slip) EQUB &4C \ This byte appears to be unused
Name: indicatorMin [Show more] Type: Variable Category: Dashboard Summary: The minimum value shown on each indicator ****************************************************************************** .indicatorMin EQUB 0 \ Minimum value shown on indicator 0 (compass) EQUB 57 \ Minimum value shown on indicator 1 (airspeed) EQUB 0 \ Minimum value shown on indicator 2 (altimeter small) EQUB 0 \ Minimum value shown on indicator 3 (altimeter large) EQUB 30 \ Minimum value shown on indicator 4 (vertical speed) EQUB 33 \ Minimum value shown on indicator 5 (turn) EQUB 91 \ Minimum value shown on indicator 6 (slip) EQUB &F4 \ This byte appears to be unused
.indicatorMax EQUB 255 \ Maximum value shown on indicator 0 (compass) EQUB 122 \ Maximum value shown on indicator 1 (airspeed) EQUB 255 \ Maximum value shown on indicator 2 (altimeter small) EQUB 255 \ Maximum value shown on indicator 3 (altimeter large) EQUB 104 \ Maximum value shown on indicator 4 (vertical speed) EQUB 72 \ Maximum value shown on indicator 5 (turn) EQUB 120 \ Maximum value shown on indicator 6 (slip) EQUB &4C \ This byte appears to be unused
Name: indicatorLineT [Show more] Type: Variable Category: Dashboard Summary: Line buffer storage for the indicator line's |x-delta| (T) Deep dive: Line buffers
Context: See this variable on its own page References: This variable is used as follows: * DrawIndicatorLine calls indicatorLineT
.indicatorLineT \ Storage for the x-delta of the current line on \ indicators 0-7, so we can erase it again (this value \ matches the value of T passed to DrawVectorLine) EQUB 2 \ Line x-delta for indicator 0 (compass) EQUB 2 \ Line x-delta for indicator 1 (airspeed) EQUB 2 \ Line x-delta for indicator 2 (altimeter small) EQUB 2 \ Line x-delta for indicator 3 (altimeter large) EQUB 2 \ Line x-delta for indicator 4 (vertical speed) EQUB 2 \ Line x-delta for indicator 5 (turn) EQUB 2 \ Line x-delta for indicator 6 (slip) EQUB 2 \ Line x-delta for indicator 7 (artificial horizon)
Name: indicatorLineU [Show more] Type: Variable Category: Dashboard Summary: Line buffer storage for the indicator line's |y-delta| (U) Deep dive: Line buffers
Context: See this variable on its own page References: This variable is used as follows: * DrawIndicatorLine calls indicatorLineU
.indicatorLineU \ Storage for the y-delta of the current line on \ indicators 0-7, so we can erase it again (this value \ matches the value of U passed to DrawVectorLine) EQUB 2 \ Line y-delta for indicator 0 (compass) EQUB 2 \ Line y-delta for indicator 1 (airspeed) EQUB 2 \ Line y-delta for indicator 2 (altimeter small) EQUB 2 \ Line y-delta for indicator 3 (altimeter large) EQUB 2 \ Line y-delta for indicator 4 (vertical speed) EQUB 2 \ Line y-delta for indicator 5 (turn) EQUB 2 \ Line y-delta for indicator 6 (slip) EQUB 2 \ Line y-delta for indicator 7 (artificial horizon)
Name: indicatorLineV [Show more] Type: Variable Category: Dashboard Summary: Line buffer storage for the indicator line's direction (V) Deep dive: Line buffers
Context: See this variable on its own page References: This variable is used as follows: * DrawIndicatorLine calls indicatorLineV
.indicatorLineV \ Storage for the direction of the current line on \ indicators 0-7, so we can erase it again (this value \ matches the value of V passed to DrawVectorLine) EQUB 2 \ Direction for indicator 0 (compass) EQUB 2 \ Direction for indicator 1 (airspeed) EQUB 0 \ Direction for indicator 2 (altimeter small) EQUB 0 \ Direction for indicator 3 (altimeter large) EQUB 0 \ Direction for indicator 4 (vertical speed) EQUB 0 \ Direction for indicator 5 (turn) EQUB 0 \ Direction for indicator 6 (slip) EQUB 0 \ Direction for indicator 7 (artificial horizon)
Name: yJoyCoord [Show more] Type: Variable Category: Dashboard Summary: Temporary storage
Context: See this variable on its own page References: This variable is used as follows: * DrawIndicatorBar calls yJoyCoord * DrawJoystickCross calls yJoyCoord * UpdateIndicator (Part 14 of 15) calls yJoyCoord
.yJoyCoord EQUB 0 \ Temporary storage, typically used for storing \ y-coordinates when drawing indicators EQUB 0 \ The y-coordinate of the top of the current vertical \ bar for indicator 9 (rudder), so we can erase it when \ required
Name: xJoyCoord [Show more] Type: Variable Category: Dashboard Summary: Temporary storage
Context: See this variable on its own page References: This variable is used as follows: * DrawJoystickCross calls xJoyCoord * UpdateIndicator (Part 14 of 15) calls xJoyCoord
.xJoyCoord EQUB 0 \ Temporary storage, typically used for storing \ x-coordinates when drawing indicators EQUB 0 \ The y-coordinate of the top of the current vertical \ bar for indicator 11 (thrust), so we can erase it when \ required EQUB &4D, &0D \ These bytes appear to be unused EQUB &0C, &08 EQUB &15, &20 EQUB &20, &20 EQUB &20, &20 EQUB &20, &4C
Name: indicator0To6 [Show more] Type: Variable Category: Dashboard Summary: The first indicator counter
Context: See this variable on its own page References: This variable is used as follows: * UpdateDashboard calls indicator0To6
.indicator0To6 EQUB &44 \ The first indicator counter, which cycles through \ indicators 0 to 6, and is used to denote the first \ indicator that gets refreshed in UpdateDashboard
Name: indicator7To11 [Show more] Type: Variable Category: Dashboard Summary: The second indicator counter
Context: See this variable on its own page References: This variable is used as follows: * UpdateDashboard calls indicator7To11
.indicator7To11 EQUB &59 \ The second indicator counter, which cycles through \ indicators 7 to 10, and is used to denote the second \ indicator that gets refreshed in UpdateDashboard
Name: joyCoord [Show more] Type: Variable Category: Dashboard Summary: Temporary storage
Context: See this variable on its own page References: This variable is used as follows: * DrawIndicatorBar calls joyCoord * DrawJoystickCross calls joyCoord * DrawOrthoLine calls joyCoord
.joyCoord EQUB &23 \ Temporary storage, typically used to store coordinates \ when drawing the crosses on the joystick position \ display EQUB &31, &3A \ These bytes appear to be unused
Name: altitudeMinutes [Show more] Type: Variable Category: Dashboard Summary: The value of the altimeter's large "minute" hand
Context: See this variable on its own page References: This variable is used as follows: * UpdateIndicator (Part 4 of 15) calls altitudeMinutes * UpdateIndicator (Part 5 of 15) calls altitudeMinutes
.altitudeMinutes EQUB &4C \ The value of the altimeter's large "minute" hand, \ in the range 0 to 104 to match the 1,000 feet range \ of the large hand
Name: ApplyFlightModel (Part 1 of 7) [Show more] Type: Subroutine Category: Flight model Summary: Apply the flight model to the plane, starting by calculating the effect of gravity and the undercarriage springs Deep dive: The flight model On-ground calculations
Context: See this subroutine on its own page References: This subroutine is called as follows: * UpdateFlightModel (Part 4 of 4) calls ApplyFlightModel

This part does the following: * Set gravity to the following vector, rotated to the plane's frame of reference using matrix 4: [ xGravity ] [ 0 ] [ yGravity ] = matrix4 x [ -512 ] [ zGravity ] [ 0 ] * If we are on the ground with the undercarriage down, and we are accelerating downwards, we push back up to simulate the springs in the undercarriage by setting the gravity vector to the following, again rotating it to the plane's frame of reference using matrix 4: [ xGravity ] [ 0 ] [ yGravity ] = matrix4 x [ -512 - (dyVelocity / 2 + 1) ] [ zGravity ] [ 0 ]
.ApplyFlightModel LDX #253 \ Set the gravity vector (which is stored in point 253) JSR SetPointToOrigin \ to (0, 0, 0), so: \ \ (xGravity, yGravity, zGravity) = (0, 0, 0) \ When we are flying normally, the gravity vector is: \ \ (0, &FE00, 0) = (0, -512, 0) \ \ so, as you would expect, gravity pulls the plane down \ along the y-axis (which is the up-down axis) LDA #&FE \ Set A = &FE to set as yGravityHi for when we are \ flying normally LDX onGround \ Set L to the value of onGround, so we can check it STX L \ again in part 5 BEQ fmod1 \ If onGround is zero then we are not on the ground, \ so jump to fmod1 to skip the following and set the \ gravity vector to (0, -512, 0) LDX ucStatus \ If ucStatus is zero then the undercarriage is up, so BEQ fmod1 \ jump to fmod1 to skip the following and set the \ gravity vector to (0, -512, 0) LDX dyVelocityHi \ If dyVelocity is positive then the rate of change of BPL fmod1 \ velocity in the vertical axis is positive - i.e. the \ plane is accelerating upwards - so jump to fmod1 to \ skip the following and set the gravity vector to \ (0, -512, 0) \ If we get here then we are on the ground, the \ undercarriage is down, and dyVelocity is negative, so \ the plane is accelerating down and we need to change \ the gravity vector so it pushes upwards, to simulate \ the springs in the undercarriage STX Q \ Set (Q A) = (dyVelocityHi dyVelocityLo) LDA dyVelocityLo \ = dyVelocity SEC \ Set (Q A) = (Q A) / 2 ROR Q \ = dyVelocity / 2 ROR A \ \ making sure to retain the correct sign in bit 7 of Q \ (as dyVelocity is negative) EOR #&FF \ Set yGravityLo = ~A STA yGravityLo \ = 255 - A LDA #254 \ Set yGravityHi = 254 - Q - 1 CLC \ = 253 - Q SBC Q \ \ So the above does this: \ \ yGravity = (253 255) - (Q A) \ = -513 - (Q A) \ = -512 - (dyVelocity / 2 + 1) \ \ where dyVelocity is negative, so this reduces the \ gravitational pull by half of the plane's acceleration \ down - in other words, the wheels push up by half the \ acceleration downwards, bouncing the plane up like a \ spring .fmod1 STA yGravityHi \ Set yGravityHi = A, to set the y-axis of the gravity \ vector as required \ Finally, we calculate the gravity vector's coordinates \ in the world, so we get the gravity vector rotated by \ the plane's orientation, i.e. relative to the plane \ rather than the world \ \ We can get away with only rotating in the pitch and \ roll axes as the gravity vector only has an non-zero \ value in the y-axis, so the yaw rotation wouldn't \ affect the result anyway LDA #253 \ Set GG to ID of the gravity vector (which is stored in STA GG \ point 253), to pass to the call to SetPointCoords 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 vertical vector by a yaw angle has no \ effect JSR SetPointCoords \ Calculate the coordinates for the gravity vector, \ using matrix 4, which only performs the pitch and roll \ rotations \ So (xGravity, yGravity, zGravity) now contains the \ gravity vector, rotated to the plane's point of view
Name: ApplyFlightModel (Part 2 of 7) [Show more] Type: Subroutine Category: Flight model Summary: Convert velocity to the plane's perspective and calculate various aerodynamic forces Deep dive: The flight model
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

This part does the following: * Rotate the plane's velocity vector in (xVelocity, yVelocity, zVelocity) to the plane's frame of reference using matrix 1, and store the result in (xVelocityP, yVelocityP, zVelocityP): [ xVelocityP ] [ xVelocity ] [ yVelocityP ] = matrix1 x [ yVelocity ] [ zVelocityP ] [ zVelocity ] * Call the ApplyAerodynamics routine to check for stalling: * If we are already stalling and not going fast enough to pull out of the stall, make the stalling sound and move on. * If we are not already stalling, or we are possibly going fast enough to pull out of the stall (zVelocityPHi >= 11), we perform the stall check: zVelocityP <= |yVelocityP| * 4 If this is true, then we are stalling. * If we have just started stalling (i.e. we weren't already stalling but we are now), check that we are not too close to the ground (so we check that yPlaneLo >= 20), and assuming we aren't, apply a roll to the plane to simulate one of the wings stalling before the other: zTurn = zTurn +/- (A 0) >> 5 where: * The +/- sign is the sign of yTurn * A = xTurnTop EOR #%00111111 * The same routine calculates various aerodynamic forces, as follows: [ xMoments ] = [ yVelocityP * 2 - xTurn * 250 / 256 ] [ yMoments ] = [ xVelocityP * 2 - yTurn * 250 / 256 ] * maxv * [ zMoments ] = [ -zTurn * 2 ] airDensity [ xLiftDrag ] = [ xVelocityP * 2 * 4 ] [ yLiftDrag ] = [ yVelocityP * 2 * 4 ] * maxv * airDensity [ zLiftDrag ] = [ zVelocityP * 2 ] zSlipMoment = xLiftDrag where: airDensity = ~yPlaneHi * 2 maxv = max(|xVelocityP|, |yVelocityP|, |zVelocityP|) If zLiftDrag is positive we also do: yFlapsLift = zLiftDrag xMoments = xMoments + (zLiftDrag * 8) when the flaps are off xMoments - (zLiftDrag * 4) when the flaps are on * Call the ApplyFlightControl routine to calculate the effects of the primary flight controls (elevator, rudder and ailerons), as follows: xControls = zLiftDrag * elevatorPosition (pitch) yControls = zLiftDrag * rudderPosition (yaw) zControls = zLiftDrag * aileronPosition (roll) This routine also implements the "instant centre" feature. * Call the ScaleFlightForces routine to scale all the flight forces by the relevant force and scale factors, as follows: scaledForce = unscaledForce * forceFactor * 2 ^ scaleFactor where the 11 forces are: * (xLiftDrag, yLiftDrag, zLiftDrag) * (xMoments, yMoments, zMoments) * zSlipMoment * yFlapsLift * (xControls, yControls, zControls) The scaled results are stored in xMomentsSc, xLiftDragSc and so on.
\ We now rotate (xVelocity, yVelocity, zVelocity), using \ matrix 1, into (xVelocityP, yVelocityP, zVelocityP) \ \ In other words, the following code takes the plane's \ velocity vector in (xVelocity, yVelocity, zVelocity), \ rotates it by the plane's orientation and stores the \ result in the (xVelocityP, yVelocityP, zVelocityP) \ vector \ \ The (xVelocity, yVelocity, zVelocity) vector is the \ velocity of the plane with respect to the outside \ world, so yVelocity is the vertical speed of the plane \ (as shown on the vertical speed indicator), which is \ unaffected by how the plane is orientated (a plane \ dropping out of the sky is still dropping out of the \ sky, even when it's tumbling) \ \ The (xVelocityP, yVelocityP, zVelocityP) vector is the \ velocity of the plane with respect to the plane \ itself, so zVelocityP is the forward speed of the \ plane from the point of view of the pilot - it's the \ speed of the air rushing past us as we fly, so this is \ the speed we show on the airspeed indicator \ \ The following code calculates the latter from the \ former by setting point 255 to the speed vector, \ rotating it by the plane's orientation angles, and \ storing the result in the velocity vector LDX #LO(xVelocityHi) \ Set X so the call to CopyWorkToPoint copies the \ coordinates from (xVelocity, yVelocity, zVelocity) LDY #255 \ Set Y so the call to CopyWorkToPoint copies the \ coordinates to point 255 STY GG \ Set GG to point ID 255, to pass to the call to \ SetPointCoords JSR CopyWorkToPoint \ Copy the coordinates from (xVelocity, yVelocity, \ zVelocity) to point 255 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 255 LDX #LO(xVelocityPLo) \ Set X so the call to CopyPointToWork copies the \ coordinates to (xVelocityP, yVelocityP, zVelocityP) LDY #255 \ Set Y so the call to CopyPointToWork copies the \ coordinates from point 255 JSR CopyPointToWork \ Copy the coordinates from point 255 to \ (xVelocityP, yVelocityP, zVelocityP) JSR ApplyAerodynamics \ Check for stalling and calculate various aerodynamic \ forces JSR ApplyFlightControl \ Calculate the effects of the primary flight controls \ (elevator, rudder and ailerons), and implement the \ "instant centre" feature JSR ScaleFlightForces \ Scale all the flight forces by the relevant force \ and scale factors
Name: ApplyFlightModel (Part 3 of 7) [Show more] Type: Subroutine Category: Flight model Summary: If we are near to an exploding alien, apply turbulence Deep dive: The flight model Explosions and turbulence
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

This part does the following: * If we are near an exploding alien, calculate the amount of turbulence and apply it to the (xControlsSc, yControlsSc, zControlsSc) vector, as follows: * Set the level of turbulence in the range 0 to 1920, to be inversely proportional to the distance from the explosion (so it is 0 if we are far away, 1920 if we are very close). * We randomly add or subtract this amount from each axis (with each axis being individually signed randomly), to give: xControlsSc = xControlsSc +/- turbulence / 2 yControlsSc = yControlsSc +/- turbulence / 2 zControlsSc = zControlsSc +/- turbulence / 2
LDA hitTimer \ If the hit timer is zero, then there are no exploding BEQ fmod3 \ aliens, so jump to fmod3 to skip this part \ We now apply turbulence to the plane by applying a \ random amount to xControlsSc, yControlsSc and \ zControlsSc, with the random amount being positive or \ negative, and with a larger magnitude the closer we \ are to the alien LDX #&6A \ Set X so in the following loop, the call to AddScaled \ updates xControlsSc, yControlsSc and zControlsSc, one \ on each iteration of the loop \ \ The comments below are for xControlsSc .fmod2 JSR NextRandomNumber \ Set A to point to the next item in the randomNumbers \ list TAY \ Set P to the next random number from the list LDA randomNumbers+1,Y STA P LDA distanceFromHit \ If distanceFromHit >= 16, jump to fmod3 to skip the CMP #16 \ following, as we are too far from the exploding alien BCS fmod3 \ to feel the effects EOR #&0F \ As A < 16, this flips the value of A so instead of \ being in the range 0 to 15, it's reversed to the \ range 15 to 0 - so A is now 15 if we are really close \ to the exploding alien, or 0 if we are further away ASL A \ Double the value of A, so it's in the range 0 to 30, \ with the number being higher the closer we are to the \ exploding alien LSR P \ We set P to a random number above, so this sets the C \ flag randomly, so the call to AddScaled randomly adds \ or subtracts the amount of turbulence in A LDY #1 \ Set the scale factor in Y so we add or subtract \ (A 0) >> 2, which is in the range 0 to 1920, as \ (30 0) >> 2 is 1920 JSR AddScaled \ Set xControlsSc = xControlsSc +/- (A 0) >> 2 \ \ So this changes xControlsSc by an amount in the range \ 0 to 1920, with a higher amount the closer we are to \ the exploding alien, with the sign being random (i.e. \ it is random whether we add or subtract the amount, \ but the amount itself is not random) INX \ Increment the value of X so the next iteration updates \ the next of xControlsSc, yControlsSc and zControlsSc CPX #&6D \ Loop back until we have applied turbulence to all BNE fmod2 \ three axes
Name: ApplyFlightModel (Part 4 of 7) [Show more] Type: Subroutine Category: Flight model Summary: Calculate the dxTurn and xLinear vectors, and the slipRate Deep dive: The flight model
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

This part does the following: * Call the ApplyTurnAndThrust routine to: * Set the dxTurn vector as follows: [ dxTurn ] [ xMomentsSc ] [ xControlsSc ] [ dyTurn ] = [ yMomentsSc ] + [ yControlsSc ] [ dzTurn ] [ zMomentsSc ] [ zControlsSc ] [ yGravity ] [ 0 ] + [ 0 ] - [ 0 ] [ 0 ] [ zSlipMoment ] * Set the xLinear vector, depending on the turning moments and the forces from the engine: If zVelocityPHi >= 48 (so forward speed >= 500 mph), we calculate: [ xLinear ] [ 0 ] [ xLiftDragSc ] [ yLinear ] = [ yFlapsLiftSc ] - [ yLiftDragSc ] [ zLinear ] [ 0 ] [ (&EA zLinearLo) ] If zVelocityPHi < 48 (so forward speed < 500 mph), we calculate: [ xLinear ] [ 0 ] [ xLiftDragSc ] [ 0 ] [ yLinear ] = [ yFlapsLiftSc ] - [ yLiftDragSc ] + [ 0 ] [ zLinear ] [ 0 ] [ zLiftDragSc ] [ zEngine ] where zEngine is 0 if the engine is off, or the following if the engine is on: zEngine = max(0, thrustScaled - (max(0, zVelocityP) / 16)) * airDensity / 512 and: airDensity = ~yPlaneHi * 2 and thrustScaled is the thrust in (thrustHi thrustLo), but: * Doubled if thrust >= 1024 * Doubled if zVelocity is in the range 512 to 1023 * Retract the flaps if we are going too fast. * Set the slip rate shown on the slip indicator as follows: slipRate = -int(xLinear / 256)
.fmod3 JSR ApplyTurnAndThrust \ Set the dxTurn and xLinear vectors, depending on \ the turning moments and the forces from the engine LDA xLinearLo \ Set the C flag to bit 7 of xLinearLo, flipped, to EOR #%10000000 \ round the slip rate in the following calculation to ASL A \ the nearest integer LDA #0 \ Set slipRate = 0 - xLinearHi - (1 - C) SBC xLinearHi \ = -int(xLinear / 256) STA slipRate
Name: ApplyFlightModel (Part 5 of 7) [Show more] Type: Subroutine Category: Flight model Summary: Calculate the forces for when we are on the ground Deep dive: The flight model On-ground calculations
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

This part does the following: * If we are on the ground, then: * Apply ground steering to yTurn if the rudder is used and forward speed is >= 20. * Stop the plane from rolling by setting the roll rate in dzTurn to 0. * If the undercarriage is up, prevent the plane from pitching forward below the level of the ground. * Calculate the effect of being on the ground on the forces and landing status: * If the plane is stationary, set landingStatus = %01000000 and skip to the side velocity step below. * If the plane is travelling forwards at a speed of 10 or less, set zLinear = -256 and landingStatus = %01000000 and skip to the side velocity step below. * If the plane is on the runway, the undercarriage is down and the brakes are off, set landingStatus = 0 and skip to the side velocity step below. * Otherwise, set landingStatus = 0 and subtract the following from zLinearHi (from the least slowdown to the biggest slowdown): * 7 if the plane is not on the runway and: undercarriage is down and brakes are off * 11 if the plane is on the runway and: undercarriage is up or undercarriage is down and brakes are on * 50 if the plane is not on the runway and: undercarriage is up or undercarriage is down and brakes are on * 248 if the plane is going backwards (zVelocityP < 0) In other words, the following factors slow us down when travelling fowards along the ground: * Having the brakes on * Having the undercarriage up * Having a bad approach * Calculate the effect of side velocity on xLinear: xLinear = -xVelocityPLo * 128
LDA L \ Fetch the value of onGround that we stored in L above BNE fmod4 \ If onGround is non-zero then we are on the ground, so \ jump to fmod4 to keep going JMP fmod17 \ We are not on the ground, so jump to fmod17 to move on \ to the next part .fmod4 \ We start by processing the effect of the rudder \ control on the ground steering (as the rudder control \ doubles up as the ground steering control) LDX #0 \ Set (X Y) = 0, to use as the value of yTurn below LDY #0 LDA zVelocityPHi \ If the forward airspeed is negative, jump to fmod6 to BMI fmod6 \ set (X Y) = 0 BNE fmod5 \ If the high byte of the forward airspeed is non-zero, \ jump to fmod5 to set (X Y) = rudderPosition \ If we get here then the high byte of the forward \ airspeed is zero LDA zVelocityPLo \ If the low byte of the forward airspeed is less than CMP #20 \ 20, jump to fmod6 to set (X Y) = 0 BCC fmod6 .fmod5 LDY rudderPosition \ If the position of the rudder is positive, jump to BPL fmod6 \ fmod6 to skip the following instruction DEX \ Decrement X to &FF, so X contains the correct sign in \ the 16-bit number: \ \ (X Y) = rudderPosition .fmod6 \ By the time we get here, (X Y) contains: \ \ * 0 if the forward airspeed is negative or < 20 \ \ * rudderPosition otherwise \ \ This is the effect of ground steering, which is \ controlled by the rudder control when we are on the \ ground, and only works when the plane is travelling \ forward with a minimum speed of 20 (as it works by \ applying brakes to the wheels, which needs speed to \ work) \ \ Let's call this figure groundSteering STY yTurnHi \ Set (A yTurnHi) = (X Y) TXA \ = groundSteering LDX #1 \ Set X as a shift counter in the following loop, so we \ shift left by 2 places .fmod7 ASL yTurnHi \ Set (A yTurnHi) = (A yTurnHi) << 1 ROL A DEX \ Decrement the shift counter BPL fmod7 \ Loop back until we have shifted left by 2 places, so: \ \ (A yTurnHi) = (A yTurnHi) * 4 \ = groundSteering * 4 STA yTurnTop \ Set yTurn = (A yTurnHi) \ \ So we have now set yTurn to groundSteering * 4, so the \ plane steers on the ground when we apply the rudder LDX #&82 \ Set (dzTurnTop dzTurnHi) = 0 JSR ResetVariable \ \ This also sets A = 0 STA dzTurnLo \ Set dzTurnLo = 0, so by now we have: \ \ (dzTurnTop dzTurnHi dzTurnLo) = 0 \ We now work out the effect of the various landing \ configurations to get the amount that the plane is \ being slowed down by being on the ground LDY ucStatus \ If ucStatus is non-zero then the undercarriage is BNE fmod9 \ down, so jump to fmod9 to move on to the brake checks LDA xRotationHi \ If the plane's rotation about the x-axis is positive AND dxTurnTop \ or the calculated rate of change of rotation around BPL fmod8 \ x-axis is positive, jump to fmod8 skip the following \ If we get here then both the following are negative: \ \ * The plane's current rotation about the x-axis \ \ * The calculated rate of change of rotation around \ the x-axis \ \ In other words, the nose has dipped below the forward \ horizontal and is heading down further, which can't \ happen when the undercarriage is up and we are on the \ ground, so we now set dxTurn to 0 to stop the plane \ from pitching any further into the ground LDX #&80 \ Set (dxTurnTop dxTurnHi) = 0 JSR ResetVariable \ \ This also sets A = 0 STA dxTurnLo \ Set dxTurnLo = 0, so by now we have: \ \ (dxTurnTop dxTurnHi dxTurnLo) = 0 .fmod8 JSR CheckPlaneOnRunway \ Check whether the plane is over the runway BCC fmod10 \ If the plane is on the runway, then jump to fmod10 to \ set A = 11 LDA #50 \ Set A = 50 and jump to fmodll (this BNE is effectively BNE fmod11 \ a JMP as A is never zero) .fmod9 LDX brakesStatus \ If brakesStatus is non-zero then the brakes are on, so BNE fmod8 \ jump to fmod8 \ If we get here then the brakes are off and the \ undercarriage is down JSR CheckPlaneOnRunway \ Check whether the plane is over the runway BCC fmod15 \ If the plane is on the runway, then jump to fmod15 to \ setlandingStatus = 0 LDA #7 \ Set A = 7 and jump to fmod11 (this BNE is effectively BNE fmod11 \ a JMP as A is never zero) .fmod10 LDA #11 \ Set A = 11 .fmod11 LDX zVelocityPHi \ If the plane is going backwards, jump to fmod12 to set BMI fmod12 \ A = 248 BNE fmod13 \ If the plane is moving forwards, jump to fmod13 to \ subtract A from zLinearHi \ If we get here then zVelocityPHi = 0 LDX zVelocityPLo \ If the plane is stationary (i.e. both zVelocityPLo and BEQ fmod14 \ zVelocityPHi = 0), jump to fmod14 to leave zLinear \ untouched and set landingStatus = %01000000 CPX #11 \ If the plane is moving forwards at a speed of 11 or BCS fmod13 \ more, jump to fmod13 to subtract A from zLinearHi LDA #0 \ Set zLinear = -256 STA zLinearLo LDA #&FF STA zLinearHi BNE fmod14 \ Jump to fmod14 to set landingStatus = %01000000 (this \ BNE is effectively a JMP as A is never zero) .fmod12 LDA #248 \ Set A = 248 .fmod13 \ We do not reach this point if any of the following are \ true: \ \ * The plane is stationary, in which case we already \ moved on with landingStatus = %01000000 \ \ * The plane is travelling forwards at a speed of 10 \ or less, in which case we already moved on with \ zLinear = -256 and landingStatus = %01000000 \ \ * The plane is on the runway, the undercarriage is \ down, the brakes are off, in which case we already \ moved on with landingStatus = 0 \ \ Otherwise, we get here and A is one of the following: \ \ * 248 if the plane is going backwards \ \ * 11 if the plane is on the runway and: \ either undercarriage is up \ or undercarriage is down, brakes are on \ \ * 50 if the plane is not on the runway and: \ either undercarriage is up \ or undercarriage is down, brakes are on \ \ * 7 if the plane is not on the runway and: \ undercarriage is down, brakes are off \ \ We now subtract the value of A from zLinearHi STA P \ Set zLinearHi = zLinearHi - A SEC LDA zLinearHi SBC P STA zLinearHi JMP fmod15 \ Jump to fmod15 to set landingStatus = 0 .fmod14 LDA #%01000000 \ Set A = %01000000 to use as the value of landingStatus BNE fmod16 \ Jump to fmod16 to set the value of landingStatus (this \ BNE is effectively a JMP as A is never zero) .fmod15 LDA #0 \ Set A = 0 to use as the value of landingStatus .fmod16 STA landingStatus \ Set landingStatus to the value in A LDA xVelocityPLo \ Set P = xVelocityPLo / 2 LSR A \ STA P \ and shift bit 0 of xVelocityPLo into the C flag LDX #0 \ Set X = 0 STX slipRate \ Set slipRate = 0 TXA \ Set A = 0 ROR A \ Shift the C flag into bit 7 of V, so bit 7 of V now STA V \ contains the bit 0 of xVelocityPLo that we shifted \ into the C flag above LDA xVelocityPHi \ Set Q = P with the sign bit from xVelocityPHi AND #%10000000 \ = xVelocityPLo / 2 with the correct sign ORA P STA Q \ If xVelocityPLo is %vvvvvvvv and the sign bit of \ xVelocityPHi is %s, then this sets \ \ (Q V) = %svvvvvvv v0000000 \ \ which is xVelocityPLo << 7, or xVelocityPLo * 128 TXA \ Set A = 0 SEC \ Set xLinear = (0 0) - (Q V) SBC V \ = -xVelocityPLo * 128 STA xLinearLo \ \ starting with the low bytes TXA \ And then the high bytes SBC Q STA xLinearHi
Name: ApplyFlightModel (Part 6 of 7) [Show more] Type: Subroutine Category: Flight model Summary: Adjust the plane's velocity and turn rate, rotate the plane according to the forces on the aircraft, process landing Deep dive: The flight model
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

This part does the following: * Rotate (xLinear, yLinear, zLinear) from the plane's frame of reference to the outside world's frame of reference using matrix 2, then subtract 16 from yLinearHi and store the result in (dxVelocity, dyVelocity, dzVelocity): [ dxVelocity ] [ xLinear ] [ 0 ] [ dyVelocity ] = matrix2 x [ yLinear ] - [ 4096 ] [ dzVelocity ] [ zLinear ] [ 0 ] * Call the AdjustVelocity routine to adjust the plane's velocity as follows: [ xVelocity ] [ xVelocity ] [ dxVelocity ] [ yVelocity ] = [ yVelocity ] + [ dyVelocity ] * 2 [ zVelocity ] [ zVelocity ] [ dzVelocity ] * Call the AdjustTurn routine to adjust the plane's turn rate as follows: [ xTurn ] [ xTurn ] [ dxTurn ] [ yTurn ] = [ yTurn ] + [ dyTurn ] [ zTurn ] [ zTurn ] [ dzTurn ] * Rotate (xTurn, yTurn, zTurn) using matrix 3, which rotates by the current roll angle, and store the result in (dxRotation, dyRotation, dzRotation): [ dxRotation ] [ xTurn ] [ dyRotation ] = matrix3 x [ yTurn ] [ dzRotation ] [ zTurn ] * Call the AdjustRotation routine to move the plane as follows: [ xPlane ] [ xPlane ] [ xVelocity ] [ yPlane ] = [ yPlane ] + [ yVelocity ] [ zPlane ] [ zPlane ] [ zVelocity ] and adjust the plane's rotation as follows: [ xRotation ] [ xRotation ] [ dxRotation ] [ yRotation ] = [ yRotation ] + [ dyRotation ] [ zRotation ] [ zRotation ] [ dzRotation ] The plane is flipped around if this makes it point backwards. * Make the engine sound. * Call the ProcessLanding routine to check to see if we are landing, and if we are, process the landing and the effect on the plane. * Call the ShowUpsideDownBar routine to show or hide the bar in the artificial horizon that shows whether the plane is upside down.
.fmod17 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 LDA #252 \ Set GG to the ID of the xLinear vector (which is STA GG \ stored in point 252), to pass to the call to \ SetPointCoords JSR SetPointCoords \ Calculate the coordinates of the xLinear vector in \ (xLinear, yLinear, zLinear) LDA yLinearHi \ Set yLinearHi = yLinearHi - 16 SEC SBC #16 STA yLinearHi LDX #LO(dxVelocityLo) \ Set X so the call to CopyPointToWork copies the \ coordinates to (dxVelocity, dyVelocity, dzVelocity) LDY #252 \ Set Y so the call to CopyPointToWork copies the \ coordinates from the xLinear vector (in point 252) JSR CopyPointToWork \ Copy (xLinear, yLinear, zLinear) to \ (dxVelocity, dyVelocity, dzVelocity) JSR AdjustVelocity \ Adjust the plane's velocity as follows: \ \ [ xVelocity ] [ xVelocity ] [ dxVelocity ] \ [ yVelocity ] = [ yVelocity ] + [ dyVelocity ] * 2 \ [ zVelocity ] [ zVelocity ] [ dzVelocity ] JSR AdjustTurn \ Adjust the plane's turn rate as follows: \ \ [ xTurn ] [ xTurn ] [ dxTurn ] \ [ yTurn ] = [ yTurn ] + [ dyTurn ] \ [ zTurn ] [ zTurn ] [ dzTurn ] LDX #LO(xTurnHi) \ Set X so the call to CopyWorkToPoint copies the \ coordinates from (xTurnHi, yTurnHi, zTurnHi) \ We now rotate (xTurn, yTurn, zTurn), using matrix 3, \ into (dxRotation, dyRotation, dzRotation) LDY #254 \ Set Y so the call to CopyWorkToPoint copies the \ coordinates to point 254 STY GG \ Set GG to point ID 254, to pass to the call to \ SetPointCoords JSR CopyWorkToPoint \ Copy the coordinates from (xTurn, yTurn, zTurn) \ to point 254 LDA #18 \ Set the matrix number so the call to SetPointCoords STA matrixNumber \ uses matrix 3 in the calculation, so it rotates the \ point by the plane's current roll angle JSR SetPointCoords \ Calculate the coordinates for point 254 LDX #LO(dxRotationLo) \ Set X so the call to CopyPointToWork copies the \ coordinates to (dxRotation, dyRotation, dzRotation) LDY #254 \ Set Y so the call to CopyPointToWork copies the \ coordinates from point 254 JSR CopyPointToWork \ Copy the coordinates from point 254 to \ (dxRotation, dyRotation, dzRotation) JSR AdjustRotation \ Move the plane according to its velocity as follows: \ \ [ xPlane ] [ xPlane ] [ xVelocity ] \ [ yPlane ] = [ yPlane ] + [ yVelocity ] \ [ zPlane ] [ zPlane ] [ zVelocity ] \ \ and adjust the plane's rotation as follows: \ \ [ xRotation ] [ xRotation ] [ dxRotation ] \ [ yRotation ] = [ yRotation ] + [ dyRotation ] \ [ zRotation ] [ zRotation ] [ dzRotation ] \ \ The plane is flipped around if this makes it point \ backwards LDA #7 \ Make the engine sound JSR ToggleEngineSound JSR ProcessLanding \ Check to see if we are landing, and if we are, process \ the landing and the effect on the plane JSR ShowUpsideDownBar \ Show or hide the bar in the artificial horizon that \ shows whether the plane is upside down
Name: ApplyFlightModel (Part 7 of 7) [Show more] Type: Subroutine Category: Flight model Summary: Calculate fuel usage Deep dive: The flight model
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

This routine calculates the following: * Calculate fuel usage, depending on the current thrust, and decrease the fuel level as required.
LDA fuelLevel \ If the fuel tank is empty, jump to fmod19 to turn the BEQ fmod19 \ engine off, as we just ran out of fuel LDA engineStatus \ If the engine is off, jump to fmod20 to return from BEQ fmod20 \ the subroutine \ Otherwise the engine is on and we are not yet out of \ fuel, so we need to calculate the correct consumption \ rate and deplete the fuel supplies LDA thrustHi \ Set (R A) = (thrustHi thrustLo) STA R \ = thrust LDA thrustLo LDX #3 \ We now shift (R A) right by four places, so set a \ shift counter in X .fmod18 LSR R \ Set (R A) = (R A) / 2 ROR A \ = thrust / 2 DEX \ Decrement the shift counter BPL fmod18 \ Loop back until we have shifted (R A) four times \ By now we have: \ \ (R A) = thrust / 16 \ \ and because the maximum value of thrust is 1280, we \ know R must be 0, so: \ \ A = thrust / 16 \ \ We use this value of A as the amount of fuel used in \ this iteration of the main loop, so the higher the \ thrust, the more fuel is used \ \ That said, we don't just take this off the fuel level \ in fuelLevel - instead, we keep track of the fuel \ used in two bytes, fuelUsedLo and fuelUsedHi \ \ We add the fuel used in A to fuelUsedLo, and when \ we've used 256 units and fuelLevelLo wraps around \ from 255 to 0, we then add 4 to fuelUsedHi, and when \ we've filled up fuelUsedHi and it wraps from 255 to 0, \ that's when we take one unit of fuel off the main \ fuel level in fuelLevel \ \ In summary, to use up one unit of fuelLevel, we have \ to use up 256 * 64 = 16384 units from the above \ calculation CLC \ Set fuelUsedLo = fuelUsedLo + A ADC fuelUsedLo STA fuelUsedLo BCC fmod20 \ If the addition didn't overflow, jump to fmod20 \ to return from the subroutine LDA #4 \ The addition overflowed, so set: CLC \ ADC fuelUsedHi \ fuelUsedHi = fuelUsedHi + 4 STA fuelUsedHi BCC fmod20 \ If the addition didn't overflow, jump to fmod20 \ to return from the subroutine \ The addition overflowed, so it's time to reduce our \ fuel level LDA fuelLevel \ If the fuel tank is already empty, jump to fmod19 to BEQ fmod19 \ turn the engine off, as we just ran out of fuel DEC fuelLevel \ Otherwise decrement the fuel level by 1 BNE fmod20 \ If the fuel tank is still not empty, jump to fmod20 to \ return from the subroutine .fmod19 LDA #0 \ The fuel tank is emptry, so turn the engine off JSR SetEngine .fmod20 RTS \ Return from the subroutine
Name: AdjustTurn [Show more] Type: Subroutine Category: Flight model Summary: Adjust the plane's turn rate
Context: See this subroutine on its own page References: This subroutine is called as follows: * ApplyFlightModel (Part 6 of 7) calls AdjustTurn

This routine adjusts the plane's turn rate by adding a 24-bit unsigned vector to each axis of the plane's turn rate. Specifically, it does the following for each of the three axes (x, y and z): (xTurnTop xTurnHi xTurnLo) += (dxTurnTop dxTurnHi dxTurnLo) so that's: [ xTurn ] [ xTurn ] [ dxTurn ] [ yTurn ] = [ yTurn ] + [ dyTurn ] [ zTurn ] [ zTurn ] [ dzTurn ] where (dxTurn dyTurn dzTurn) and (xTurn yTurn zTurn) are vectors made up of unsigned 24-bit numbers representing angles.
.AdjustTurn LDX #2 \ Set a counter in X to work through the three axes (the \ comments below cover the iteration for the x-axis) .atur1 LDA dxTurnLo,X \ Set xTurn = xTurn + dxTurn CLC \ ADC xTurnLo,X \ starting with the low bytes STA xTurnLo,X LDA dxTurnHi,X \ And then the high bytes ADC xTurnHi,X STA xTurnHi,X LDA dxTurnTop,X \ And then the top bytes ADC xTurnTop,X STA xTurnTop,X DEX \ Decrement the loop counter to move to the next axis BPL atur1 \ Loop back until we have processed all three axes RTS \ Return from the subroutine
Name: AdjustVelocity [Show more] Type: Subroutine Category: Flight model Summary: Adjust the plane's velocity vector
Context: See this subroutine on its own page References: This subroutine is called as follows: * ApplyFlightModel (Part 6 of 7) calls AdjustVelocity

This routine adjusts the plane's velocity vector by adding a 16-bit signed vector to each axis of the plane's velocity vector. Specifically, it does the following for each of the three axes (x, y and z): (xVelocityTop xVelocityHi xVelocityLo) += (dxVelocityHi dxVelocityLo) * 2 so that's: [ xVelocity ] [ xVelocity ] [ dxVelocity ] [ yVelocity ] = [ yVelocity ] + [ dyVelocity ] * 2 [ zVelocity ] [ zVelocity ] [ dzVelocity ] where (dxVelocity dyVelocity dzVelocity) is a vector made up of signed 16-bit numbers and (xVelocity zVelocity zVelocity) is a vector made up of signed 24-bit numbers.
.AdjustVelocity LDX #2 \ Set a counter in X to work through the three axes (the \ comments below cover the iteration for the x-axis) .avel1 LDA #0 \ Set R = 0, to use as the top byte in (R A V) STA R LDA dxVelocityLo,X \ Set (A V) = (dxVelocityHi dxVelocityLo) STA V \ = dxVelocity LDA dxVelocityHi,X BPL avel2 \ If (A V) is negative, decrement R to &FF so it has the DEC R \ correct sign for (R A V) .avel2 ASL V \ Set (R A V) = (R A V) << 1 ROL A \ = dxVelocity * 2 ROL R PHA \ Store the high byte on the stack, so we can retrieve \ it below LDA xVelocityLo,X \ Set xVelocity = xVelocity + (R A V) CLC \ = xVelocity + dxVelocity * 2 ADC V \ STA xVelocityLo,X \ starting with the low bytes PLA \ And then the high bytes, retrieving the high byte ADC xVelocityHi,X \ that we stored on the stack above STA xVelocityHi,X LDA xVelocityTop,X \ And then the top bytes ADC R STA xVelocityTop,X DEX \ Decrement the loop counter to move to the next axis BPL avel1 \ Loop back until we have processed all three axes RTS \ Return from the subroutine
Name: AdjustRotation [Show more] Type: Subroutine Category: Flight model Summary: Move the plane and adjust its rotation
Context: See this subroutine on its own page References: This subroutine is called as follows: * ApplyFlightModel (Part 6 of 7) calls AdjustRotation

This routine calculates the following: [ xPlane ] [ xPlane ] [ xVelocity ] [ yPlane ] = [ yPlane ] + [ yVelocity ] [ zPlane ] [ zPlane ] [ zVelocity ] to move the plane, and the following: [ xRotation ] [ xRotation ] [ dxRotation ] [ yRotation ] = [ yRotation ] + [ dyRotation ] [ zRotation ] [ zRotation ] [ dzRotation ] to rotate the plane. The plane is flipped around if this makes it point backwards.
.AdjustRotation LDX #2 \ Set a counter in X to work through the three axes (the \ comments below cover the iteration for the x-axis) .arot1 \ We now do the following calculation: \ \ xPlane = xPlane + xVelocity \ \ Looking at this in more detail, we take the 24-bit \ xPlane coordinate and append an extra bottom byte in \ xPlaneBot, like this: \ \ (xPlaneTop xPlaneHi xPlaneLo xPlaneBot) \ \ and then we add the 16-bit velocity, like this: \ \ (xVelocityTop xVelocityHi) \ \ The xPlaneBot byte is not used anywhere else, it's \ just used to keep track of the fractional part of this \ calculation \ \ so in terms of the original xPlane coordinate, we are \ effectively adding xVelocityTop, but keeping track of \ the fractional tally in xPlaneBot \ \ Fiknally, to support negative velocities, we extend \ xVelocity with new high and top bytes, set to 0 or &FF \ depending on the sign of xVelocity, so that's: \ \ (0 0 xVelocityTop xVelocityHi) \ \ for positive velocities, or: \ \ (&FF &FF xVelocityTop xVelocityHi) \ \ for negative velocities LDA #0 \ Set R = 0 to act as the high and top bytes in the STA R \ following addition LDA xPlaneBot,X \ Set xPlaneBot = xPlaneBot + xVelocityHi CLC \ ADC xVelocityHi,X \ so we've added the fractional parts and set the C flag STA xPlaneBot,X \ accordingly LDA xVelocityTop,X \ Set A = xVelocityTop, ready to add the xPlaneLo and \ xVelocityTop bytes BPL arot2 \ If A is negative, decrement R to &FF so we can use it DEC R \ as the high and top bytes for the velocity .arot2 ADC xPlaneLo,X \ Set xPlaneLo = xPlaneLo + xVelocityTop STA xPlaneLo,X \ \ so now we've added the low bytes LDA xPlaneHi,X \ And then we do the high bytes ADC R STA xPlaneHi,X LDA xPlaneTop,X \ And then the top bytes ADC R \ STA xPlaneTop,X \ so we now have the result we want: \ \ (xPlaneTop xPlaneHi xPlaneLo xPlaneBot) += \ (xVelocityTop xVelocityHi) LDA xRotationLo,X \ Set xRotation = xRotation + dxRotation CLC \ ADC dxRotationLo,X \ starting with the low bytes STA xRotationLo,X LDA xRotationHi,X \ And then the high bytes ADC dxRotationHi,X STA xRotationHi,X DEX \ Decrement the loop counter to move to the next axis BPL arot1 \ Loop back until we have processed all three axes ASL A \ At the end of the above loop, A = xRotationHi, so this EOR xRotationHi \ checks whether bit 6 and 7 of xRotationHi are the same BPL arot4 \ and if they are, it jumps to arot4 to return from the \ subroutine \ \ Bits 6 and 7 of xRotationHi are the same when the \ rotation angle is either 0 to 63 or 192 to 255 - in \ other words, when the plane is facing forwards: \ \ * 0 = straight ahead (bit 6 clear, bit 7 clear) \ * 64 = vertical up (bit 6 set, bit 7 clear) \ * 128 = backwards (bit 6 clear, bit 7 set) \ * 192 = nosedive (bit 6 set, bit 7 set) \ \ so this only runs the following if the plane is now \ pointing backwards, in which case we set bit 7 of the \ rotation in the y- and z-axes (which adds 128, or \ rotates by 180 degrees), and reflect the x-axis by \ subtracting the rotation from 128 LDX #1 \ Set a counter in X to work through the y- and z-axes .arot3 LDA yRotationHi,X \ Flip the sign of yRotationHi and zRotationHi EOR #%10000000 STA yRotationHi,X DEX \ Decrement the loop counter to move to the next axis BPL arot3 \ Loop back until we have processed both axes LDA #0 \ Set xRotation = (128 0) - xRotation, starting with the SEC \ low bytes SBC xRotationLo STA xRotationLo LDA #128 \ And then the high bytes SBC xRotationHi STA xRotationHi .arot4 RTS \ Return from the subroutine
Name: ApplyAerodynamics (Part 1 of 3) [Show more] Type: Subroutine Category: Flight model Summary: Set up various variables to use in the aerodynamics calculations Deep dive: The flight model
Context: See this subroutine on its own page References: This subroutine is called as follows: * ApplyFlightModel (Part 2 of 7) calls ApplyAerodynamics

This part does the following: * Set a number of variables that are used by the calculations in part 3: xLiftDrag = xVelocityP * 2 yLiftDrag = yVelocityP * 2 zLiftDrag = zVelocityP * 2 (J I) = |yVelocityP| * 4 (SS RR) = max(|velocityP|) * 2 * ~yPlaneHi / 256 Arguments: L The current value of onGround: * 0 = we are not on the ground * 1 = we are on the ground
.ApplyAerodynamics \ We start by finding the maximum velocity from the \ perspective of the plane, across all three axes \ \ We do this by finding the maximum of |xVelocityP|, \ |yVelocityP| and |zVelocityP| LDA #0 \ Set (SS RR) = 0, so we can use it to capture the STA RR \ maximum value of |xVelocityP|, |yVelocityP| and STA SS \ |zVelocityP| LDX #2 \ Set a counter in X to work through the three axes (the \ comments below cover the iteration for the x-axis) .aero1 LDA xVelocityPLo,X \ Set P = xVelocityPLo STA P ASL A \ Set xLiftDrag = xVelocityP << 1 STA xLiftDragLo,X \ = xVelocityP * 2 LDA xVelocityPHi,X \ PHA \ and push xVelocityPHi onto the stack ROL A STA xLiftDragHi,X PLA \ Set A = xVelocityPHi, so we have \ \ (A P) = (xVelocityPHi xVelocityPLo) \ = xVelocityP BPL aero2 \ If A is positive, then skip the following as (A P) is \ already positive LDA #0 \ Set (A P) = -(A P) SEC \ SBC P \ so by this point we have (A P) = |xVelocityP| STA P LDA #0 SBC xVelocityPHi,X .aero2 STA Q \ Set (Q P) = (A P) \ = |xVelocityP| CPX #1 \ If X <> 1, jump to aero3 to skip the following, so BNE aero3 \ (J I) only gets set on the y-axis iteration LDA P \ Set (A I) = (Q P) STA I \ = |yVelocityP| LDA Q ASL I \ Set (A I) = (A I) << 2 ROL A \ = (Q P) << 2 ASL I \ = |yVelocityP| * 4 ROL A STA J \ Set (J I) = (A I) \ = |yVelocityP| * 4 LDA Q \ Set A to the high byte of (Q P), so we have \ (A P) = |xVelocityP| once again .aero3 \ We now compare the two 16-bit values in (A P) and \ (SS RR), by first comparing the high bytes, and if \ they are equal, comparing the low bytes \ \ It then sets (SS RR) to the higher value, so this does \ the following: \ \ (SS RR) = max((SS RR), (Q P)) \ = max((SS RR), |xVelocityP|) \ \ Note that at this point, (A P) and (Q P) are the same CMP SS \ If A < SS, jump to aero5 to leave (SS RR) alone, as BCC aero5 \ (SS RR) is the higher value BNE aero4 \ If A <> SS, i.e. A > SS, jump to aero4 with A = Q, so \ we set (SS RR) to (Q P), as (Q P) is the higher value \ If we get here then A = SS, so now we compare the low \ bytes LDA P \ If P < RR, jump to aero5 to leave (SS RR) alone, as CMP RR \ (SS RR) is the higher value BCC aero5 LDA Q \ If we get here then A = SS and P >= RR, so set A = Q \ so we set (SS RR) to (Q P), as (Q P) is the higher \ value .aero4 STA SS \ Set (SS RR) = (A P) LDA P \ = max((SS RR), |xVelocityP|) STA RR .aero5 DEX \ Decrement the loop counter to move to the next axis BPL aero1 \ Loop back until we have processed all three axes \ So we now have the following: \ \ xLiftDrag = xVelocityP * 2 \ \ yLiftDrag = yVelocityP * 2 \ \ zLiftDrag = zVelocityP * 2 \ \ (J I) = |yVelocityP| * 4 \ \ (SS RR) = max(0, |xVelocityP|, |yVelocityP|, \ |zVelocityP|) \ \ Let's call this last one max(|velocityP|) ASL RR \ Set (SS RR) = (SS RR) << 1 ROL SS \ = max(|velocityP|) * 2 LDY SS \ Set (Y X) = (SS RR) LDX RR \ = max(|velocityP|) * 2 JSR ScaleByAltitude \ Set (Y X V) = (Y X) * ~yPlaneHi \ = max(|velocityP|) * 2 * ~yPlaneHi STY SS \ Set (SS RR) = (Y X) STX RR \ = max(|velocityP|) * 2 * ~yPlaneHi / 256
Name: ApplyAerodynamics (Part 2 of 3) [Show more] Type: Subroutine Category: Flight model Summary: Check whether the plane is stalling, and if it is, simulate one wing stalling before the other, and make the stalling sound Deep dive: The flight model Stalling and recovery
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

This part does the following: * If we are already stalling and not going fast enough to pull out of the stall, make the stalling sound and move on * If we are not already stalling, or we are possibly going fast enough to pull out of the stall (zVelocityPHi >= 11), we perform the stall check: zVelocityP <= |yVelocityP| * 4 If this is true, then we are stalling. * If we have just started stalling (i.e. we weren't already stalling but we are now), check that we are not too close to the ground (yPlaneLo >= 20), and assuming we aren't, apply a roll to the plane to simulate one of the wings stalling before the other: zTurn = zTurn +/- (A 0) >> 5 where: * The +/- sign is the sign of yTurn * A = xTurnTop EOR #%00111111 * Set the force factor for yLiftDrag according to the stalling state.
\ In the following, we jump to aero9 to make the \ stalling beep if both of these are true: \ \ * The force factor for yLiftDrag = 39, which \ indicates that we are already stalling \ \ * zVelocityPHi < 11, which indicates that the \ forward speed of the plane is too low to pull us \ out of the current stall \ \ In other words, if we are already stalling and the \ speed is not yet high enough to stop the stall, we \ jump straight to the part of the routine that makes \ the stalling sound LDA forceFactor+4 \ If the force factor for yLiftDrag <> 39, skip the CMP #39 \ following three instructions as we are not currently BNE aero6 \ stalling LDA zVelocityPHi \ If zVelocityPHi < 11, jump to aero9 as the plane is CMP #11 \ stalling BCC aero9 .aero6 \ If we get here then we are either not stalling, or we \ are already stalling but are possibly going fast \ enough to pull out of the stall, so we now perform the \ stalling check \ \ The plane stalls if this is true: \ \ zVelocityP <= |yVelocityP| * 4 \ \ in other words, when the plane is moving forwards at \ less than a quarter of the speed at which it is moving \ up or down, we stall \ \ When we first stall, then the plane rolls slightly to \ simulate one of the wings stalling before the other \ (though this is skipped if the plane is flying very \ low, at less than 20 feet above the ground) \ \ A reminder that we set the following in part 1: \ \ (J I) = |yVelocityP| * 4 \ \ We now compare the two 16-bit values in (J I) and \ zVelocityP, by first comparing the high bytes, and if \ they are equal, comparing the low bytes \ \ If it turns out that (J I) < zVelocityP, we jump to \ aero10 to maintain normal flight, so that's when: \ \ |yVelocityP| * 4 < zVelocityP \ \ but if (J I) >= zVelocityP, we are stalling, so that's \ when: \ \ zVelocityP <= |yVelocityP| * 4 LDA J \ If J < zVelocityPHi, then (J I) < zVelocityP, so jump CMP zVelocityPHi \ to aero10 to maintain normal flight BCC aero10 BNE aero7 \ If J <> zVelocityPHi, i.e. J > zVelocityPHi, then \ (J I) > zVelocity so jump to aero7 as we are going \ slowly enough to stall \ If we get here then J = zVelocityPHi, so now we \ compare the low bytes LDA I \ If I < zVelocityPLo, then (J I) < zVelocityP, so jump CMP zVelocityPLo \ to aero10 to maintain normal flight BCC aero10 .aero7 \ If we get here then (J I) >= zVelocityP, so: \ \ zVelocityP <= |yVelocityP| * 4 \ \ which means we are stalling \ \ We now check the plane's height above the ground in \ yPlane, as we don't roll the plane if it is less than \ 20 feet from the ground (probably as this would kill \ us instantly as the wings hit the ground, which would \ be a bit unfair, even if this is what would happen in \ real life) LDA yPlaneHi \ If yPlaneHi is non-zero, i.e. yPlane >= 256, then jump BNE aero8 \ to aero8 to skip the following three instructions, as \ we are high enough off the ground to roll the plane LDA yPlaneLo \ If yPlaneLo < 20, jump to aero9 to skip the roll, as CMP #20 \ we are too close to the ground BCC aero9 .aero8 LDA forceFactor+4 \ If the force factor for yLiftDrag = 39 then we are CMP #39 \ already stalling, so jump to aero9 to make the BEQ aero9 \ stalling sound without applying any roll \ Otherwise we are not already stalling, but we are now, \ so we apply a roll to the plane to simulate one of the \ wings stalling before the other LDA yTurnTop \ Set the C flag to the sign bit of yTurn so we can pass ASL A \ it to the AddScaled routine, so the roll will be in \ direction of the current yaw (i.e. the wing dips as \ the plane slips in that direction) LDY #4 \ Set the scale factor in Y so we add or subtract \ (A 0) >> 5 LDX #2 \ Set X so the call to AddScaled updates the zTurn \ variable LDA xTurnTop \ Set A = xTurnTop with bits 0 to 5 flipped, so (A 0) is EOR #%00111111 \ larger when the current pitch rate is smaller, and \ vice versa JSR AddScaled \ Set zTurn = zTurn +/- (A 0) >> 5 \ \ which applies a roll in the direction of the current \ yaw, with the amount of roll being inversely \ proportional to the current rate of pitch .aero9 LDA L \ Fetch the current value of onGround BNE aero10 \ If onGround is non-zero then we are on the ground, so \ skip the following, as we can't be stalling if we are \ on the ground LDA #4 \ Make sound #4, a short, low beep to indicate that the JSR MakeSound \ plane is stalling LDA #39 \ Set A = 39 so that the force factor for yLiftDrag is BNE aero11 \ set to 39 below (this BNE is effectively a JMP as A is \ never zero) .aero10 LDA #156 \ Set A = 156 so that the force factor for yLiftDrag is \ set to 156 below .aero11 STA forceFactor+4 \ Set the force factor for yLiftDrag to the value in A, \ which will be either 39 (if we are stalling) or 156 \ (in normal flight)
Name: ApplyAerodynamics (Part 3 of 3) [Show more] Type: Subroutine Category: Flight model Summary: Calculate various aerodynamic figures Deep dive: The flight model
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

This part calculates the following: [ xMoments ] = [ yVelocityP * 2 - xTurn * 250 / 256 ] [ yMoments ] = [ xVelocityP * 2 - yTurn * 250 / 256 ] * maxv * airDensity [ zMoments ] = [ -zTurn * 2 ] [ xLiftDrag ] = [ xVelocityP * 2 * 4 ] [ yLiftDrag ] = [ yVelocityP * 2 * 4 ] * maxv * airDensity [ zLiftDrag ] = [ zVelocityP * 2 ] zSlipMoment = xLiftDrag where: airDensity = ~yPlaneHi * 2 maxv = max(|xVelocityP|, |yVelocityP|, |zVelocityP|) If zLiftDrag is positive we also do: yFlapsLift = zLiftDrag xMoments = xMoments + (zLiftDrag * 8) when the flaps are off xMoments - (zLiftDrag * 4) when the flaps are on
JSR GetMoments \ Set the following: \ \ xTemp3 = yLiftDrag - (xTurn * 250 / 256) \ = yVelocityP * 2 - (xTurn * 250 / 256) \ \ yTemp3 = xLiftDrag - (yTurn * 250 / 256) \ = xVelocityP * 2 - (yTurn * 250 / 256) \ \ zTemp3 = -zTurn * 2 \ A reminder that we set the following in part 1: \ \ (SS RR) = max(|velocityP|) * 2 * ~yPlaneHi / 256 LDA RR \ Set (S R) = (SS RR) with bit 0 of the low byte cleared AND #%11111110 \ to convert this into a value with the sign in bit 0 STA R \ and the value as follows (we drop the "* 2" as bit 0 LDA SS \ is now the sign bit): STA S \ \ (S R) = max(|velocityP|) * ~yPlaneHi / 256 LDX #5 \ Set X as a loop counter from 5 down to 0 LDA #0 \ Change the rounding in Multiply16x16Mix so that it STA mult1+1 \ rounds down, i.e. uses floor to round .aero12 CPX #3 \ If X >= 3, jump to aero13 to skip the following BCS aero13 \ For X = 0 to 2, we now fetch the relevant axis of \ xTemp3, which we set above to: \ \ xTemp3 = yVelocityP * 2 - (xTurn * 250 / 256) \ \ yTemp3 = xVelocityP * 2 - (yTurn * 250 / 256) \ \ zTemp3 = -zTurn * 2 LDY xTemp3Lo,X \ Set (A Y) = xTemp3 (or yTemp3 or zTemp3) LDA xTemp3Hi,X JMP aero14 \ Jump to aero14 .aero13 \ For X = 3 to 5, we now fetch the relevant axis of \ xLiftDrag, which we set in part 1 to: \ \ xLiftDrag = xVelocityP * 2 \ \ yLiftDrag = yVelocityP * 2 \ \ zLiftDrag = zVelocityP * 2 LDY xMomentsLo,X \ Set (A Y) = xLiftDrag (or yLiftDrag or zLiftDrag) LDA xMomentsHi,X .aero14 STA J \ Set (J I) = (A Y) STY I \ \ = xTemp3 for X = 0 to 2 \ \ xVelocityP * 2 for X = 3 to 5 LDA #0 \ Set K = 0, so Multiply16x16Mix doesn't negate the STA K \ result, and returns the sign of the result in K STX VV \ Store the loop counter in VV, so we can retrieve it \ below JSR Multiply16x16Mix \ Call Multiply16x16Mix to calculate: \ \ (H G W) = (J I) * (S R) / 256 \ \ = xTemp3 * max(|velocityP|) * ~yPlaneHi \ \ xVelocityP * 2 * max(|velocityP|) * ~yPlaneHi LDA K \ If the result of the multiplication is positive, jump BPL aero15 \ to aero15 to skip the following SEC \ The result of the multiplication is negative, so now LDA #0 \ we negate (H G W), starting with the low bytes SBC W STA W LDA G \ Then the middle bytes SBC #0 STA G BCS aero15 \ And finally the high bytes DEC H .aero15 LDX VV \ Retrieve the value of the loop counter X that we \ stored in VV above LDY #0 \ Set Y = 0 to act as a shift counter in the loop below, \ so by default it shifts the result left by 1 place LDA G \ Set (H A W) = (H G W) CPX #3 \ If X < 3, jump to aero16 BCC aero16 CPX #5 \ If X = 5, jump to aero16 BEQ aero16 INY \ If we get here then X = 3 or 4, so increment Y to 2 so INY \ the following loop shifts (H A W) left by 3 places \ We now shift (H A W) left by Y + 1 places .aero16 ASL W \ Set (H A W) = (H A W) << 1 ROL A ROL H DEY \ Decrement the shift counter BPL aero16 \ Loop back until we have shifted left by Y + 1 places, \ so that's the same as: \ \ (H A W) = (H A W) * 8 if X = 3 or 4 \ \ (H A W) = (H A W) * 2 otherwise STA xMomentsLo,X \ Set xMoments or xLiftDrag = (H A) LDA H STA xMomentsHi,X DEX \ Decrement the loop counter to move onto the next BPL aero12 \ Loop back until we have set xMoments to zMoments and \ xLiftDrag to zLiftDrag, so we now have: \ \ xMoments = xTemp3 * maxv * ~yPlaneHi * 2 \ = (yVelocityP * 2 - (xTurn * 250 / 256)) \ * maxv * ~yPlaneHi * 2 \ \ yMoments = yTemp3 * maxv * ~yPlaneHi * 2 \ = (xVelocityP * 2 - (yTurn * 250 / 256)) \ * maxv * ~yPlaneHi * 2 \ \ zMoments = zTemp3 * maxv * ~yPlaneHi * 2 \ = -zTurn * 2 * maxv * ~yPlaneHi * 2 \ \ xLiftDrag = xVelocityP * 2 * maxv * ~yPlaneHi * 8 \ \ yLiftDrag = yVelocityP * 2 * maxv * ~yPlaneHi * 8 \ \ zLiftDrag = zVelocityP * 2 * maxv * ~yPlaneHi * 2 \ \ where: \ \ maxv = max(|xVelocityP|, |yVelocityP|, |zVelocityP|) LDA #128 \ Change the rounding in Multiply16x16Mix back to the STA mult1+1 \ default, so it rounds to the nearest integer LDA xLiftDragLo \ Set zSlipMoment = xLiftDrag STA zSlipMomentLo LDA xLiftDragHi STA zSlipMomentHi LDA zLiftDragHi \ If zLiftDrag is negative, jump to aero19 to return BMI aero19 \ from the subroutine \ If zLiftDrag is positive, then we now move on to set \ yFlapsLift and xMoments STA W \ Set the following: STA yFlapsLiftHi \ LDA #0 \ (G W A) = (0 zLiftDragHi zLiftDragLo) STA G \ = zLiftDrag LDA zLiftDragLo \ STA yFlapsLiftLo \ yFlapsLift = zLiftDrag LDX #2 \ Set X = 2 to act as a shift counter in the loop below, \ so by default it shifts the result left by 3 places LDA flapsStatus \ Set A to the current flap status (0 if flaps are off, \ 1 if they are on) PHP \ Store the flags on the stack, so we can check the flap \ status later on BEQ aero17 \ If the flaps are off, skip the following instruction LDX #1 \ Set X = 1 so the following loop shifts (G W A) left by \ 2 places \ We now shift (G W A) left by X + 1 places, so that's: \ \ (G W A) = zLiftDrag << 3 if the flaps are off \ \ (G W A) = zLiftDrag << 2 if the flaps are on .aero17 ASL A \ Set (G W A) = (G W A) << 1 ROL W ROL G DEX \ Decrement the shift counter BPL aero17 \ Loop back until we have shifted left by X + 1 places PLP \ Restore the processor flags, so this jumps to aero18 BEQ aero18 \ if flapsStatus is zero, i.e. the flaps are off SEC \ The flaps are on, so negate (G W) JSR Negate16Bit .aero18 \ We now have the following: \ \ (G W A) = zLiftDrag << 3 if the flaps are off \ \ (G W A) = -zLiftDrag << 2 if the flaps are on CLC \ Set xMoments = xMoments + (G W) LDA W \ ADC xMomentsLo \ starting with the low bytes STA xMomentsLo LDA G \ And then the high bytes ADC xMomentsHi STA xMomentsHi \ So if zLiftDrag is negative, we now have the \ following: \ \ xMoments = xMoments + zLiftDrag << 3 when the flaps \ = xMoments + (zLiftDrag * 8) are off \ \ xMoments = xMoments - zLiftDrag << 2 when the flaps \ = xMoments - (zLiftDrag * 4) are on .aero19 RTS \ Return from the subroutine
Name: ScaleFlightForces [Show more] Type: Subroutine Category: Flight model Summary: Scale the flight forces by the relevant scale factors Deep dive: The flight model Ripping the wings off
Context: See this subroutine on its own page References: This subroutine is called as follows: * ApplyFlightModel (Part 2 of 7) calls ScaleFlightForces

For each of the 11 flight forces, this routine calculates the following: scaledForce = unscaledForce * forceFactor * 2 ^ scaleFactor The 11 flight forces are: * (xMoments, yMoments, zMoments) * (xLiftDrag, yLiftDrag, zLiftDrag) * zSlipMoment * yFlapsLift * (xControls, yControls, zControls) The scaled results are stored in xMomentsSc, xLiftDragSc and so on. This routine also tests for high g-forces, which occur if: |yLiftDrag| >= 2048 / 39 = 53 if we are stalling 2048 / 156 = 13 if we are not stalling
.ScaleFlightForces LDX #12 \ We are about to work our way through the flight \ forces, so set a counter in X to act as an index into \ these three tables: \ \ * The unscaled forces from xMoments to zControls \ \ * The forceFactor table \ \ * The scaleFactor table \ \ * The scaled forces from xMomentsSc to zControlsSc \ \ These three tables have the same structure, so we can \ iterate through all of them using an index in X that \ goes from 12 to 0 while skipping over 8 and 9 .scal1 CPX #9 \ If X <> 9, jump to scal2 to skip the following BNE scal2 \ instruction LDX #7 \ If we get here then X = 9, so set X = 7 so we skip \ indexes 8 and 9 .scal2 LDA forceFactor,X \ Set R to the force factor for the X-th force STA R LDY xMomentsLo,X \ Set (A Y) to the X-th unscaled force LDA xMomentsHi,X JSR Multiply8x16-6 \ Store X in VV and set (G W V) = (A Y) * R \ \ Also set A to the high byte of the result in G, so we \ have: \ \ (A W V) = (A Y) * R \ \ so: \ \ (A W) = (A Y) * R / 256 LDX VV \ Retrieve X from VV so it once again contains the loop \ index CPX #4 \ If X <> 4, skip the following section BNE scal4 \ If we get here then X = 4, and the force factor for \ yLiftDrag determines whether the plane is stalling \ (156) or is in normal flight (39) TAY \ If the high byte of (A Y) * R is positive, then jump BPL scal3 \ to scal3 to skip the following instruction EOR #&FF \ Set A = ~A, so A now contains the high byte of the \ 24-bit result, flipped, so it contains: \ \ |A Y| * R / 256 .scal3 CMP #8 \ If A < 8, jump to scal4 to skip the next three BCC scal4 \ instructions \ If we get here then A >= 8, so: \ \ |A Y| * R / 256 >= 8 \ \ |A Y| * R >= 2048 \ \ |A Y| >= 2048 / 39 = 53 if we are stalling \ 2048 / 156 = 13 if we are not stalling \ \ where |A Y| = |yLiftDrag| LDA #3 \ Make sound #3, a long, medium beep that indicates high JSR MakeSound \ g-forces are ripping the wings off LDX #4 \ Set X = 4 once again, as it will have been corrupted \ by the call to MakeSound .scal4 LDY scaleFactor,X \ Set Y = the X-th entry in the scaleFactor table, which \ contains the number of places to shift the result, \ with a negative number meaning a right shift, a \ positive number meaning a left shift, and a value of \ zero having no effect \ \ In other words, we multiply the result in (G W V) by \ 2^scaleFactor, where scaleFactor is a signed integer BEQ scal7 \ If the entry is zero, jump to scal7 to write the \ result to the scaled force BPL scal6 \ If the entry is positive, jump to scal6 to shift the \ result to the left \ If we get here then the entry is negative, so we shift \ the result to the right LDA #0 \ Set R = 0 to use as the byte to feed into the top byte STA R \ when right-shifting (G W V) LDA G \ If the high byte in G is negative, decrement R to &FF BPL scal5 \ so it feeds in bits of the correct sign DEC R .scal5 LSR R \ Shift (G W V) right by one place, shifting in bits ROR G \ from R into G ROR W ROR V INY \ Increment the shift counter BNE scal5 \ Loop back until we have shifted right by -Y places BEQ scal7 \ Jump to scal7 to write the result to the scaled force .scal6 ASL V \ Shift (G W V) left by one place ROL W ROL G DEY \ Increment the shift counter BNE scal6 \ Loop back until we have shifted left by Y places .scal7 LDA G \ Set (xMomentsScTop xMomentsScHi xMomentsScLo) STA xMomentsScTop,X \ = (G W V) LDA W \ STA xMomentsScHi,X \ so we have: LDA V \ STA xMomentsScLo,X \ scaledForce = unscaledForce * forceFactor \ * 2 ^ scaleFactor DEX \ Decrement the loop counter to move to the next flight \ factor BPL scal1 \ Loop back until we have processed all 13 flight \ factors RTS \ Return from the subroutine
Name: Multiply8x16 [Show more] Type: Subroutine Category: Maths Summary: Multiply an 8-bit and a 16-bit number Deep dive: Times tables and nibble arithmetic
Context: See this subroutine on its own page References: This subroutine is called as follows: * ScaleByAltitude calls Multiply8x16 * ApplyFlightControl calls entry point Multiply8x16-6 * ProcessLanding (Part 7 of 7) calls entry point Multiply8x16-6 * ScaleFlightForces calls entry point Multiply8x16-6 * GetMoments calls entry point Multiply8x16-2

This routine multiplies an unsigned 8-bit and a signed 16-bit number, as follows: (G W V) = (Q P) * R It uses the following algorithm: (Q P) * R = (Q << 8 + P) * R = (Q << 8) * R + (P * R) = (Q * R) << 8 + (P * R) Arguments: (Q P) A signed 16-bit number R An unsigned 8-bit number Returns: (G W V) The result, (Q P) * R A The high byte of the result (G) Other entry points: Multiply8x16-2 Store X in VV before doing the calculation Multiply8x16-6 Store X in VV and calculate (G W V) = (A Y) * R
STY P \ Set (Q P) = (A Y) STA Q STX VV \ Store X in VV .Multiply8x16 LDX Q \ Set X to the high byte of the argument in (Q P) BPL muly1 \ If X is positive, jump to muly1 to skip the following LDA #0 \ Set (X P) = 0 - (Q P) SEC \ SBC P \ Starting with the low bytes STA P LDA #0 \ And then the high bytes SBC Q TAX .muly1 \ By this point, (X P) is positive, and we have: \ \ (X P) = |Q P| LDY R \ Set (A V) = X * Y JSR Multiply8x8 \ = |Q| * R STA G \ Set (G W) = (A V) LDA V \ = |Q| * R STA W LDY R \ Set (A V) = X * Y LDX P \ = P * R JSR Multiply8x8 \ We now calculate the following: \ \ (G W V) = (G W 0) + (0 A V) \ = (G W) << 8 + (P * R) \ = (|Q| * R) << 8 + (P * R) \ \ which is the result we want \ \ We we don't need to calculate the lowest bytes, as we \ already know that V + 0 = V, so we just do the middle \ and high bytes CLC \ Set (G W V) = (G W 0) + (0 A V) ADC W \ STA W \ starting with the middle bytes LDA #0 \ And then the top bytes ADC G STA G LDX Q \ If the original argument (Q P) was positive, the BPL GetMoments-1 \ result already has the correct sign, so return from \ the subroutine (as GetMoments-1 contains an RTS) LDA #0 \ Otherwise we want to negate (G W V), so start with by SEC \ negating V SBC V STA V \ And fall through into Negate16Bit to negate (G W) and \ return that as our result
Name: Negate16Bit [Show more] Type: Subroutine Category: Maths Summary: Negate a 16-bit number
Context: See this subroutine on its own page References: This subroutine is called as follows: * AddScaled calls Negate16Bit * ApplyAerodynamics (Part 3 of 3) calls Negate16Bit * ApplyFlightControl calls Negate16Bit * ProcessLanding (Part 7 of 7) calls Negate16Bit

Arguments: (G W) The 16-bit value to be negated C flag Must be set on entry to get the correct result Returns: (G W) The original value, negated, i.e. -(G W) A The high byte of the negated result, (G)
.Negate16Bit LDA #0 \ Negate (G W) by setting (G W) = 0 - (G W), starting SBC W \ with the low byte STA W LDA #0 \ And then the high byte SBC G STA G RTS \ Return from the subroutine
Name: GetMoments [Show more] Type: Subroutine Category: Flight model Summary: Calculate the pitching, rolling and yawing moments due to the current pitch, roll and yaw rates
Context: See this subroutine on its own page References: This subroutine is called as follows: * ApplyAerodynamics (Part 3 of 3) calls GetMoments * Multiply8x16 calls entry point GetMoments-1

This routine calculates: xTemp3 = yLiftDrag - (xTurn * 250 / 256) yTemp3 = xLiftDrag - (yTurn * 250 / 256) zTemp3 = -zTurn * 2 which contain the moments due to the plane's current movement. These then get scaled by altitude and put into (xMoments yMoments zMoments). Other entry points: GetMoments-1 Contains an RTS
.GetMoments LDX #1 \ Set a counter in X to work through the x- and y-axes .torq1 LDA #125 \ Set R = 125 STA R LDA xTurnHi,X \ Set (Q P) = (xTurnTop xTurnHi) STA P \ = xTurn LDA xTurnTop,X STA Q ASL P \ Set (Q P) = (Q P) * 2 ROL Q \ = xTurn * 2 JSR Multiply8x16-2 \ Store X in VV and set: \ \ (G W V) = (Q P) * R \ = xTurn * 2 * 125 \ = xTurn * 250 LDA VV \ Set A and X to the axis number that the above call TAX \ stored in VV, which is the same as the axis counter X \ (so it's 0 for the x-axis and 1 for the y-axis) EOR #1 \ Set Y to the opposite axis number to X, so when we are TAY \ calculating xTemp3 in the following and X = 0, Y = 1 \ so we use yLiftDrag, and when we are calculating \ yTemp3, X = 1 and Y = 0, so we use xLiftDrag SEC \ Set xTemp3 = yLiftDrag - (G W) LDA xLiftDragLo,Y \ = yLiftDrag - (xTurn * 250 / 256) SBC W STA xTemp3Lo,X LDA xLiftDragHi,Y SBC G STA xTemp3Hi,X DEX \ Decrement the loop counter to move to the next axis BPL torq1 \ Loop back until we have processed both axes SEC \ Set (A zTemp3Lo) = 0 - (zTurnTop zTurnHi) LDA #0 \ = -zTurn SBC zTurnHi \ STA zTemp3Lo \ starting with the high bytes LDA #0 \ And then the low bytes SBC zTurnTop ASL zTemp3Lo \ Set (zTemp3Hi zTemp3Lo) = (A zTemp3Lo) << 1 ROL A \ = -zTurn * 2 STA zTemp3Hi RTS \ Return from the subroutine
Name: ApplyFlightControl [Show more] Type: Subroutine Category: Flight model Summary: Calculate the effects of the primary flight controls (elevator, rudder and ailerons), and implement the "instant centre" feature Deep dive: The flight model On-ground calculations
Context: See this subroutine on its own page References: This subroutine is called as follows: * ApplyFlightModel (Part 2 of 7) calls ApplyFlightControl

This routine sets the following, depending on the current position of each primary flight control: xControls = zLiftDrag * elevatorPosition (pitch) yControls = zLiftDrag * rudderPosition (yaw) zControls = zLiftDrag * aileronPosition (roll) It also implements the "instant centre" feature for the aileron (roll) and the ground steering controls (the latter is controlled by using the rudder keys when on the ground). This feature is for keyboard users only, and instantly centres the control when you press the key for the opposite direction, so if we press "S" to roll left, then pressing "D" will instantly centre the joystick before rolling to the right. This feature does not apply to the elevator or rudder, though it does apply to the rudder controls when on the ground, as they double-up as brake controls for ground steering, which does have this feature. Arguments: L The current value of onGround: * 0 = we are not on the ground * 1 = we are on the ground
.ApplyFlightControl LDX #2 \ Set a counter in X to work through the three axes, so \ we can work our way through the three primary flight \ controls as follows: \ \ * 2 = aileron, for roll around the z-axis \ * 1 = rudder, for yaw around the y-axis \ * 0 = elevator, for pitch around the x-axis \ \ We also work through the aileron, rudder and elevator \ key pairs, whose values are stored in these key logger \ offsets in (keyLoggerHi keyLoggerLo), to check \ whether any of the relevant control keys are being \ pressed \ \ We set a counter in X to count down through these \ index values, from 2 to 1 to 0 .fcon1 LDA elevatorPosition,X \ Fetch the position of the flight control that affects BEQ fcon7 \ the axis we are currently processing LDY 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 BNE fcon3 \ If Y is non-zero then a key in this key pair is being \ pressed, so jump down to fcon3 TAY \ No key is being pressed for this axis, so copy the \ current position of the flight control into Y BMI fcon2 \ If the control's current position is negative then \ jump to fcon2 CPY #4 \ If the control's current position >= 4, jump to fcon6 BCS fcon6 \ to set the control's position to A, which will make no \ difference as A already contains the current value BCC fcon5 \ The control's current position < 4, so jump to fcon5 \ to zero the control (this BCC is effectively a JMP as \ we just passed through a BCS) .fcon2 \ If we get here then the control's current position in \ Y is negative CPY #&FD \ If the control's current position >= -3, jump to fcon5 BCS fcon5 \ to zero the control BCC fcon6 \ The control's current position < -3, so jump to fcon6 \ to set the control's position to A, which will make no \ difference as A already contains the current value \ (this BCC is effectively a JMP as we just passed \ through a BCS) .fcon3 \ If we get here then a key is being pressed for this \ control and A contains the control's current position CPX #1 \ If the axis in X < 1, then an elevator key is being BCC fcon7 \ pressed, so jump to fcon7 to leave the contol's \ position alone BNE fcon4 \ If the axis in X <> 0, then an aileron key is being \ pressed, so jump to fcon4 to check whether to apply \ the aileron's "instant centre" feature \ If we get here then a rudder key is being pressed LDY L \ Fetch the current value of onGround BEQ fcon7 \ If onGround is zero then we are not on the ground, so \ jump to fcon7 to leave the contol's position alone, \ otherwise keep going to check whether to apply the \ ground steering's "instant centre" feature .fcon4 \ If we get here then either an aileron key is being \ pressed, or we are on the ground and a rudder key is \ being pressed \ \ Pressing the rudder key while on the ground controls \ ground steering, by applying the brakes to the \ individual wheels EOR keyLoggerHi,X \ The relevant entry in keyLoggerHi will be -1 or +1 \ depending on the direction of the aileron or rudder \ key being applied, so this EOR compares the key's \ direction with the current position, setting bit 7 \ if the two have different signs, and clearing bit 7 \ if they have the same sign BPL fcon7 \ If bit 7 is clear, then the key's direction and the \ current position of the control have the same sign, so \ jump to fcon7 to leave the contol's position alone \ \ Otherwise we are pressing a key in the opposite \ direction to the current aileron or ground steering \ position, so we instantly move the control back to the \ centre point .fcon5 \ We jump here if control's current position is: \ \ * Positive and < 4 \ * Negative and >= -3 \ \ i.e. -3 <= position <= 3 \ \ In either case, we set the position to 0, so this \ implements a dead zone around the control's centre \ point, and the "instant centre" feature for the \ aileron and ground steering LDA #0 \ Set A = 0 to set as the control's new position, i.e. \ centre the control .fcon6 STA elevatorPosition,X \ Set the control's new position to the value in A .fcon7 LDA elevatorPosition,X \ Fetch the updated position of the flight control for \ the current axis BPL fcon8 \ If the control's new position is positive, jump to \ fcon8 to skip the following EOR #&FF \ Negate A using two's complement, so: CLC \ ADC #1 \ A = |A| \ = |controlPosition| .fcon8 STA R \ Set R = |A| \ = |controlPosition| LDY zLiftDragLo \ Set (A Y) = zLiftDrag LDA zLiftDragHi JSR Multiply8x16-6 \ Store X in VV and set: \ \ (G W V) = (A Y) * R \ = zLiftDrag * |controlPosition| \ \ Also set A to the high byte of the result, so (A W V) \ also contains the result LDX VV \ Retrieve X from VV so it once again contains the axis \ index LDY elevatorPosition,X \ If the position of the flight control is positive, BPL fcon9 \ jump to fcon9 to skip the following SEC \ Negate (G W) and return the high byte in A, so (A W) JSR Negate16Bit \ contains the negated result, so we now have the \ following: \ \ (A W V) = zLiftDrag * controlPosition .fcon9 STA xControlsHi,X \ Set xControls = (A W) LDA W \ = zLiftDrag * controlPosition STA xControlsLo,X DEX \ Decrement the loop counter to move to the next axis BEQ fcon7 \ If this is the elevator axis (X = 0), loop back to \ fcon7 to skip the "instant centre" check, as this \ feature doesn't apply to pitching BPL fcon1 \ If this is the rudder y-axis (X = 1), loop back to \ fcon1 to apply the "instant centre" check, as this \ feature applies to the ground steering RTS \ Return from the subroutine
Name: ApplyTurnAndThrust (Part 1 of 2) [Show more] Type: Subroutine Category: Flight model Summary: Calculate the (dxTurn, dyTurn, dzTurn) vector
Context: See this subroutine on its own page References: This subroutine is called as follows: * ApplyFlightModel (Part 4 of 7) calls ApplyTurnAndThrust

This part of the routine calculates the following: [ dxTurn ] [ xMomentsSc ] [ xControlsSc ] [ dyTurn ] = [ yMomentsSc ] + [ yControlsSc ] [ dzTurn ] [ zMomentsSc ] [ zControlsSc ] [ yGravity ] [ 0 ] + [ 0 ] - [ 0 ] [ 0 ] [ zSlipMoment ] Arguments: L The current value of onGround: * 0 = we are not on the ground * 1 = we are on the ground
.ApplyTurnAndThrust LDX #2 \ Set a counter in X to work through the three axes (the \ comments below cover the iteration for the x-axis) .turn1 LDA xMomentsScLo,X \ Set dxTurn = xMomentsSc + xControlsSc CLC \ ADC xControlsScLo,X \ starting with the low bytes STA dxTurnLo,X LDA xMomentsScHi,X \ And then the high bytes ADC xControlsScHi,X STA dxTurnHi,X LDA xMomentsScTop,X \ And then the top bytes ADC xControlsScTop,X STA dxTurnTop,X DEX \ Decrement the loop counter to move to the next axis BPL turn1 \ Loop back until we have added all three axes, so we \ now have the following, where all values are 24-bit \ numbers: \ \ dxTurn = xMomentsSc + xControlsSc \ dyTurn = yMomentsSc + yControlsSc \ dzTurn = zMomentsSc + zControlsSc LDA #0 \ Set S = 0, to use as the top byte of yGravity STA S LDA yGravityLo \ Set dxTurn = dxTurn + yGravity CLC \ ADC dxTurnLo \ starting with the low bytes STA dxTurnLo \ We now add the high bytes, but because dxTurn is a \ 24-bit number and yGravity is only a 16-bit number, \ we have to add an extra top byte to yGravity, which we \ do in S as follows: \ \ (S yGravityHi yGravityLo) LDA yGravityHi \ Set A to the high byte of yGravity BPL turn2 \ If yGravityHi is negative, decrement S to &FF so it DEC S \ has the correct sign for (S yGravityHi yGravityLo) .turn2 ADC dxTurnHi \ We then add the high bytes STA dxTurnHi LDA dxTurnTop \ And finally we add the top bytes, so we now have: ADC S \ STA dxTurnTop \ dxTurn = dxTurn + yGravity SEC \ Set dzTurn = dzTurn - zSlipMomentSc LDA dzTurnLo \ SBC zSlipMomentScLo \ starting with the low bytes STA dzTurnLo LDA dzTurnHi \ And then the high bytes SBC zSlipMomentScHi STA dzTurnHi LDA dzTurnTop \ And then the top bytes SBC zSlipMomentScTop STA dzTurnTop
Name: ApplyTurnAndThrust (Part 2 of 2) [Show more] Type: Subroutine Category: Flight model Summary: Calculate the (xLinear, yLinear, zLinear) vector
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

This part of the routine calculates the following. If zVelocityPHi >= 48 (so forward speed >= 500 mph), we calculate: [ xLinear ] [ 0 ] [ xLiftDragSc ] [ yLinear ] = [ yFlapsLiftSc ] - [ yLiftDragSc ] [ zLinear ] [ 0 ] [ (&EA zLinearLo) ] If zVelocityPHi < 48 (so forward speed < 500 mph), we calculate: [ xLinear ] [ 0 ] [ xLiftDragSc ] [ 0 ] [ yLinear ] = [ yFlapsLiftSc ] - [ yLiftDragSc ] + [ 0 ] [ zLinear ] [ 0 ] [ zLiftDragSc ] [ zEngine ] where zEngine is 0 if the engine is off, or the following if the engine is on: zEngine = max(0, thrustScaled - (max(0, zVelocityP) / 16)) * airDensity / 512 and: airDensity = ~yPlaneHi * 2 and thrustScaled is the thrust in (thrustHi thrustLo), but: * Doubled if thrust >= 1024 * Doubled if zVelocity is in the range 512 to 1023 It also calls the RetractFlapsIfFast routine to retract the flaps if we are going too fast.
SEC \ Set xLinear = -xLiftDragSc / 256 LDA #0 \ SBC xLiftDragScHi \ starting with the low bytes STA xLinearLo LDA #0 \ And then the high bytes SBC xLiftDragScTop STA xLinearHi SEC \ Set yLinear = (yFlapsLiftSc - yLiftDragSc) / 256 LDA yFlapsLiftScHi \ SBC yLiftDragScHi \ starting with the low bytes STA yLinearLo LDA yFlapsLiftScTop \ And then the high bytes SBC yLiftDragScTop STA yLinearHi LDA zVelocityPHi \ Set A to the high byte of the forward airspeed from \ the perspective of the plane BMI turn3 \ If it is negative, jump down to turn3 to set A = 0 and \ skip the following checks, as we only retract the \ flaps if we are going to fast in a forward direction PHA \ Store A on the stack so we can retrieve it after the \ following call to RetractFlapsIfFast JSR RetractFlapsIfFast \ Retract the flaps if we are going too fast PLA \ Retrieve the value of A from the stack CMP #48 \ If zVelocityPHi < 48, jump to turn4 BCC turn4 LDA #&EA \ Set A = &EA and jump to turn10 to set zLinearHi to BNE turn10 \ &EA and return from the subroutine (this BNE is \ effectively a JMP as A is never zero) .turn3 LDA #0 \ We jump here if the value of zVelocityPHi in A is \ negative, in which case we set A = 0, so we now have: \ \ A = max(0, zVelocityPHi) .turn4 LDX engineStatus \ If engineStatus is zero, then the engine is not BEQ turn8 \ running, so jump to turn8 to set the forward force \ from the engine to zero STA H \ Set X = max(0, zVelocityPHi) STA G \ Set G = max(0, zVelocityPHi) LDA zVelocityPLo \ Set A to the low byte of the forward airspeed LDX #3 \ Set X as a shift counter in the following loop, so we \ shift right by 4 places .turn5 LSR G \ Set (G A) = (G A) >> 1 ROR A DEX \ Decrement the shift counter BPL turn5 \ Loop back until we have shifted right by X + 1 places, \ so: \ \ (G A) = (G A) >> 4 \ = max(0, zVelocityP) / 16 STA W \ Set (G W) = (G A) \ = max(0, zVelocityP) / 16 LDY thrustHi \ Set Y to the high byte of the current thrust STY R \ Set (R A) = (thrustHi thrustLo) LDA thrustLo \ = thrust LDX L \ Fetch the current value of onGround BEQ turn7 \ If onGround is zero then we are not on the ground, so \ jump to turn7 CPY #4 \ If thrustHi < 4, jump to turn6 to skip the following BCC turn6 \ two instructions ASL A \ Set (R A) = (R A) << 1 ROL R \ \ so we double the value of (R A) if the thrust is >= \ (4 0), i.e. >= 1024 .turn6 LDY H \ If max(0, zVelocityPHi) >= 4, jump to turn7 to skip CPY #4 \ the following BCS turn7 CPY #1 \ If max(0, zVelocityPHi) < 1, jump to turn7 to skip BCC turn7 \ the following ASL A \ Set (R A) = (R A) << 1 ROL R \ \ so we double the value of (R A) again if zVelocityPHi \ is 2 or 3, which means zVelocity is in the range (2 0) \ to (3 255), or 512 to 1023 \ By now we have set (R A) to the thrust, scaled up as \ follows: \ \ * Doubled if thrust >= 1024 \ \ * Doubled if zVelocity is in the range 512 to 1023 \ \ Let's call this thrustScaled .turn7 SEC \ Set (Y X) = (R A) - (G W) SBC W \ = thrustScaled - (max(0, zVelocityP) / 16) TAX \ \ starting with the low bytes LDA R \ And then the high bytes SBC G TAY BPL turn9 \ If Y is positive, skip to turn9, otherwise keep going \ to set (Y X) = 0, so this means: \ \ (Y X) = max(0, thrustScaled \ - (max(0, zVelocityP) / 16)) .turn8 LDY #0 \ Set (Y X) = 0 LDX #0 .turn9 JSR ScaleByAltitude \ Set (Y X V) = (Y X) * ~yPlaneHi \ \ so (Y X) = (Y X) * ~yPlaneHi / 256 \ = max(0, thrustScaled \ - (max(0, zVelocityP) / 16)) \ * ~yPlaneHi / 256 \ \ or (Y X) = 0 if the engine is off TXA \ Set zLinear = (Y X) - zLiftDragSc / 256 SEC \ SBC zLiftDragScHi \ starting with the low bytes STA zLinearLo TYA \ And then the high bytes, so now we have the following SBC zLiftDragScTop \ when the engine is on: \ \ zLinear = max(0, thrustScaled \ - (max(0, zVelocityP) / 16)) \ * ~yPlaneHi / 256 \ - zLiftDragSc / 256 .turn10 STA zLinearHi \ Store the high byte of the result in zLinearHi RTS \ Return from the subroutine
Name: CheckPlaneOnRunway [Show more] Type: Subroutine Category: Flight model Summary: Check whether the plane is over the runway Deep dive: Take-offs and landings On-ground calculations
Context: See this subroutine on its own page References: This subroutine is called as follows: * ApplyFlightModel (Part 5 of 7) calls CheckPlaneOnRunway * ProcessLanding (Part 1 of 7) calls CheckPlaneOnRunway * ProcessLanding (Part 6 of 7) calls CheckPlaneOnRunway

The runway runs north-south, and the runway's anchor point is in the southwest corner of the runway rectangle. The plane is on the runway if: * The x-axis distance between the plane and the runway's anchor point is less than 256 (so the runway is 256 wide, and this checks that the plane is not to the side of the runway) * The z-axis distance between the plane and the runway's anchor point is positive, and less than 24 * 256 (so the runway is 24 * 256 long, and this checks that the plane is not too far to the south or the north of the runway) In other words, the runway is a long, thin strip with a north-south alignment with the strip being 24 times longer than it is wide, and with the anchor point at the southwest corner. Returns: C flag Determines whether the plane is over the runway: * Clear if the plane is over the runway * Set if the plane is not over the runway
.CheckPlaneOnRunway LDA xPlaneLo \ Set A to the high byte of the following: SEC \ SBC xObjectLo+1 \ (xPlaneHi xPlaneLo) - (xObjectHi xObjectLo) LDA xPlaneHi \ SBC xObjectHi+1 \ for object ID 1, which is the runway, so this \ calculates the distance in the x-axis between the \ plane and the runway's anchor point BNE crun1 \ If the high byte is non-zero, then the plane is more \ than 256 from the runway's anchor point, so jump to \ crun1 to return from the subroutine with a negative \ result \ If we get here then the plane is over the runway in \ the x-axis, so now we check the z-axis LDA zPlaneLo \ Set A to the high byte of the following: SEC \ SBC zObjectLo+1 \ (zPlaneHi zPlaneLo) - (zObjectHi zObjectLo) LDA zPlaneHi \ SBC zObjectHi+1 \ for object ID 1, which is the runway, so this \ calculates the distance in the z-axis between the \ plane and the runway's anchor point BMI crun1 \ If the result is negative then the plane is south of \ the anchor point, so it can't be on the runway, so \ jump to crun1 to return from the subroutine with a \ negative result CMP #24 \ Finally, if the high byte is less than 24, then this \ will clear the C flag, otherwise it will set the C \ flag \ By this point, the C flag is only clear if: \ \ * The distance between the plane and the runway's \ anchor point in the x-axis is < 256 \ \ * The distance between the plane and the runway's \ anchor point in the z-axis is < 24 * 256 RTS \ Return from the subroutine .crun1 SEC \ Set the C flag to indicate that the plane is not over \ the runway RTS \ Return from the subroutine
Name: ProcessLanding (Part 1 of 7) [Show more] Type: Subroutine Category: Flight model Summary: If this is an emergency landing, make the landing a bumpy one Deep dive: Take-offs and landings
Context: See this subroutine on its own page References: This subroutine is called as follows: * ApplyFlightModel (Part 6 of 7) calls ProcessLanding

This part checks whether the plane is on the ground but not on the runway, and if this is the case, it implements a bumpy ride by randomly changing the plane's height and roll, with larger changes at bigger speeds. Arguments: L The current value of onGround: * 0 = we are not on the ground * 1 = we are on the ground
.ProcessLanding LDA L \ Fetch the current value of onGround BEQ clan1 \ If onGround is zero then we are not on the ground, so \ jump to clan1 to skip the bumpy ride JSR CheckPlaneOnRunway \ Check whether the plane is over the runway BCC clan1 \ If the plane is on the runway, then jump to clan1 to \ skip the bumpy ride \ If we get here then we are on the ground but we aren't \ on the runway, so this must be an emergency landing \ outside the airport, and we apply a suitably bumpy \ ride, depending on the plane's velocity ASL landingStatus \ Shift landingStatus left and set bit 0 LDX #&EE \ Set X so the call to ApplyBumpyRide updates the yPlane \ variable, so it makes the plane bump up by a random \ amount CLC \ Clear the C flag so the call to ApplyBumpyRide \ adds a random amount to yPlane (rather than taking it \ away) LDY #8 \ Set the scale factor in Y so the random amount we bump \ the plane up by is in the range 0 to zVelocityP >> 9, \ so if we try to land at 100 mph, when zVelocityP is \ (9 64) = 2368, then this would bump the plane up \ by a random value in the range 0 to 4 (as 2368 >> 9 \ is 4) JSR ApplyBumpyRide \ Bump the plane up by a random height in the range 0 to \ zVelocityP >> 9, so the bump height is higher with \ higher forward velocity (so the faster we are trying \ to land, the more the plane bounces up when it hits \ the runway) LDX #&EC \ Set X so the call to ApplyBumpyRide updates the \ zRotation variable, so it makes the plane roll by a \ random amount LDY #4 \ Set the scale factor in Y so the random amount we roll \ the plane by is in the range 0 to zVelocityP >> 5, \ so if we try to land at 100 mph, when zVelocityP is \ (9 64) = 2368, then this would roll the plane by a \ random value in the range 0 to 74 (as 2368 >> 5 is 74) LDA VIA+&64 \ Read the 6522 User VIA T1C-L timer 1 low-order \ counter (SHEILA &64), which decrements one million \ times a second and will therefore be pretty random LSR A \ Set the C flag randomly, so the call to ApplyBumpyRide \ randomly adds or subtracts the random roll amount JSR ApplyBumpyRide \ Roll the plane up by a random angle in the range 0 to \ zVelocityP >> 5, in a random direction, so the roll \ amount is higher with higher forward velocity (so the \ faster we are trying to land, the more the plane rolls \ when it hits the runway)
Name: ProcessLanding (Part 2 of 7) [Show more] Type: Subroutine Category: Flight model Summary: If we are too high to be touching the ground then we can't be landing, so stop the landing checks Deep dive: Take-offs and landings
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

This part checks whether the plane is touching the ground, and aborts the landing checks if it isn't.
.clan1 LDA yPlaneHi \ If the high byte of the plane's y-coordinate is BMI clan3 \ negative, then jump to clan3 to zero it (as we can't \ be below ground level) BEQ clan4 \ If the high byte of the plane's y-coordinate is zero, \ then jump to clan4 to keep checking for a good landing CMP #2 \ If the high byte of the plane's y-coordinate is < 2, BCC clan2 \ skip the following instruction STA reached512ft \ The high byte of the plane's y-coordinate is >= 2, so \ set reached512ft to a non-zero value to indicate that \ we have reached 512 feet .clan2 LDA #0 \ If we get here then either the high byte of the STA onGround \ plane's y-coordinate is positive and non-zero, in \ which case we are at least 256 feet above the ground, \ or the plane is further from the ground than the \ distance from the cockpit to the lowest part of the \ plane, in which case we have not touched down, so \ set onGround to 0 to indicate that we are not on the \ ground RTS \ Return from the subroutine .clan3 LDX #&EE \ If we get here then yPlaneHi is negative, so set JSR ResetVariable \ (yPlaneHi yPlaneLo) = 0 as we can't be below ground .clan4 LDA yLandingGear \ If yLandingGear < yPlaneLo then the plane is further CMP yPlaneLo \ from the ground than the distance from the cockpit to BCC clan2 \ the lowest part of the plane, so we are still flying \ above the ground and have not touched down, so jump \ up to clan2 to return from the subroutine
Name: ProcessLanding (Part 3 of 7) [Show more] Type: Subroutine Category: Flight model Summary: If we are taxiing, restrict the plane's vertical movement and roll Deep dive: Take-offs and landings
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

This part checks whether we are in the process of landing, and if so we jump to part 6. Otherwise we have already landed and are taxxing, so we set the plane's height, vertical velocity and roll accordingly.
\ If we get here then the plane is close enough to the \ ground to be touching down LDX L \ Fetch the current value of onGround BEQ clan10 \ If L is zero then we are not on the ground, so jump \ to clan10 to skip to part 6 \ If we get here then we are already on the ground, so \ we are in the process of landing STA yPlaneLo \ Set the low byte of the plane's y-coordinate to A, \ which contains the value of yLandingGear, so this sets \ the plane's height to the correct value for taxiing, \ i.e. the distance between the cockpit and the lowest \ part of the plane LDX yVelocityTop \ If the top byte of the plane's vertical velocity is BPL clan5 \ positive, skip the following LDX #&8A \ We are on the ground and the plane's vertical velocity JSR ResetVariable \ is negative, so set (yVelocityTop yVelocityHi) = 0 as \ the plane can't travel down into the ground .clan5 LDX #&EC \ Set (zRotationHi zRotationLo) = 0 to set the plane to JSR ResetVariable \ the horizontal position (i.e. zero roll angle) LDX #&02 \ Set (zTurnTop zTurnHi) = 0 to stop the plane turning JSR ResetVariable \ around the z-axis (i.e. stop the plane rolling) LDX ucStatus \ If ucStatus is non-zero then the undercarriage is BNE clan9 \ down, so jump to clan9
Name: ProcessLanding (Part 4 of 7) [Show more] Type: Subroutine Category: Flight model Summary: If we are taxiing with the undercarriage up, restrict pitching Deep dive: Take-offs and landings
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

If we are taxiing with the undercarriage up, then if the plane is pitching forwards into the ground, the plane is made level and all pitching is stopped.
\ If we get here then we are on the ground but the \ undercarriage is up LDX landingStatus \ If bit 7 of landingStatus is set, skip the following BMI clan6 \ instruction ASL landingStatus \ Shift landingStatus left by one place .clan6 LDX xRotationHi \ If the high byte of the plane's rotation in the x-axis BPL clan8 \ is positive, skip the following LDX #&EA \ Set (xRotationHi xRotationLo) = 0 to set the plane to JSR ResetVariable \ the horizontal position (i.e. zero pitch angle) .clan7 LDX #&00 \ Set (xTurnTop xTurnHi) = 0 to stop the plane turning JSR ResetVariable \ around the x-axis (i.e. stop the plane pitching) .clan8 RTS \ Return from the subroutine
Name: ProcessLanding (Part 5 of 7) [Show more] Type: Subroutine Category: Flight model Summary: If we are taxiing with the undercarriage down, restrict pitching and tilt the plane backwards Deep dive: Take-offs and landings
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

If we are taxiing with the undercarriage down, then the plane gets set to the default tilt of 9.84 degrees (as the undercarriage is taller than the rear tail wheel).
.clan9 \ If we get here then we are on the ground and the \ undercarriage is down LDX xRotationHi \ If the high byte of the plane's rotation in the x-axis BMI clan8 \ is negative, jump to clan8 to return from the \ subroutine as the plane is already tilted backwards CPX #7 \ If the high byte of the plane's rotation in the x-axis BCC clan8 \ is positive and less than 7, jump to clan8 to return \ from the subroutine LDA #7 \ Set (xRotationHi xRotationLo) = (7 0), which is the STA xRotationHi \ default tilt of the plane - the undercarriage is LDA #0 \ taller than the rear tail wheel, so the whole plane STA xRotationLo \ points up by 7/256 = 9.84 degrees when its wheels are \ on the ground LDX xTurnTop \ If the high byte of the xTurn rate is positive, jump BPL clan7 \ to clan7 to stop the plane from pitching and return \ from the subroutine RTS \ Return from the subroutine
Name: ProcessLanding (Part 6 of 7) [Show more] Type: Subroutine Category: Flight model Summary: If we are not taxiing, process the landing Deep dive: Take-offs and landings
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

This part processes the touchdown part of the landing, bouncing the plane and crashing if we are coming down too fast.
.clan10 \ When we jump here from part 3, we know that: \ \ * We are not on the ground \ \ * yLandingGear >= yPlaneLo \ \ * A = yLandingGear \ \ The second condition means that the plane is closer to \ the ground than the distance between the plane and the \ lowest part of the plane, so we have touched down and \ the bottom part of the plane is now below ground level \ (which we now need to fix) SEC \ Set A = (yLandingGear - yPlaneLo) / 2 SBC yPlaneLo \ LSR A \ so A contains half the distance that the wheels would \ be below the ground if we didn't fix this CLC \ Set yPlaneLo = yLandingGear + A ADC yLandingGear \ STA yPlaneLo \ so the plane bounces up by the distance in A, which is \ greater, the further "below" the ground the wheels are LDA yVelocityTop \ If the top byte of the plane's vertical velocity is BPL clan11 \ positive, skip the following as the plane is already \ travelling upwards SEC \ Negate the plane's vertical velocity, so the plane LDA #0 \ bounces up by the same speed as it was originally SBC yVelocityHi \ heading towards the ground STA yVelocityHi LDA #0 SBC yVelocityTop .clan11 STA yVelocityTop \ Update the top byte of the plane's vertical velocity \ (this instruction should really be before the clan11 \ label, as it has no effect if we jump straight here) \ We now do various velocity checks to make sure we are \ not coming down to fast (if we are, we crash) LSR A \ Shift bit 0 of the top byte of the plane's vertical \ velocity into the C flag BNE clan13 \ If the above shift left any set bits in A, then the \ top byte of the plane's vertical velocity is >= 2, \ so yVelocity >= 512, so jump to clan13 to crash the \ plane, as the we are coming down far too fast to land LDA yVelocityHi \ Set A = (yVelocityTop yVelocityHi) / 2 ROR A \ = yVelocity / 2 \ \ by shifting the low byte right by one place and \ shifting the C flag into the top bit STA R \ Set R = A \ = yVelocity / 2 LDX ucStatus \ If ucStatus is non-zero, then the undercarriage BNE clan12 \ is down, so jump to clan12 \ If we get here then we are descending slowly enough \ to make the landing, but the undercarriage is up, so \ the propellor will smash into the ground DEX \ Set propellorStatus = 255 to denote that the propellor STX propellorStatus \ is broken, so we can't turn the engine on again CMP #160 \ If A < 160, i.e. yVelocity < 320, jump to clan14 to BCC clan14 \ turn the engine off before continuing the landing \ checks .clan12 CMP #110 \ If A < 110, i.e. yVelocity < 220, jump to clan15 to BCC clan15 \ continue the landing checks .clan13 \ If we get here then at least one of the following is \ true: \ \ * yVelocity >= 512 \ \ * The undercarriage is up and yVelocity >= 320 \ \ * The undercarriage is down and yVelocity >= 220 \ \ * The undercarriage is down, we are not landing on \ the runway, and yVelocity >= 160 \ \ In all these cases we are coming down too fast for the \ relevant conditions, so the landing has failed \ \ The above means that if the undercarriage is up, we \ can successfully land at higher vertical speeds then \ when the undercarriage is down, though in this case \ both the propellor and engine get destroyed JMP Crash \ Crash the plane and return from the subroutine using a \ tail call .clan14 LDA #0 \ If we get here then the undercarriage is up and we are JSR SetEngine \ making a high-speed landing, so turn off the engine .clan15 \ If we get here then we are descending slowly enough \ to make the landing JSR CheckPlaneOnRunway \ Check whether the plane is over the runway BCC clan16 \ If the plane is on the runway, then jump to clan16 LDA R \ We are not landing on the runway, so set the vertical STA yVelocityHi \ velocity to R, which halves the vertical velocity LDX ucStatus \ If ucStatus is zero, then the undercarriage is up, so BEQ clan16 \ skip the following \ If we get here then the undercarriage is down CMP #80 \ If A < 80, i.e. yVelocity < 160, jump to clan17 to BCC clan17 \ continue the landing checks without making the sound \ of a touchdown BCS clan13 \ Jump to clan13 to crash the plane, as we are \ descending too fast for an emergency landing with the \ undercarriage down .clan16 \ If we get here then either we are landing on the \ runway, or we are making an emergency landing with \ the undercarriage up - in either case, we make a sound \ (though we don't make a sound if this is an emergency \ landing with the undercarriage down) LDA #26 \ Make sound #26, the sound of us making contact with JSR MakeSound \ the ground while landing JSR ResetEngineSound \ Reset the pitch of the engine sound
Name: ProcessLanding (Part 7 of 7) [Show more] Type: Subroutine Category: Flight model Summary: We have successfully touched down without crashing, so process the effects of landing on the plane Deep dive: Take-offs and landings
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

This part processes a successful landing.
.clan17 LDY zRotationLo \ Set (A Y) = zRotation LDA zRotationHi JSR Multiply8x16-6 \ Set (G W V) = (A Y) * R \ = zRotation * yVelocity / 2 \ \ Note that the value of yVelocity used is the original \ value, rather than the halved value that we set in \ part 5 if this is an emergency landing SEC \ Negate (G W), so (G W) = -zRotation * yVelocity / 2 JSR Negate16Bit LDA #0 \ Set T = 0 to act as a high byte in (T G W) STA T LDA G \ Set (T A W) = (T G W) BPL clan18 \ If the high byte in G is positive, jump to clan18 to \ skip the following instruction DEC T \ Decrement T to &FF so it has the correct sign for \ (T A W), so we now have: \ \ (T A W) = -zRotation * yVelocity / 2 .clan18 LDX #1 \ We now want to halve (T A W) twice, so set a shift \ counter in X .clan19 LSR T \ Set (T A W) = (T A W) / 2 ROR A ROR W DEX \ Decrement the shift counter BPL clan19 \ Loop back until we have shifted right by two places, \ so now we have the following: \ \ (A W) = (A W) / 4 \ = (-zRotation * yVelocity / 2) / 4 \ = -zRotation * yVelocity / 8 \ \ We can ignore the value of T as it was only used to \ feed bits of the correct polarity into the high byte STA zTurnTop \ Set (zTurnTop zTurnHi) = (A W) LDA W \ = -zRotation * yVelocity / 8 STA zTurnHi \ \ so this applies a turn moment to the plane that is in \ the opposite direction to the current roll rotation, \ and which is proportionate to the speed, so if we come \ in fast and at a large roll angle, then the plane will \ be turned fast in the opposite direction LDX #&EC \ Set (zRotationHi zRotationLo) = 0 JSR ResetVariable LDA xRotationHi \ If the high byte of the plane's rotation around the BPL clan20 \ x-axis is positive, then the plane is tilting \ backwards, so jump to clan20 to skip the following LDA ucStatus \ If ucStatus is non-zero, then the undercarriage BNE clan20 \ is down, so jump to clan20 LDX #&EA \ If we get here then the undercarriage is up and the JSR ResetVariable \ plane is tilted forwards as we're landing, so we set \ (xRotationHi xRotationLo) = 0 to belly-flop the \ plane forwards onto the ground, so it lands (and \ slides) horizontally along the ground .clan20 LDA R \ If R >= 12, i.e. yVelocity >= 24, jump to clan21 to CMP #12 \ return from the subroutine BCS clan21 LDA yPlaneLo \ If yPlaneLo <> yLandingGear, jump to clan21 to return CMP yLandingGear \ from the subroutine BNE clan21 LDA #1 \ If we get here then the vertical velocity is <= 24, STA onGround \ and the distance between the plane and the ground is \ equal to the distance from the cockpit to the bottom \ of the plane... in other words, we are exactly on the \ ground and moving very slowly, so set onGround = 1 to \ denote that we are on the ground .clan21 RTS \ Return from the subroutine
Name: ApplyBumpyRide [Show more] Type: Subroutine Category: Flight model Summary: Apply a random amount of roll or bumpiness to the plane Deep dive: Take-offs and landings
Context: See this subroutine on its own page References: This subroutine is called as follows: * ProcessLanding (Part 1 of 7) calls ApplyBumpyRide

Add zVelocityPHi, randomised and scaled, to the 16-bit variable specified in X. Specifically, we add (A 0) right-shifted by Y + 1 places, where A is the current value of zVelocityPHi, AND'd with a random number to produce a random number in the range 0 to zVelocityPHi. So this calculates: (A 0) variable = variable + ------- 2^(Y+1) where A is a random number in the range 0 to zVelocityPHi. When applied to the zRotation variable, this rolls the plane randomly, and when applied to the yPlane variable, it bumps the plane up or down. The amount of roll or bumpiness is dependent on the forward velocity of the plane, so the higher the speed, the bumpier the ride. Arguments: X The offset from xTurnHi of the low byte of the variable to zero: * &EC = (zRotationHi zRotationLo) to apply roll * &EE = (yPlaneHi yPlaneLo) to apply bumpiness Y The scale factor C flag If this is set, subtract the scaled value, so we calculate: (A 0) variable = variable - ------- 2^(Y+1)
.ApplyBumpyRide LDA zVelocityPHi \ If zVelocityPHi is negative, return from the BMI ScaleByAltitude-1 \ subroutine (as ScaleByAltitude-1 contains an RTS) AND VIA+&64 \ AND with the 6522 User VIA T1C-L timer 1 low-order \ counter (SHEILA &44), which decrements one million \ times a second and will therefore be pretty random \ \ Because we are AND'ing zVelocityPHi with a random \ number, the result will be random but can't be higher \ than the original value of zVelocityPHi, so this \ produces a random number in the range 0 to \ zVelocityPHi \ Fall through into AddScaled with the N flag clear (as \ we passed through the BMI above)
Name: AddScaled [Show more] Type: Subroutine Category: Maths Summary: Add or subtract a scaled amount to a variable
Context: See this subroutine on its own page References: This subroutine is called as follows: * ApplyAerodynamics (Part 2 of 3) calls AddScaled * ApplyFlightModel (Part 3 of 7) calls AddScaled

Add a scaled value to the 16-bit variable specified in X. Specifically, we add (A 0) right-shifted by Y + 1 places, so this calculates: variable = variable + (A 0) >> (Y + 1) Arguments: A The top byte of the value to scale and add X The offset from xTurnHi of the low byte of the variable to zero: * &02 = (zTurnTop zTurnHi) * &6A = (xControlsScTop xControlsScHi) * &6B = (yControlsScTop yControlsScHi) * &6C = (zControlsScTop zControlsScHi) * &EC = (zRotationHi zRotationLo) * &EE = (yPlaneHi yPlaneLo) Y The scale factor N flag If this is set, invert A, so we calculate: variable = variable + ~(A 0) >> (Y + 1) C flag If this is set, subtract the scaled value, so we calculate: variable = variable - (A 0) >> (Y + 1)
.AddScaled PHP \ Store the processor flags on the stack, so we can \ retrieve them later BPL adds1 \ If the N flag is clear (i.e. we called this routine \ after loading or processing a positive number), skip \ the following instruction EOR #&FF \ Set A = ~A \ \ so if we call the routine after loading or processing \ a negative number in A, then A is now positive, \ i.e. A = |A| .adds1 STA G \ Set (G A) = (A 0) LDA #0 \ We now shift (G A) right by Y + 1 places .adds2 LSR G \ Set (G A) = (G A) >> 1 ROR A DEY \ Decrement the shift counter in Y BPL adds2 \ Loop back until we have shifted right by Y + 1 places STA W \ Set (G W) = (G A) \ = (A 0) right-shifted by Y + 1 places \ = (A 0) / 2^(Y+1) PLP \ Retrieve the flags from the original call to the \ routine BCC adds3 \ If we called the routine with the C flag clear, skip \ the following instruction JSR Negate16Bit \ We know the C flag is set at this point as we just \ passed through a BCC, so this negates (G W) .adds3 LDA W \ Add (G W) to the variable specified in X, starting CLC \ with the low bytes ADC xTurnHi,X STA xTurnHi,X LDA G \ And then the high bytes ADC xTurnTop,X STA xTurnTop,X RTS \ Return from the subroutine
Name: ScaleByAltitude [Show more] Type: Subroutine Category: Flight model Summary: Multiply the high byte of the plane's altitude by a 16-bit number
Context: See this subroutine on its own page References: This subroutine is called as follows: * ApplyAerodynamics (Part 1 of 3) calls ScaleByAltitude * ApplyTurnAndThrust (Part 2 of 2) calls ScaleByAltitude * ApplyBumpyRide calls entry point ScaleByAltitude-1

Calculate: (Y X V) = (Y X) * ~yPlaneHi Arguments: (Y X) A signed 16-bit number Returns: (Y X V) The result, (Y X) * ~yPlaneHi Other entry points: ScaleByAltitude-1 Contains an RTS
.ScaleByAltitude STY Q \ Set (Q P) = (Y X) STX P LDA yPlaneHi \ Set R = ~yPlaneHi EOR #&FF STA R JSR Multiply8x16 \ Set (G W V) = (Q P) * R \ = (Y X) * ~yPlaneHi \ \ and also set A to the high byte of the result TAY \ Set (Y X V) = (G W V) LDX W RTS \ Return from the subroutine
Name: ResetVariable [Show more] Type: Subroutine Category: Utility routines Summary: Set a 16-bit in the variable workspace to 0
Arguments: X The offset from xTurnHi of the low byte of the variable to zero: * &00 = (xTurnTop xTurnHi) * &02 = (zTurnTop zTurnHi) * &80 = (dxTurnTop dxTurnHi) * &82 = (dzTurnTop dzTurnHi) * &8A = (yVelocityTop yVelocityHi) * &EA = (xRotationHi xRotationLo) * &EC = (zRotationHi zRotationLo) * &EE = (yPlaneHi yPlaneLo) In the case of the two 24-bit variables, X is the offset of the high byte, and we have to zero the low byte manually after the routine call Returns: A A is set to 0
.ResetVariable LDA #0 \ Zero the X-th byte from xTurnHi STA xTurnHi,X STA xTurnTop,X \ Zero the X-th byte from xTurnTop RTS \ Return from the subroutine
Name: Entry [Show more] Type: Subroutine Category: Setup Summary: The main entry point for the game: move code into lower memory and call it
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
ORG &5E00 .Entry LDA #129 \ Call OSBYTE with A = 129, X = 0 and Y = &FF to detect LDX #0 \ the machine type. This call is undocumented and is not LDY #&FF \ the recommended way to determine the machine type JSR OSBYTE \ (OSBYTE 0 is the correct way), but this call returns \ the following: \ \ * X = Y = 0 if this is a BBC Micro with MOS 0.1 \ * X = Y = &FF if this is a BBC Micro with MOS 1.20 CPX #0 \ This checks the MOS version, but presumably this NOP \ contained some kind of copy protection or decryption NOP \ code that has been replaced by NOPs in this \ unprotected version of the game LDA #200 \ Call OSBYTE with A = 200, X = 3 and Y = 0 to disable LDX #3 \ the ESCAPE key and clear memory if the BREAK key is LDY #0 \ pressed JSR OSBYTE LDY #0 \ We now copy the following blocks in memory: \ \ * &5800-&5BFF is copied to &0400-&07FF \ * &5C00-&5DFF is copied to &0B00-&0CFF \ \ so we set up a byte counter in Y \ \ Note that the &5800-&5BFF block gets copied again in \ the DrawCanopy routine, so it ends up at &0D00-&10FF .entr1 LDA &5800,Y \ Copy the Y-th byte of &5800 to the Y-th byte of &0400 STA &0400,Y LDA &5900,Y \ Copy the Y-th byte of &5900 to the Y-th byte of &0500 STA &0500,Y LDA &5A00,Y \ Copy the Y-th byte of &5A00 to the Y-th byte of &0600 STA &0600,Y LDA &5B00,Y \ Copy the Y-th byte of &5B00 to the Y-th byte of &0700 STA &0700,Y LDA &5C00,Y \ Copy the Y-th byte of &5C00 to the Y-th byte of &0B00 STA &0B00,Y LDA &5D00,Y \ Copy the Y-th byte of &5D00 to the Y-th byte of &0C00 STA &0C00,Y DEY \ Decrement the loop counter BNE entr1 \ Loop back until we have copied a whole page of bytes NOP \ Presumably this contained some kind of copy protection NOP \ or decryption code that has been replaced by NOPs in NOP \ this unprotected version of the game NOP NOP NOP NOP JMP SetupScreen \ Jump to the routine that we just moved to &0B00 to \ set up the screen and continue the setup process Save AVIA.bin
Code between &0D00 and &10FF starts out at &5800 before being moved COPYBLOCK &0D00, &1100, &5800 Code between &0B00 and &0CFF starts out at &5C00 before being moved COPYBLOCK &0B00, &0D00, &5C00 SAVE "3-assembled-output/AVIA.bin", LOAD%, P%