\ ****************************************************************************** \ \ AVIATOR SOURCE \ \ Aviator was written by Geoffrey J Crammond and is copyright Acornsoft 1983 \ \ The code on this site has been reconstructed from a disassembly of the \ original game binaries \ \ The commentary is copyright Mark Moxon, and any misunderstandings or mistakes \ in the documentation are entirely my fault \ \ The terminology and notations used in this commentary are explained at \ https://aviator.bbcelite.com/terminology \ \ The deep dive articles referred to in this commentary can be found at \ https://aviator.bbcelite.com/deep_dives \ \ ------------------------------------------------------------------------------ \ \ This source file produces the following binary file: \ \ * AVIA.bin \ \ ****************************************************************************** GUARD &6000 \ Guard against assembling over screen memory \ ****************************************************************************** \ \ Configuration variables \ \ ****************************************************************************** LOAD% = &1100 \ The load address of the main code binary CODE% = &0B00 \ The address of the main game code once the code has \ been rearranged. The code starts off at &1100-&5EFF \ and is moved in three stages: \ \ * &5800-&5BFF is copied to &0400-&07FF \ * &5C00-&5DFF is copied to &0B00-&0CFF \ * &0400-&07FF is copied to &0D00-&10FF \ \ This address points to the start of the executable \ code after all these moves, which is at &0B00 nextAxis = 40 \ The xObject, yObject and zObject tables each contain \ 40 bytes, and they are positioned one after the other \ in memory, so if we access, say, the x-coordinate for \ object Y at xObjectHi+Y, then adding #nextAxis to Y \ will point xObjectHi+Y to the y-coordinate, and adding \ it again will point it to the z-coordinate \ The following configuration variables represent screen \ addresses, with names in the following format: \ \ row1_char2_3 = Row 1, character block 2, byte 3 \ \ The first batch refers to screen addresses in the \ canopy (character rows 0 to 19) row1_char1_0 = &5948 \ Second block on second row row1_char38_0 = &5A68 \ Last block but one on second row row1_char39_0 = &5A70 \ Last block on second row row3_char1_0 = &5BC8 \ Top-left corner of the on-screen score display row6_char1_0 = &5F88 \ Top-left corner of the canopy-wide rectangle \ containing the gun sights on rows 6, 7 and 8 row6_char20_0 = &6020 \ Top block of the vertical line in the gun sights row7_char20_0 = &6160 \ Bottom block of the vertical line in the gun sights row8_char11_0 = &6258 \ Left end of horizontal bar in the gun sights \ The second batch refers to screen addresses in the \ dashboard (character rows 20 to 31) row21_char20_7 = &72E7 \ Top block of joystick position display y-axis row22_char20_7 = &7427 \ Second block of joystick position display y-axis row23_char20_7 = &7567 \ Third block of joystick position display y-axis row24_char20_7 = &76A7 \ Fourth block of joystick position display y-axis and \ right-middle block of joystick position display x-axis row25_char20_7 = &77E7 \ Fifth block of joystick position display y-axis row26_char20_7 = &7927 \ Sixth block of joystick position display y-axis row27_char20_7 = &7A67 \ Bottom block of joystick position display y-axis row24_char18_7 = &7697 \ Left block of joystick position display x-axis row24_char19_7 = &769F \ Left-middle block of joystick position display x-axis row24_char21_7 = &76AF \ Right block of joystick position display x-axis row23_char12_4 = &7524 \ Left block of artificial horizon centre marker row23_char13_2 = &752A \ Middle block of artificial horizon centre marker row23_char14_4 = &7534 \ Right block of artificial horizon centre marker row25_char13_1 = &77A9 \ Bottom middle block of artificial horizon row25_char34_7 = &7857 \ Left spur of the radar's cross row25_char35_6 = &785E \ Bottom pixel of the top spur of the radar's cross row25_char35_7 = &785F \ Centre and right spur of the radar's cross row26_char35_0 = &7998 \ Top pixel of the bottom spur of the radar's cross row26_char35_1 = &7999 \ Bottom pixel of the bottom spur of the radar's cross row28_char26_5 = &7BD5 \ Centre block of the slip-and-turn indicator row29_char20_4 = &7CE4 \ Joystick indicator block (above middle of rudder) row30_char0_2 = &7D82 \ Theme indicator row30_char32_2 = &7E82 \ Undercarriage indicator row30_char35_2 = &7E9A \ Flaps indicator row30_char37_2 = &7EAA \ Brakes indicator VIA = &FE00 \ Memory-mapped space for accessing internal hardware, \ such as the video ULA, 6845 CRTC and 6522 VIAs (also \ known as SHEILA) OSBYTE = &FFF4 \ The address for the OSBYTE routine OSWORD = &FFF1 \ The address for the OSWORD routine OSWRCH = &FFEE \ The address for the OSWRCH routine OSCLI = &FFF7 \ The address for the OSCLI routine \ ****************************************************************************** \ \ Name: Zero page \ Type: Workspace \ Address: &0070 to &008F \ Category: Workspaces \ Summary: Mainly temporary variables that are used a lot \ \ ****************************************************************************** ORG &0070 .P SKIP 1 \ Temporary storage, used in a number of places .Q SKIP 1 \ Temporary storage, used in a number of places \ \ This is called DTIP in the original source code .R SKIP 1 \ Temporary storage, used in a number of places .S SKIP 1 \ Temporary storage, used in a number of places .T SKIP 1 \ Temporary storage, used in a number of places .U SKIP 1 \ Temporary storage, used in a number of places .V SKIP 1 \ Temporary storage, used in a number of places .W SKIP 1 \ Temporary storage, used in a number of places .G SKIP 1 \ Temporary storage, used in a number of places .H SKIP 1 \ Temporary storage, used in a number of places .I SKIP 1 \ Temporary storage, used in a number of places .J SKIP 1 \ Temporary storage, used in a number of places .K SKIP 1 \ Temporary storage, used in a number of places .L SKIP 1 \ Temporary storage, used in a number of places .M SKIP 1 \ Temporary storage, used in a number of places .N SKIP 1 \ Temporary storage, used in a number of places .PP SKIP 1 \ Temporary storage, used in a number of places .QQ SKIP 1 \ Temporary storage, used in a number of places .RR SKIP 1 \ Temporary storage, used in a number of places .SS SKIP 1 \ Temporary storage, used in a number of places .TT SKIP 1 \ Temporary storage, used in a number of places .UU SKIP 1 \ Temporary storage, used in a number of places .VV SKIP 1 \ Temporary storage, used in a number of places .WW SKIP 1 \ Temporary storage, used in a number of places .GG SKIP 1 \ Temporary storage, used in a number of places \ \ This is called PP in the original source code .HH SKIP 1 \ Temporary storage, used in a number of places .linesToShowEnd SKIP 1 \ The index of the first empty entry in the linesToShow \ list \ \ Set to 0 in ResetLineLists .lineId SKIP 1 \ The line ID, used to pass lines to routines \ \ Line 0 is the horizon \ Lines 1-11 are the runway \ Lines 12-15 are bullets .lineCounter SKIP 1 \ Temporary storage, typically used to loop through \ lines .linesToShowPointer SKIP 1 \ A pointer into the linesToShow list to keep track of \ where we have processed up to \ \ Set to 255 in ResetLineLists .linesToHidePointer SKIP 1 \ A pointer into the linesToHide list to keep track of \ where we have processed up to \ \ Set to 255 in ResetLineLists .linesToHideEnd SKIP 1 \ The index of the last entry in the linesToHide list \ \ Set to 255 in ResetLineLists \ ****************************************************************************** \ \ Name: Stack variables \ Type: Workspace \ Address: &0100 to &0175 \ Category: Workspaces \ Summary: Variables that share page 1 with the stack \ \ ****************************************************************************** ORG &0100 .lineBufferU SKIP 96 \ Line buffer storage for the line's |y-delta| (U) \ \ Stores information about lines that are drawn \ on-screen, so they can be quickly erased without \ having to spend precious time recalculating the line \ coordinates. The information is stored when a line is \ drawn by the DrawClippedLine routine, and is read by \ the EraseCanopyLines routine when the line is erased \ \ We can buffer up to 96 lines, with 48 in each of the \ two line buffers, so the maximum number of lines on \ screen at any one time is 48 lines out of the 193 \ lines defined in the world ORG &0160 .mx1Lo SKIP 1 \ The value of mx1, used when constructing the rotation \ matrices (low byte) \ \ Stored as a 16-bit value (mx1Hi mx1Lo) .my1Lo SKIP 1 \ The value of my1, used when constructing the rotation \ matrices (low byte) \ \ Stored as a 16-bit value (my1Hi my1Lo) .mz1Lo SKIP 1 \ The value of mz1, used when constructing the rotation \ matrices (low byte) \ \ Stored as a 16-bit value (mz1Hi mz1Lo) .mx2Lo SKIP 1 \ The value of mx2, used when constructing the rotation \ matrices (low byte) \ \ Stored as a 16-bit value (mx2Hi mx2Lo) .my2Lo SKIP 1 \ The value of my2, used when constructing the rotation \ matrices (low byte) \ \ Stored as a 16-bit value (my2Hi my2Lo) .mz2Lo SKIP 1 \ The value of mz2, used when constructing the rotation \ matrices (low byte) \ \ Stored as a 16-bit value (mz2Hi mz2Lo) ORG &0170 .mx1Hi SKIP 1 \ The value of mx1, used when constructing the rotation \ matrices (high byte) \ \ Stored as a 16-bit value (mx1Hi mx1Lo) .my1Hi SKIP 1 \ The value of my1, used when constructing the rotation \ matrices (high byte) \ \ Stored as a 16-bit value (my1Hi my1Lo) .mz1Hi SKIP 1 \ The value of mz1, used when constructing the rotation \ matrices (high byte) \ \ Stored as a 16-bit value (mz1Hi mz1Lo) .mx2Hi SKIP 1 \ The value of mx2, used when constructing the rotation \ matrices (high byte) \ \ Stored as a 16-bit value (mx2Hi mx2Lo) .my2Hi SKIP 1 \ The value of my2, used when constructing the rotation \ matrices (high byte) \ \ Stored as a 16-bit value (my2Hi my2Lo) .mz2Hi SKIP 1 \ The value of mz2, used when constructing the rotation \ matrices (high byte) \ \ Stored as a 16-bit value (mz2Hi mz2Lo) \ ****************************************************************************** \ \ Name: Main variable workspace \ Type: Workspace \ Address: &0400 to &07FF and &0900 to &0CFF \ Category: Workspaces \ Summary: The main block of game variables \ \ ****************************************************************************** ORG &0400 .pointStatus SKIP 216 \ Each point's status byte \ \ * Bit 0: \ \ * 0 = we have not yet projected this point \ \ * 1 = we have already projected this point \ \ * Bit 2: \ \ * 0 = yPoint is positive \ \ * 1 = yPoint is negative \ \ * Bit 3: \ \ * 0 = xPoint is positive \ \ * 1 = xPoint is negative \ \ * Bit 4: \ \ * 0 = |yPoint| * 2 < |zPoint| \ \ * 1 = |yPoint| * 2 >= |zPoint| \ \ * Bit 5: \ \ * 0 = |xPoint| < |zPoint| \ \ * 1 = |xPoint| >= |zPoint| \ \ * Bit 7: \ \ * 0 = the point's coordinates and visibility have \ not been calculated \ \ * 1 = the point's coordinates and visibility have \ already been calculated \ \ Zeroed in ResetVariables .objectStatus SKIP 40 \ Each object's status byte \ \ * Bit 6: \ \ * 0 = the object's coordinates and visibility have \ not been calculated in this iteration of the \ main loop \ \ * 1 = the object's coordinates and visibility have \ already been calculated in this iteration of \ the main loop \ \ * Bit 7: \ \ * 0 = the object is not visible \ \ * 1 = the object is visible \ \ Zeroed in ResetVariables and in part 2 of the main \ loop .linesToShow SKIP 200 \ A list of line IDs for lines that are visible .relatedPoints SKIP 56 \ Contains a list, from relatedPoints+1 onwards, with \ the list size in relatedPoints \ \ Point IDs get added in part 3 of ProcessLine when \ those points are part of an object - only points \ that we haven't already processed are added \ \ The maximum size of the list is 49 \ \ Zeroed in ResetVariables .linesToHide SKIP 256 \ A list of line IDs for lines that are not visible .zPointLo SKIP 252 \ The low byte of the z-coordinate for the point with \ ID X is at zPointLo,X \ \ Stored as a 16-bit value (zPointHi zPointLo) .zLinearLo SKIP 1 \ Low byte of point 252 (z-coordinate) \ \ Point 252 is used to store the sum of all the forces \ on the plane when calculating the flight model \ \ Stored as a 16-bit value (zLinearHi zLinearLo) .zGravityLo SKIP 1 \ Low byte of point 253 (z-coordinate) \ \ Point 253 is used to store the gravity vector when \ calculating the flight model \ \ Stored as a 16-bit value (zGravityHi zGravityLo) .zTempPoint1Lo SKIP 1 \ Low byte of point 254 (z-coordinate) \ \ Used as temporary point storage when rotating points \ in space \ \ Stored as a 16-bit value (zTempPoint1Hi zTempPoint1Lo) .zTempPoint2Lo SKIP 1 \ Low byte of point 255 (z-coordinate) \ \ Used as temporary point storage when rotating points \ in space \ \ Stored as a 16-bit value (zTempPoint2Hi zTempPoint2Lo) ORG &0900 .xPointLo SKIP 252 \ The low byte of the x-coordinate for the point with \ ID X is at xPointLo,X \ \ Stored as a 16-bit value (xPointHi xPointLo) .xLinearLo SKIP 1 \ Low byte of point 252 (x-coordinate) \ \ Point 252 is used to store the sum of all the forces \ on the plane when calculating the flight model \ \ Stored as a 16-bit value (xLinearHi xLinearLo) .xGravityLo SKIP 1 \ Low byte of point 253 (x-coordinate) \ \ Point 253 is used to store the gravity vector when \ calculating the flight model \ \ Stored as a 16-bit value (xGravityHi xGravityLo) .xTempPoint1Lo SKIP 1 \ Low byte of point 254 (x-coordinate) \ \ Used as temporary point storage when rotating points \ in space \ \ Stored as a 16-bit value (xTempPoint1Hi xTempPoint1Lo) .xTempPoint2Lo SKIP 1 \ Low byte of point 255 (x-coordinate) \ \ Used as temporary point storage when rotating points \ in space \ \ Stored as a 16-bit value (xTempPoint2Hi xTempPoint2Lo) .yPointLo SKIP 252 \ The low byte of the y-coordinate for the point with \ ID X is at yPointLo,X \ \ Stored as a 16-bit value (yPointHi yPointLo) .yLinearLo SKIP 1 \ Low byte of point 252 (y-coordinate) \ \ Point 252 is used to store the sum of all the forces \ on the plane when calculating the flight model \ \ Stored as a 16-bit value (yLinearHi yLinearLo) .yGravityLo SKIP 1 \ Low byte of point 253 (y-coordinate) \ \ Point 253 is used to store the gravity vector when \ calculating the flight model \ \ Stored as a 16-bit value (yGravityHi yGravityLo) .yTempPoint1Lo SKIP 1 \ Low byte of point 254 (y-coordinate) \ \ Used as temporary point storage when rotating points \ in space \ \ Stored as a 16-bit value (yTempPoint1Hi yTempPoint1Lo) .yTempPoint2Lo SKIP 1 \ Low byte of point 255 (z-coordinate) \ \ Used as temporary point storage when rotating points \ in space \ \ Stored as a 16-bit value (yTempPoint2Hi yTempPoint2Lo) .yPointHi SKIP 252 \ The high byte of the y-coordinate for the point with \ ID X is at yPointHi,X \ \ Stored as a 16-bit value (yPointHi yPointLo) .yLinearHi SKIP 1 \ High byte of point 252 (y-coordinate) \ \ Point 252 is used to store the sum of all the forces \ on the plane when calculating the flight model \ \ Stored as a 16-bit value (yLinearHi yLinearLo) .yGravityHi SKIP 1 \ High byte of point 253 (y-coordinate) \ \ Point 253 is used to store the gravity vector when \ calculating the flight model \ \ Stored as a 16-bit value (yGravityHi yGravityLo) .yTempPoint1Hi SKIP 1 \ High byte of point 254 (y-coordinate) \ \ Used as temporary point storage when rotating points \ in space \ \ Stored as a 16-bit value (yTempPoint1Hi yTempPoint1Lo) .yTempPoint2Hi SKIP 1 \ High byte of point 255 (y-coordinate) \ \ Used as temporary point storage when rotating points \ in space \ \ Stored as a 16-bit value (yTempPoint2Hi yTempPoint2Lo) .xTurnHi SKIP 1 \ Turn rate around the x-axis (high byte) \ \ Stored as a 24-bit value (xTurnTop xTurnHi xTurnLo) .yTurnHi SKIP 1 \ Turn rate around the y-axis (high byte) \ \ Stored as 35 * the turn rate in 180 degrees per minute \ \ Shown on indicator 5 \ \ Stored as a 24-bit value (yTurnTop yTurnHi yTurnLo) .zTurnHi SKIP 1 \ Turn rate around the z-axis (high byte) \ \ Stored as a 24-bit value (zTurnTop zTurnHi zTurnLo) .xVelocityPLo SKIP 1 \ Plane velocity along the x-axis from the perspective \ of the pilot (low byte) \ \ Stored as a 16-bit value (xVelocityPHi xVelocityPLo) .yVelocityPLo SKIP 1 \ Plane velocity along the y-axis from the perspective \ of the pilot (low byte) \ \ Stored as a 16-bit value (yVelocityPHi yVelocityPLo) .zVelocityPLo SKIP 1 \ Plane velocity along the a-axis from the perspective \ of the pilot (low byte) \ \ This is the same as the forward airspeed \ \ 100 mph is stored as 9.25 * 256 = 2368 \ \ so 50 mph = 0.5 * 2368 = 1184 = (4 160) \ so 70 mph = 0.7 * 2368 = 1658 = (6 122) \ so 100 mph = 1 * 2368 = 2368 = (9 64) \ so 400 mph = 4 * 2368 = 9472 = (37 00) \ \ Shown on indicator 1 \ \ Stored as a 16-bit value (zVelocityPHi zVelocityPLo) .xTemp3Lo SKIP 1 \ The low byte of the xTemp3 temporary variable \ \ Stored as a 16-bit value (xTemp3Hi xTemp3Lo) .yTemp3Lo SKIP 1 \ The low byte of the yTemp3 temporary variable \ \ Stored as a 16-bit value (yTemp3Hi yTemp3Lo) .zTemp3Lo SKIP 1 \ The low byte of the zTemp3 temporary variable \ \ Stored as a 16-bit value (zTemp3Hi zTemp3Lo) .xPlaneBot SKIP 1 \ The bottom byte of the plane's coordinate (x-axis) \ \ Stored as a 32-bit value (xPlaneTop xPlaneHi xPlaneLo \ xPlaneBot) .yPlaneBot SKIP 1 \ The bottom byte of the plane's coordinate (y-axis) \ \ Stored as a 32-bit value (yPlaneTop yPlaneHi yPlaneLo \ yPlaneBot) .zPlaneBot SKIP 1 \ The bottom byte of the plane's coordinate (z-axis) \ \ Stored as a 32-bit value (zPlaneTop zPlaneHi zPlaneLo \ zPlaneBot) .elevatorPosition SKIP 1 \ Elevator position (pitch) \ \ The controls that affect rotation around the x-axis \ \ Shown on indicator 8 or 10 as the joystick y-position .rudderPosition SKIP 1 \ Rudder position (yaw) \ \ The controls that affect rotation around the y-axis \ \ Shown on indicator 9 .aileronPosition SKIP 1 \ Aileron position (roll) \ \ The controls that affect rotation around the z-axis \ \ Shown on indicator 8 or 10 as the joystick x-position .thrustLo SKIP 1 \ Thrust (low byte) \ \ Thrust is in the range 0 to 1280 \ \ Shown on indicator 11 \ \ Stored as a 16-bit value (thrustHi thrustLo) .xTurnTop SKIP 1 \ Turn rate around the x-axis (top byte) \ \ Stored as a 24-bit value (xTurnTop xTurnHi xTurnLo) .yTurnTop SKIP 1 \ Turn rate around the y-axis (top byte) \ \ Stored as 35 * the turn rate in 180 degrees per minute \ \ Shown on indicator 5 \ \ Stored as a 24-bit value (yTurnTop yTurnHi yTurnLo) .zTurnTop SKIP 1 \ Turn rate around the z-axis (top byte) \ \ Stored as a 24-bit value (zTurnTop zTurnHi zTurnLo) .xVelocityPHi SKIP 1 \ Plane velocity along the x-axis from the perspective \ of the pilot (high byte) \ \ Stored as a 16-bit value (xVelocityPHi xVelocityPLo) .yVelocityPHi SKIP 1 \ Plane velocity along the y-axis from the perspective \ of the pilot (high byte) \ \ Stored as a 16-bit value (yVelocityPHi yVelocityPLo) .zVelocityPHi SKIP 1 \ Plane velocity along the z-axis from the perspective \ of the pilot (high byte) \ \ This is the same as the forward airspeed \ \ 100 mph is stored as 9.25 * 256 = 2368 \ \ so 50 mph = 0.5 * 2368 = 1184 = (4 160) \ so 70 mph = 0.7 * 2368 = 1658 = (6 122) \ so 100 mph = 1 * 2368 = 2368 = (9 64) \ so 400 mph = 4 * 2368 = 9472 = (37 00) \ \ Shown on indicator 1 \ \ Stored as a 16-bit value (zVelocityPHi zVelocityPLo) .xTemp3Hi SKIP 1 \ The high byte of the xTemp3 temporary variable \ \ Stored as a 16-bit value (xTemp3Hi xTemp3Lo) .yTemp3Hi SKIP 1 \ The high byte of the yTemp3 temporary variable \ \ Stored as a 16-bit value (yTemp3Hi yTemp3Lo) .zTemp3Hi SKIP 1 \ The high byte of the zTemp3 temporary variable \ \ Stored as a 16-bit value (zTemp3Hi zTemp3Lo) .xTurnLo SKIP 1 \ Turn rate around the x-axis (low byte) \ \ Stored as a 24-bit value (xTurnTop xTurnHi xTurnLo) .yTurnLo SKIP 1 \ Turn rate around the y-axis (low byte) \ \ Stored as a 24-bit value (yTurnTop yTurnHi yTurnLo) .zTurnLo SKIP 1 \ Turn rate around the z-axis (low byte) \ \ Stored as a 24-bit value (zTurnTop zTurnHi zTurnLo) SKIP 3 \ These bytes appear to be unused .thrustHi SKIP 1 \ Thrust (high byte) \ \ Thrust is in the range 0 to 1280 \ \ Shown on indicator 11 \ \ Stored as a 16-bit value (thrustHi thrustLo) .xMomentsScLo SKIP 1 \ Scaled angular force due to forces on the plane in \ the x-axis (low byte) \ \ Stored as a 24-bit value (xMomentsScTop xMomentsScHi \ xMomentsScLo) .yMomentsScLo SKIP 1 \ Scaled angular force due to forces on the plane in \ the y-axis (low byte) \ \ Stored as a 24-bit value (yMomentsScTop yMomentsScHi \ yMomentsScLo) .zMomentsScLo SKIP 1 \ Scaled angular force due to forces on the plane in \ the z-axis (low byte) \ \ Stored as a 24-bit value (zMomentsScTop zMomentsScHi \ zMomentsScLo) .xLiftDragScLo SKIP 1 \ Scaled linear force due to lift in the x-axis (low \ byte) \ \ Stored as a 24-bit value (xLiftDragScTop xLiftDragScHi \ xLiftDragScLo) .yLiftDragScLo SKIP 1 \ Scaled linear force due to side forces in the y-axis \ (low byte) \ \ Stored as a 24-bit value (yLiftDragScTop yLiftDragScHi \ yLiftDragScLo) .zLiftDragScLo SKIP 1 \ Scaled linear force due to drag in the z-axis (low \ byte) \ \ Stored as a 24-bit value (zLiftDragScTop zLiftDragScHi \ zLiftDragScLo) .zSlipMomentScLo SKIP 1 \ Scaled angular force in the z-axis due to aircraft \ slip (low byte) \ \ Stored as a 24-bit value (zSlipMomentScTop \ zSlipMomentScHi zSlipMomentScLo) .yFlapsLiftScLo SKIP 1 \ Scaled linear force in the y-axis due to lift from the \ flaps (low byte) \ \ Stored as a 24-bit value (yFlapsLiftScTop \ yFlapsLiftScHi yFlapsLiftScLo) SKIP 2 \ These bytes appear to be unused .xControlsScLo SKIP 1 \ Scaled angular force due to the plane's controls in \ the x-axis (low byte) \ \ Stored as a 24-bit value (xControlsScTop xControlsScHi \ xControlsScLo) .yControlsScLo SKIP 1 \ Scaled angular force due to the plane's controls in \ the y-axis (low byte) \ \ Stored as a 24-bit value (yControlsScTop yControlsScHi \ yControlsScLo) .zControlsScLo SKIP 1 \ Scaled angular force due to the plane's controls in \ the z-axis (low byte) \ \ Stored as a 24-bit value (zControlsScTop zControlsScHi \ zControlsScLo) .axisKeyUsage SKIP 3 \ The following locations are updated when keys are \ pressed in UpdateFlightModel: \ \ * axisKeyUsage = elevator \ \ * axisKeyUsage+1 = rudder \ \ * axisKeyUsage+2 = aileron \ \ In each case, the value is updated by adding the \ relevant keyLoggerLo value, which is 1 in each case, \ so these count up by 1 every time a relevant axis \ control key is pressed (in any direction), so they \ measure "axis control key usage" .dxTurnLo SKIP 1 \ Rate of change of the plane's turn rate in the x-axis \ (low byte) \ \ Stored as a 24-bit value (dxTurnTop dxTurnHi dxTurnLo) .dyTurnLo SKIP 1 \ Rate of change of the plane's turn rate in the y-axis \ (low byte) \ \ Stored as a 24-bit value (dyTurnTop dyTurnHi dyTurnLo) .dzTurnLo SKIP 1 \ Rate of change of the plane's turn rate in the z-axis \ (low byte) \ \ Stored as a 24-bit value (dzTurnTop dzTurnHi dzTurnLo) SKIP 13 \ These bytes appear to be unused .xMomentsLo SKIP 1 \ Angular force due to airflow over the plane in the \ x-axis (low byte) \ \ Stored as a 16-bit value (xMomentsHi xMomentsLo) .yMomentsLo SKIP 1 \ Angular force due to airflow over the plane in the \ y-axis (low byte) \ \ Stored as a 16-bit value (yMomentsHi yMomentsLo) .zMomentsLo SKIP 1 \ Angular force due to airflow over the plane in the \ z-axis (low byte) \ \ Stored as a 16-bit value (zMomentsHi zMomentsLo) .xLiftDragLo SKIP 1 \ Linear force due to lift in the x-axis (low byte) \ \ Stored as a 16-bit value (xLiftDragHi xLiftDragLo) .yLiftDragLo SKIP 1 \ Linear force due to side forces in the y-axis (low \ byte) \ \ Stored as a 16-bit value (yLiftDragHi yLiftDragLo) .zLiftDragLo SKIP 1 \ Linear force due to drag in the z-axis (low byte) \ \ Stored as a 16-bit value (zLiftDragHi zLiftDragLo) .zSlipMomentLo SKIP 1 \ Angular force in the z-axis due to aircraft slip (low \ byte) \ \ Stored as a 16-bit value (zSlipMomentHi zSlipMomentLo) .yFlapsLiftLo SKIP 1 \ Linear force in the y-axis due to lift from the flaps \ (low byte) \ \ Stored as a 16-bit value (yFlapsLiftHi yFlapsLiftLo) SKIP 2 \ These bytes appear to be unused .xControlsLo SKIP 1 \ Angular force due to the plane's controls in the \ x-axis (low byte) \ \ Stored as a 16-bit value (xControlsHi xControlsLo) .yControlsLo SKIP 1 \ Angular force due to the plane's controls in the \ y-axis (low byte) \ \ Stored as a 16-bit value (yControlsHi yControlsLo) .zControlsLo SKIP 1 \ Angular force due to the plane's controls in the \ z-axis (low byte) \ \ Stored as a 16-bit value (zControlsHi zControlsLo) SKIP 3 \ These bytes appear to be unused .xMomentsHi SKIP 1 \ Angular force due to airflow over the plane in the \ x-axis (high byte) \ \ Stored as a 16-bit value (xMomentsHi xMomentsLo) .yMomentsHi SKIP 1 \ Angular force due to airflow over the plane in the \ y-axis (high byte) \ \ Stored as a 16-bit value (yMomentsHi yMomentsLo) .zMomentsHi SKIP 1 \ Angular force due to airflow over the plane in the \ z-axis (high byte) \ \ Stored as a 16-bit value (zMomentsHi zMomentsLo) .xLiftDragHi SKIP 1 \ Linear force due to lift in the x-axis (high byte) \ \ Stored as a 16-bit value (xLiftDragHi xLiftDragLo) .yLiftDragHi SKIP 1 \ Linear force due to side forces in the y-axis (high \ byte) \ \ Stored as a 16-bit value (yLiftDragHi yLiftDragLo) .zLiftDragHi SKIP 1 \ Linear force due to drag in the z-axis (high byte) \ \ Stored as a 16-bit value (zLiftDragHi zLiftDragLo) .zSlipMomentHi SKIP 1 \ Angular force in the z-axis due to aircraft slip (high \ byte) \ \ Stored as a 16-bit value (zSlipMomentHi zSlipMomentLo) .yFlapsLiftHi SKIP 1 \ Linear force in the y-axis due to lift from the flaps \ (high byte) \ \ Stored as a 16-bit value (yFlapsLiftHi yFlapsLiftLo) SKIP 2 \ These bytes appear to be unused .xControlsHi SKIP 1 \ Angular force due to the plane's controls in the \ x-axis (high byte) \ \ Stored as a 16-bit value (xControlsHi xControlsLo) .yControlsHi SKIP 1 \ Angular force due to the plane's controls in the \ y-axis (high byte) \ \ Stored as a 16-bit value (yControlsHi yControlsLo) .zControlsHi SKIP 1 \ Angular force due to the plane's controls in the \ z-axis (high byte) \ \ Stored as a 16-bit value (zControlsHi zControlsLo) SKIP 3 \ These bytes appear to be unused .xMomentsScHi SKIP 1 \ Scaled angular force due to forces on the plane in \ the x-axis (high byte) \ \ Stored as a 24-bit value (xMomentsScTop xMomentsScHi \ xMomentsScLo) .yMomentsScHi SKIP 1 \ Scaled angular force due to forces on the plane in \ the y-axis (high byte) \ \ Stored as a 24-bit value (yMomentsScTop yMomentsScHi \ yMomentsScLo) .zMomentsScHi SKIP 1 \ Scaled angular force due to forces on the plane in \ the z-axis (high byte) \ \ Stored as a 24-bit value (zMomentsScTop zMomentsScHi \ zMomentsScLo) .xLiftDragScHi SKIP 1 \ Scaled linear force due to lift in the x-axis (high \ byte) \ \ Stored as a 24-bit value (xLiftDragScTop xLiftDragScHi \ xLiftDragScLo) .yLiftDragScHi SKIP 1 \ Scaled linear force due to side forces in the y-axis \ (high byte) \ \ Stored as a 24-bit value (yLiftDragScTop yLiftDragScHi \ yLiftDragScLo) .zLiftDragScHi SKIP 1 \ Scaled linear force due to drag in the z-axis (high \ byte) \ \ Stored as a 24-bit value (zLiftDragScTop zLiftDragScHi \ zLiftDragScLo) .zSlipMomentScHi SKIP 1 \ Scaled angular force in the z-axis due to aircraft \ slip (high byte) \ \ Stored as a 24-bit value (zSlipMomentScTop \ zSlipMomentScHi zSlipMomentScLo) .yFlapsLiftScHi SKIP 1 \ Scaled linear force in the y-axis due to lift from the \ flaps (high byte) \ \ Stored as a 24-bit value (yFlapsLiftScTop \ yFlapsLiftScHi yFlapsLiftScLo) SKIP 2 \ These bytes appear to be unused .xControlsScHi SKIP 1 \ Scaled angular force due to the plane's controls in \ the x-axis (high byte) \ \ Stored as a 24-bit value (xControlsScTop xControlsScHi \ xControlsScLo) .yControlsScHi SKIP 1 \ Scaled angular force due to the plane's controls in \ the y-axis (high byte) \ \ Stored as a 24-bit value (yControlsScTop yControlsScHi \ yControlsScLo) .zControlsScHi SKIP 1 \ Scaled angular force due to the plane's controls in \ the z-axis (high byte) \ \ Stored as a 24-bit value (zControlsScTop zControlsScHi \ zControlsScLo) .xPlaneTop SKIP 1 \ The top byte of the plane's location, which is the \ byte above the high byte in xPlaneHi \ \ Stored as a 32-bit value (xPlaneTop xPlaneHi xPlaneLo \ xPlaneBot) .yPlaneTop SKIP 1 \ The top byte of the plane's location, which is the \ byte above the high byte in yPlaneHi \ \ Stored as a 32-bit value (yPlaneTop yPlaneHi yPlaneLo \ yPlaneBot) .zPlaneTop SKIP 1 \ The top byte of the plane's location, which is the \ byte above the high byte in zPlaneHi \ \ Stored as a 32-bit value (zPlaneTop zPlaneHi zPlaneLo \ zPlaneBot) .xMomentsScTop SKIP 1 \ Scaled angular force due to forces on the plane in \ the x-axis (top byte) \ \ Stored as a 24-bit value (xMomentsScTop xMomentsScHi \ xMomentsScLo) .yMomentsScTop SKIP 1 \ Scaled angular force due to forces on the plane in \ the y-axis (top byte) \ \ Stored as a 24-bit value (yMomentsScTop yMomentsScHi \ yMomentsScLo) .zMomentsScTop SKIP 1 \ Scaled angular force due to forces on the plane in \ the z-axis (top byte) \ \ Stored as a 24-bit value (zMomentsScTop zMomentsScHi \ zMomentsScLo) .xLiftDragScTop SKIP 1 \ Scaled linear force due to lift in the x-axis (top \ byte) \ \ Stored as a 24-bit value (xLiftDragScTop xLiftDragScHi \ xLiftDragScLo) .yLiftDragScTop SKIP 1 \ Scaled linear force due to side forces in the y-axis \ (top byte) \ \ Stored as a 24-bit value (yLiftDragScTop yLiftDragScHi \ yLiftDragScLo) .zLiftDragScTop SKIP 1 \ Scaled linear force due to drag in the z-axis (top \ byte) \ \ Stored as a 24-bit value (zLiftDragScTop zLiftDragScHi \ zLiftDragScLo) .zSlipMomentScTop SKIP 1 \ Scaled angular force in the z-axis due to aircraft \ slip (top byte) \ \ Stored as a 24-bit value (zSlipMomentScTop \ zSlipMomentScHi zSlipMomentScLo) .yFlapsLiftScTop SKIP 1 \ Scaled linear force in the y-axis due to lift from the \ flaps (top byte) \ \ Stored as a 24-bit value (yFlapsLiftScTop \ yFlapsLiftScHi yFlapsLiftScLo) SKIP 2 \ These bytes appear to be unused .xControlsScTop SKIP 1 \ Scaled angular force due to the plane's controls in \ the x-axis (top byte) \ \ Stored as a 24-bit value (xControlsScTop xControlsScHi \ xControlsScLo) .yControlsScTop SKIP 1 \ Scaled angular force due to the plane's controls in \ the y-axis (top byte) \ \ Stored as a 24-bit value (yControlsScTop yControlsScHi \ yControlsScLo) .zControlsScTop SKIP 1 \ Scaled angular force due to the plane's controls in \ the z-axis (top byte) \ \ Stored as a 24-bit value (zControlsScTop zControlsScHi \ zControlsScLo) SKIP 3 \ These bytes appear to be unused .dxTurnHi SKIP 1 \ Rate of change of the plane's turn rate in the x-axis \ (high byte) \ \ Stored as a 24-bit value (dxTurnTop dxTurnHi dxTurnLo) .dyTurnHi SKIP 1 \ Rate of change of the plane's rate turn in the y-axis \ (high byte) \ \ Stored as a 24-bit value (dyTurnTop dyTurnHi dyTurnLo) .dzTurnHi SKIP 1 \ Rate of change of the plane's rate turn in the z-axis \ (high byte) \ \ Stored as a 24-bit value (dzTurnTop dzTurnHi dzTurnLo) .dxVelocityLo SKIP 1 \ Rate of change of the plane's velocity in the x-axis \ (low byte) \ \ Stored as a 16-bit value (dxVelocityHi dxVelocityLo) .dyVelocityLo SKIP 1 \ Rate of change of the plane's velocity in the y-axis \ (low byte) \ \ Stored as a 16-bit value (dxVelocityHi dxVelocityLo) .dzVelocityLo SKIP 1 \ Rate of change of the plane's velocity in the z-axis \ (low byte) \ \ Stored as a 16-bit value (dxVelocityHi dxVelocityLo) .dxRotationLo SKIP 1 \ Rate of change of the plane's rotation in the x-axis \ (low byte) \ \ Stored as a 16-bit value (dxVelocityHi dxVelocityLo) .dyRotationLo SKIP 1 \ Rate of change of the plane's rotation in the y-axis \ (low byte) \ \ Stored as a 16-bit value (dyVelocityHi dyVelocityLo) .dzRotationLo SKIP 1 \ Rate of change of the plane's rotation in the z-axis \ (low byte) \ \ Stored as a 16-bit value (dzVelocityHi dzVelocityLo) .xVelocityHi SKIP 1 \ Plane velocity in the x-axis from the perspective \ of the outside world (high byte) \ \ Stored as a 24-bit value (xVelocityTop xVelocityHi \ xVelocityLo) .yVelocityHi SKIP 1 \ Plane velocity in the y-axis from the perspective \ of the outside world (high byte) \ \ This is the same as the vertical speed, and is from \ the perspective of the world outside the plane, so \ yVelocity is the vertical speed of the plane, \ irrespective of how the plane is orientated \ \ For the plane's speed from the point of view of the \ plane, see (xVelocityP yVelocityP zVelocityP) \ \ Stored as 128/425 * vertical speed in feet per minute, \ so: \ \ 1000 feet/minute is stored as 128/425 * 1000 = 301 \ \ 4000 feet/minute is stored as 128/425 * 4000 = 1205 \ \ Shown on indicator 4 \ \ Stored as a 24-bit value (yVelocityTop yVelocityHi \ yVelocityLo) .zVelocityHi SKIP 1 \ Plane velocity in the z-axis from the perspective \ of the outside world (high byte) \ \ Stored as a 24-bit value (zVelocityTop zVelocityHi \ zVelocityLo) .xVelocityLo SKIP 1 \ Plane velocity in the x-axis from the perspective \ of the outside world (low byte) \ \ Stored as a 24-bit value (xVelocityTop xVelocityHi \ xVelocityLo) .yVelocityLo SKIP 1 \ Plane velocity in the y-axis from the perspective \ of the outside world (low byte) \ \ This is the same as the vertical speed, and is from \ the perspective of the world outside the plane, so \ yVelocity is the vertical speed of the plane, \ irrespective of how the plane is orientated \ \ For the plane's speed from the point of view of the \ plane, see (xVelocityP yVelocityP zVelocityP) \ \ Stored as 128/425 * vertical speed in feet per minute, \ so: \ \ 1000 feet/minute is stored as 128/425 * 1000 = 301 \ \ 4000 feet/minute is stored as 128/425 * 4000 = 1205 \ \ Shown on indicator 4 \ \ Stored as a 24-bit value (yVelocityTop yVelocityHi \ yVelocityLo) .zVelocityLo SKIP 1 \ Plane velocity in the z-axis from the perspective \ of the outside world (low byte) \ \ Stored as a 24-bit value (zVelocityTop zVelocityHi \ zVelocityLo) SKIP 1 \ This byte appears to be unused .dxTurnTop SKIP 1 \ Rate of change of the plane's turn rate in the x-axis \ (top byte) \ \ Stored as a 24-bit value (dxTurnTop dxTurnHi dxTurnLo) .dyTurnTop SKIP 1 \ Rate of change of the plane's turn rate in the y-axis \ (top byte) \ \ Stored as a 24-bit value (dyTurnTop dyTurnHi dyTurnLo) .dzTurnTop SKIP 1 \ Rate of change of the plane's turn rate in the z-axis \ (top byte) \ \ Stored as a 24-bit value (dzTurnTop dzTurnHi dzTurnLo) .dxVelocityHi SKIP 1 \ Rate of change of the plane's velocity in the x-axis \ (high byte) \ \ Stored as a 16-bit value (dxVelocityHi dxVelocityLo) .dyVelocityHi SKIP 1 \ Rate of change of the plane's velocity in the y-axis \ (high byte) \ \ Stored as a 16-bit value (dxVelocityHi dxVelocityLo) .dzVelocityHi SKIP 1 \ Rate of change of the plane's velocity in the z-axis \ (high byte) \ \ Stored as a 16-bit value (dxVelocityHi dxVelocityLo) .dxRotationHi SKIP 1 \ Rate of change of the plane's rotation in the x-axis \ (high byte) \ \ Stored as a 16-bit value (dxVelocityHi dxVelocityLo) .dyRotationHi SKIP 1 \ Rate of change of the plane's rotation in the y-axis \ (high byte) \ \ Stored as a 16-bit value (dyVelocityHi dyVelocityLo) .dzRotationHi SKIP 1 \ Rate of change of the plane's rotation in the z-axis \ (high byte) \ \ Stored as a 16-bit value (dzVelocityHi dzVelocityLo) .xVelocityTop SKIP 1 \ Plane velocity in the x-axis from the perspective \ of the outside world (top byte) \ \ Stored as a 24-bit value (xVelocityTop xVelocityHi \ xVelocityLo) .yVelocityTop SKIP 1 \ Plane velocity in the y-axis from the perspective \ of the outside world (top byte) \ \ This is the same as the vertical speed, and is from \ the perspective of the world outside the plane, so \ yVelocity is the vertical speed of the plane, \ irrespective of how the plane is orientated \ \ For the plane's speed from the point of view of the \ plane, see (xVelocityP yVelocityP zVelocityP) \ \ Stored as 128/425 * vertical speed in feet per minute, \ so: \ \ 1000 feet/minute is stored as 128/425 * 1000 = 301 \ \ 4000 feet/minute is stored as 128/425 * 4000 = 1205 \ \ Shown on indicator 4 \ \ Stored as a 24-bit value (yVelocityTop yVelocityHi \ yVelocityLo) .zVelocityTop SKIP 1 \ Plane velocity in the z-axis from the perspective \ of the outside world (top byte) \ \ Stored as a 24-bit value (zVelocityTop zVelocityHi \ zVelocityLo) .slipRate SKIP 1 \ Slip rate \ \ Shown on indicator 6 SKIP 3 \ These bytes appear to be unused .keyLoggerLo SKIP 6 \ Key logger (low byte) \ \ Populated with values from keyTable1Lo or keyTable2Lo \ when a key is pressed, or 0 if neither is pressed: \ \ L or < (elevator dive/pitch) = -1 or 1 \ A or + (rudder yaw left/right) = -1 or 1 \ S or D (aileron bank left/right) = -1 or 1 \ W or E (throttle down/up) = -15 or 15 \ U or B (undercarriage, brakes) = 4 or 7 \ F or SHIFT (flaps, fire) = 5 or 8 SKIP 2 \ These bytes appear to be unused .xTemp2Lo SKIP 1 \ The low byte of the xTemp2 temporary variable \ \ Stored as a 24-bit value (xTemp2Top xTemp2Hi xTemp2Lo) .yTemp2Lo SKIP 1 \ The low byte of the yTemp2 temporary variable \ \ Stored as a 24-bit value (yTemp2Top yTemp2Hi yTemp2Lo) .zTemp2Lo SKIP 1 \ The low byte of the zTemp2 temporary variable \ \ Stored as a 24-bit value (zTemp2Top zTemp2Hi zTemp2Lo) SKIP 5 \ These bytes appear to be unused .keyLoggerHi SKIP 6 \ Key logger (high byte) \ \ Populated with values from keyTable1Hi or keyTable2Hi \ when a key is pressed, or 0 if neither is pressed: \ \ L or < (elevator dive/pitch) = -1 or 1 \ A or + (rudder yaw left/right) = -1 or 1 \ S or D (aileron bank left/right) = -1 or 1 \ W or E (throttle down/up) = -15 or 15 \ U or B (undercarriage, brakes) = 4 or 7 \ F or SHIFT (flaps, fire) = 5 or 8 SKIP 2 \ These bytes appear to be unused .xTemp2Hi SKIP 1 \ The high byte of the xTemp2 temporary variable \ \ Stored as a 24-bit value (xTemp2Top xTemp2Hi xTemp2Lo) .yTemp2Hi SKIP 1 \ The high byte of the yTemp2 temporary variable \ \ Stored as a 24-bit value (yTemp2Top yTemp2Hi yTemp2Lo) .zTemp2Hi SKIP 1 \ The high byte of the zTemp2 temporary variable \ \ Stored as a 24-bit value (zTemp2Top zTemp2Hi zTemp2Lo) .xTemp2Top SKIP 1 \ The top byte of the xTemp2 temporary variable \ \ Stored as a 24-bit value (xTemp2Top xTemp2Hi xTemp2Lo) .yTemp2Top SKIP 1 \ The top byte of the yTemp2 temporary variable \ \ Stored as a 24-bit value (yTemp2Top yTemp2Hi yTemp2Lo) .zTemp2Top SKIP 1 \ The top byte of the zTemp2 temporary variable \ \ Stored as a 24-bit value (zTemp2Top zTemp2Hi zTemp2Lo) .gunSights SKIP 1 \ Gun sights status \ \ * Bit 6 = 1 while "I" is being held down \ \ * Bit 7 = 1 when sights are being shown \ 0 when sights are not being shown .isObject SKIP 1 \ Temporary storage, used to store the object ID when we \ check the visibility of an object in ProcessLine and \ call SetObjectCoords to set its coordinates .pointId SKIP 1 \ Temporary storage, used to store the ID of the current \ point when checking a line's visibility in the \ ProcessLine routine .maxCoord SKIP 1 \ Temporary storage, used to store the maximum start \ point coordinate when clipping lines .colourLogic SKIP 1 \ Determines the logic and bit patterns used to draw the \ canopy view: \ \ * %00000000 = erase lines \ Gets set to this value after each screen flip \ Sets AND logic for screen writing \ Sets bit patterns to erase the screen \ See EraseCanopyLines \ \ * %01000000 when colourCycle is %11110000 \ Sets ORA logic for screen writing \ See FlipColours \ \ * %10000000 when colourCycle is %00001111 \ Sets ORA logic for screen writing \ See ResetLineLists, FlipColours \ \ Set to %10000000 for each new game .colourCycle SKIP 1 \ Determines which of the two canopy screens we are \ showing, so we can use colour cycling for smooth \ animation \ \ * %00001111 = show colour 1, hide colour 2 \ \ * %11110000 = show colour 2, hide colour 1 \ \ We show a colour by mapping it to white, and hide a \ colour by mapping it to black \ \ Set to %00001111 for each new game .matrixAxis SKIP 1 \ The axis to be processed by the matrix routines, \ specifically those that populate the matrices \ \ * 0 = x-axis \ \ * 1 = y-axis \ \ * 2 = z-axis .onGround SKIP 1 \ "On the ground" status \ \ * 0 = we are not on the ground \ \ * 1 = we are on the ground \ \ Set to 1 in ResetVariables (on the ground) .previousListEnd SKIP 1 \ Used to store the value of linesToHideEnd at the \ start of each iteration of the main loop, so we can \ refer to it at the end of the main loop to see if \ we have added anything to the list during the main \ loop .startStatus SKIP 1 \ Temporary storage, used to store the point status byte \ for the start point of a projected line .pointCount SKIP 1 \ Temporary storage, used as a counter in ProcessLine \ to check the start and end points of the line .pressingT SKIP 1 \ Set to 0 in the main loop if "T" is not being pressed, \ otherwise set to 1, to prevent holding down "T" from \ constantly switching the engine on and off .showRunwayDashes SKIP 1 \ Determines whether the dashes down the middle of the \ runway are close enough to be visible: \ \ * Bit 7: \ \ * 0 = the runway dashes are visible \ \ * 1 = the runway dashes are not visible .matrixNumber SKIP 1 \ The matrix used in matrix operations: \ \ * 0 = matrix 1 \ * 9 = matrix 2 \ * 18 = matrix 3 \ * 27 = matrix 4 .objectId SKIP 1 \ Temporary storage for an object ID (0 to 39) \ \ This is called OB in the original source code .pressingTab SKIP 1 \ Bit 7 determines whether TAB is being pressed \ \ * 0 = not being pressed \ \ * 128 = being pressed \ \ This value is set in the ToggleJoystick routine .showLine SKIP 1 \ Determines whether a line is visible: \ \ * Bit 6: \ \ * 0 = the line fits into Aviator's 3D world \ \ * 1 = the line calculations overflowed, so the \ line does not fit into Aviator's 3D world \ \ * Bit 7: \ \ * 0 = the line is visible \ \ * 1 = the line is not visible .objectAnchorPoint SKIP 1 \ Used to store the anchor point of the current object \ \ This is the point to which all the other points in \ the object, i.e. those in xObjectPoint, yObjectPoint \ and zObjectPoint, are relative \ \ In other words, this is effectively the object's \ coordinate .lineBuffer1Count SKIP 1 \ Offset of the last line stored in buffer 1 \ \ * -1 = buffer 1 is empty \ \ * 0 to 46 = buffer 1 contains lineBuffer1Count + 1 \ lines but is not full \ \ * 47 = buffer 1 is full and contains 48 lines \ \ We can buffer up to 96 lines, with 48 in each of the \ two line buffers, so the maximum number of lines on \ screen at any one time is 48 lines out of the 193 \ lines defined in the world \ \ Set to -1 in ResetVariables (buffer 1 empty) .lineBuffer2Count SKIP 1 \ Offset of the last line stored in buffer 2 \ \ * 47 = buffer 2 is empty \ \ * 48 to 94 = buffer 2 contains lineBuffer2Count + 1 \ lines but is not full \ \ * 95 = buffer 2 is full, contains 48 lines \ \ We can buffer up to 96 lines, with 48 in each of the \ two line buffers, so the maximum number of lines on \ screen at any one time is 48 lines out of the 193 \ lines defined in the world \ \ Set to 47 in ResetVariables (buffer 2 empty) .pressingUFBS SKIP 5 \ Determines whether any of the following keys are \ being pressed: \ \ * pressingUFBS = 1 while the undercarriage key "U" \ is being pressed, 0 otherwise \ \ * pressingUFBS+1 = 1 while the flaps key "F" is \ being pressed, 0 otherwise \ \ * pressingUFBS+2 is not used \ \ * pressingUFBS+3 = 1 while the brake key "B" is \ being pressed, 0 otherwise \ \ * pressingUFBS+4 = 1 while the fire key SHIFT is \ being pressed, 0 otherwise .pointsToAward SKIP 1 \ Used to store the points scored from flying skills, so \ they can be added to the score once the task has been \ completed .scoreDisplayTimer SKIP 1 \ Counter for removing the score after displaying it \ for a fixed amount of time .gunSoundCounter SKIP 1 \ Counter for the number of firing sounds we make when \ firing our guns (which makes the sound of two shots) .xDashesVectorLo SKIP 1 \ The low byte of the xDashesVector temporary variable \ \ Stored as a 16-bit value (xDashesVectorHi \ xDashesVectorLo) .yDashesVectorLo SKIP 1 \ The low byte of the yDashesVector temporary variable \ \ Stored as a 16-bit value (yDashesVectorHi \ yDashesVectorLo) .zDashesVectorLo SKIP 1 \ The low byte of the yDashesVector temporary variable \ \ Stored as a 16-bit value (zDashesVectorHi \ zDashesVectorLo) .xDashesVectorHi SKIP 1 \ The high byte of the xDashesVector temporary variable \ \ Stored as a 16-bit value (xDashesVectorHi \ xDashesVectorLo) .yDashesVectorHi SKIP 1 \ The high byte of the yDashesVector temporary variable \ \ Stored as a 16-bit value (yDashesVectorHi \ yDashesVectorLo) .zDashesVectorHi SKIP 1 \ The high byte of the yDashesVector temporary variable \ \ Stored as a 16-bit value (zDashesVectorHi \ zDashesVectorLo) .xTemp1Lo SKIP 1 \ The low byte of the xTemp1 temporary variable \ \ Stored as a 16-bit value (xTemp1Hi xTemp1Lo) .yTemp1Lo SKIP 1 \ The low byte of the yTemp1 temporary variable \ \ Stored as a 16-bit value (yTemp1Hi yTemp1Lo) .zTemp1Lo SKIP 1 \ The low byte of the zTemp1 temporary variable \ \ Stored as a 16-bit value (zTemp1Hi zTemp1Lo) .xTemp1Hi SKIP 1 \ The high byte of the xTemp1 temporary variable \ \ Stored as a 16-bit value (xTemp1Hi xTemp1Lo) .yTemp1Hi SKIP 1 \ The high byte of the yTemp1 temporary variable \ \ Stored as a 16-bit value (yTemp1Hi yTemp1Lo) .zTemp1Hi SKIP 1 \ The high byte of the zTemp1 temporary variable \ \ Stored as a 16-bit value (zTemp1Hi zTemp1Lo) .alien SKIP 0 \ Temporary storage, used as a flag to indicate the \ alien when updating the radar (shares a memory \ location with the objCount variable) .objCount SKIP 1 \ Temporary storage, used as a loop counter in the \ object group visibility checks (shares a memory \ location with the alien variable) .themeStatus SKIP 1 \ Theme status \ \ * Positive (bit 7 = 0) = the Theme is enabled and \ the value is the number of aliens we still have \ to add to the current wave (this starts at 8 and \ decreases down to 0 as each new alien is added) \ \ * Negative (bit 7 = 1) = the Theme is not enabled \ \ Set to 255 (Theme not enabled) in ResetVariables .landingStatus SKIP 1 \ The current landing status \ \ A flag that determines whether we have landed safely \ on the runway, and therefore whether we fill up with \ fuel and so on \ \ * Positive = do all the landing-based tasks \ mentioned below \ \ * 0 = do not enable the Theme on firing \ do not fill up with fuel \ do not award points for landing \ \ * Negative = do not enable the Theme on firing \ do not fill up with fuel \ \ Set to 1 in ResetVariables (do all landing tasks) \ \ Set to %01000000 when speed is 0 in ApplyFlightModel \ \ Gets shifted left with a 1 inserted in bit 0 in \ ProcessLanding if we are doing an emergency landing .engineStatus SKIP 1 \ Engine status \ \ * 0 = engine is off \ \ * Non-zero = engine is on .xRotationLo SKIP 1 \ Plane rotation angle around the x-axis (low byte) \ \ Same as the plane's pitch angle \ \ Stored as a 16-bit value (xRotationHi xRotationLo) .yRotationLo SKIP 1 \ Plane rotation angle around the y-axis (low byte) \ \ Same as the plane's yaw angle, which is also the \ direction of the compass (i.e. the plane's heading) \ \ Stored as a 16-bit value (yRotationHi yRotationLo) .zRotationLo SKIP 1 \ Plane rotation angle around the z-axis (low byte) \ \ Same as the plane's roll angle \ \ Stored as a 16-bit value (zRotationHi zRotationLo) .xPlaneLo SKIP 1 \ Plane longitude/x-coordinate (low byte) \ \ Set to &C6E5 in ResetVariables \ \ Stored as a 32-bit value (xPlaneTop xPlaneHi xPlaneLo \ xPlaneBot) .yPlaneLo SKIP 1 \ Plane altitude/y-coordinate (low byte) \ \ Stored as the altitude in feet x 4 \ \ Shown on indicator 2 \ \ Set to (0 10) in ResetVariables \ \ Stored as a 32-bit value (yPlaneTop yPlaneHi yPlaneLo \ yPlaneBot) .zPlaneLo SKIP 1 \ Plane latitude/z-coordinate (low byte) \ \ Set to &485C in ResetVariables \ \ Stored as a 32-bit value (zPlaneTop zPlaneHi zPlaneLo \ zPlaneBot) .yLandingGear SKIP 1 \ The vertical distance between the cockpit and the \ lowest part of the plane \ \ * 5 when undercarriage is up \ \ * 10 when undercarriage is down .firingStatus SKIP 1 \ Firing status \ \ * 0 = no bullets in the air \ \ * Non-zero = guns fired, bullets in the air \ \ This is called FRFLAG in the original source code .ucStatus SKIP 1 \ Undercarriage status \ \ * 0 = undercarriage is up \ \ * Non-zero = undercarriage is down \ \ Set to 1 (undercarriage is down) in ResetVariables .flapsStatus SKIP 1 \ Flaps status \ \ * 0 = flaps are off (raised) \ \ * Non-zero = flaps are on (dropped) \ \ Set to 0 (flaps are off) in ResetVariables SKIP 1 \ This byte appears to be unused .brakesStatus SKIP 1 \ Brakes status \ \ * 0 = brakes are off \ \ * Non-zero = brakes are on \ \ Set to 1 (brakes are on) in ResetVariables SKIP 1 \ This byte appears to be unused .propellorStatus SKIP 1 \ Propellor status \ \ * 0 = propellor is working \ \ * Non-zero = propellor is broken \ \ If we make a crash landing with the undercarriage up, \ the propellor breaks and we can't turn the engine on .alienSpeed SKIP 1 \ The speed at which the aliens move, which starts at 10 \ for the first wave, then 14 for the second, 18 for the \ third, and 22 for all subsequent waves \ \ Set to 10 in ResetVariables .reached512ft SKIP 1 \ Have we reached 512 feet in altitude since taking off? \ \ * 0 = no \ \ * Non-zero = yes \ \ The height measured is 512 feet, rather than the 500 \ feet mentioned in the manual, as this is set to \ non-zero when the plane's altitude in yPlaneHi >= 2 .xRotationHi SKIP 1 \ Plane rotation angle around the x-axis (high byte) \ \ Same as the plane's pitch angle \ \ * 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) \ \ Set to 7 in ResetVariables \ \ Stored as a 16-bit value (xRotationHi xRotationLo) .yRotationHi SKIP 1 \ Plane rotation angle around the y-axis (high byte) \ \ Same as the plane's yaw angle, which is also the \ direction of the compass (i.e. the plane's heading) \ \ * 0 = north (bit 6 clear, bit 7 clear) \ * 64 = east (bit 6 set, bit 7 clear) \ * 128 = south (bit 6 clear, bit 7 set) \ * 192 = west (bit 6 set, bit 7 set) \ \ Shown on indicator 0 \ \ Stored as a 16-bit value (yRotationHi yRotationLo) .zRotationHi SKIP 1 \ Plane rotation angle around the z-axis (high byte) \ \ Same as the plane's roll angle \ \ * 0 = horizontal (bit 6 clear, bit 7 clear) \ * 64 = vertical right (bit 6 set, bit 7 clear) \ * 128 = upside down (bit 6 clear, bit 7 set) \ * 192 = vertical left (bit 6 set, bit 7 set) \ \ Stored as a 16-bit value (zRotationHi zRotationLo) .xPlaneHi SKIP 1 \ Plane longitude/x-coordinate (high byte) \ \ Set to &C6E5 in ResetVariables \ \ Stored as a 32-bit value (xPlaneTop xPlaneHi xPlaneLo \ xPlaneBot) .yPlaneHi SKIP 1 \ Plane altitude/y-coordinate (high byte) \ \ Stored as the altitude in feet x 4 \ \ Shown on indicator 2 \ \ Set to (0 10) in ResetVariables \ \ Stored as a 32-bit value (yPlaneTop yPlaneHi yPlaneLo \ yPlaneBot) .zPlaneHi SKIP 1 \ Plane latitude/z-coordinate (high byte) \ \ Set to &485C in ResetVariables \ \ Stored as a 32-bit value (zPlaneTop zPlaneHi zPlaneLo \ zPlaneBot) \ ****************************************************************************** \ \ AVIATOR MAIN GAME CODE \ \ Produces the binary file AVIA.bin that contains the main game code. \ \ ****************************************************************************** CLEAR &0B00, &0D00 \ Clear the guard on the &0B00-&0CFF workspace so we can \ assemble the loader code into this address range ORG CODE% \ ****************************************************************************** \ \ Name: SetupScreen \ Type: Subroutine \ Category: Setup \ Summary: Set up the screen mode and load the dashboard image \ \ ****************************************************************************** .SetupScreen LDA #22 \ Switch to screen mode 5 with the following VDU JSR OSWRCH \ command: LDA #5 \ JSR OSWRCH \ VDU 22, 5 LDY #0 \ We now want to write the VDU command to disable the \ cursor, whose bytes are in the variable at \ disableCursor, so set up a counter in Y .sscr1 LDA disableCursor,Y \ Write the Y-th value from disableCursor JSR OSWRCH INY \ Increment the loop counter CPY #10 \ Loop back to write the next character until we have BNE sscr1 \ written all 10, in other words: \ \ VDU 23, 0, 10, 23, 0, 0, 0, 0, 0, 0 LDA #31 \ Move the text cursor to column 4, row 10 with the JSR OSWRCH \ following VDU command: LDA #4 \ JSR OSWRCH \ VDU 31, 4, 10 LDA #10 JSR OSWRCH LDY #0 \ We now want to print the "Please wait" message in \ variable pleaseWaitText, so set up a counter in Y .sscr2 LDA pleaseWaitText,Y \ Print the Y-th character from pleaseWaitText JSR OSWRCH INY \ Increment the loop counter CPY #11 \ Loop back to print the next character until we have BNE sscr2 \ printed all 11 ("Please wait") LDX #LO(loadDashboard) \ Set (Y X) to point to loadDashboard ("L.DASHBD 7100") LDY #HI(loadDashboard) JSR OSCLI \ Call OSCLI to run the OS command in loadDashboard, \ which loads the dashboard image into the screen LDA #129 \ Call OSBYTE with A = 129, X = &FF and Y = 0 to scan LDX #&FF \ the keyboard for &FF centiseconds (2.56 seconds) LDY #0 JSR OSBYTE JMP DrawCanopy \ Jump down to DrawCanopy to continue the setup process \ ****************************************************************************** \ \ Name: loadDashboard \ Type: Variable \ Category: Setup \ Summary: The OS command string for loading the dashboard image \ \ ****************************************************************************** .loadDashboard EQUS "L.DASHBD 7100" \ This is short for "*LOAD DASHBD 7100" EQUB 13 \ ****************************************************************************** \ \ Name: disableCursor \ Type: Variable \ Category: Setup \ Summary: The VDU command for disabling the cursor \ \ ****************************************************************************** .disableCursor EQUB 23, 0, 10, 23 \ Set 6845 register R10 = 23 EQUB 0, 0, 0 \ EQUB 0, 0, 0 \ This is the "cursor start" register, so this sets the \ cursor start line at 23, effectively disabling the \ cursor \ ****************************************************************************** \ \ Name: pleaseWaitText \ Type: Variable \ Category: Setup \ Summary: The "Please wait" message shown when the game loads \ \ ****************************************************************************** .pleaseWaitText EQUS "Please wait" EQUB 13 \ ****************************************************************************** \ \ Name: DrawCanopy \ Type: Subroutine \ Category: Setup \ Summary: Move code around, clear the edges of the canopy view, draw the \ canopy edges and rivets, and jump to the main code \ \ ****************************************************************************** .DrawCanopy LDA #140 \ Call OSBYTE with A = 140 to select the tape filing JSR OSBYTE \ system (i.e. do a *TAPE command) LDY #0 \ We now copy the following block in memory: \ \ * &0400-&07FF is copied to &0D00-&10FF \ \ so we set up a byte counter in Y \ \ Note that this is the same block that was copied from \ &5800-&5BFF by the Entry routine, so in all we end up \ moving code as follows: \ \ * &5800-&5BFF is copied to &0400-&07FF \ then to &0D00-&10FF \ * &5C00-&5DFF is copied to &0B00-&0CFF \ \ As the rest of the main code file runs from &1100 to \ &57FF, this gives us a continuous block of code from \ &0B00 to &57FF, followed by screen memory at &5800 to \ &7FFF .dcan1 LDA &0400,Y \ Copy the Y-th byte of &0400 to the Y-th byte of &0D00 STA &0D00,Y LDA &0500,Y \ Copy the Y-th byte of &0500 to the Y-th byte of &0E00 STA &0E00,Y LDA &0600,Y \ Copy the Y-th byte of &0600 to the Y-th byte of &0F00 STA &0F00,Y LDA &0700,Y \ Copy the Y-th byte of &0700 to the Y-th byte of &1000 STA &1000,Y DEY \ Decrement the loop counter BNE dcan1 \ 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 \ The following two calls to ClearRows clear the first \ two character rows on-screen LDA #&58 \ Set (Q P) = &5800, so the call to ClearRows starts STA Q \ clearing from the start of the first character row LDA #0 \ (i.e. row 0) STA P LDA #2 \ Set R = 2, so we clear 2 character rows STA R LDY #0 \ Set Y = 0, so we clear 256 bytes per row, or 32 of the \ 40 character blocks in each screen row (128 pixels) JSR ClearRows \ Call ClearRows to clear the first 256 bytes of the \ first two character rows on-screen, which blanks out \ the first 32 character blocks (blocks 0 to 31) of the \ top two character rows (rows 0 and 1) LDA #&58 \ Set (Q P) = &58FF, so the call to ClearRows starts STA Q \ clearing from character block 32 in the first LDA #&FF \ character row on-screen STA P LDA #2 \ Set R = 2, so we clear 2 character rows STA R LDY #64 \ Set Y = 64, so we clear 64 bytes per row, or 8 of the \ 40 character blocks in each screen row (32 pixels) JSR ClearRows \ Call ClearRows to clear the last 64 bytes of the first \ two character rows on-screen, which blanks out the \ last 8 character blocks (blocks 32 to 39) of the top \ two character rows (rows 0 and 1) \ The following two calls to ClearRows clear a \ 4-pixel-wide column on the left and right edges of the \ screen LDA #&5A \ Set (Q P) = &5A7F, so the call to ClearRows starts STA Q \ clearing from the start of the third character row LDA #&7F \ (i.e. row 2) STA P LDA #18 \ Set R = 18, so we clear 18 character rows STA R LDY #8 \ Set Y = 8, so we clear 8 bytes per row, or 1 of the \ 40 character blocks in each screen row (4 pixels) JSR ClearRows \ Call ClearRows to clear the first byte of character \ rows 2 to 20, which blanks out the first character \ block (block 0) on all 18 rows, i.e. the first four \ pixels LDA #&5B \ Set (Q P) = &5BB7, so the call to ClearRows starts STA Q \ clearing from the start of the last character block LDA #&B7 \ on the third character row (i.e. row 2) STA P LDA #18 \ Set R = 18, so we clear 18 character rows STA R LDY #8 \ Set Y = 8, so we clear 8 bytes per row, or 1 of the \ 40 character blocks in each screen row (4 pixels) JSR ClearRows \ Call ClearRows to clear the last byte of character \ rows 2 to 20, which blanks out the last character \ block (block 39) on all 18 rows, i.e. the last four \ pixels \ We now draw the edges of the canopy view, starting \ with the left edge, then the right edge, and then the \ horizontal line along the top (so this does not \ include the bottom edge, which has already been \ displayed as part of the dashboard image, and it also \ doesn't include the diagonal top corners, which are \ drawn by the main game code) LDX #3 \ Move the graphics cursor to (3, 96) LDY #96 JSR VduMove LDX #3 \ Draw a line to (3, 239) LDY #239 JSR VduDraw LDX #156 \ Move the graphics cursor to (156, 96) LDY #96 JSR VduMove LDX #156 \ Draw a line to (156, 239) LDY #239 JSR VduDraw LDX #8 \ Move the graphics cursor to (8, 248) LDY #248 JSR VduMove LDX #151 \ Draw a line to (151, 248) LDY #248 JSR VduDraw \ We now draw the square rivets around the edge of the \ canopy view, starting with the three rivets up each \ side of the screen LDY #121 \ Set Y = 121 so the first rivets are drawn at height \ 121, i.e. at (0, 121) and (158, 121) .dcan2 LDX #0 \ Draw a square rivet at (0, Y) JSR DrawRivet LDX #158 \ Draw a square rivet at (158, Y) JSR DrawRivet TYA \ Set Y = Y + 48 CLC \ ADC #48 \ so the next rivet we draw will be 48 pixels higher TAY CPY #9 \ Loop back to draw the next rivet until Y = 9, at which BNE dcan2 \ point Y has wrapped round off the top of the screen \ back to the bottom and we will have drawn three rivets \ up each side of the canopy view \ Finally, we draw the six square rivets along the top \ of the canopy view LDY #255 \ Set X and Y so the first rivet is at (19, 255) LDX #19 .dcan3 JSR DrawRivet \ Draw a square rivet at (X, Y) TXA \ Set X = X + 24 CLC \ ADC #24 \ so the next rivet we draw will be 24 pixels to the TAX \ right CPX #163 \ Loop back to draw the next rivet until X = 163, at BNE dcan3 \ which point we will have drawn six rivets along the \ top of the canopy view JMP StartGame \ Jump to StartGame to start the game \ ****************************************************************************** \ \ Name: VduPoint \ Type: Subroutine \ Category: Graphics \ Summary: Draw a point on-screen using the standard VDU routines \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ X The x-coordinate of the point (0-159) \ \ Y The y-coordinate of the point (0-255) \ \ ****************************************************************************** .VduPoint LDA #69 \ Set A = 69 so the following VDU 25 command plots a \ point at an absolute position on-screen BNE VduPlot \ Jump to VduPlot to do the move (this BNE is \ effectively a JMP as A is never zero \ ****************************************************************************** \ \ Name: VduMove \ Type: Subroutine \ Category: Graphics \ Summary: Move the graphics cursor using the standard VDU routines \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ X The x-coordinate to move the graphics cursor to (0-159) \ \ Y The y-coordinate to move the graphics cursor to (0-255) \ \ ****************************************************************************** .VduMove LDA #4 \ Set A = 4 so the following VDU 25 command moves the \ graphics cursor an absolute position on-screen BNE VduPlot \ Jump to VduPlot to do the move (this BNE is \ effectively a JMP as A is never zero \ ****************************************************************************** \ \ Name: VduDraw \ Type: Subroutine \ Category: Graphics \ Summary: Draw a line using the standard VDU routines \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ X The x-coordinate of the end of the line (0-159) \ \ Y The y-coordinate of the end of the line (0-255) \ \ ****************************************************************************** .VduDraw LDA #5 \ Set A = 5 to denote "draw line absolute in the current \ graphics foreground colour" \ ****************************************************************************** \ \ Name: VduPlot \ Type: Subroutine \ Category: Graphics \ Summary: Perform a plot command using the standard VDU routines \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ A The type of plot command: \ \ * 4 = Move cursor to coordinate \ \ * 5 = Draw line from cursor to coordinate \ \ * 69 = Draw point at coordinate \ \ X The x-coordinate for the plot command (0-159) \ \ Y The y-coordinate for the plot command (0-255) \ \ ****************************************************************************** .VduPlot PHA \ Store the value of A on the stack, so we can retrieve \ it after the call to OSWRCH LDA #25 \ Start a VDU 25 command, which is the equivalent of a JSR OSWRCH \ PLOT command in BBC BASIC, and which has the following \ format: \ \ VDU 25, K, X; Y; \ \ where K is the plotting mode (i.e. move or draw) and \ X and Y are the relevant coordinates as 16-bit numbers PLA \ Retrieve the value of A that we stored on the stack JSR OSWRCH \ Write the K parameter of the VDU command, i.e. the \ type of plot command LDA #0 \ Set P = 0 STA P TXA \ Set A = X, so now (P A) contains the x-coordinate as a \ 16-bit number ASL A \ Set (P A) = (P A) * 8 ROL P \ = x-coordinate * 8 ASL A \ ROL P \ This gives the screen location of the x-coordinate in ASL A \ terms of VDU coordinates, where the screen is always ROL P \ 1280 pixels wide, which is 8 times the number of \ pixels in 160-pixel-wide mode 5 (i.e. 8 * 160 = 1280) JSR OSWRCH \ Write (P A), writing the low byte in A first LDA P \ And then the high byte in P JSR OSWRCH LDA #0 \ Set P = 0 STA P TYA \ Set A = Y, so now (P A) contains the y-coordinate as a \ 16-bit number ASL A \ Set (P A) = (P A) * 4 ROL P \ = y-coordinate * 4 ASL A \ ROL P \ This gives the screen location of the y-coordinate in \ terms of VDU coordinates, where the screen is always \ 1024 pixels high, which is 4 times the number of \ pixels in 256-pixel-high mode 5 (i.e. 4 * 256 = 1024) JSR OSWRCH \ Write (P A), writing the low byte in A first LDA P \ And then the high byte in P JSR OSWRCH RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: DrawRivet \ Type: Subroutine \ Category: Graphics \ Summary: Draw a square rivet (2 pixels across, 4 pixels high) \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ X The x-coordinate of the top-left corner of the rivet \ \ Y The y-coordinate of the top-left corner of the rivet \ \ ****************************************************************************** .DrawRivet JSR VduPoint \ Draw a point at (X, Y) DEY \ Draw a point at (X, Y - 1) JSR VduPoint DEY \ Draw a point at (X, Y - 2) JSR VduPoint DEY \ Draw a point at (X, Y - 3) JSR VduPoint INX \ Draw a point at (X + 1, Y - 3) JSR VduPoint INY \ Draw a point at (X + 1, Y - 2) JSR VduPoint INY \ Draw a point at (X + 1, Y - 1) JSR VduPoint INY \ Draw a point at (X + 1, Y) JSR VduPoint DEX \ Restore X to its original value RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: ClearRows \ Type: Subroutine \ Category: Graphics \ Summary: Clear the specified number of rows on-screen \ \ ------------------------------------------------------------------------------ \ \ This routine zeroes a block of Y bytes on R screen rows, starting from screen \ address (Q P) on the first row. \ \ A value of Y = 0 will zero 256 bytes. \ \ In other words, (Q P) represents the top-left pixel to blank, Y represents \ the width of the area to blank (with a value of 8 per character block), and R \ contains the number of rows to blank. \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ Y The width of each character row to zero (in bytes), \ 0 indicates 256 bytes \ \ (Q P) The screen address to start zeroing from \ \ R The number of character rows to zero \ \ ****************************************************************************** .ClearRows STY S \ Store the width of each character row in S .crow1 LDA #0 \ We are about to zero a block of memory, so set A = 0 \ so we can use it as our overwrite value LDY S \ Fetch the width of each character row, which we stored \ in S above .crow2 STA (P),Y \ Zero the Y-th byte of the page at (Q P), which sets 4 \ pixels to black DEY \ Decrement the byte pointer BNE crow2 \ Loop back until we have zeroed (Q P) to (Q P) + Y LDA P \ Set (Q P) = (Q P) + 320 CLC \ ADC #LO(320) \ starting with the low bytes STA P LDA Q \ And then the high bytes ADC #HI(320) STA Q DEC R \ Decrement the row counter in R BNE crow1 \ Loop back until we have zeroed R rows RTS \ Return from the subroutine SKIP 30 \ These bytes appear to be unused EQUB &FF, &FF EQUB &FF, &FF EQUB &FF, &FF EQUB &FF, &FF EQUB &FF, &FF EQUB &FF, &FF EQUB &FF, &FF EQUB &FF, &FF EQUB &FF, &FF EQUB &FF, &FF EQUB &FF, &FF EQUB &FF, &FF EQUB &FF, &FF EQUB &FF, &FF EQUB &FF, &FF EQUB &FF, &FF SKIP 32 EQUB &40 \ ****************************************************************************** \ \ Name: ProjectPoint (Part 1 of 3) \ Type: Subroutine \ Category: 3D geometry \ Summary: Project a point onto the screen (i.e. convert from 3D coordinates \ to screen coordinates) \ \ ------------------------------------------------------------------------------ \ \ This routine projects a point in 3D space onto the screen. If the point in 3D \ space is (xPoint, yPoint, zPoint), then the resulting projection is at pixel \ coordinate (x, y) on-screen, where: \ \ x = 80 + 256 * (xPoint / zPoint) \ y = 96 + 256 * (2 * yPoint / zPoint) \ \ The result is stored in (xPoint, yPoint), while zPoint is left alone so we can \ check its sign. The coordinates are all signed 16-bit values. \ \ Also, various bits are set in the point's status byte. These describe the \ relationship between the x- and y-coordinates of the point, which we can use \ to quickly determine whether the line containing this point is on-screen. We \ also set a bit that records that we have projected this point, so we don't \ repeat the process during this iteration of the main loop. \ \ The routine breaks down as follows: \ \ * Part 1 sets the various bits in the point's status byte \ \ * Part 2 calculates the divisions by zPoint \ \ * Part 3 calculates the addition of (80, 96) \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ GG The point ID to process \ \ xPoint+GG The point's x-coordinate in 3D space \ \ yPoint+GG The point's y-coordinate in 3D space \ \ zPoint+GG The point's z-coordinate in 3D space \ \ ------------------------------------------------------------------------------ \ \ Returns: \ \ pointStatus+GG The following bits are set as follows, so they can be \ used in ProcessLinesToShow to determine the visibility \ of the line containing this point: \ \ * Bit 0: \ \ * 0 = we have not projected this point before \ \ * 1 = we have already projected this point \ \ * Bit 2: \ \ * 0 = yPoint is positive \ \ * 1 = yPoint is negative \ \ * Bit 3: \ \ * 0 = xPoint is positive \ \ * 1 = xPoint is negative \ \ * Bit 4: \ \ * 0 = |yPoint| * 2 < |zPoint| \ \ * 1 = |yPoint| * 2 >= |zPoint| \ \ * Bit 5: \ \ * 0 = |xPoint| < |zPoint| \ \ * 1 = |xPoint| >= |zPoint| \ \ xPoint+GG The pixel x-coordinate of the point, projected onto the \ screen \ \ yPoint+GG The pixel y-coordinate of the point, projected onto the \ screen \ \ ****************************************************************************** .ProjectPoint LDX GG \ Set X to the point ID to process LDA #0 \ Set N = 0, which we use for collecting set bits to STA N \ apply to the point's status byte LDA #%00010000 \ Set bit 4 of R, for use in ScaleUp STA R LDA pointStatus,X \ Set Y = A = the status byte for the point to process TAY AND #1 \ If bit 0 of the status byte is clear, then we have not BEQ proj1 \ yet projected this point onto the screen, so jump to \ proj1 to skip the following instruction and move on \ to the projection process RTS \ Bit 0 of the point's status byte is set, which means \ we have already projected this point, so return from \ the subroutine .proj1 TYA \ Set bit 0 of the point's status byte in pointStatus ORA #1 \ to indicate that we have now called ProjectPoint for STA pointStatus,X \ this point, i.e. the point has been projected (or soon \ will be, anyway) \ The first step is to set the four bits in the point's \ status byte, which we will return from the subroutine \ to be used in ProcessLinesToShow for determining the \ visibility of the line containing this point \ We first set (Q P) = |zPoint| \ \ bumping (Q P) up to 1 if zPoint = 0, so (Q P) has a \ minimum value of 1 LDA zPointHi,X \ Set A = the high byte of the point's z-coordinate BMI proj4 \ If the z-coordinate is negative, jump to proj4 to \ change its sign STA Q \ Set Q = the high byte of the point's z-coordinate BEQ proj2 \ If the high byte of the z-coordinate is 0, jump to \ proj2 LDA zPointLo,X \ Set P = the low byte of the point's z-coordinate, so STA P \ (Q P) now contains the point's z-coordinate, as \ required JMP proj5 \ Jump to proj5 to move on to the point's x-coordinate .proj2 \ If we get here then the high byte of the z-coordinate \ is 0 LDA zPointLo,X \ Set A = the low byte of the point's z-coordinate BNE proj3 \ If the low byte of the point's z-coordinate is \ non-zero, skip the following instructions \ If we get here then both the high and low bytes of the \ z-coordinate are 0, so zPoint = 0 LDA #1 \ Set A = 1 to use as the low byte of (Q P), so we will \ end up with (Q P) = 1, as required .proj3 STA P \ Set P = the low byte of the point's z-coordinate, or \ 1 if the low byte was zero, so (Q P) now contains \ |zPoint|, with a minimum value of 1, as required JMP proj5 \ Jump to proj5 to move on to the point's x-coordinate .proj4 \ If we get here then the high byte of the z-coordinate \ is negative, so now we negate it to make it positive LDA #0 \ Negate zPoint and store it in (Q P), starting with the SEC \ low bytes SBC zPointLo,X STA P LDA #0 \ And then the high bytes, so (Q P) now contains the SBC zPointHi,X \ point's z-coordinate made positive, i.e. |zPoint|, STA Q \ as required .proj5 \ We now set (QQ PP) = |xPoint| LDA xPointHi,X \ Set A = the high byte of the point's x-coordinate BMI proj6 \ If the x-coordinate is negative, jump to proj6 to \ change its sign STA QQ \ Set QQ = the high byte of the point's x-coordinate LDA xPointLo,X \ Set PP = the low byte of the point's x-coordinate, so STA PP \ (QQ PP) now contains |xPoint|, as required JMP proj7 \ Jump to proj7 to move on to the point's y-coordinate .proj6 \ If we get here then the high byte of the x-coordinate \ is negative, so now we negate it to make it positive LDA #0 \ Negate xPoint and store it in (QQ PP), starting with SEC \ the low bytes SBC xPointLo,X STA PP LDA #0 \ And then the high bytes, so (QQ PP) now contains the SBC xPointHi,X \ point's x-coordinate made positive, i.e. |xPoint|, STA QQ \ as required LDA N \ Set bit 3 of N (so we set bit 3 of the point's status ORA #%00001000 \ byte when we're done) to indicate that xPoint is STA N \ negative .proj7 \ We now set (SS RR) = |yPoint| * 2 LDA yPointHi,X \ Set A = the high byte of the point's y-coordinate BMI proj8 \ If the y-coordinate is negative, jump to proj8 to \ change its sign STA SS \ Set SS = the high byte of the point's y-coordinate LDA yPointLo,X \ Set A = the high byte of the point's y-coordinate, so \ we now have: \ \ (SS A) = |yPoint| ASL A \ Set (SS RR) = (SS A) * 2 ROL SS \ = |yPoint| * 2 STA RR JMP proj9 \ Jump to proj9 to move on to the next stage .proj8 \ If we get here then the high byte of the y-coordinate \ is negative, so now we negate it to make it positive \ before multiplying by 2 LDA #0 \ Negate yPoint and store it in (A RR), starting with SEC \ the low bytes SBC yPointLo,X STA RR LDA #0 \ And then the high bytes, so (A RR) now contains the SBC yPointHi,X \ point's y-coordinate made positive, so \ we now have: \ \ (A RR) = |yPoint| ASL RR \ Set (SS RR) = (A RR) * 2 ROL A \ = |yPoint| * 2 STA SS LDA N \ Set bit 2 of N (so we set bit 2 of the point's status ORA #%00000100 \ byte when we're done) to indicate that yPoint is STA N \ negative .proj9 \ We now set bit 5 of the status byte in N by comparing \ the |xPoint| and |zPoint| values we just calculated LDA QQ \ If QQ < Q, jump to proj11 to leave bit 5 clear CMP Q BCC proj11 BNE proj10 \ If QQ > Q, jump to proj10 to set bit 5 \ If we get here then QQ = Q LDA PP \ If PP < P, jump to proj11 to leave bit 5 clear CMP P BCC proj11 .proj10 \ If we get here then QQ = Q and PP >= P, which means \ (QQ PP) >= (Q P), i.e. |xPoint| >= |zPoint| LDA #%00100000 \ Set bit 5 of N (so we set bit 5 of the point's status ORA N \ byte when we're done) to indicate that STA N \ |xPoint| >= |zPoint| .proj11 \ We now set bit 4 of the status byte in N by comparing \ the |yPoint| and |zPoint| values we just calculated LDA SS \ If SS < Q, jump to proj13 to leave bit 4 clear CMP Q BCC proj13 BNE proj12 \ If SS > Q, jump to proj12 to set bit 4 \ If we get here then SS = Q LDA RR \ If RR < P, jump to proj13 to leave bit 4 clear CMP P BCC proj13 .proj12 \ If we get here then SS = Q and RR >= P, which means \ (SS RR) >= (Q P), i.e. |yPoint| * 2 >= |zPoint| LDA N \ Set bit 4 of N (so we set bit 4 of the point's status ORA #%00010000 \ byte when we're done) to indicate that STA N \ |yPoint| * 2 >= |zPoint| \ ****************************************************************************** \ \ Name: ProjectPoint (Part 2 of 3) \ Type: Subroutine \ Category: 3D geometry \ Summary: Calculate the screen coordinates of the projected point \ \ ****************************************************************************** .proj13 \ By this point, we have the following: \ \ (QQ PP) = |xPoint| \ (SS RR) = |yPoint| * 2 \ (Q P) = |zPoint| \ \ with (Q P) set to a minimum value of 1 LDY P \ Set (X Y) = (Q P) LDX Q \ = |zPoint| \ \ so we call ScaleUp with a non-zero (X Y), as we know \ (Q P) is at least 1 JSR ScaleUp \ Set (A Y) = (X Y), scaled up until it doesn't fit into \ 16 bits any more, and set WW to the minimum number of \ bits in the original number TAX \ Set (TT S) = the A-th entry from the division table LDA divisionHi,X \ with bits 0 to 2 cleared (as they contain unrelated STA TT \ data) LDA divisionLo,X AND #%11111000 STA S STY K \ Set K = Y, the low byte of the result from ScaleUp LDA WW \ Set UU = WW STA UU LDY PP \ Set (X Y) = (QQ PP) LDX QQ \ = |xPoint| JSR DivideScaled \ Set (Q P) = (X Y) divided by (TT S) \ = |xPoint| / |zPoint| \ \ And set WW to the scale factor of the result LDA Q \ Set (QQ PP) = (Q P) STA QQ LDA P STA PP LDA WW \ Set VV = WW STA VV LDY RR \ Set (X Y) = (SS RR) LDX SS \ = |yPoint| * 2 JSR DivideScaled \ Set (Q P) = (X Y) divided by (TT S) \ = |yPoint| * 2 / |zPoint| \ \ And set WW to the scale factor of the result JSR ScaleDown \ Scale (Q P) and (QQ PP) down by the correct amounts in \ UU, VC and WW to give screen coordinates in (SS QQ) \ and (RR Q) \ ****************************************************************************** \ \ Name: ProjectPoint (Part 3 of 3) \ Type: Subroutine \ Category: 3D geometry \ Summary: Move the projected coordinates to the centre of the screen and \ update the point's status byte \ \ ****************************************************************************** \ By this point we have the following projected \ coordinates for the point whose ID is in GG: \ \ (SS QQ) = screen x-coordinate of 3D point projected \ onto the screen \ \ (RR Q) = screen y-coordinate of 3D point projected \ onto the screen \ \ We now move the projected coordinate to the correct \ place on screen, as the projected coordinates have the \ origin straight ahead of us, i.e. in the centre of the \ screen, while the screen has the origin in the \ bottom-left corner \ \ In other words, we add (80, 96) to the projected \ coordinates so that a projected coordinate of (0, 0), \ which is straight ahead of us, will appear in the \ centre of the canopy view, which is 160 x 192 pixels \ in size \ \ We also set the correct sign for the projected \ coordinate LDX GG \ Set X to the point ID to process LDA zPointHi,X \ If the point's z-coordinate is negative, jump to BMI proj14 \ proj14 LDA xPointHi,X \ If the point's x-coordinate is positive, jump to BPL proj16 \ proj16 as the coordinates have the same sign JMP proj15 \ Otherwise jump to proj15 as the coordinates have \ opposite signs .proj14 \ If we get here then the point's z-coordinate is \ negative LDA xPointHi,X \ If the point's x-coordinate is negative, jump to BMI proj16 \ proj16 as the coordinates have the same sign .proj15 \ If we get here then either the point's z-coordinate is \ positive and the point's x-coordinate is negative, or \ the point's z-coordinate is negative and the point's \ x-coordinate is positive - in other words, the point's \ x- and z-coordinates have opposite signs LDA #80 \ Set (xPointHi xPointLo) = 80 - (SS QQ) SEC \ SBC QQ \ starting with the low bytes STA xPointLo,X LDA #0 \ And then the high bytes SBC SS STA xPointHi,X JMP proj17 \ Jump to proj17 to move on to the y-coordinate .proj16 \ If we get here then either the point's z-coordinate is \ positive and the point's x-coordinate is positive, or \ the point's z-coordinate is negative and the point's \ x-coordinate is negative - in other words, the point's \ x- and z-coordinates have the same sign LDA #80 \ Set (xPointHi xPointLo) = 80 + (SS QQ) CLC \ ADC QQ \ starting with the low bytes STA xPointLo,X LDA #0 \ And then the high bytes ADC SS STA xPointHi,X .proj17 \ We now move the y-coordinate in the projected result \ to the correct place on screen, by adding 96 to the \ result and setting the correct sign in the process LDX GG \ Set X to the point ID to process LDA zPointHi,X \ If the point's z-coordinate is negative, jump to BMI proj18 \ proj18 LDA yPointHi,X \ If the point's y-coordinate is positive, jump to BPL proj20 \ proj20 as the coordinates have the same sign JMP proj19 \ Otherwise jump to proj19 as the coordinates have \ opposite signs .proj18 \ If we get here then the point's z-coordinate is \ negative LDA yPointHi,X \ If the point's y-coordinate is negative, jump to BMI proj20 \ proj20 as the coordinates have the same sign .proj19 \ If we get here then either the point's z-coordinate is \ positive and the point's y-coordinate is negative, or \ the point's z-coordinate is negative and the point's \ y-coordinate is positive - in other words, the point's \ y- and z-coordinates have opposite signs LDA #96 \ Set (yPointHi yPointLo) = 96 - (RR Q) SEC \ SBC Q \ starting with the low bytes STA yPointLo,X LDA #0 \ And then the high bytes SBC RR STA yPointHi,X JMP proj21 \ Jump to proj17 to move on to the point's status byte .proj20 LDA #96 \ Set (yPointHi yPointLo) = 96 + (RR Q) CLC \ ADC Q \ starting with the low bytes STA yPointLo,X LDA #0 \ And then the high bytes ADC RR STA yPointHi,X .proj21 LDA pointStatus,X \ Apply any set bits from N to the point's status byte ORA N STA pointStatus,X RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: DivideScaled \ Type: Subroutine \ Category: Maths \ Summary: Divide a 16-bit number by a scaled 16-bit number \ \ ------------------------------------------------------------------------------ \ \ The commentary in this routine is a work in progress. \ \ This routine calculates: \ \ (Q P) = (X Y) divided by (TT S) \ \ where (X Y) is a positive 16-bit number and (TT S) is the entry in the \ (divisionHi divisionLo) table for the denominator after being scaled by the \ ScaleUp routine. \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ (X Y) A positive 16-bit number containing a number to be \ divided (the dividend) \ \ Z flag Set according to the high byte in X (i.e. the routine is \ called after setting register X) \ \ R Contains %00010000 (which is set at the start of \ ProjectPoint) \ \ (TT S) The lookup from (divisionHi divisionLo) for the high \ byte of the scaled up denominator (i.e. the number we \ are dividing by) \ \ K The low byte of the scaled up denominator \ \ ------------------------------------------------------------------------------ \ \ Returns: \ \ (Q P) (X Y) divided by the denominator used to look up the \ value of (TT S) \ \ ****************************************************************************** JSR ScaleUp \ This instruction appears to be an unused duplicate of \ the first instruction of DivideScaled, so perhaps this \ is a remnant of an unused entry point DivideScaled-3, \ which would run ScaleUp twice? .DivideScaled JSR ScaleUp \ Set (A Y) = (X Y), scaled up until it doesn't fit into \ 16 bits any more, and set WW to the minimum number of \ bits in the original number STA J \ Set (J I) = (A Y) STY I LDX TT \ Set X = TT \ = %TTTTtttt LDY J \ Set Y = J \ = %JJJJjjjj LDA highNibble,X \ Set T = (X AND %11110000) OR (Y >> 4) ORA shift4Right,Y \ = %TTTTJJJJ STA T AND #%11110000 \ Set U = (T AND %11110000) OR (Y AND %00001111) ORA lowNibble,Y \ = %TTTTjjjj STA U AND #%00001111 \ Set V = (U AND %00001111) OR (X << 4) ORA shift4Left,X \ = %ttttjjjj STA V AND #%11110000 \ Set Y = (V AND %11110000) OR (Y >> 4) ORA shift4Right,Y \ = %ttttJJJJ TAY LDX S \ Set X = S \ = %SSSSssss AND #%00001111 \ Set X = (Y AND %00001111) OR (X AND %11110000) ORA highNibble,X \ = %SSSSJJJJ TAX \ So by this point we have: \ \ T = %TTTTJJJJ \ U = %TTTTjjjj \ V = %ttttjjjj \ Y = %ttttJJJJ \ X = %SSSSJJJJ \ \ where the following are arguments that were passed to \ the routine: \ \ (TT S) = (%TTTTtttt %SSSSssss) \ \ (J I) = (%JJJJjjjj %IIIIiiii) \ \ The first one is the denominator's lookup from the \ division table, while the second is the dividend, \ scaled up \ We now calculate (Q P) = (TT S) * (1 J I) \ \ (I am unsure about the following) LDA timesTable,X \ Set A = %SSSS * %JJJJ CLC \ Set P = A + (%tttt * %jjjj) LDX V \ = (%SSSS * %JJJJ) + (%tttt * %jjjj) ADC timesTable,X STA P LDX T \ Set Q = (%TTTT * %JJJJ) + 1 LDA timesTable,X ADC #1 STA Q \ So (Q P) = (%SSSS * %JJJJ) + (%tttt * %jjjj) \ + ((%TTTT * %JJJJ) + 1) << 8 LDX U \ Set X = (%TTTT * %jjjj) + (%tttt * %JJJJ) LDA timesTable,X ADC timesTable,Y TAX LDY #0 \ Set Y = 0, to use as the high byte of (Y A) BCC divs1 \ If the above addition didn't overflow, skip the \ following instruction LDY #16 \ The above addition overflowed, so set Y = 16 to use as \ the high byte of (Y A), where Y = %00010000 .divs1 LDA shift4Left,X \ Set A = (X << 4) ADC P \ Set (Y A) = (Y A) + P \ \ starting with the low bytes BCC divs2 \ And then the high bytes INY .divs2 ADC J \ Set (Y A) = (Y A) + J \ \ starting with the low bytes BCC divs3 \ And then the high bytes INY .divs3 ADC S \ Set (Y A) = (Y A) + S \ \ just doing the low bytes STA P \ Set (A P) = (Y A) TYA ADC shift4Right,X \ Set A = A + X << 4 + Q ADC Q BCC divs4 \ If the addition didn't overflow, jump to divs4 CLC \ Set A = A + TT ADC TT SEC \ Set the C flag to feed into bit 7 of (A P) when we \ shift it right JMP divs5 \ Skip the following instruction .divs4 ADC TT \ Set A = A + TT \ \ and set the C flag to the carry .divs5 ROR A \ Set (A P) = (A P) >> 1 ROR P STA Q \ Set (Q P) = (A P) LDA I \ Set A = I = %IIIIiiii BEQ divs6 \ If I = 0, jump to divs6 AND #%11110000 \ Set A = %IIII0000 LDX TT \ Set X = %TTTTtttt ORA shift4Right,X \ Set A = %IIII0000 OR %0000TTTT \ = %IIIITTTT TAY \ Set Y = %IIIITTTT AND #%11110000 \ Set X = %IIII0000 OR %0000tttt ORA lowNibble,X \ = %IIIItttt TAX LDA timesTable,X \ Set X = %IIII * %tttt TAX \ = %XXXXxxxx CLC \ Set the C flag from adding %xxxx0000 + %IIIIiiii LDA shift4Left,X ADC I LDA timesTable,Y \ Set A = %IIII * %TTTT + %XXXX + C ADC shift4Right,X \ = %IIII * %TTTT + (%IIII * %tttt) >> 4 + C ROR A \ Set A = A >> 1 CLC \ Set (Q P) = (Q P) + A ADC P \ STA P \ starting with the low bytes BCC divs6 INC Q \ And then the high bytes BNE divs6 LDA #&FF \ Set (Q P) = &FFFF STA Q STA P .divs6 LDA K \ If bits 6 and 7 of K are clear, jump to divs9 to AND #%11000000 \ return from the subroutine BEQ divs9 STA K \ Clear bits 0 to 5 of K CLC \ Set W = TT >> 1 + 1 LDA TT ADC #1 ROR A STA W LSR A \ Set A = W >> 1 BIT K \ If bit 6 of K is set, jump to divs7 BVS divs7 LDA #0 \ Bit 6 of K is clear, so set A = 0 BIT K \ Set the flags for K again .divs7 BPL divs8 \ If bit 7 of K is clear, jump to divs8 CLC \ Bit 7 of K is set, so set A = A + W ADC W .divs8 TAY \ Set Y = A LDX Q \ Set X = Q JSR Multiply8x8 \ Set (A V) = X * Y \ = A * Q STA G \ Set (Q P) = (Q P) - A LDA P \ SEC \ starting with the high bytes SBC G STA P BCS divs9 \ If the subtraction didn't underflow, skip the next \ instruction DEC Q \ Decrement the high byte .divs9 RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: ScaleUp \ Type: Subroutine \ Category: Maths \ Summary: Scale up a 16-bit number until it doesn't fit into 16 bits any \ more \ \ ------------------------------------------------------------------------------ \ \ Given a positive 16-bit argument, this routine scales the number up until it \ doesn't fit into 16 bits any more. It does this by doubling it (i.e. shifting \ it left) until a set bit pops out of the left end). \ \ The number of shifts is returned (in the form of the minimum number of binary \ digits in the original number) so we know how much the value was scaled up by. \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ (X Y) A positive 16-bit number containing a coordinate \ magnitude \ \ Z flag Set according to the high byte in X (i.e. the routine is \ called after setting register X) \ \ R Contains %00010000 (which is set at the start of \ ProjectPoint) \ \ ------------------------------------------------------------------------------ \ \ Returns: \ \ (A Y) The original argument in (X Y), left-shifted until a set \ bit pops out of the left end, so the leftmost set bit is \ not in (A Y) \ \ WW WW + 1 is the minimum number of binary digits that the \ original value of (X Y) fitted into (from which we can \ calculate the number of shifts we had to do to scale the \ number up) \ \ ****************************************************************************** .ScaleUp BEQ scup4 \ If the high byte in X = 0, jump down to scup4 LDA divisionLo,X \ Set A = int(log2(X)) AND #%00000111 \ \ so we know X fits into a minimum of A + 1 binary \ digits CLC \ Set WW = A + 8 ADC #8 \ STA WW \ so (X Y) fits into WW + 1 binary digits CMP #13 \ Set the flags for the WW < 13 comparison below TXA \ Set (A T) = (X Y) STY T BCC scup3 \ If WW < 13, jump to scup3 .scup1 \ If we get here, then (X Y) fits into 13 + 1 digits or \ more, i.e. it fits into 14 digits or more \ \ Also, (A T) = (X Y) ASL T \ Left-shift (A T) until we shift a 1 out of bit 7 of ROL A \ the high byte in A BCC scup1 LDY T \ Set Y = T \ So now we have: \ \ (A Y) = (A T) \ \ which is the result that we want RTS \ Return from the subroutine .scup2 ASL T \ Left-shift (A T) by one place ROL A .scup3 \ If we get here, then (X Y) fits into fewer than 13 + 1 \ binary digits, i.e. it fits into 13 digits or fewer \ \ Also, (A T) = (X Y) BIT R \ If bit 4 of A is clear, loop back to scup2 to keep BEQ scup2 \ shifting (A T) left until bit 4 of A is set (we check \ bit 4 because R = %00010000) TAY \ Set (Y X) = (A T) LDX T \ \ so (Y X) contains the original value of (X Y), shifted \ left until bit 4 of X is set (note that the X and Y \ have swapped round here) \ \ Let's say (Y X) = (%AAAAaaaa %TTTTtttt), which is the \ original (X Y) shifted left until bit 4 is set LDA shift4Right,X \ Set A = (X >> 4) OR (Y << 4) ORA shift4Left,Y \ = (%TTTTtttt >> 4) OR (%AAAAaaaa << 4) \ = %aaaaTTTT LDY shift4Left,X \ Set Y = X << 4 \ = %TTTTtttt << 4 \ = %tttt0000 \ So we now have: \ \ (A Y) = (%aaaaTTTT %tttt0000) \ = (%AAAAaaaa %TTTTtttt) << 4 \ = (A T) << 4 \ = (X Y) << 4 \ \ which is the result that we want RTS \ Return from the subroutine .scup4 \ If we get here, then the high byte in X = 0 CPY #0 \ If the low byte in Y = 0, jump down to scup8 BEQ scup8 LDA divisionLo,Y \ Set WW = int(log2(Y)) AND #%00000111 \ STA WW \ so we know Y fits into a minimum of WW + 1 binary \ digits CMP #4 \ Set the flags for the WW < 4 comparison below TYA \ Set (A Y) = (Y 0) LDY #0 BCC scup7 \ If WW < 4, jump to scup7 .scup5 \ If we get here, then (X Y) fits into 4 + 1 digits or \ more, i.e. it fits into 5 digits or more, so \ (X Y) = (0 Y) \ \ Also, (A Y) = (Y 0), which effectively does the first \ 8 left-shifts for us, as we know none of those shifts \ will shift a 1 out of bit 7 of the high byte ASL A \ Left-shift (A Y) until we shift a 1 out of bit 7 of BCC scup5 \ the high byte in A (we don't need to shift the low \ byte as we know it's 0) \ So now we have: \ \ (A Y) = (X Y) shifted left until we shift a 1 out of \ bit 7 \ \ which is the result that we want RTS \ Return from the subroutine .scup6 \ If we get here, then (X Y) fits into fewer than 4 + 1 \ binary digits, i.e. it fits into 4 digits or fewer, so \ (X Y) = (0 Y) = (0 %0000yyyy) \ \ Also, (A Y) = (Y 0), which effectively does the first \ 8 left-shifts for us, as we know none of those shifts \ will shift a 1 out of bit 7 of the high byte ASL A \ Left-shift (A Y) by one place (we don't need to shift \ the low byte as we know it's 0) .scup7 BIT R \ If bit 4 of A is clear, loop back to scup6 to keep BEQ scup6 \ shifting (A Y) left until bit 4 of A is set (we check \ bit 4 because R = %00010000), so now we have something \ like this (depending on how many of %yyyy are set): \ \ (A Y) = (%0001yyy0 %00000000) TAX \ Set A = A << 4 LDA shift4Left,X \ \ which moves the result into the top byte of (A Y) like \ this: \ \ (A Y) = (%yyy00000 %00000000) \ \ which is the result that we want RTS \ Return from the subroutine .scup8 \ If we get here, then (X Y) = 0 TSX \ We can only get here if we called this routine from INX \ the DivideScaled routine, as the only other call of INX \ this routine is from ProjectPoint, when we know we TXS \ are calling it with a value of at least 1 in (X Y) \ \ These instructions remove two bytes from the top of \ the stack so the RTS below returns an extra level up \ the call chain, and as DivideScaled itself must have \ been called from ProjectPoint, this returns us to \ ProjectPoint with the following results LDA #0 \ Set (Q P) = 0 STA Q STA P LDX UU \ Set WW = UU - 1 DEX STX WW RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: ScaleDown (Part 1 of 4) \ Type: Subroutine \ Category: Maths \ Summary: Scale down the results of divisions done using the ScaleUp and \ DivideScaled routines \ \ ------------------------------------------------------------------------------ \ \ The commentary in this routine is a work in progress. \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ UU Scale factor for the z-coordinate from ScaleUp routine \ \ VV Scale factor for the x-coordinate from ScaleUp routine \ \ WW Scale factor for the y-coordinate from ScaleUp routine \ \ (QQ PP) Result from DivideScaled routine for x-coordinate \ \ (Q P) Result from DivideScaled routine for y-coordinate \ \ ------------------------------------------------------------------------------ \ \ Returns: \ \ (SS QQ) The correctly scaled x-coordinate \ \ (RR Q) The correctly scaled y-coordinate \ \ ****************************************************************************** .ScaleDown LDA #0 \ Set RR = 0, to use as the high byte in the scaled STA RR \ y-coordinate (RR Q P), where (RR Q) in the final \ result is the integer, and P is the fractional part \ (which we discard) STA SS \ Set SS = 0, to use as the high byte in the scaled \ x-coordinate (SS QQ PP), where (SS QQ) in the final \ result is the integer, and PP is the fractional part \ (which we discard) LDA #7 \ Set T = 0 STA T LDA VV \ Set X = VV - UU + 1 SEC \ SBC UU \ so X is the imbalance between the x- and z-coordinates TAX \ in terms of scale factors, and because the division INX \ was x / z, this is the scale factor we need to apply \ to the x-coordinate, as 2^VV / 2^UU = 2^(VV - UU) LDA WW \ Set Y = WW - UU + 1 SEC \ SBC UU \ so Y is the imbalance between the y- and z-coordinates TAY \ in terms of scale factors, and because the division INY \ was y / z, this is the scale factor we need to apply \ to the y-coordinate, as 2^WW / 2^UU = 2^(WW - UU) CPY #7 \ If Y < 7, jump to down1 BCC down1 JMP down2 \ Y >= 7, so jump to down2 .down1 CPX #7 \ If X < 7, jump to down6 to do the scaling with T = 0 BCC down6 .down2 \ If we get here then at least one of X and Y is >= 7 LDA VV \ Set A = VV - WW SEC \ SBC WW \ so A is the imbalance between the x- and y-coordinates \ in terms of scale factors BEQ down3 \ If A = 0, i.e. VV = WW, jump to down3 BPL down4 \ If A > 0, i.e. VV > WW, jump to down4 \ At this point, VV < WW LDA Q \ If (Q P) < 0, jump to down6 to do the scaling with BMI down6 \ T = 0 JMP down5 \ (Q P) >= 0, so jump to down5 .down3 LDA Q \ If (Q P) < 0, jump to down6 to do the scaling with BMI down6 \ T = 0 .down4 LDA QQ \ If (QQ PP) < 0, jump to down6 to do the scaling with BMI down6 \ T = 0 .down5 \ We get here if at least one of X and Y is >= 7, and \ any of the following are true: \ \ * VV = WW and (Q P) >= 0 and (QQ PP) >= 0 \ i.e. x-scale = y-scale and x-coord >= 0 and \ y-coord >= 0 \ \ * VV > WW and (QQ PP) >= 0 \ i.e. x-scale > y-scale and x-coord >= 0 \ \ * VV < WW and (Q P) >= 0 \ i.e. x-scale < y-scale and y-coord >= 0 INC T \ Increment T to 1, so we decrease X and Y by one less \ in the next part \ ****************************************************************************** \ \ Name: ScaleDown (Part 2 of 4) \ Type: Subroutine \ Category: Maths \ Summary: Balance the scale factors for the x- and y-coordinates \ \ ****************************************************************************** .down6 \ If Y >= 0, we decrease X and Y by Y - T TYA \ If Y < 0, jump to down9 to skip the decrement loop BMI down9 JMP down8 \ Y >= 0, so jump to down8 to decrement X and Y .down7 DEX \ Decrement X and Y DEY .down8 CPY T \ While Y >= T, loop back to decrement X and Y BCS down7 .down9 \ If X >= 0, we decrease X and Y by X - T TXA \ If X < 0, jump to down12 to skip the decrement loop BMI down12 JMP down11 \ X >= 0, so jump to down11 to decrement X and Y .down10 DEX \ Decrement X and Y DEY .down11 CPX T \ While X >= T, loop back to decrement X and Y BCS down10 \ ****************************************************************************** \ \ Name: ScaleDown (Part 3 of 4) \ Type: Subroutine \ Category: Maths \ Summary: Scale the x-coordinate \ \ ****************************************************************************** .down12 \ We now shift the x-coordinate in (SS QQ PP) by X \ places in the correct direction, discarding the \ fractional part in PP when we are done, but only after \ rounding (SS QQ) to the nearest integer TXA \ If X < 0, jump to down15 to shift the x-coordinate BMI down15 \ right by X places, with A set to X BNE down13 \ If X > 0, jump to down13 to shift the x-coordinate \ left by X places ASL PP \ X = 0, so shift bit 7 of PP into the C flag and jump JMP down17 \ to down17 to round the result to the nearest integer, \ without shifting the x-coordinate first .down13 \ We now shift (SS QQ PP) left by X places LDA QQ \ Set (SS A PP) = (SS QQ PP) .down14 ASL PP \ Set (SS A PP) = (SS A PP) << 1 ROL A ROL SS DEX \ Decrement the shift counter BNE down14 \ Loop back until we have shifted left by X places STA QQ \ Set (SS QQ PP) = (SS A PP) ASL PP \ Shift bit 7 of PP into the C flag, so it contains bit \ 7 of the fractional part JMP down17 \ Jump to down17 to round the result to the nearest \ integer .down15 \ If we get here then we want to shift the result right \ by the number of places in A, where A is negative EOR #&FF \ Set X = -A so we can use it as a shift counter for the CLC \ number of places to shift ADC #1 TAX \ We now shift (SS QQ PP) right by X places, and because \ SS = 0 and we are going to discard the fractional part \ in PP, we only actually need to shift QQ LDA QQ \ Set A = QQ .down16 LSR A \ Set A = A >> 1 DEX \ Decrement the shift counter BNE down16 \ Loop back until we have shifted right by X places STA QQ \ Set QQ = A, so now we have shifted (SS QQ) right by \ X places, and the C flag is set to the last bit that \ we shifted out of QQ, which would be bit 7 of the \ fractional part .down17 \ We now round up the result, if bit 7 of the fractional \ part is set \ The C flag contains the next bit that would have been \ left-shifted out of PP into the result in (SS QQ), or \ right-shifted out of the result in (SS QQ), so if that \ bit is set, we need to round up the result BCC down18 \ If the next bit is clear, then jump to down18 to move \ on to scaling the y-coordinate, as we do not need to \ round up the result INC QQ \ Otherwise increment the low byte of the result in BNE down18 \ (SS QQ) to round it up, and if we did that without \ overflowing, jump to down18 to move on to scaling the \ y-coordinate INC SS \ If the increment overflowed the low byte, increment \ the high byte of the result in (SS QQ) to round it up LDA SS \ If the high byte is < &40, jump to down18 to move on CMP #&40 \ to scaling the y-coordinate BCC down18 LDA #&3F \ Otherwise set (SS QQ) = &3FFF as the highest value we STA SS \ can return as the x-coordinate LDA #&FF STA QQ \ ****************************************************************************** \ \ Name: ScaleDown (Part 4 of 4) \ Type: Subroutine \ Category: Maths \ Summary: Scale the y-coordinate \ \ ****************************************************************************** .down18 \ We now shift the y-coordinate in (RR Q P) by Y \ places in the correct direction, discarding the \ fractional part in P when we are done, but only after \ rounding (RR Q) to the nearest integer TYA \ If Y < 0, jump to down21 to shift the y-coordinate BMI down21 \ right by Y places, with A set to Y BNE down19 \ If Y > 0, jump to down19 to shift the y-coordinate \ right by Y places ASL P \ Y = 0, so shift bit 7 of P into the C flag and jump JMP down23 \ to down23 to round the result to the nearest integer, \ without shifting the y-coordinate first .down19 \ We now shift (RR Q P) left by X places LDA Q \ Set (RR A P) = (RR Q P) .down20 ASL P \ Set (RR A P) = (RR A P) << 1 ROL A ROL RR DEY \ Decrement the shift counter BNE down20 \ Loop back until we have shifted left by Y places STA Q \ Set (RR Q P) = (RR A P) ASL P \ Shift bit 7 of P into the C flag, so it contains bit \ 7 of the fractional part JMP down23 \ Jump to down23 to round the result to the nearest \ integer .down21 \ If we get here then we want to shift the result right \ by the number of places in A, where A is negative EOR #&FF \ Set Y = -A so we can use it as a shift counter for the CLC \ number of places to shift ADC #1 TAY \ We now shift (RR Q P) right by Y places, and because \ RR = 0 and we are going to discard the fractional part \ in P, we only actually need to shift Q LDA Q \ Set A = Q .down22 LSR A \ Set A = A >> 1 DEY \ Decrement the shift counter BNE down22 \ Loop back until we have shifted right by Y places STA Q \ Set Q = A, so now we have shifted (RR Q) right by \ Y places, and the C flag is set to the last bit that \ we shifted out of Q, which would be bit 7 of the \ fractional part .down23 \ We now round up the result, if bit 7 of the fractional \ part is set \ The C flag contains the next bit that would have been \ left-shifted out of PP into the result in (RR Q), or \ right-shifted out of the result in (RR Q), so if that \ bit is set, we need to round up the result BCC down24 \ If the next bit is clear, then jump to return from the \ subroutine, as we do not need to round up the result INC Q \ Otherwise increment the low byte of the result in BNE down24 \ (RR Q) to round it up, and if we did that without \ overflowing, jump to down24 to return from the \ subroutine as we now have our result INC RR \ If the increment overflowed the low byte, increment \ the high byte of the result in (RR Q) to round it up LDA RR \ If the high byte is < &40, jump to down24 to return CMP #&40 \ from the subroutine as we now have our result BCC down24 LDA #&3F \ Otherwise set (RR Q) = &3FFF as the highest value we STA RR \ can return as the y-coordinate LDA #&FF STA Q .down24 RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: DrawClippedLine (Part 1 of 6) \ Type: Subroutine \ Category: Drawing lines \ Summary: Clip a line to fit on-screen, starting with the line deltas \ Deep dive: Line buffers \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ L The point ID for the line's start point \ \ M The point ID for the line's end point \ \ ------------------------------------------------------------------------------ \ \ Other entry points: \ \ DrawClippedHorizon Set bit 1 of the line direction in V (for the horizon) \ \ ****************************************************************************** .DrawClippedHorizon LDA #%00000010 \ For the horizon, set bit 1 of A to use as the starting BNE draw1 \ value for the line direction in V (so bit 1 of V gets \ set) .DrawClippedLine LDA #0 \ Set A = 0 to use as the starting value for the line \ direction in V .draw1 STA V \ Set V = A to set the starting value for the line \ direction in V (we will set bits 6 and 7 later) LDA #0 \ Set TT = 0, which we will use to store information STA TT \ about whether the line's start point fits on screen STA UU \ Set UU = 0, which we will use to store information \ about whether the line's end point fits on screen \ The first step is to calculate the line's |x-delta| \ and |y-delta| values, updating the line direction \ information in V as we do so LDX L \ Set X to the point ID for the line's start point LDY M \ Set Y to the point ID for the line's end point LDA xPointLo,X \ Set (RR R) = the X-th entry from (xPointHi xPointLo) STA R \ LDA xPointHi,X \ i.e. the x-coordinate of the line's start point STA RR LDA yPointLo,X \ Set (SS S) = the X-th entry from (yPointHi yPointLo) STA S \ LDA yPointHi,X \ i.e. the y-coordinate of the line's start point STA SS LDA xPointLo,Y \ Set (QQ W) = the Y-th entry from (xPointHi xPointLo) STA W \ \ starting with the low byte in W SEC \ Set T = W - R SBC R \ STA T \ which is the low byte of the calculation: \ \ (I T) = (QQ W) - (RR R) LDA xPointHi,Y \ Set (QQ W) = the Y-th entry from (xPointHi xPointLo) STA QQ \ \ i.e. the x-coordinate of the line's end point SBC RR \ Set I = QQ - RR STA I \ \ so we now have: \ \ (I T) = (QQ W) - (RR R) \ = x-coordinate of the line's end point \ - x-coordinate of the line's start point \ \ so (I T) is the line's x-delta BPL draw2 \ If the high byte in I is positive, then so is (I T), \ so jump to draw2 to skip the following, as the x-delta \ is positive \ Otherwise the x-delta is negative, so we need to set \ the x-direction in V and negate x-delta so it is \ positive (as we want to calculate |x-delta|) LDA #%10000000 \ Set bit 7 of V to indicate a negative x-delta in the ORA V \ line direction in V STA V LDA #0 \ Negate (I T), starting with the low bytes SEC SBC T STA T LDA #0 \ And then negating the high bytes SBC I \ STA I \ so now (I T) is positive, and contains |x-delta| .draw2 LDA yPointLo,Y \ Set (H G) = the Y-th entry from (yPointHi yPointLo) STA G \ \ starting with the low byte in G SEC \ Set U = G - S SBC S \ STA U \ which is the low byte of the calculation: \ \ (J U) = (H G) - (SS S) LDA yPointHi,Y \ Set (H G) = the Y-th entry from (yPointHi yPointLo) STA H \ \ i.e. the y-coordinate of the line's end point SBC SS \ Set J = H - SS STA J \ \ so we now have: \ \ (J U) = (H G) - (SS S) \ = y-coordinate of the line's end point \ - y-coordinate of the line's start point \ \ so (J U) is the line's y-delta BPL draw3 \ If the high byte in J is positive, then so is (J U), \ so jump to draw3 to skip the following, as the y-delta \ is positive \ Otherwise the y-delta is negative, so we need to set \ the y-direction in V and negate y-delta so it is \ positive (as we want to calculate |y-delta|) LDA #%01000000 \ Set bit 6 of V to indicate a negative y-delta in the ORA V \ line direction in V STA V LDA #0 \ Negate (J U), starting with the low bytes SEC SBC U STA U LDA #0 \ And then negating the high bytes SBC J \ STA J \ so now (J U) is positive, and contains |y-delta| \ ****************************************************************************** \ \ Name: DrawClippedLine (Part 2 of 6) \ Type: Subroutine \ Category: Drawing lines \ Summary: Work out whether the line's start point is on-screen \ \ ****************************************************************************** .draw3 LDA #0 \ The next step is to work out where the line's \ coordinates lie in relation to the visible screen, \ i.e. are the coordinates to the right or left of the \ screen bounds, or above or below \ \ We will capture this information for a single \ coordinate in the top 4 bits of A, so we start by \ setting A = 0 so we can populate it with information \ of how the line's coordinates relate to the screen \ edges \ We start by looking at the y-coordinate for the start \ point, and will store the result in bits 4 and 5 of \ the final result in A LDX S \ Set (Y X) = (SS S) LDY SS \ = the y-coordinate of the line's start point BEQ draw4 \ If the high byte in Y is zero, jump to draw4 PHP \ Set X = 0, but without affecting the processor flags, LDX #0 \ which will still be set according to the value of the PLP \ high byte in Y CLC \ Clear the C flag BMI draw5 \ If the high byte in Y is negative, jump to draw5 DEX \ Decrement X from 0 to 255 .draw4 SEC \ Set the C flag .draw5 \ By this point we have the following, depending on the \ value of the high byte of the start point's \ y-coordinate: \ \ * If SS < 0, C = 0 and X = 0 \ * If SS = 0, C = 1 and X = S \ * If SS > 0, C = 1 and X = 255 ROR A \ Put the C flag into bit 7 of A, which will end up \ being bit 4 in the final result (as we are going to \ shift three more bits into A). Note that we flip this \ bit in the EOR below CPX #152 \ The result of this comparison depends on the value \ that we gave to X above: \ \ * If SS < 0, X = 0 and 0 < 152, so C = 0 \ * If SS = 0, X = S, so if S < 152, C = 0 \ if S >= 152, C = 1 \ * If SS > 0, X = 255 and 255 >= 152, so C = 1 ROR A \ Rotate the C flag into bit 7 of A, which will end up \ being bit 5 in the final result (as we are going to \ shift two more bits into A) EOR #%01000000 \ Flip bit 6 \ We now have the following in bits 6 and 7, which will \ be shifted down to bits 4 and 5 in the final result: \ \ * Bit 6 (4) is set if SS < 0 \ clear if SS >= 0 \ \ * Bit 7 (5) is clear if SS < 0 \ or SS = 0 and S < 152 \ set if SS = 0 and S >= 152 \ or SS > 0 \ \ The visible part of the canopy screen has its origin \ (0, 0) at the bottom-left corner of the canopy, just \ above the dashboard, and it is 19 character blocks \ high, which is 8 * 19 = 152 pixels high, so this sets \ bits 0 and 1 in the final result depending on whether \ the start y-coordinate is within this range, and if \ not, whether it is above or below the range - in other \ words, it determines whether this coordinate is \ on-screen or off-screen, as follows: \ \ Bit 6 (4) Bit 7 (5) \ Off top of screen 0 1 \ On-screen 0 0 \ Off bottom of screen 1 0 \ \ We never have both bits set \ We now repeat the above process for the start point's \ x-coordinate in (RR R), putting the result into bits 6 \ and 7 of A while shifting the above result into bits 4 \ and 5 \ \ The process is slightly different as this time the \ on-screen x-coordinate range is 4 to 155 LDX R \ Set (Y X) = (RR R) LDY RR \ = the x-coordinate of the line's start point BEQ draw6 \ If the high byte in Y is zero, jump to draw6 PHP \ Set X = 0, but without affecting the processor flags, LDX #0 \ which will still be set according to the value of the PLP \ high byte in Y BMI draw6 \ If the high byte in Y is negative, jump to draw6 DEX \ Decrement X from 0 to 255 .draw6 \ By this point we have the following, depending on the \ value of the high byte of the start point's \ x-coordinate: \ \ * If RR < 0, X = 0 \ * If RR = 0, X = R \ * If RR > 0, X = 255 CPX #4 \ The result of this comparison depends on the value \ that we gave X above: \ \ * If RR < 0, X = 0 and 0 < 4, so C = 0 \ * If RR = 0, X = R, so if R < 4, C = 0 \ if R >= 4, C = 1 \ * If RR > 0, X = 255 and 255 >= 4, so C = 1 ROR A \ Rotate the C flag into bit 7 of A, which will end up \ being bit 6 in the final result (as we are going to \ shift one more bit into A). Note that we flip this \ bit in the EOR below CPX #156 \ The result of this comparison depends on the value \ that we gave X above: \ \ * If RR < 0, X = 0 and 0 < 156, so C = 0 \ * If RR = 0, X = R, so C = 0 if R < 156, \ C = 1 if R >= 156 \ * If RR > 0, X = 255 and 255 >= 156, so C = 1 ROR A \ Rotate the C flag into bit 7 of A, which is where it \ will stay EOR #%01000000 \ Flip bit 6 \ We now have the following in bits 6 and 7: \ \ * Bit 6 is clear if RR > 0 \ or RR = 0 and R >= 4 \ i.e. if (RR R) >= 4 \ set if RR < 0 \ or RR = 0 and R < 4 \ i.e. if (RR R) < 4 \ \ * Bit 7 is clear if RR < 0 \ or RR = 0 and R < 156 \ i.e. if (RR R) < 156 \ set if RR = 0 and R >= 156 \ or RR > 0 \ i.e. if (RR R) >= 156 \ \ The visible part of the canopy screen has its origin \ (0, 0) at the bottom-left corner of the canopy, just \ above the dashboard, but the rivets and the left edge \ of the canopy take up the first four pixels, so the \ leftmost x-coordinate inside the canopy is 4 \ \ Similarly, the right edge of the canopy is four pixels \ wide, and the whole screen is 160 pixels wide, so the \ rightmost x-coordinate inside the canopy is 155 \ \ So the above sets bits 6 and 7 in the final result \ depending on whether the start x-coordinate is within \ this range, and if not, whether it is to the left or \ right of the range - in other words, it determines \ whether this coordinate is on-screen or off-screen, \ as follows: \ \ Bit 6 Bit 7 \ Off right of screen 0 1 \ On-screen 0 0 \ Off left of screen 1 0 \ \ We never have both bits set STA TT \ Store A in TT \ \ So TT contains the clipping requirements for the start \ point, in bits 4 to 7, as follows: \ \ Bit 4 Bit 5 \ Off top of screen 0 1 \ On-screen 0 0 \ Off bottom of screen 1 0 \ \ Bit 6 Bit 7 \ Off right of screen 0 1 \ On-screen 0 0 \ Off left of screen 1 0 \ ****************************************************************************** \ \ Name: DrawClippedLine (Part 3 of 6) \ Type: Subroutine \ Category: Drawing lines \ Summary: Work out whether the line's end point is on-screen \ \ ****************************************************************************** \ We now repeat the above process, but for the line's \ end point, whose y-coordinate is in (H G) and \ x-coordinate is in (QQ W). Refer to the previous part \ for a detailed description LDA #0 \ Set A = 0 so we can capture bits 4 to 7 for the end \ point LDX G \ Set (Y X) to the end point's y-coordinate in (H G) LDY H BEQ draw7 \ This section sets the following: PHP \ LDX #0 \ * If H < 0, C = 0 and X = 0 PLP \ * If H = 0, C = 1 and X = G CLC \ * If H > 0, C = 1 and X = 255 BMI draw8 DEX .draw7 SEC \ See above .draw8 ROR A \ This section sets bits 6 and 7, which will become CPX #152 \ bits 4 and 5 of the final result: ROR A \ EOR #%01000000 \ Bit 6 (4) Bit 7 (5) \ Off top of screen 0 1 \ On-screen 0 0 \ Off bottom of screen 1 0 LDX W \ Set (Y X) to the end point's x-coordinate in (QQ W) LDY QQ BEQ draw9 \ This section sets the following: PHP \ LDX #0 \ * If QQ < 0, X = 0 PLP \ * If QQ = 0, X = W BMI draw9 \ * If QQ > 0, X = 255 DEX .draw9 CPX #4 \ This section sets bits 6 and 7: ROR A \ CPX #156 \ Bit 6 Bit 7 ROR A \ Off right of screen 0 1 EOR #%01000000 \ On-screen 0 0 \ Off left of screen 1 0 STA UU \ Store A in UU \ \ So UU contains the clipping requirements for the end \ point, in bits 4 to 7, as follows: \ \ Bit 4 Bit 5 \ Off top of screen 0 1 \ On-screen 0 0 \ Off bottom of screen 1 0 \ \ Bit 6 Bit 7 \ Off right of screen 0 1 \ On-screen 0 0 \ Off left of screen 1 0 \ ****************************************************************************** \ \ Name: DrawClippedLine (Part 4 of 6) \ Type: Subroutine \ Category: Drawing lines \ Summary: Calculate the starting point and direction for our clipped vector \ line \ \ ****************************************************************************** \ In this part, the end-goal is to calculate the start \ point and direction for an on-screen vector line, \ which we can pass them to the DrawCanopyLine routine \ later on \ \ This means we want to calculate a pixel coordinate in \ (R, S) and a direction in V, by clipping the current \ start and end points to fit on-screen, if necessary LDX L \ Set X to the point ID for the line's start point LDY M \ Set Y to the point ID for the line's end point LDA zPointHi,Y \ If the z-coordinate for the line's end point is BPL draw10 \ positive, then it's in front of us, so jump to draw10 LDA V \ The end point is behind us, so flip bits 6 and 7 in V EOR #%11000000 \ to reverse the line direction in both axes STA V \ The end point is behind us, so we can't use the end \ point as our vector line's starting point, so now we \ check whether we can use the start point LDA TT \ If TT is zero then the start point is on-screen, so BEQ draw15 \ jump to draw15 to use the current values of (R, S) as \ our pixel coordinate, which works because RR and SS \ have to be zero for the start point to be on-screen, \ so (RR R) = (0 R) = R and (SS S) = (0 S) = S, so we \ can just use the low bytes as the two coordinates, \ i.e. (R, S) BNE draw12 \ TT is non-zero, so the start point is off-screen, so \ jump to draw12 to clip the start of the line so it \ fits on-screen (this BNE is effectively a JMP as A is \ never zero) .draw10 \ If we get here then the end point is in front of us LDA zPointHi,X \ If the z-coordinate for the line's start point is BPL draw11 \ positive, which is in front of us, jump to draw11 JSR SwapLinePoints \ The start point is behind us and the end point is in \ front of us, so copy the end point's coordinates and \ clipping information into the start point, so the \ start point is now in front of us LDA TT \ If TT is zero then the start point is on-screen, so BEQ draw15 \ jump to draw15 BNE draw12 \ TT is non-zero, so the start point is off-screen, so \ jump to draw12 to clip the start of the line (this BNE \ is effectively a JMP as A is never zero) .draw11 \ If we get here then both the start and end points are \ in front of us LDA TT \ If TT is non-zero then the start point is off-screen, BNE draw14 \ so jump to draw14 to potentially clip from the end of \ the line (i.e. if the end point is off-screen) LDA UU \ If UU is non-zero then the end point is off-screen, so BNE draw15 \ jump to draw13 via draw15 to clip from the end of the \ line BEQ draw21 \ Both TT and UU are zero, so both points are on-screen \ and we don't need to do any clipping, so jump to \ draw21 .draw12 \ If we get here then the one point is off-screen but \ in front of us, and the other point is behind us and \ can't be used as our vector line starting point, and \ we've set the start point to be the point that is in \ front of us JSR ClipStartOfLine \ Clip the line at the start to move the start point \ on-screen, so we can use it as our vector line's \ starting point JMP draw15 \ We now have an on-screen pixel coordinate in (R, S), \ so jump to draw15 to move on to the next stage .draw13 \ If we get here then both the start and end points are \ off-screen, but they are both in front of us, so we \ need to clip both ends of the line JSR ClipBestEndOfLine \ Clip the line at either the start or end point, \ whichever is best, so that it fits on-screen JMP draw15 \ We now have an on-screen pixel coordinate in (R, S), \ so jump to draw15 to move on to the next stage .draw14 \ If we get here then both the start and end points are \ in front of us and the start point is off-screen LDA UU \ If UU is non-zero then the end point is also BNE draw13 \ off-screen, so jump to draw13 to clip both ends of the \ line \ If we get here then the start point is off-screen but \ the end point is on-screen, so we now use the end \ point for our vector line's starting point LDA V \ Flip bits 6 and 7 in V to reverse the line direction EOR #%11000000 STA V LDA W \ Set (R, S) to the end point's coordinate in (W, G) STA R LDA G STA S \ ****************************************************************************** \ \ Name: DrawClippedLine (Part 5 of 6) \ Type: Subroutine \ Category: Drawing lines \ Summary: Calculate the deltas for our clipped vector line \ \ ****************************************************************************** .draw15 LDA #4 \ If bit 7 of V is set, so the direction of the x-delta BIT V \ is to the left, set A = 4 BMI draw16 LDA #155 \ Otherwise the direction of the x-delta is to the \ right, so set A = 155 .draw16 STA W \ Set W = 4 or 155, depending on the direction of the \ x-delta, so W is the value of the x-coordinate at the \ edge of the screen in the direction given in V LDA #0 \ If bit 6 of V is set, so the direction of the y-delta BVS draw17 \ is down, set A = 0 LDA #151 \ Otherwise the direction of the y-delta is up, so set \ A = 151 .draw17 STA G \ Set G = 0 or 151, depending on the direction of the \ y-delta, so W is the value of the y-coordinate at the \ edge of the screen in the direction given in V JMP draw19 \ Jump to draw19 .draw18 LSR I \ Right-shift (I T) ROR T LSR J \ Right-shift (J U) ROR U .draw19 \ Back in part 1 we set the following values: \ \ * (I T) is the line's |x-delta| \ * (J U) is the line's |y-delta| \ \ We now reduce these 16-bit values to 8-bit values by \ shifting them until they fit into one byte each LDA I \ If the high bytes of either of the line delta's is ORA J \ non-zero, loop up to draw18 to right-shift both deltas BNE draw18 \ We now have the following: \ \ * T = |x-delta| \ * U = |y-delta| LDA #255 \ If T <> 255, jump to draw20 CMP T BNE draw20 LSR T \ T = 255, which is too big to use as our line delta, so LSR U \ divide both T and U by 2 .draw20 CMP U \ If U <> 255, jump to draw21 BNE draw21 LSR T \ U = 255, which is too big to use as our line delta, so LSR U \ divide both T and U by 2 .draw21 INC T \ Increment T to give us our final |x-delta|, so it is \ at least 1 INC U \ Increment U to give us our final |y-delta|, so it is \ at least 1 \ ****************************************************************************** \ \ Name: DrawClippedLine (Part 6 of 6) \ Type: Subroutine \ Category: Drawing lines \ Summary: Add the clipped line to the line buffer and draw it \ \ ****************************************************************************** LDA colourCycle \ If bit 7 of colourCycle is set, i.e. %11110000, jump BMI draw23 \ down to draw23 to add a line to buffer 1 LDX lineBuffer2Count \ If lineBuffer2Count <> 95, line buffer 2 is not full, CPX #95 \ so jump down to draw22 to add a new line to the buffer BNE draw22 RTS \ Return from the subroutine .draw22 INX \ Increment the value in lineBuffer2Count as we are STX lineBuffer2Count \ about to add a new line to line buffer 2 JMP draw25 \ Jump down to draw25 to buffer the line and draw it .draw23 LDX lineBuffer1Count \ If lineBuffer1Count <> 47, line buffer 1 is not full, CPX #47 \ so jump down to draw24 to add a new line to the buffer BNE draw24 RTS \ Return from the subroutine .draw24 INX \ Increment the value in lineBuffer1Count as we are STX lineBuffer1Count \ about to add a new line to line buffer 1 .draw25 LDA R \ Save the start x-coordinate in lineBufferR STA lineBufferR,X LDA W \ Save the max/min x-coordinate in lineBufferW STA lineBufferW,X LDA S \ Save the start y-coordinate in lineBufferS STA lineBufferS,X LDA G \ Save the max/min y-coordinate in lineBufferG STA lineBufferG,X LDA T \ Save the |x-delta| in lineBufferT STA lineBufferT,X LDA U \ Save the |y-delta| in lineBufferU STA lineBufferU,X LDA V \ Save the direction in lineBufferV STA lineBufferV,X \ Fall through into DrawCanopyLine to draw the line \ ****************************************************************************** \ \ Name: DrawCanopyLine (Part 1 of 9) \ Type: Subroutine \ Category: Drawing lines \ Summary: Draw a line in the canopy view \ Deep dive: Line buffers \ \ ------------------------------------------------------------------------------ \ \ The commentary in this routine is a work in progress. \ \ The code in this routine is modified by the ModifyDrawRoutine routine, and by \ the DrawCanopyLine routine itself. \ \ The default code (i.e. the unmodified version in the source) is run when U < T \ (shallow horizontal slope), bit 6 of V is clear (y-axis up) and bit 7 is set \ (x-axis left). \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ R Start point x-coordinate \ \ S Start point y-coordinate \ \ T Relative magnitude of line's vector |x-delta| \ \ U Relative magnitude of line's vector |y-delta| \ \ V Direction of vector (T, U): \ \ * Bit 7 is the direction of the x-delta \ \ * Bit 6 is the direction of the y-delta \ \ * Bit 1 is set if this is the horizon line \ \ * Bit 0 is set if the line has been clipped \ \ Direction is like a clock, so positive (clear) is up and \ right \ \ W Max/min x-coordinate for the end of the line \ \ G Max/min y-coordinate for the end of the line \ \ xTemp1Lo The x-coordinate of the start of the line, if clipped \ \ yTemp1Lo The y-coordinate of the start of the line, if clipped \ \ ****************************************************************************** .DrawCanopyLine LDA R \ Set X = R / 4 LSR A \ LSR A \ so X is the number of the character block containing TAX \ pixel (R, S), as each character block is 4 pixels wide LDA S \ Set Y = S / 8 LSR A \ LSR A \ so Y is the number of the character row containing LSR A \ pixel (R, S), as each character row is 8 pixels high TAY CLC \ Set P = X-th byte of xLookupLo LDA xLookupLo,X \ + Y-th byte of yLookupLo ADC yLookupLo,Y \ = LO(X * 8) + LO(screen address) STA P LDA xLookupHi,X \ Set Q = X-th byte of xLookupHi ADC yLookupHi,Y \ + Y-th byte of yLookupHi STA Q \ = HI(X * 8) + HI(screen address) \ So (Q P) is the screen address of the pixel row \ containing pixel (R, S), out by 8 bytes for each row \ above or below the top of the dashboard LDA U \ If U < T, the line is a shallow horizontal slope, so CMP T \ jump down to dlin1 to draw the line BCC dlin1 JMP dlin41 \ Otherwise U >= T and the line is a steep vertical \ slope, so jump down to part 4 to draw the line \ ****************************************************************************** \ \ Name: DrawCanopyLine (Part 2 of 9) \ Type: Subroutine \ Category: Drawing lines \ Summary: Modify the line drawing routine for a shallow horizontal slope \ \ ------------------------------------------------------------------------------ \ \ The code in this routine is modified by the ModifyDrawRoutine routine, and by \ the DrawCanopyLine routine itself. \ \ The default code (i.e. the unmodified version in the source) is run when: \ \ * Bit 7 of V is clear, so we step along the x-axis in a positive direction, \ i.e. to the right \ \ * Bit 6 of V is set, so we step along the y-axis in a negative direction, \ i.e. down the screen \ \ ****************************************************************************** .dlin1 \ If we get here then the line is a shallow horizontal \ slope BIT V \ If bit 7 of V is set, jump to dlin3 to step along the BMI dlin3 \ x-axis in a negative direction, i.e. to the left \ If we get here then bit 7 of V is clear, so we step \ along the x-axis in a positive direction, i.e. to the \ right, which we implement by modifying the routine's \ code, setting it back to the default LDA #5 \ Modify the following instruction at dlin20: STA dlin20+1 \ \ BCC dlin22 -> BCC dlin22 \ \ i.e. set it back to the default LDA #9 \ Modify the following instruction at dlin21: STA dlin21+1 \ \ BNE dlin25 -> BNE dlin25 \ \ i.e. set it back to the default .dlin2 LDA #LO(colour1L2R) \ Modify the following instruction at dlin33: STA dlin33+1 \ \ LDA colour1L2R,X -> LDA colour1L2R,X \ \ Note that this is a two-layer modification, as the \ LDA #LO(colour1L2R) instruction gets modified by the \ ModifyDrawRoutine routine as follows: \ \ * LO(colour1L2R) when colourLogic = %01000000 \ so the bit pattern lookup table uses colour 1 \ i.e. LDA colour1L2R,X \ \ * LO(colour2L2R) when colourLogic = %01000000 \ so the bit pattern lookup table uses colour 2 \ i.e. LDA colour2L2R,X \ \ * LO(colour1Row) when colourLogic = %00000000 \ and colourCycle = %00001111 \ so the bit pattern lookup is always %00001111 \ i.e. LDA colour1Row,X \ \ * LO(colour2Row) when colourLogic = %00000000 \ and colourCycle = %11110000 \ so the bit pattern lookup is always %11110000 \ i.e. LDA colour2Row,X \ \ In other words, this instruction has already been \ modified to implement the current colour cycle LDA #39 \ Set I = 39, the number of the last character block STA I \ on the row BNE dlin5 \ Jump to dlin5 to skip the code modifications for the \ other value of bit 7 (this BNE is effectively a JMP as \ A is never zero) .dlin3 \ If we get here then bit 7 of V is set, so we step \ along the x-axis in a negative direction, i.e. to the \ left, which we implement by modifying the routine's \ code LDA #&24 \ Modify the following instruction at dlin20: STA dlin20+1 \ \ BCC dlin22 -> BCC dlin27 LDA #&28 \ Modify the following instruction at dlin21: STA dlin21+1 \ \ BNE dlin25 -> BNE dlin30 .dlin4 \ Modify the following instruction at dlin33: LDA #LO(colour1R2L) \ STA dlin33+1 \ LDA colour1L2R,X -> LDA colour1R2L,X \ \ Note that this is a two-layer modification, as the \ LDA #LO(colour1R2L) instruction gets modified by the \ ModifyDrawRoutine routine as follows: \ * LO(colour1R2L) when colourLogic = %01000000 \ so the bit pattern lookup table uses colour 1 \ i.e. LDA colour1R2L,X \ \ * LO(colour2R2L) when colourLogic = %01000000 \ so the bit pattern lookup table uses colour 2 \ i.e. LDA colour2R2L,X \ \ * LO(colour1Row) when colourLogic = %00000000 \ and colourCycle = %00001111 \ so the bit pattern lookup is always %00001111 \ i.e. LDA colour1Row,X \ \ * LO(colour2Row) when colourLogic = %00000000 \ and colourCycle = %11110000 \ so the bit pattern lookup is always %11110000 \ i.e. LDA colour2Row,X \ \ In other words, this instruction has already been \ modified to implement the current colour cycle LDA #0 \ Set I = 0, the number of the first character block STA I \ on the row .dlin5 BIT V \ If bit 6 of V is set, jump to dlin6 to step along the BVS dlin6 \ y-axis in a negative direction, i.e. down the screen \ If we get here then bit 6 of V is clear, so we step \ along the y-axis in a positive direction, i.e. up the \ screen, which we implement by modifying the routine's \ code LDA #&98 \ Modify the following instruction at dlin35: STA dlin35 \ \ INY -> TYA LDA #&88 \ Modify the following instruction at dlin36: STA dlin36 \ \ TYA -> DEY LDA #&C8 \ Modify the following instruction at dlin37: STA dlin37+1 \ \ ADC #&38 -> ADC #&C8 LDA #&FE \ Modify the following instruction at dlin38: STA dlin38+1 \ \ ADC #1 -> ADC #&FE LDA #158 \ Set J = 158 - G SEC \ SBC G \ So J contains the y-coordinate of the end of the line, STA J \ flipped to match screen memory (so higher values of \ the y-coordinate are lower down the screen) JMP dlin7 \ Jump to dlin7 to skip the code modifications for the \ other value of bit 6 .dlin6 \ If we get here then bit 6 of V is set, so we step \ along the y-axis in a negative direction, i.e. down \ the screen, which we implement by modifying the \ routine's code, setting it back to the default LDA #&C8 \ Modify the following instruction at dlin35: STA dlin35 \ \ INY -> INY \ \ i.e. set it back to the default LDA #&98 \ Modify the following instruction at dlin36: STA dlin36 \ \ TYA -> TYA \ \ i.e. set it back to the default LDA #&38 \ Modify the following instruction at dlin37: STA dlin37+1 \ \ ADC #&38 -> ADC #&38 \ \ i.e. set it back to the default LDA #1 \ Modify the following instruction at dlin38: STA dlin38+1 \ \ ADC #1 -> ADC #1 \ \ i.e. set it back to the default LDA #160 \ Set J = 160 - G SEC \ SBC G \ So J contains the y-coordinate of the end of the line, STA J \ flipped to match screen memory (so higher values of \ the y-coordinate are lower down the screen) \ ****************************************************************************** \ \ Name: DrawCanopyLine (Part 3 of 9) \ Type: Subroutine \ Category: Drawing lines \ Summary: Draw a line as a shallow horizontal slope \ \ ****************************************************************************** .dlin7 \ By the time we get here, the code has been modified to \ work with the step directions given in bits 6 and 7 of \ V, as well as the current colour cycle \ \ To keep things simple, we will only document the \ default code, which is for a shallow horizontal slope \ with the following: \ \ * Bit 7 of V is clear, so we step along the x-axis \ in a positive direction, i.e. to the right \ \ * Bit 6 of V is set, so we step along the y-axis in \ a negative direction, i.e. down the screen \ \ * The current colour cycle is drawing in colour 1, \ using the pixel bitmaps at colour1L2R \ \ By this point, we also have the following variables \ set: \ \ * (Q P) is the screen address of the pixel row \ containing pixel (R, S), out by 8 bytes for each \ row above or below the top of the dashboard \ \ * I = 39, the number of the last character block on \ a screen row, heading left to right \ \ * J = 160 - G, the y-coordinate of the end of the \ line, flipped around so it increases as we go down \ the screen \ \ The last two have different values with different line \ directions, but these are the values for the default \ case that we're considering here LDA #159 \ Set Y = 159 - S SEC \ SBC S \ This gets added to the screen address in (Q P) that we TAY \ set above, to give the screen address of the starting \ point at coordinate (R, S) LDA #255 \ Set RR = 255 - T SEC \ = 255 - |x-delta| SBC T STA RR CLC \ Set SS = RR ADC #1 \ = 255 - T + 1 STA SS \ \ This is the starting value for the slope error LDA V \ If bits 0 and 1 of V are both clear, then this is not AND #%00000011 \ the horizon line or a clipped line, so jump to dlin8 BEQ dlin8 \ to skip the following LDA U \ If U < 2, jump to dlin8 to skip the following CMP #2 BCC dlin8 \ If we get here then U >= 2, and this is either the \ horizon line or the line has been clipped LDA #255 \ Set SS = 255 STA SS .dlin8 LDA R \ Set X = bits 0 and 1 of R, so X is the pixel number AND #%00000011 \ in the character row for pixel (R, S), in the range TAX \ 0 to 3 \ We are going to use QQ to keep track of the current \ character block number as we work our way along the \ line, drawing as we go, so we need to initialise it to \ the character block number of the starting point in \ (R, S) LDA R \ Set QQ = R / 4 LSR A \ LSR A \ so QQ is the number of the character block containing STA QQ \ pixel (R, S), as each character block is 4 pixels wide LDA SS \ Set A = SS, so it contains the current slope error BIT V \ If bit 7 of V is set, then we are stepping along the BMI dlin10 \ x-axis in a negative direction, i.e. to the left, so \ jump to dlin10 CPX #1 \ If X < 1 (i.e. X = 0), jump to dlin13 BCC dlin13 BNE dlin9 \ If X <> 1 (i.e. X = 2 or 3), jump to dlin9 CLC \ If we get here then X = 1, so clear the C flag and BCC dlin14 \ jump to dlin14 (this BCC is effectively a JMP as we \ know the C flag is clear) .dlin9 \ If we get here then X = 2 or 3 CPX #3 \ If X < 3 (i.e. X = 2), jump to dlin16 BCC dlin16 CLC \ If we get here then X = 3, so clear the C flag and BCC dlin18 \ jump to dlin18 (this BCC is effectively a JMP as we \ know the C flag is clear) .dlin10 \ If we get here then bit 7 of V is set, so we are \ stepping along the x-axis in a negative direction, \ i.e. to the left CPX #1 \ If X < 1 (i.e. X = 0), jump to dlin18 BCC dlin18 BNE dlin11 \ If X <> 1 (i.e. X = 2 or 3), jump to dlin11 CLC \ If we get here then X = 1, so clear the C flag and BCC dlin16 \ jump to dlin16 (this BCC is effectively a JMP as we \ know the C flag is clear) .dlin11 CPX #3 \ If X < 3 (i.e. X = 2), jump to dlin14 BCC dlin14 CLC \ If we get here then X = 3, so clear the C flag and BCC dlin13 \ jump to dlin13 (this BCC is effectively a JMP as we \ know the C flag is clear) \ ****************************************************************************** \ \ Name: DrawCanopyLine (Part 4 of 9) \ Type: Subroutine \ Category: Drawing lines \ Summary: Draw a part of the line, working down the screen \ \ ------------------------------------------------------------------------------ \ \ The code in this routine is modified by the ModifyDrawRoutine, and by the \ DrawCanopyLine routine itself. \ \ The default code (i.e. the unmodified version in the source) is run when: \ \ * The current colour cycle is drawing in colour 1, using the pixel bitmaps \ at colour1L2R and OR logic for updating the screen \ \ ****************************************************************************** .dlin12 \ This forms the start of a loop that works its way \ along the line. We only jump to this part from the end \ of the loop, which is joined at different points from \ part 3 above \ \ The loop is joined with A already set to SS, which \ keeps track of the slope error as we step along the \ x-axis one pixel at a time \ \ So SS keeps track of the y-axis position by adding \ the y-delta in U, and when adding U to A overflows \ because the slope error has reached a whole number \ (i.e. 256), we move down to the next a pixel row \ \ The following commentary assumes we are moving along \ the x-axis from left to right, and down the screen. \ The code is modified in-place for drawing in the \ other directions, which is note in the commentary \ \ In the following, X is used to store the offset of the \ pixel byte we are going to draw, from the colour1L2R \ table (this is modified to cater for different colour \ cycles, but for simplicity we just consider the case \ of drawing from left to right in colour 1) CLC \ Clear the C flag to reset the carry for the additions \ below LDA SS \ Set A = SS, so it contains the current slope error .dlin13 LDX #0 \ Set X = 0 to point to pixel 0, which is the pixel \ byte 1000 when drawing from left to right ADC U \ Set A = A + U, to add the y-delta to the slope error BCC dlin15 \ If the addition didn't overflow, jump to dlin15 to \ increment X to move to the second pixel along, \ which is the pixel byte 1100 when drawing from left \ to right JSR dlin32 \ Otherwise the slope error just overflowed, so we need \ to move down a pixel row, so call dlin32 to draw the \ pixel byte pointed to by X and move down to the next \ character row .dlin14 \ If we get here then we either just draw the first \ pixel in the pixel byte, i.e. 1000, and moved up or \ down a pixel row, or we just jumped here from part 3 \ \ In both cases, we want to start drawing the next pixel \ byte from the second pixel along, i.e. 0100 LDX #3 \ Set X = 3 so the next instruction increments X to 4, \ which is the pixel byte 0100 when drawing from left \ to right .dlin15 INX \ Increment X to set the next pixel in the pixel byte \ (i.e. step through the colour1L2R table) ADC U \ Set A = A + U, to add the y-delta to the slope error BCC dlin17 \ If the addition didn't overflow, jump to dlin17 to \ increment X to move to the third pixel along, \ which is the pixel byte 1110 when drawing from left \ to right JSR dlin32 \ Otherwise the slope error just overflowed, so we need \ to move down a pixel row, so call dlin32 to draw the \ pixel byte pointed to by X and move down to the next \ character row .dlin16 \ If we get here then we either just draw the second \ pixel in the pixel byte, i.e. 0100 or 1100, and moved \ down a pixel row, or we just jumped here from part 3 \ \ In both cases, we want to start drawing the next pixel \ byte from the second pixel along, i.e. 0010 LDX #6 \ Set X = 6 so the next instruction increments X to 7, \ which is the pixel byte 0010 when drawing from left \ to right .dlin17 INX \ Increment X to set the next pixel in the pixel byte \ (i.e. step through the colour1L2R table) ADC U \ Set A = A + U, to add the y-delta to the slope error BCC dlin19 \ If the addition didn't overflow, jump to dlin19 to \ increment X to move to the fourth pixel along, \ which is the pixel byte 1111 when drawing from left \ to right JSR dlin32 \ Otherwise the slope error just overflowed, so we need \ to move down a pixel row, so call dlin32 to draw the \ pixel byte pointed to by X and move down to the next \ character row .dlin18 \ If we get here then we either just draw the third \ pixel in the pixel byte, i.e. 0010 or 0110 or 1110, \ and moved down a pixel row, or we just jumped here \ from part 3 \ \ In both cases, we want to start drawing the next pixel \ byte from the second pixel along, i.e. 0001 LDX #8 \ Set X = 8 so the next instruction increments X to 9, \ which is the pixel byte 0001 when drawing from left \ to right .dlin19 INX \ Increment X to set the next pixel in the pixel byte \ (i.e. step through the colour1L2R table) ADC U \ Set A = A + U, to add the y-delta to the slope error .dlin20 BCC dlin22 \ If the addition didn't overflow, jump to dlin22 to \ draw the pixel byte but without going down a row, as \ we have reached the end of the pixel byte and need to \ draw it before moving on the next pixel byte to the \ right \ \ Gets modified by the DrawCanopyLine routine: \ \ * BCC dlin22 when bit 7 of V is clear \ \ * BCC dlin27 when bit 7 of V is set \ \ In other words, this instruction has already been \ modified to implement the current drawing direction JSR dlin32 \ Otherwise the slope error just overflowed, so we need \ to move down a pixel row, so call dlin32 to draw the \ pixel byte pointed to by X and move down to the next \ character row .dlin21 BNE dlin25 \ Jump to dlin25 to move on to the next character block, \ as we just moved down a pixel row after drawing the \ fourth pixel in the pixel byte, so we need to move on \ to the next pixel byte along \ \ The dlin32 routine ends with a BEQ just before the RTS \ so this BNE is effectively a JMP, as we had to pass \ through the BEQ in dlin32 to return from the above \ call \ \ Gets modified by the DrawCanopyLine routine: \ \ * BNE dlin25 when bit 7 of V is clear \ \ * BNE dlin30 when bit 7 of V is set \ \ In other words, this instruction has already been \ modified to implement the current drawing direction .dlin22 \ If we get here then we need to draw the current pixel \ byte as pointed to by X, and then move to the next \ character block along, without going down a row STA SS \ Set SS = A to store the updated slope error in SS .dlin23 LDA colour1L2R,X \ Fetch the X-th pixel byte from colour1L2R \ \ Gets modified by the ModifyDrawRoutine routine: \ \ * LDA colour1L2R,X when colourLogic = %10000000 \ \ * LDA colour2L2R,X when colourLogic = %01000000 \ \ * LDA colour1Row,X when colourLogic = %00000000 \ and colourCycle = %00001111 \ \ * LDA colour2Row,X when colourLogic = %00000000 \ and colourCycle = %11110000 \ \ In other words, this instruction has already been \ modified to implement the current colour cycle .dlin24 ORA (P),Y \ OR the pixel byte with the current screen contents \ \ Gets modified by the ModifyDrawRoutine routine: \ \ * ORA (P),Y when colourLogic = %01000000 \ \ * AND (P),Y when colourLogic = %00000000 \ \ In other words, this instruction has already been \ modified to implement the current drawing logic STA (P),Y \ Update the Y-th byte of (Q P) with the result, which \ sets 4 pixels to the pixel pattern in A .dlin25 LDA P \ Set (Q P) = (Q P) + 8 CLC \ ADC #8 \ starting with the low bytes STA P BCC dlin26 \ And then the high bytes, so (Q P) now points to the INC Q \ next character block to the right .dlin26 INC QQ \ Increment QQ, which contains the current character \ block number LDA QQ \ If QQ <> I, then we haven't yet reached the right edge CMP I \ of the screen (whose character block number we set in BNE dlin12 \ I in part 2), so jump back to dlin12 to keep drawing \ the line JMP dlin65 \ Otherwise we have reached the edge of the screen, so \ jump to dlin65 to process the clipped part of the \ line, if applicable \ ****************************************************************************** \ \ Name: DrawCanopyLine (Part 5 of 9) \ Type: Subroutine \ Category: Drawing lines \ Summary: Draw a part of the line, working up the screen \ \ ------------------------------------------------------------------------------ \ \ The code in this routine is modified by the ModifyDrawRoutine, and by the \ DrawCanopyLine routine itself. \ \ The default code (i.e. the unmodified version in the source) is run when: \ \ * The current colour cycle is drawing in colour 1, using the pixel bitmaps \ at colour1L2R and OR logic for updating the screen \ \ ****************************************************************************** .dlin27 \ This routine draws a part of a line (one pixel row, \ i.e. a byte) and moves us up to the previous pixel row \ \ We call this subroutine with: \ \ * A = the current slope error \ \ * X = the index of the pixel byte at colour1R2L STA SS \ Set SS = A .dlin28 LDA colour1R2L,X \ Fetch the X-th pixel byte from colour1R2L \ \ Gets modified by the ModifyDrawRoutine routine: \ \ * LDA colour1R2L,X when colourLogic = %10000000 \ \ * LDA colour2R2L,X when colourLogic = %01000000 \ \ * LDA colour1Row,X when colourLogic = %00000000 \ and colourCycle = %00001111 \ \ * LDA colour2Row,X when colourLogic = %00000000 \ and colourCycle = %11110000 \ \ In other words, this instruction has already been \ modified to implement the current colour cycle .dlin29 ORA (P),Y \ OR the pixel byte with the current screen contents \ \ Gets modified by the ModifyDrawRoutine routine: \ \ * ORA (P),Y when colourLogic = %01000000 \ \ * AND (P),Y when colourLogic = %00000000 \ \ In other words, this instruction has already been \ modified to implement the current drawing logic STA (P),Y \ Update the Y-th byte of (Q P) with the result, which \ sets 4 pixels to the pixel pattern in A .dlin30 LDA P \ Set (Q P) = (Q P) - 8 SEC \ SBC #8 \ starting with the low bytes STA P BCS dlin31 \ And then the high bytes, so (Q P) now points to the DEC Q \ previous character block to the left .dlin31 DEC QQ \ Decrement QQ, which contains the current character \ block number LDA QQ \ If QQ <> I, then we haven't yet reached the right edge CMP I \ of the screen (whose character block number we set in BNE dlin12 \ I in part 2), so jump back to dlin12 to keep drawing \ the line JMP dlin65 \ Otherwise we have reached the edge of the screen, so \ jump to dlin65 to process the clipped part of the \ line, if applicable \ ****************************************************************************** \ \ Name: DrawCanopyLine (Part 6 of 9) \ Type: Subroutine \ Category: Drawing lines \ Summary: Draw a part of the line, working along the screen \ \ ------------------------------------------------------------------------------ \ \ The code in this routine is modified by the ModifyDrawRoutine, and by the \ DrawCanopyLine routine itself. \ \ The default code (i.e. the unmodified version in the source) is run when: \ \ * The current colour cycle is drawing in colour 1, using the pixel bitmaps \ at colour1L2R and OR logic for updating the screen \ \ * Bit 6 of V is set, so we step along the y-axis in a negative direction, \ i.e. down the screen \ \ ****************************************************************************** .dlin32 \ This routine draws a part of a line (one pixel row, \ i.e. a byte) and moves us down to the next character \ row (though it may be modified to move up a row for \ lines that are being drawn in that direction) \ \ We call this subroutine with: \ \ * A = the current slope error \ \ * X = the index of the pixel byte at colour1L2R \ \ * The C flag is always set ADC RR \ Set SS = A + RR + C STA SS \ = slope error + (255 - |x-delta|) + 1 \ = slope error + 256 - |x-delta| \ = slope error - |x-delta| .dlin33 LDA colour1L2R,X \ Fetch the X-th pixel byte from colour1L2R \ \ Gets modified by the DrawCanopyLine routine, which in \ turn gets modified by the ModifyDrawRoutine routine: \ \ * colour1L2R when colourLogic = %01000000 \ \ * colour2L2R when colourLogic = %01000000 \ \ * colour1Row when colourLogic = %00000000 \ and colourCycle = %00001111 \ \ * colour2Row when colourLogic = %00000000 \ and colourCycle = %11110000 \ \ In other words, this instruction has already been \ modified to implement the current colour cycle .dlin34 ORA (P),Y \ OR the pixel byte with the current screen contents \ \ Gets modified by the ModifyDrawRoutine routine: \ \ * ORA (P),Y when colourLogic = %01000000 \ \ * AND (P),Y when colourLogic = %00000000 \ \ In other words, this instruction has already been \ modified to implement the current drawing logic STA (P),Y \ Update the Y-th byte of (Q P) with the result, which \ sets 4 pixels to the pixel pattern in A .dlin35 INY \ Increment Y to point to the next screen byte, i.e. the \ offset of the next row in the character block \ \ Gets modified by the DrawCanopyLine routine: \ \ * INY when bit 6 of V is set \ \ * TYA when bit 6 of V is clear \ \ In other words, this instruction has already been \ modified to implement the current drawing direction .dlin36 TYA \ Set A to the offset of the next row in the character \ block \ \ Gets modified by the DrawCanopyLine routine: \ \ * TYA when bit 6 of V is set \ \ * DEY when bit 6 of V is clear \ \ In other words, this instruction has already been \ modified to implement the current drawing direction AND #7 \ If A mod 7 <> 0 then we haven't reached the end of the BNE dlin39 \ 8-row character block, so jump to dlin39 to skip the \ following \ Otherwise we have reached the last row of the \ character block, so we now add &138 to (Q P) to move \ to the address of the start of the character block \ in the row below (as each character row in mode 5 \ contains &140 bytes, so this is &140 - 8 to cater for \ the block we just finished) LDA P \ We start by adding &38 to the low byte in P CLC .dlin37 ADC #&38 \ Add &38 to the low byte \ \ Gets modified by the DrawCanopyLine routine: \ \ * ADC #&38 when bit 6 of V is set \ \ * ADC #&E8 when bit 6 of V is clear \ \ In other words, this instruction has already been \ modified to implement the current drawing direction STA P \ Store the updated low byte in P LDA Q \ Then add the high bytes .dlin38 ADC #1 \ Add &1 to the high byte \ \ Gets modified by the DrawCanopyLine routine: \ \ * ADC #1 when bit 6 of V is set \ \ * ADC #&FE when bit 6 of V is clear \ \ In other words, this instruction has already been \ modified to implement the current drawing direction STA Q \ Which we store in Q, so now we have: \ \ (Q P) = (Q P) + &138 \ \ so (Q P) is the address of the start of the character \ block in the row below .dlin39 LDA SS \ Set A = SS, so it contains the current slope error CPY J \ If the current y-coordinate in Y = J, then we have CLC \ reached the y-coordinate of the end of the line (which BEQ dlin40 \ we set in part 2), so jump to dlin40 to stop drawing \ the line and move on to the clipped part of the line RTS \ Return from the subroutine with the C flag clear .dlin40 TSX \ Remove two bytes from the top of the stack, so the INX \ next RTS returns us to caller of the DrawCanopyLine INX \ routine rather than the caller of dlin32 (so this TXS \ effectively breaks out of the current line-drawing \ loop) JMP dlin65 \ Jump to dlin65 to process the clipped part of the \ line, if applicable \ ****************************************************************************** \ \ Name: DrawCanopyLine (Part 7 of 9) \ Type: Subroutine \ Category: Drawing lines \ Summary: Modify the line drawing routine for a steep vertical slope \ \ ------------------------------------------------------------------------------ \ \ The code in this routine is modified by the ModifyDrawRoutine routine, and by \ the DrawCanopyLine routine itself. \ \ The default code (i.e. the unmodified version in the source) is run when: \ \ * Bit 7 of V is clear, so we step along the x-axis in a positive direction, \ i.e. to the right \ \ * Bit 6 of V is clear, so we step along the y-axis in a positive direction, \ i.e. up the screen \ \ ****************************************************************************** .dlin41 \ If we get here then the line is a steep vertical slope BIT V \ If bit 6 of V is set, jump to dlin42 to step along the BVS dlin42 \ y-axis in a negative direction, i.e. down the screen \ If we get here then bit 6 of V is clear, so we step \ along the y-axis in a positive direction, i.e. up the \ screen, which we implement by modifying the routine's \ code LDA #&98 \ Modify the following instruction at dlin53: STA dlin53 \ \ TYA -> TYA \ \ i.e. set it back to the default LDA #&88 \ Modify the following instruction at dlin54: STA dlin54 \ \ DEY -> DEY \ \ i.e. set it back to the default LDA #&C8 \ Modify the following instruction at dlin37: STA dlin55+1 \ \ ADC #&C8 -> ADC #&C8 \ \ i.e. set it back to the default LDA #&FE \ Modify the following instruction at dlin38: STA dlin56+1 \ \ ADC #&FE -> ADC #&FE \ \ i.e. set it back to the default LDA #7 \ Set J = 7 STA J \ \ So J contains the y-coordinate of the bottom of the \ screen, flipped to match screen memory (so higher \ values of the y-coordinate are lower down the screen) BNE dlin43 \ Jump to dlin43 to skip the code modifications for the \ other value of bit 6 (this BNE is effectively a JMP as \ A is never zero) .dlin42 \ If we get here then bit 6 of V is set, so we step \ along the y-axis in a negative direction, i.e. down \ the screen, which we implement by modifying the \ routine's code, setting it back to the default LDA #&C8 \ Modify the following instruction at dlin53: STA dlin53 \ \ TYA -> INY LDA #&98 \ Modify the following instruction at dlin54: STA dlin54 \ \ DEY -> TYA LDA #&38 \ Modify the following instruction at dlin37: STA dlin55+1 \ \ ADC #&C8 -> ADC #&38 LDA #1 \ Modify the following instruction at dlin38: STA dlin56+1 \ \ ADC #&FE -> ADC #1 LDA #160 \ Set J = 160 STA J \ \ So J contains the y-coordinate of the end of the line, \ flipped to match screen memory (so higher values of \ the y-coordinate are lower down the screen) .dlin43 BIT V \ If bit 7 of V is set, jump to dlin44 to step along the BMI dlin44 \ x-axis in a negative direction, i.e. to the left \ If we get here then bit 7 of V is clear, so we step \ along the x-axis in a positive direction, i.e. to the \ right, which we implement by modifying the routine's \ code, setting it back to the default LDA #&1D \ Modify the following instruction at dlin52: STA dlin52+1 \ \ BCS dlin57 -> BCS dlin57 \ \ i.e. set it back to the default LDA W \ Set I = W + 1 CLC ADC #1 STA I JMP dlin45 \ Jump to dlin45 to skip the code modifications for the \ other value of bit 7 .dlin44 \ If we get here then bit 7 of V is set, so we step \ along the x-axis in a negative direction, i.e. to the \ left, which we implement by modifying the routine's \ code LDA #&3F \ Modify the following instruction at dlin52: STA dlin52+1 \ \ BCS dlin57 -> BCS dlin61 LDA W \ Set I = W - 1 SEC SBC #1 STA I \ ****************************************************************************** \ \ Name: DrawCanopyLine (Part 8 of 9) \ Type: Subroutine \ Category: Drawing lines \ Summary: Draw a line as a steep vertical slope \ \ ****************************************************************************** .dlin45 \ By the time we get here, the code has been modified to \ work with the step directions given in bits 6 and 7 of \ V \ \ To keep things simple, we will only document the \ default code, which is for a steep vertical slope with \ the following: \ \ * Bit 7 of V is clear, so we step along the x-axis \ in a positive direction, i.e. to the right \ \ * Bit 6 of V is clear, so we step along the y-axis \ in a positive direction, i.e. up the screen \ \ By this point, we also have the following variables \ set: \ \ * (Q P) is the screen address of the pixel row \ containing pixel (R, S), out by 8 bytes for each \ row above or below the top of the dashboard \ \ * I = W + 1, the x-coordinate for the end of the \ line + 1 \ \ * J = 7, the y-coordinate of the end of the line \ \ The last two have different values with different line \ directions, but these are the values for the default \ case that we're considering here LDA #159 \ Set Y = 159 - S SEC \ SBC S \ This gets added to the screen address in (Q P) that we TAY \ set above, to give the screen address of the starting \ point at coordinate (R, S) LDA #255 \ Set RR = 255 - U SEC \ = 255 - |y-delta| SBC U STA RR CLC \ Set SS = RR + 1 ADC #1 \ = 255 - U + 1 STA SS \ \ This is the starting value for the slope error LDA V \ If bits 0 and 1 of V are both clear, jump to dlin46 AND #%00000011 BEQ dlin46 LDA T \ If T < 2, jump to dlin46 CMP #2 BCC dlin46 LDA #255 \ Set SS = 255 for the starting slope error STA SS .dlin46 LDA R \ Set X = bits 0 and 1 of R, so X is the pixel number AND #%00000011 \ in the character row for pixel (R, S) TAX .dlin47 LDA #%00001000 \ Set A to a pixel byte with pixel 0 set to colour 1 \ \ Gets modified by the ModifyDrawRoutine routine: \ \ * LDA #8 when colourLogic = %10000000 \ \ * LDA #&80 when colourLogic = %01000000 \ \ In other words, this instruction has already been \ modified to implement the current colour logic CPX #0 \ If X = 0, then the pixel number for point (R, S) is 0, BEQ dlin49 \ so skip the following loop as the pixel is already in \ the right place \ Otherwise we right-shift A by X places to move the \ pixel to the right place .dlin48 LSR A \ Shift A to the right by one place DEX \ Decrement the shift counter BNE dlin48 \ Loop back until we have shifted A right by X places, \ so the pixel is now in the right place for point \ (R, S) within this pixel row .dlin49 STA H \ Store the pixel byte in H, which contains a single \ pixel in the correct colour in position X CLC \ Clear the C flag for the addition below LDX R \ Set X = R, so it contains the x-coordinate of the \ start of the line .dlin50 LDA H \ Set A to the pixel byte for point (R, S) within this \ pixel row \ \ Gets modified by the ModifyDrawRoutine routine: \ \ * LDA H when colourLogic = %01000000 \ or %10000000 \ \ * LDA #%00001111 when colourLogic = %00000000 \ and colourCycle = %00001111 \ \ * LDA #%11110000 when colourLogic = %00000000 \ and colourCycle = %11110000 \ \ In other words, this instruction has already been \ modified to implement the current colour cycle .dlin51 ORA (P),Y \ OR the pixel byte with the current screen contents \ \ Gets modified by the ModifyDrawRoutine routine: \ \ * ORA (P),Y when colourLogic = %01000000 \ \ * AND (P),Y when colourLogic = %00000000 \ \ In other words, this instruction has already been \ modified to implement the current drawing logic STA (P),Y \ Update the Y-th byte of (Q P) with the result, which \ sets 4 pixels to the pixel pattern in A, so this sets \ the pixel at screen coordinate (R, S) to the current \ colour LDA SS \ Set A = SS + T ADC T \ = slope error + |x-delta| \ \ so this updates the slope error as we move up one \ the screen one pixel at a time .dlin52 BCS dlin57 \ If the above addition overflowed, then the slope error \ just overflowed, so jump to dlin57 to move along the \ x-axis \ \ Gets modified by the DrawCanopyLine routine: \ \ * BCC dlin57 when bit 7 of V is clear \ \ * BCC dlin61 when bit 7 of V is set \ \ In other words, this instruction has already been \ modified to implement the current drawing direction STA SS \ Set SS = A to store the updated slope error in SS .dlin53 TYA \ Store the current y-coordinate in A \ \ Gets modified by the DrawCanopyLine routine: \ \ * TYA when bit 6 of V is clear \ \ * INY when bit 6 of V is set \ \ In other words, this instruction has already been \ modified to implement the current drawing direction .dlin54 DEY \ Decrement Y to move up a pixel row \ \ Gets modified by the DrawCanopyLine routine: \ \ * DEY when bit 6 of V is clear \ \ * TYA when bit 6 of V is set \ \ In other words, this instruction has already been \ modified to implement the current drawing direction AND #7 \ If A mod 7 <> 0 then we haven't reached the top of the BNE dlin50 \ 8-row character block, so loop back to dlin50 to draw \ the next row \ Otherwise we have reached the top row of the character \ block, so we now subtract &138 from (Q P) to move to \ the address of the bottom of the character block in \ the row above (as each character row in mode 5 \ contains &140 bytes, so this is &140 - 8 to cater for \ the block we just finished) \ \ Subtracting &138 is the same as adding &FEC8, so \ that's what we do now LDA P \ We start by adding &C8 to the low byte in P CLC .dlin55 ADC #&C8 \ Add &C8 to the low byte \ \ Gets modified by the DrawCanopyLine routine: \ \ * ADC #&C8 when bit 6 of V is clear \ \ * ADC #&38 when bit 6 of V is set \ \ In other words, this instruction has already been \ modified to implement the current drawing direction STA P \ Store the updated low byte in P LDA Q \ Then add the high bytes .dlin56 ADC #&FE \ Add &FE to the high byte \ \ Gets modified by the DrawCanopyLine routine: \ \ * ADC #&FE when bit 6 of V is clear \ \ * ADC #1 when bit 6 of V is set \ \ In other words, this instruction has already been \ modified to implement the current drawing direction STA Q \ Which we store in Q, so now we have: \ \ (Q P) = (Q P) + &FEC8 \ = (Q P) - &138 \ \ so (Q P) is the address of the end of the character \ block in the row above CPY J \ If the current y-coordinate in Y <> J, then we have CLC \ not yet reached the y-coordinate of the end of the BNE dlin50 \ line (which we set in part 7), so loop back to dlin50 \ to keep drawing the line JMP dlin65 \ Otherwise we have reached the y-coordinate of the end \ of the line, so jump to dlin65 to process the clipped \ part of the line, if applicable .dlin57 \ If we get here then the slope error just overflowed, \ and we are drawing up the screen (bit 7 of V is clear) \ \ We reached with a BCS, so we know the C flag is set \ \ We now need to move the line along the x-axis to the \ right by 1 pixel, which we do by shifting the single \ pixel in H to the right, and if it falls off the right \ end, adjusting the screen address in (Q P) to point to \ the next character block along ADC RR \ Set SS = A + RR + C STA SS \ = slope error + 255 - |y-delta| + 1 \ = slope error + 256 - |y-delta| \ = slope error - |y-delta| INX \ Increment X to move along the x-axis to the right LDA H \ Set A to the single pixel byte in H and shift it right LSR A \ to move the pixel along by one place .dlin58 CMP #%00000000 \ If the pixel byte is non-zero, then the pixel hasn't BNE dlin60 \ fallen off the end, so jump to dlin60 to skip the \ following \ \ Gets modified by the ModifyDrawRoutine routine: \ \ * CMP #%00000000 when colourLogic = %10000000 \ \ * CMP #%00001000 when colourLogic = %01000000 \ \ In other words, this instruction has already been \ modified to implement the current drawing logic LDA P \ Set (Q P) = (Q P) + 8 CLC \ ADC #8 \ starting with the low bytes STA P .dlin59 LDA #%00001000 \ Set A to a pixel byte with the leftmost pixel set, so \ we can use this as our new single pixel byte in H \ below (so shifting across by one character block only \ moves the single pixel right by one pixel) \ \ Gets modified by the ModifyDrawRoutine routine: \ \ * LDA #8 when colourLogic = %10000000 \ \ * LDA #&80 when colourLogic = %01000000 \ \ In other words, this instruction has already been \ modified to implement the current drawing logic BCC dlin60 \ And now add the high bytes, so (Q P) now points to the INC Q \ next character block to the right .dlin60 STA H \ Store the updated one-pixel byte in H CPX I \ If the current x-coordinate in X <> I, then we have CLC \ not yet reached the x-coordinate of the end of the BNE dlin53 \ line (which we set in part 7), so loop back to dlin53 \ to keep drawing the line BEQ dlin65 \ Otherwise we have reached the x-coordinate of the end \ of the line, so jump to dlin65 to process the clipped \ part of the line, if applicable (this BEQ is \ effectively a JMP as we just passed through a BNE) .dlin61 \ If we get here then the slope error just overflowed, \ and we are drawing down the screen (bit 7 of V is set) \ \ We reached with a BCS, so we know the C flag is set \ \ We now need to move the line along the x-axis to the \ left by 1 pixel, which we do by shifting the single \ pixel in H to the left, and if it falls off the left \ end, adjusting the screen address in (Q P) to point to \ the previous character block ADC RR \ Set SS = A + RR + C STA SS \ = slope error + 255 - |y-delta| + 1 \ = slope error + 256 - |y-delta| \ = slope error - |y-delta| DEX \ Decrement X to move along the x-axis to the left LDA H \ Set A to the single pixel byte in H and shift it left ASL A \ to move the pixel along by one place .dlin62 CMP #%00010000 \ If the pixel byte is not %00010000, then the pixel BNE dlin64 \ hasn't fallen off the end, so jump to dlin64 to skip \ the following \ \ Gets modified by the ModifyDrawRoutine routine: \ \ * CMP #%00010000 when colourLogic = %10000000 \ \ * CMP #%00000000 when colourLogic = %01000000 \ \ In other words, this instruction has already been \ modified to implement the current drawing logic LDA P \ Set (Q P) = (Q P) - 8 SEC \ SBC #8 \ starting with the low bytes STA P .dlin63 LDA #%00000001 \ Set A to a pixel byte with the rightmost pixel set, so \ we can use this as our new single pixel byte in H \ below (so shifting across by one character block only \ moves the single pixel left by one pixel) \ \ Gets modified by the ModifyDrawRoutine routine: \ \ * LDA #1 when colourLogic = %10000000 \ \ * LDA #16 when colourLogic = %01000000 \ \ In other words, this instruction has already been \ modified to implement the current drawing logic BCS dlin64 \ And now subtract the high bytes, so (Q P) now points DEC Q \ to the previous character block to the left .dlin64 STA H \ Store the updated one-pixel byte in H CPX I \ If the current x-coordinate in X <> I, then we have CLC \ not yet reached the x-coordinate of the end of the BNE dlin53 \ line (which we set in part 7), so loop back to dlin53 \ to keep drawing the line \ Otherwise we have reached the x-coordinate of the end \ of the line, so fall through into dlin65 to process \ the clipped part of the line, if applicable \ ****************************************************************************** \ \ Name: DrawCanopyLine (Part 9 of 9) \ Type: Subroutine \ Category: Drawing lines \ Summary: If the line was clipped, draw a line from the clipped coordinates \ to the edge of the screen \ \ ------------------------------------------------------------------------------ \ \ If the line has been clipped, this part reverses the line's direction in V and \ switches the start point at (R, S) to the other end of the line, before \ jumping to the start of the line-drawing routine to draw the reversed line. \ \ ****************************************************************************** .dlin65 LDA V \ Set A = V >> 1, setting the C flag to bit 0 of V LSR A BCS dlin66 \ If bit 0 if V is set, which means the line has been \ clipped by the ClipStartOfLine routine, jump to dlin66 RTS \ Otherwise bit 0 of V is clear and the line has not \ been clipped, so we return from the subroutine .dlin66 ASL A \ Shift A left so it contains V again EOR #%11000000 \ Flip bits 6 and 7 of V to reverse the direction of the STA V \ line LDA xTemp1Lo \ Set (R, S) = (xTemp1Lo, yTemp1Lo), which we set to the STA R \ start coordinate of the clipped line in the LDA yTemp1Lo \ ClipStartOfLine routine STA S LDA #4 \ Set A = 4, to use as the value of W if bit 7 of V is \ set (i.e. when we step along the x-axis in a negative \ direction, to the left) BIT V \ If bit 7 of V is set, jump to dlin67 to skip the next BMI dlin67 \ instruction LDA #155 \ Set A = 155, to use as the value of W if bit 7 of V is \ clear (i.e. when we step along the x-axis in a \ positive direction, to the right) .dlin67 STA W \ Set W to the value in A (4 or 155), so W contains the \ x-coordinate for the edge of the screen in the step \ direction (i.e. 4 if we are stepping left, 155 if we \ are stepping right) LDA #0 \ Set A = 0, to use as the value of W if bit 6 of V is \ set (i.e. when we step along the y-axis in a negative \ direction, down the screen) BVS dlin68 \ If bit 6 of V is set, jump to dlin68 to skip the next \ instruction LDA #151 \ Set A = 151, to use as the value of W if bit 6 of V is \ clear (i.e. when we step along the y-axis in a \ positive direction, up the screen) .dlin68 STA G \ Set G to the value in A (0 or 151), so G contains the \ y-coordinate for the edge of the screen in the step \ direction (i.e. 0 if we are stepping down, 151 if we \ are stepping up) JMP DrawCanopyLine \ Jump up to DrawCanopyLine to draw the reversed line \ from (xTemp1Lo, yTemp1Lo) to the edge of the screen \ ****************************************************************************** \ \ Name: ModifyDrawRoutine \ Type: Subroutine \ Category: Drawing lines \ Summary: Modify the drawing routines to draw in the correct colour for the \ current colour cycle \ \ ------------------------------------------------------------------------------ \ \ This routine modifies the DrawCanopyLine routine, depending on the value of \ the colourLogic and colourCycle variables. See the DrawCanopyView routine for \ details of the colour-cycling system that this forms part of. \ \ This is what the ModifyDrawRoutine routine does: \ \ If colourLogic = %00000000 (erase lines): \ \ * Modify the drawing logic in DrawCanopyLine to AND \ \ * Modify DrawCanopyLine so it fetches bit patterns from: \ \ * colour1Row if colourCycle = %00001111 \ \ * colour2Row if colourCycle = %11110000 \ \ In other words, the bit pattern it fetches is always the same as the value \ of colourCycle, as colour1Row contains %00001111 and colour2Row contains \ %11110000 \ \ * Modify DrawCanopyLine (part 3) so it pokes the value of colourCycle as a \ bit pattern in the screen-updating routine at dlin50 \ \ If colourLogic = %01000000 (draw lines in colour 2): \ \ * Modify the drawing logic in DrawCanopyLine to OR (the default) \ \ * Modify DrawCanopyLine so it fetches bit patterns from colour2L2R and \ colour2R2L (colour 2) instead of colour1L2R and colour1R2L (colour 1) \ \ If colourLogic = %10000000 (draw lines in colour 1): \ \ * Modify the drawing logic in DrawCanopyLine to OR (the default) \ \ * Restore the DrawCanopyLine routine back to its default code, so we draw \ in colour 1 \ \ ****************************************************************************** .ModifyDrawRoutine LDA colourLogic \ If colourLogic is non-zero, jump to modd3 BNE modd3 \ If we get here then colourLogic is %00000000 LDA #&31 \ Set A to the opcode for the AND (P),Y instruction STA dlin24 \ Modify the following instruction in DrawCanopyLine \ (part 4): \ \ ORA (P),Y -> AND (P),Y STA dlin29 \ Modify the following instruction in DrawCanopyLine \ (part 5): \ \ ORA (P),Y -> AND (P),Y STA dlin34 \ Modify the following instruction in DrawCanopyLine \ (part 6): \ \ ORA (P),Y -> AND (P),Y STA dlin51 \ Modify the following instruction in DrawCanopyLine \ (part 8): \ \ ORA (P),Y -> AND (P),Y LDA colourCycle \ If bit 7 of colourCycle is set, i.e. %11110000, jump BMI modd1 \ down to modd1 LDA #LO(colour1Row) \ Bit 7 of colourCycle is clear, i.e. %00001111, so set \ A to LO(colour1Row) so the instructions below are \ modified to the following: \ \ LDA #LO(colour1L2R) -> LDA #LO(colour1Row) \ LDA colour1L2R,X -> LDA colour1Row,X \ LDA #LO(colour1R2L) -> LDA #LO(colour1Row) \ LDA colour1R2L,X -> LDA colour1Row,X BNE modd2 \ Jump down to modd2 (this BNE is effectively a JMP as \ A is never zero) .modd1 LDA #LO(colour2Row) \ Bit 7 of colourCycle is set, i.e. %11110000, so set \ A to LO(colour2Row) so the instructions below are \ modified to the following: \ \ LDA #LO(colour1L2R) -> LDA #LO(colour2Row) \ LDA colour1L2R,X -> LDA colour2Row,X \ LDA #LO(colour1R2L) -> LDA #LO(colour2Row) \ LDA colour1R2L,X -> LDA colour2Row,X .modd2 \ Modify the following instruction in DrawCanopyLine \ (part 2) where LO(colourA) is the value of A: \ STA dlin2+1 \ LDA #LO(colour1L2R) -> LDA #LO(colourA) \ Modify the following instruction in DrawCanopyLine \ (part 4) where colourA is the value of A: \ STA dlin23+1 \ LDA colour1L2R,X -> LDA colourA,X \ Modify the following instruction in DrawCanopyLine \ (part 2) where LO(colourA) is the value of A: \ STA dlin4+1 \ LDA #LO(colour1R2L) -> LDA #LO(colourA) \ Modify the following instruction in DrawCanopyLine \ (part 5) where colourA is the value of A: \ STA dlin28+1 \ LDA colour1R2L,X -> LDA colourA,X LDA colourCycle \ Modify the following instruction in DrawCanopyLine STA dlin50+1 \ (part 2): LDA #&A9 \ STA dlin50 \ LDA H -> LDA #colourCycle \ \ as the opcode for the LDA #n instruction is &A9 RTS \ Return from the subroutine .modd3 \ If we get here then colourLogic is non-zero LDA #&11 \ Set A to the opcode for the ORA (P),Y instruction STA dlin24 \ Modify the following instruction in DrawCanopyLine \ (part 4): \ \ ORA (P),Y -> ORA (P),Y \ \ i.e. set them back to the default STA dlin29 \ Modify the following instruction in DrawCanopyLine \ (part 5): \ \ ORA (P),Y -> ORA (P),Y \ \ i.e. set them back to the default STA dlin34 \ Modify the following instruction in DrawCanopyLine \ (part 6): \ \ ORA (P),Y -> ORA (P),Y \ \ i.e. set them back to the default STA dlin51 \ Modify the following instruction in DrawCanopyLine \ (part 8): \ \ ORA (P),Y -> ORA (P),Y \ \ i.e. set them back to the default LDA colourLogic \ If bit 7 of colourLogic is set, i.e. %10000000, jump BMI modd4 \ to modd4 \ If we get here then colourLogic is %01000000 \ Modify the following instructions in DrawCanopyLine \ (parts 2 and 4): LDA #LO(colour2L2R) \ STA dlin2+1 \ LDA #LO(colour1L2R) -> LDA #LO(colour2L2R) STA dlin23+1 \ LDA colour1L2R,X -> LDA colour2L2R,X \ Modify the following instructions in DrawCanopyLine \ (parts 2 and 5): LDA #LO(colour2R2L) \ STA dlin4+1 \ LDA #LO(colour1R2L) -> LDA #LO(colour2R2L) STA dlin28+1 \ LDA colour1R2L,X -> LDA colour2R2L,X \ Modify the following instructions in DrawCanopyLine \ (part 8): LDA #%10000000 \ STA dlin47+1 \ LDA #%00001000 -> LDA #%10000000 LDA #%00001000 \ STA dlin58+1 \ CMP #%00000000 -> CMP #%00001000 LDA #%10000000 \ STA dlin59+1 \ LDA #%00001000 -> LDA #%10000000 LDA #00000000 \ STA dlin62+1 \ CMP #%00010000 -> CMP #%00000000 LDA #%00010000 \ STA dlin63+1 \ LDA #%00000001 -> LDA #%00010000 BNE modd5 \ Jump down to modd5 (this BNE is effectively a JMP as \ A is never zero) .modd4 \ If we get here then colourLogic is %10000000 \ Modify the following instructions in DrawCanopyLine \ (parts 2 and 4): LDA #LO(colour1L2R) \ STA dlin2+1 \ LDA #LO(colour1L2R) -> LDA #LO(colour1L2R) STA dlin23+1 \ LDA colour1L2R,X -> LDA colour1L2R,X \ \ i.e. set them back to the default \ Modify the following instructions in DrawCanopyLine \ (parts 2 and 5): LDA #LO(colour1R2L) \ STA dlin4+1 \ LDA #LO(colour1R2L) -> LDA #LO(colour1R2L) STA dlin28+1 \ LDA colour1R2L,X -> LDA colour1R2L,X \ \ i.e. set them back to the default \ Modify the following instructions in DrawCanopyLine \ (part 8): LDA #%00001000 \ STA dlin47+1 \ LDA #%00001000 -> LDA #%00001000 LDA #%00000000 \ STA dlin58+1 \ CMP #%00000000 -> CMP #%00000000 LDA #%00001000 \ STA dlin59+1 \ LDA #%00001000 -> LDA #%00001000 LDA #%00010000 \ STA dlin62+1 \ CMP #%00010000 -> CMP #%00010000 LDA #%00000001 \ STA dlin63+1 \ LDA #%00000001 -> LDA #%00000001 \ \ i.e. set them back to the default .modd5 \ Modify the following instructions in DrawCanopyLine \ (part 2): LDA #&A5 \ STA dlin50 \ LDA H -> LDA H LDA #&79 \ STA dlin50+1 \ as the opcode for the LDA n instruction is &A5 RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: ClipBestEndOfLine \ Type: Subroutine \ Category: Drawing lines \ Summary: Clip a line at the start or end point, depending on which is best \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ (RR R) The x-coordinate of the line's start point \ \ (SS S) The y-coordinate of the line's start point \ \ TT The clipping requirements for the start point \ \ (QQ W) The x-coordinate of the line's end point \ \ (H G) The y-coordinate of the line's end point \ \ UU The clipping requirements for the end point \ \ ------------------------------------------------------------------------------ \ \ Returns: \ \ (RR R) The x-coordinate of the line's new start point, clipped \ to fit on-screen \ \ (SS S) The y-coordinate of the line's new start point, clipped \ to fit on-screen \ \ T Relative magnitude of line's vector |x-delta| \ \ U Relative magnitude of line's vector |y-delta| \ \ W Max/min x-coordinate for the new end of the line \ \ G Max/min y-coordinate for the new end of the line \ \ ------------------------------------------------------------------------------ \ \ Other entry points: \ \ AbortLine Abort drawing this line \ \ ****************************************************************************** .ClipBestEndOfLine LDA TT \ If TT and UU have a set bit in common, this means that AND UU \ both points are off-screen in the same direction, so BNE AbortLine \ the line can't possibly cross the visible part of the \ screen at any point, so jump to AbortLine to abort the \ drawing of this line LDA SS \ If the start point's y-coordinate is positive, jump to BPL clen1 \ clen1 to skip the following instruction EOR #&FF \ Flip the start point's y-coordinate so it's positive, \ so: \ \ A = |start_y| .clen1 STA maxCoord \ Store the start point's y-coordinate in maxCoord, so: \ \ maxCoord = |start_y| LDA RR \ If the start point's x-coordinate is positive, jump to BPL clen2 \ clen2 to skip the following instruction EOR #&FF \ Flip the start point's x-coordinate so it's positive, \ so: \ \ A = |start_x| .clen2 CMP maxCoord \ If the start point's x-coordinate is < maxCoord, jump BCC clen3 \ to clen3 to skip the following instruction STA maxCoord \ The start point's x-coordinate is >= maxCoord, so \ update maxCoord so it contains the maximum value: \ \ maxCoord = max(|start_x|, |start_y|) .clen3 LDA QQ \ If the end point's x-coordinate is positive, jump to BPL clen4 \ clen4 to skip the following instruction EOR #&FF \ Flip the end point's x-coordinate so it's positive, \ so: \ \ A = |end_x| .clen4 CMP maxCoord \ If |end_x| >= maxCoord, the start point has a smaller BCS ClipStartOfLine \ x-coordinate magnitude which means it is closer to the \ origin, so jump to ClipStartOfLine to clip the line at \ the start point, as it's a better candidate for \ clipping LDA H \ If the end point's y-coordinate is positive, jump to BPL clen5 \ clen5 to skip the following instruction EOR #&FF \ Flip the end point's y-coordinate so it's positive, \ so: \ \ A = |end_y| .clen5 CMP maxCoord \ If |end_y| >= maxCoord, the start point has a smaller BCS ClipStartOfLine \ y-coordinate magnitude which means it is closer to the \ origin, so jump to ClipStartOfLine to clip the line at \ the start point, as it's a better candidate for \ clipping JSR SwapLinePoints \ Otherwise the end point is the better candidate for \ clipping, so copy the end point's coordinates and \ clipping information into the start point LDA V \ We've just flipped the end point to the start, so flip EOR #%11000000 \ bits 6 and 7 in V to reverse the line direction in STA V \ both axes JMP ClipStartOfLine \ Jump to ClipStartOfLine to clip the line at the new \ start point .AbortLine TSX \ Remove two bytes from the top of the stack, which INX \ removes the return address that was put there by the INX \ last JSR instruction. This means that the RTS below TXS \ jumps two levels up the call stack, rather than one, \ so we return to the subroutine that called the \ subroutine that called ClipBestEndOfLine. The only \ call to ClipBestEndOfLine is in the DrawClippedLine \ routine, and the only call to DrawClippedLine is in \ DrawCanopyView, so this ensures that the RTS below \ returns us to DrawCanopyView without drawing the line \ and without leaving anything on the stack \ \ In short, this makes the RTS abort the drawing of this \ line RTS \ Return to the DrawCanopyView routine \ ****************************************************************************** \ \ Name: ClipStartOfLine (Part 1 of 5) \ Type: Subroutine \ Category: Drawing lines \ Summary: Check whether the line is completely off-screen \ \ ------------------------------------------------------------------------------ \ \ The commentary in this routine is a work in progress. \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ (RR R) The x-coordinate of the line's start point \ \ (SS S) The y-coordinate of the line's start point \ \ (I T) The line's |x-delta| \ \ (J U) The line's |y-delta| \ \ TT The clipping requirements for the start point \ \ Bit 4 Bit 5 \ Off top of screen 0 1 \ On-screen 0 0 \ Off bottom of screen 1 0 \ \ Bit 6 Bit 7 \ Off right of screen 0 1 \ On-screen 0 0 \ Off left of screen 1 0 \ \ V Direction of the line: \ \ * Bit 7 is the direction of the x-delta \ \ * Bit 6 is the direction of the y-delta \ \ Direction is like a clock, so positive (clear) is up and \ right \ \ ------------------------------------------------------------------------------ \ \ Returns: \ \ R The x-coordinate of the line's start point, clipped to \ fit on-screen \ \ S The y-coordinate of the line's start point, clipped to \ fit on-screen \ \ V Bit 0 is set to indicate the line has been clipped \ \ xTemp1Lo If the line has been clipped, contains a copy of R \ \ yTemp1Lo If the line has been clipped, contains a copy of S \ \ ****************************************************************************** .ClipStartOfLine LDA S \ Set (SS S) = (SS S) + 4 CLC \ ADC #4 \ starting with the low bytes STA S BCC clip1 \ If the addition didn't overflow, jump to clip1 to \ skip the following instruction INC SS \ And then we increment the high byte if the addition \ overflowed \ \ So we have now added an extra 4 to the start \ y-coordinate: \ \ (SS S) = (SS S) + 4 \ \ We add this to make the x- and y-coordinate consistent \ in terms of range, as the canopy's x-coordinates start \ at 4, but the y-coordinates start at 0. We remove this \ additional 4 at the end, after clipping the line .clip1 \ We now do some simple checks to weed out lines that \ are entirely off to one side of the screen LDA TT \ Set A = the clipping requirements for the start point BIT V \ If bit 7 of V is clear, jump to clip2 as the x-delta BPL clip2 \ of the line is positive, or to the right \ Bit 7 of V is set, so the x-delta of the line is \ negative, or to the left AND #%01000000 \ If bit 6 of TT is set, then the start point is off the BNE AbortLine \ left of the screen and the line direction is also to \ the left, so jump to AbortLine to stop drawing the \ line as it must be entirely off-screen BEQ clip3 \ Jump to clip3 to move on to the next check (this BEQ \ is effectively a JMP, as A is always zero) .clip2 \ If we get here then the x-delta of the line is \ positive, or to the right AND #%10000000 \ If bit 7 of TT is set, then the start point is off the BNE AbortLine \ right of the screen and the line direction is also to \ the right, so jump to AbortLine to stop drawing the \ line as it must be entirely off-screen BEQ clip3 \ Jump to clip3 to move on to the next check (this BEQ \ is effectively a JMP, as A is always zero) .clip3 LDA TT \ Set A = the clipping requirements for the start point BVC clip4 \ If bit 6 of V is clear, jump to clip4 as the y-delta \ of the line is positive, or up \ Bit 6 of V is set, so the y-delta of the line is \ negative, or down AND #%00010000 \ If bit 4 of TT is set, then the start point is off the BNE AbortLine \ bottom of the screen and the line direction is also \ down, so jump to AbortLine to stop drawing the \ line as it must be entirely off-screen BEQ clip5 \ Jump to clip5 to move on to the next check (this BEQ \ is effectively a JMP, as A is always zero) .clip4 \ If we get here then the y-delta of the line is \ positive, or up AND #%00100000 \ If bit 5 of TT is set, then the start point is off the BNE AbortLine \ top of the screen and the line direction is also up, \ so jump to AbortLine to stop drawing the line as it \ must be entirely off-screen \ If we get here then the line might have an on-screen \ element, so we now move on to the actual clipping \ ****************************************************************************** \ \ Name: ClipStartOfLine (Part 2 of 5) \ Type: Subroutine \ Category: Drawing lines \ Summary: Work out the deltas depending on the direction of slope \ \ ------------------------------------------------------------------------------ \ \ If slope is / \ (UU TT) = |start_x + start_y - 159| \ Clear bit 7 of WW \ \ If slope is \ \ (UU TT) = |start_y - start_x| \ Set bit 7 of WW \ \ (Q P) = |x-delta| + |y-delta| + 2 \ \ ****************************************************************************** .clip5 LDA V \ If either bit 7 or bit 6 of V are set, but not both, ASL A \ then if we EOR them we will get a 1, so this jumps to EOR V \ clip6 if this is the case BMI clip6 \ If we get here then bits 6 and 7 of V are the same, so \ the line slope is up-right or down-left, i.e. / LDA #0 \ Set WW = 0, so bit 7 is clear STA WW LDA R \ Set (UU TT) = (RR R) + (SS S) CLC \ ADC S \ starting with the low bytes STA TT LDA RR \ And then the high bytes ADC SS STA UU LDA TT \ Set (UU TT) = (UU TT) - 159 SEC \ SBC #159 \ starting with the low bytes STA TT LDA UU \ And then the high bytes, so we now have: SBC #0 \ STA UU \ (UU TT) = start_x + start_y - 159 JMP clip7 .clip6 \ If we get here then bits 6 and 7 of V are different, \ so the line slope is up-right or down-left, i.e. \ LDA #%10000000 \ Set WW = %10000000, so bit 7 is set STA WW LDA S \ Set (UU TT) = (SS S) - (RR R) SEC \ SBC R \ starting with the low bytes STA TT LDA SS \ And then the high bytes, so now we have: SBC RR \ STA UU \ (UU TT) = start_y - start_x .clip7 BPL clip8 \ If (UU TT) is positive, jump to clip8 LDA #0 \ Set (UU TT) = 0 - (UU TT) SEC \ SBC TT \ starting with the low bytes STA TT LDA #0 \ And then the high bytes, so (UU TT) is now positive, SBC UU \ so: STA UU \ \ (UU TT) = |UU TT| \ = |start_x + start_y - 159| if slope is / \ |start_y - start_x| if slope is \ .clip8 LDA T \ Set (Q P) = (I T) + (J U) CLC \ ADC U \ starting with the low bytes STA P LDA I \ And then the high bytes, so: ADC J \ STA Q \ (Q P) = |x-delta| + |y-delta| LDA P \ Set (Q P) = (Q P) + 2 CLC \ ADC #2 \ starting with the low bytes STA P BCC clip9 \ If the addition didn't overflow, jump to clip9 to \ skip the following instruction INC Q \ Increment the high byte in Q \ ****************************************************************************** \ \ Name: ClipStartOfLine (Part 3 of 5) \ Type: Subroutine \ Category: Drawing lines \ Summary: Work out the deltas depending on the steepness of slope \ \ ------------------------------------------------------------------------------ \ \ Shallow horizontal: \ Bit 7 of HH = direction of the y-delta \ (PP N) = |x-delta| + 1 \ Clear bit 6 of WW \ \ Steep vertical: \ Bit 7 of HH = direction of the x-delta \ (PP N) = |y-delta| + 1 \ Set bit 6 of WW \ Set (RR R) = (SS S), so start_x is set to start_y \ \ ****************************************************************************** .clip9 LDA #0 \ Set K = 0, though this has no effect as we don't use STA K \ K in the following LDA I \ If I < J, then (I T) < (J U), so jump to clip11 CMP J BCC clip11 BNE clip10 \ If I > J, then (I T) > (J U), so jump to clip10 \ If we get here then I = J LDA T \ If T < U, then (I T) < (J U), so jump to clip11 CMP U BCC clip11 .clip10 \ If we get here, then (I T) >= (J U), which is the same \ as |x-delta| >= |y-delta|, so this is a shallow \ horizontal slope LDA V \ Set HH = V STA HH \ \ so bit 7 of HH is set to bit 7 of V, i.e. the \ direction of the y-delta LDA T \ Set (PP N) = (I T) + 1 CLC \ ADC #1 \ starting with the low bytes STA N LDA I \ And then the high bytes, so: ADC #0 \ STA PP \ (PP N) = |x-delta| + 1 JMP clip12 \ Jump down to clip12 to move on to the next stage .clip11 \ If we get here, then (I T) < (J U), which is the same \ as |x-delta| < |y-delta|, so this is a steep vertical \ slope LDA V \ Set HH = V << 1 ASL A \ STA HH \ so bit 7 of HH is set to bit 6 of V, i.e. the \ direction of the x-delta LDA U \ Set (PP N) = (J U) + 1 CLC \ ADC #1 \ starting with the low bytes STA N LDA J \ And then the high bytes, so: ADC #0 \ STA PP \ (PP N) = |y-delta| + 1 LDA WW \ Set bit 6 of WW ORA #%01000000 STA WW LDA S \ Set (RR R) = (SS S) STA R \ LDA SS \ so the x-coordinate of the line's start point is now STA RR \ the y-coordinate of the start point \ ****************************************************************************** \ \ Name: ClipStartOfLine (Part 4 of 5) \ Type: Subroutine \ Category: Drawing lines \ Summary: Calculate where to clip the line \ \ ------------------------------------------------------------------------------ \ \ Division algorithm of some kind, I think it's this for a shallow horizontal / \ \ (QQ G) = (UU TT) * (PP N) / (Q P) \ = (start_x + start_y - 159) * (|x-delta| + 1) \ / (|x-delta| + |y-delta|) \ \ This calculates the delta to add to the coordinates in the final part, but I \ haven't got to the bottom of this \ \ 1. Double (Q P) and (PP N) until Q >= UU \ If Q = UU and P < TT, repeat until P >= TT \ i.e. repeat until (Q P) >= (UU TT) \ \ 2. Halve (Q P K) and (PP N VV) until Q < UU \ i.e. repeat until (Q P K) < (UU TT H) \ \ 3. If (Q P K) = 0, done \ Otherwise add 0.5 to (PP N) using a third byte (so add 128) \ (UU TT H) = (UU TT 0) - (Q P K) \ Jump back to step 2 while (UU TT H) >= 2 \ \ ****************************************************************************** .clip12 LDA #128 \ Set W = 128, to use in (QQ G W) STA W LDA #0 \ Set VV = 0, to use as the third byte in (PP N VV) STA VV STA K \ Set K = 0, to use as the third byte in (Q P K) STA H \ Set H = 0, to use as the third byte in (UU TT H) STA QQ \ Set QQ = 0, to use in (QQ G W) STA G \ Set G = 0, so now we have: \ \ (QQ G W) = 128 BEQ clip14 \ Jump to clip14 (this BEQ is effectively a JMP as A is \ always zero) .clip13 ASL P \ Set (Q P) = (Q P) * 2 ROL Q ASL N \ Set (PP N) = (PP N) * 2 ROL PP .clip14 LDA Q \ If Q < UU, jump up to clip13 to double the values of CMP UU \ (Q P) and (PP N) until Q >= UU BCC clip13 BNE clip15 \ If Q <> UU, i.e. Q > U, then jump to clip15 LDA P \ If we get here then Q = UU, so if P < TT, jump back to CMP TT \ clip13 to keep on doubling until P >= TT BCC clip13 .clip15 LSR Q \ Set (Q P K) = (Q P K) / 2 ROR P ROR K LSR PP \ Set (PP N VV) = (PP N VV) / 2 ROR N ROR VV .clip16 LDA Q \ If Q < UU, jump down to clip17 CMP UU BCC clip17 BNE clip15 \ If Q <> UU, i.e. Q > U, jump back to clip15 to keep on \ halving until Q < UU or Q = UU LDA P \ If we get here then Q = UU, so if P < TT, jump down to CMP TT \ clip17 BCC clip17 BNE clip15 \ If P <> TT, i.e. P > TT, jump back to clip15 to keep \ on halving until Q < UU, or Q = UU and P <= TT LDA K \ If K <= H, jump down to clip17 CMP H BCC clip17 BEQ clip17 LDA Q \ If (Q P K) <> 0, jump back to clip15 ORA P ORA K BNE clip15 BEQ clip18 \ (Q P K) = 0 so jump to clip18 (this BEQ is effectively \ a JMP as we just passed through a BNE) .clip17 \ The first time we get here, QQ = G = 0 and W = 128, so \ the following sum is (QQ G W) = 128 + (PP N VV) LDA W \ Set (QQ G W) = (QQ G W) + (PP N VV) CLC \ ADC VV \ starting with the low bytes STA W LDA G \ Then the middle bytes ADC N STA G LDA QQ \ And then the high bytes ADC PP STA QQ \ The first time we get here, H = 0, so the \ following sum is (UU TT H) = (UU TT 0) - (Q P K) LDA H \ Set (UU TT H) = (UU TT H) - (Q P K) SEC \ SBC K \ starting with the low bytes STA H LDA TT \ Then the middle bytes SBC P STA TT LDA UU \ And then the high bytes SBC Q STA UU \ The following comparisons jump back to clip16 if \ (UU TT H) >= 2 BNE clip16 \ If UU <> 0, jump back to clip16 LDA TT \ If TT <> 0, jump back to clip16 BNE clip16 LDA H \ If H >= 2, jump back to clip16 CMP #2 BCS clip16 \ By the time we get here, (UU TT H) is 1 or 0 \ ****************************************************************************** \ \ Name: ClipStartOfLine (Part 5 of 5) \ Type: Subroutine \ Category: Drawing lines \ Summary: Move the start point to the clipped position and return it \ \ ------------------------------------------------------------------------------ \ \ If bit 7 of HH clear, (RR R) and (SS S) = (RR R) + (QQ G) \ If bit 7 of HH set, (RR R) and (SS S) = (RR R) - (QQ G) \ \ If bits 6 and 7 of WW clear, (SS S) = 159 - (RR R) \ If bit 6 of WW is set, bit 7 clear, (RR RR) = 159 - (SS S) \ \ S = S - 4 to undo the bump in the first part \ \ Confirm (R, S) is on-screen and return it as the new start point \ \ ****************************************************************************** .clip18 \ By the time we get here, (QQ G W) contains the delta \ that we need to apply to the start point's x- or \ y-coordinate, multiplied by 256 so the lowest byte is \ the fractional part \ If bit 7 of W is set, that means the fractional part \ is at least 0.5, so we add 1 to the integral part in \ (QQ G) to round it up when going from the fractional \ (QQ G W) to the integer (QQ G) LDA G \ We start with the low byte, rotating bit 7 of W into ROL W \ the C flag which we then add with ADC #0 ADC #0 STA G BCC clip19 \ If the addition didn't overflow, jump to clip19 to \ skip the following instruction INC QQ \ And then we increment the high byte if the addition \ overflowed .clip19 LDA HH \ If bit 7 of HH is set, jump to clip20 to skip the BMI clip20 \ following \ Bit 7 of HH is clear, so we add the delta in (QQ G) \ to both coordinates to move the whole line so the \ start coordinate is on the edge of the screen LDA R \ Set (RR R) and (S SS) = (RR R) + (QQ G) CLC \ ADC G \ starting with the low bytes STA R STA S LDA RR \ And then the high bytes ADC QQ STA RR STA SS JMP clip21 \ Jump to clip21 to skip the following .clip20 \ Bit 7 of HH is set, so we subtract the delta in (QQ G) \ from both coordinates to move the whole line so the \ start coordinate is on the edge of the screen LDA R \ Set (RR R) and (SS S) = (RR R) - (QQ G) SEC \ SBC G \ starting with the low bytes STA R STA S LDA RR \ And then the high bytes SBC QQ STA RR STA SS .clip21 BIT WW \ If bit 7 of WW is set, jump to clip23 to skip all of BMI clip23 \ the following and return the final coordinates BVS clip22 \ If bit 6 of WW is set, jump to clip22 to skip the \ following \ Bits 6 and 7 of WW are both clear, so we update the \ y-coordinate LDA #159 \ Set (SS S) = 159 - (RR R) SEC \ SBC R \ starting with the low bytes STA S LDA #0 \ And then the high bytes SBC RR STA SS JMP clip23 \ Jump to clip23 to skip the following .clip22 \ Bit 6 of WW is set, so we update the x-coordinate LDA #159 \ Set (RR RR) = 159 - (SS S) SEC \ SBC S \ starting with the low bytes STA R LDA #0 \ And then the high bytes SBC SS STA RR .clip23 LDA RR \ If RR is non-zero, jump to clip24 to abort the line as BNE clip24 \ the (RR R) x-coordinate is off-screen LDA R \ If R >= 156, jump to clip24 to abort the line as the CMP #156 \ (RR R) x-coordinate is off the right of the screen BCS clip24 CMP #4 \ If R < 4, jump to clip24 to abort the line as the BCC clip24 \ (RR R) x-coordinate is off the left of the screen LDA S \ Set S = S - 4 SEC \ SBC #4 \ which subtracts the 4 that we added at the start of STA S \ the routine BCC clip24 \ If the subtraction just underflowed, jump to clip24 to \ abort the line as the (SS S) y-coordinate is off the \ bottom of the screen CMP #152 \ If S > 152, jump to clip24 to abort the line as the BCS clip24 \ (SS S) y-coordinate is off the top of the screen \ If we get here then we have successfully clipped the \ start point to the edge of the screen LDA #1 \ Set bit 0 of the line direction in V to indicate that ORA V \ the line has been clipped STA V LDA R \ Copy the clipped (R, S) pixel coordinate into STA xTemp1Lo \ (xTemp1Lo, yTemp1Lo) so we can access it in the LDA S \ DrawCanopyLine routine STA yTemp1Lo RTS \ Return from the subroutine .clip24 JMP AbortLine \ Jump to AbortLine to abort drawing this line and \ return from the subroutine using a tail call \ ****************************************************************************** \ \ Name: SwapLinePoints \ Type: Subroutine \ Category: Drawing lines \ Summary: Copy an end point into a start point \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ (QQ W) The x-coordinate of the line's end point \ \ (H G) The y-coordinate of the line's end point \ \ UU The clipping requirements for the end point \ \ ------------------------------------------------------------------------------ \ \ Returns: \ \ (RR R) Gets set to the x-coordinate above \ \ (SS S) Gets set to the y-coordinate above \ \ TT Gets set to the clipping requirements above \ \ ****************************************************************************** .SwapLinePoints LDA W \ Set (RR R) = (QQ W) STA R LDA QQ STA RR LDA G \ Set (SS S) = (H G) STA S LDA H STA SS LDA UU \ Set TT = UU STA TT RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: EraseCanopyLines \ Type: Subroutine \ Category: Drawing lines \ Summary: Draw all the lines from a line buffer to erase them \ Deep dive: Line buffers \ \ ------------------------------------------------------------------------------ \ \ We call this routine from DrawCanopyView, after drawing all the new lines and \ just before we flip colours. See the DrawCanopyView routine for more details \ of the colour-cycling system that this forms part of. \ \ This is what the EraseCanopyLines routine does: \ \ If colourCycle = %00001111, then colour 1 is white and colour 2 is black: \ \ * Draw the lines in line buffer 1, using AND logic and bit pattern %00001111 \ * Colour 2 on-screen is %11110000, so AND'ing with %00001111 gives 0 \ * So this erases all the lines in buffer 1, which are on-screen in colour 2 \ i.e. we erase all the lines in line buffer 1 from the hidden screen \ \ If colourCycle = %11110000, then colour 1 is black and colour 2 is white: \ \ * Draw the lines in line buffer 2, using AND logic and bit pattern %11110000 \ * Colour 1 is %00001111, so AND'ing with %11110000 gives 0 \ * So this erases all the lines in buffer 2, which are on-screen in colour 1 \ i.e. we erase all the lines in line buffer 2 from the hidden screen \ \ ****************************************************************************** .EraseCanopyLines LDA #%00000000 \ Set colourLogic = %00000000 so when we draw a line, it STA colourLogic \ erases it JSR ModifyDrawRoutine \ Modify the drawing routines to use AND logic and the \ bit patterns that match colourCycle .ecal1 LDA colourCycle \ If bit 7 of colourCycle is clear, i.e. %00001111, jump BPL ecal3 \ down to ecal3 \ If we get here, colourCycle is %11110000 LDX lineBuffer2Count \ If lineBuffer2Count <> 47, line buffer 2 is not CPX #47 \ empty, so jump down to ecal2 to draw the next line BNE ecal2 \ from the buffer RTS \ Return from the subroutine .ecal2 DEC lineBuffer2Count \ Decrement the value in lineBuffer2Count as we are \ about to draw the next line from buffer 2 JMP ecal5 \ Jump down to ecal5 to draw the next line from buffer 2 .ecal3 LDX lineBuffer1Count \ If lineBuffer1Count <> -1, line buffer 1 is not CPX #255 \ empty, so jump down to ecal4 to draw the next line BNE ecal4 \ from the buffer RTS \ Return from the subroutine .ecal4 DEC lineBuffer1Count \ Decrement the value in lineBuffer1Count as we are \ about to draw the next line from buffer 1 .ecal5 \ We now fetch the next line from the line buffer LDA lineBufferR,X \ Set R to the start x-coordinate from lineBufferR STA R STA xTemp1Lo \ Set the x-coordinate of (R, S) = (xTemp1Lo, yTemp1Lo) \ so we can access it in the DrawCanopyLine routine LDA lineBufferW,X \ Set W to the max/min x-coordinate from lineBufferW STA W LDA lineBufferS,X \ Set S to the start y-coordinate from lineBufferS STA S STA yTemp1Lo \ Set the y-coordinate of (R, S) = (xTemp1Lo, yTemp1Lo) \ so we can access it in the DrawCanopyLine routine LDA lineBufferG,X \ Set G to max/min y-coordinate from lineBufferG STA G LDA lineBufferT,X \ Set T to the |x-delta| from lineBufferT STA T LDA lineBufferU,X \ Set U to the |y-delta| from lineBufferU STA U LDA lineBufferV,X \ Set V to the direction from lineBufferV STA V JSR DrawCanopyLine \ Draw the line (to erase it) JMP ecal1 \ Loop back to ecal1 to erase the next line EQUB &17 \ This byte appears to be unused \ ****************************************************************************** \ \ Name: Multiply16x16Mix \ Type: Subroutine \ Category: Maths \ Summary: Multiply two 16-bit numbers with different sign bits (one in bit 7 \ of the high byte, the other in bit 0 of the low byte) \ \ ------------------------------------------------------------------------------ \ \ This routine multiplies two 16-bit numbers with different sign bits, where one \ argument (J I) has the sign in bit 7 of the high byte, and the other argument \ (S R) has the sign in bit 0 of the low byte. It calculates: \ \ (H G) = (J I) * (S R) >> 16 \ \ The result in (H G) has the sign in bit 7 of the high byte. \ \ The fractional part of the result is in W, so this is the full result, though \ in practice W is ignored: \ \ (H G W) = (J I) * (S R) \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ (J I) A signed 16-bit number, with the sign in bit 7 of J \ \ (S R) A signed 16-bit number, with the sign in bit 0 of R \ \ K Optionally negate (J I): \ \ * 0 = calculate (J I) * (S R) >> 16 \ \ * 128 = calculate -(J I) * (S R) >> 16 \ \ In practice, this routine is always called with K = 0 \ \ ------------------------------------------------------------------------------ \ \ Returns: \ \ (H G) The result of the multiplication, with the sign in bit 7 \ of H \ \ K The sign of the result (in bit 7) \ \ ****************************************************************************** .Multiply16x16Mix LDA J \ If J is positive, jump to mmix1 as (J I) is already a BPL mmix1 \ positive 16-bit number LDA #0 \ Negate (J I) so (J I) is now a positive 16-bit number SEC SBC I STA I LDA #0 SBC J STA J LDA K \ Flip bit 7 of K, so when K = 0 we negate the result EOR #%10000000 \ below to give the result the correct sign STA K .mmix1 LDA R \ If bit 0 of R is 0, jump to mmix2 as (S R) is positive AND #1 BEQ mmix2 LDA K \ Otherwise (S R) is negative, so flip bit 7 of K, so EOR #%10000000 \ when K = 0 we negate the result below to give the STA K \ result the correct sign .mmix2 JSR Multiply16x16 \ Calculate (H A) = (S R) * (J I) >> 16 \ \ and set the C flag if we need to increment H STA G \ Set (H G) = (H A) \ = (S R) * (J I) >> 16 BCC mmix3 \ Increment the top byte in H if required INC H .mmix3 LDA K \ If K is positive, jump to mmix4 to return from the BPL mmix4 \ subroutine as the sign of the result is already \ correct SEC \ Negate (H G) so the result has the correct sign LDA #0 SBC G STA G LDA #0 SBC H STA H .mmix4 RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: Multiply8x8 \ Type: Subroutine \ Category: Maths \ Summary: Multiply two unsigned 8-bit numbers \ Deep dive: Times tables and nibble arithmetic \ \ ------------------------------------------------------------------------------ \ \ This routine multiplies two unsigned 8-bit numbers, as follows: \ \ (A V) = X * Y \ \ It uses the following algorithm: \ \ X * Y = %XXXXxxxx * %YYYYyyyy \ \ = (%XXXX0000 + %0000xxxx) * (%YYYY0000 + %0000yyyy) \ \ = (%XXXX0000 * %YYYY0000) + (%XXXX0000 * %0000yyyy) \ + (%0000xxxx * %YYYY0000) \ + (%0000xxxx * %0000yyyy) \ \ = (%YYYY * %XXXX) << 8 + (%XXXX * %yyyy << 4) \ + (%xxxx * %YYYY << 4) \ + (%xxxx * %yyyy) \ \ = (%YYYY * %XXXX) << 8 + ((%XXXX * %yyyy) + (%xxxx * %YYYY)) << 4 \ + (%xxxx * %yyyy) \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ X An unsigned 8-bit value (0 to 255) \ \ Y An unsigned 8-bit value (0 to 255) \ \ ------------------------------------------------------------------------------ \ \ Returns: \ \ (A V) X * Y \ \ ****************************************************************************** .Multiply8x8 \ We start with X = %XXXXxxxx and Y = %YYYYyyyy LDA lowNibble,X \ Set T = (X AND %00001111) OR (Y AND %11110000) ORA highNibble,Y \ = %0000xxxx OR %YYYY0000 STA T \ = %YYYYxxxx AND #%11110000 \ Set U = (A AND %11110000) OR (X >> 4) ORA shift4Right,X \ = %YYYY0000 OR %0000XXXX STA U \ = %YYYYXXXX AND #%00001111 \ Set Y = (A AND %00001111) OR (Y << 4) ORA shift4Left,Y \ = %0000XXXX OR %yyyy0000 TAY \ = %yyyyXXXX AND #%11110000 \ Set X = (A AND %11110000) OR (X AND %00001111) ORA lowNibble,X \ = %yyyy0000 OR %0000xxxx TAX \ = %yyyyxxxx LDA timesTable,X \ Set V = %yyyy * %xxxx STA V LDX T \ Set X = T = %YYYYxxxx LDA timesTable,X \ Set A = (%YYYY * %xxxx) + (%yyyy * XXXX) CLC \ ADC timesTable,Y \ Call this %AAAAaaaa with carry C ROR A \ Set T = A rotated right by 4 places ROR A \ = %aaaCAAAA ROR A \ ROR A \ and set the C flag to bit 3 of A STA T ROR A \ Set A = %aaaaCAAA AND #%11110000 \ Set A = %aaaa0000 CLC \ Set V = V + A ADC V \ = V + %aaaa0000 STA V \ = (%yyyy * %xxxx) + %aaaa0000 LDA T \ Set A = T AND %00011111 AND #%00011111 \ = %aaaCAAAA AND %00011111 \ = %000CAAAA LDX U \ Set A = A + %YYYY * %XXXX ADC timesTable,X \ = %000CAAAA + (%YYYY * %XXXX) \ So we now have: \ \ (A V) = A << 8 + V \ \ = (%000CAAAA + (%YYYY * %XXXX)) << 8 \ + (%yyyy * %xxxx) + %aaaa0000 \ \ = %000CAAAA << 8 \ + (%YYYY * %XXXX) << 8 \ + (%yyyy * %xxxx) \ + %aaaa0000 \ \ = (%YYYY * %XXXX) << 8 \ + %000CAAAAaaaa0000 \ + (%yyyy * %xxxx) \ \ We also have the following: \ \ %CAAAAaaaa = (%YYYY * %xxxx) + (%yyyy * XXXX) \ \ and: \ \ %000CAAAAaaaa0000 = %CAAAAaaaa << 4 \ \ so combining them all, we get: \ \ (A V) = (%YYYY * %XXXX) << 8 \ + %CAAAAaaaa << 4 \ + (%yyyy * %xxxx) \ \ = (%YYYY * %XXXX) << 8 \ ((%YYYY * %xxxx) + (%yyyy * XXXX)) << 4 \ + (%yyyy * %xxxx) \ \ which is the result we want RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: ProjectAxisAngle \ Type: Subroutine \ Category: 3D geometry \ Summary: Convert the rotation angles of the plane into coordinates \ Deep dive: Rotation matrices \ \ ------------------------------------------------------------------------------ \ \ This routine converts the plane's rotation angles into a set of coordinate \ values that we can use in the SetMatrices routine to populate the four \ rotation matrices. There are six of these values, each of which is a 16-bit \ number with the sign in bit 0: \ \ * (mx1Hi mx1Lo), which we can refer to as mx1 \ * (mx2Hi mx2Lo), which we can refer to as mx2 \ \ * (my1Hi my1Lo), which we can refer to as my1 \ * (my2Hi my2Lo), which we can refer to as my2 \ \ * (mz1Hi mz1Lo), which we can refer to as mz1 \ * (mz2Hi mz2Lo), which we can refer to as mz2 \ \ The calculation is done for one axis at a time, so the same routine not only \ sets mx1 and mx2, but also my1 and my2, and mz1 and mz2. \ \ If we take the x-axis as an example, and assume for simplicity that the angle \ in xRotation is less than 90 degrees, the routine will calculate the \ following: \ \ * mx1 = -cos(xRotation) \ * mx2 = sin(xRotation) \ \ Considering the x-axis calculation, the routine sets mx1 and mx2 according to \ the current rotation of the plane around the x-axis, which is stored in \ (xRotationHi xRotationLo), which we can refer to as xRotation. The routine \ converts the angle in xRotation into a pair of Cartesian coordinates, by \ projecting a line rotated by that amount onto the Cartesian axes; in a \ sense, we are converting from Polar coordinates into Cartesian, so we can \ use those coordinates to populate the rotation matrices in SetMatrices. \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ matrixAxis The axis to be processed: \ \ * 0 = set mx1 and mx2 from xRotation \ \ * 1 = set my1 and my2 from yRotation \ \ * 2 = set mz1 and mz2 from zRotation \ \ Y Same value as matrixAxis \ \ ****************************************************************************** .ProjectAxisAngle \ The routine processes the axis defined in Y, but the \ comments below are for the x-axis, where we set the \ values of mx1 and mx2 depending on the value of \ xRotation LDA xRotationHi,Y \ Set (G A) = (xRotationHi xRotationLo) for axis Y STA G \ \ starting with the high byte STA K \ Set K = xRotationHi, so we can access the high byte \ when writing the results below LDA xRotationLo,Y \ Set the low byte of (G A) \ \ so (G A) = xRotation ASL A \ Set (G W) = (G A) << 2 ROL G \ = xRotation << 2 ASL A \ ROL G \ so (G W) is the angle, reduced into a quarter circle STA W \ by removing the top four bits \ \ The original angle is stored as the portion of a whole \ circle, where xRotation is in the range 0 to 65535 and \ 65535 represents a full circle, so shifting it to the \ left and dropping the top two bits out reduces the \ range of the angle to a quarter circle, while leaving \ the range of the top byte as 0 to 255, which is what \ we need for looking up the reduced angle in the sine \ table LDX G \ Set (X W) = (G W) to pass the rotation angle to \ Sine16Bit JSR Sine16Bit \ Set (A Y) = sin(X W) \ = sin(xRotation) STA Q \ Set (Q P) = (A Y) STY P LDA G \ Set (X W) = ~(G W) EOR #&FF \ = ~xRotation << 2 TAX \ LDA W \ This sets (X W) to the original xRotation, reduced to EOR #&FF \ the quarter circle as before, and then inverted within STA W \ that quarter - or, to put it another way, if (G W) is \ the angle within the quarter (0 to 90 degrees), then \ (X W) is now 90 - (G W) \ \ Because sin(90 - X) = -cos(X), this means the call to \ Sine16Bit returns the cosine instead of the sine JSR Sine16Bit \ Set (A Y) = sin(X W) \ = sin(~xRotation) \ = -cos(xRotation) STA S \ Set (S R) = (A Y) STY R \ We now copy the results into mx1 and mx2, setting the \ sign in bit 0 as we go (the sign is in bit 0 as this \ is a matrix number) \ \ We do this based on the value in K, which contains the \ high byte of (xRotationHi xRotationLo) for this axis \ \ * 0 to 63 (bit 6 clear, bit 7 clear) \ * 64 to 127 (bit 6 set, bit 7 clear) \ * 128 to 191 (bit 6 clear, bit 7 set) \ * 192 to 255 (bit 6 set, bit 7 set) \ \ These four ranges correspond to the four quadrants of \ the rotation angle. For example, for the z-axis, which \ points into the screen, we can visualise the quadrants \ by imagining the plane doing a full 360-degree roll to \ the right; for the first quarter the plane is still \ upright, for the second and third quarters it is \ upside down, and then for the final quarter it is \ upright again \ \ The following sets the m1 and m2 values according to \ which quadrant this particular axis is rotated into LDY matrixAxis \ Set Y to the axis once again BIT K \ If bit 6 of K is set, jump to axis2 BVS axis2 BMI axis1 \ If bit 7 of K is set, jump to axis1 \ If we get here then K has: \ \ * Bit 6 clear \ * Bit 7 clear \ \ so this is the first quadrant of the axis' rotation LDA Q \ Set (mx2Hi mx2Lo) = (Q P) STA mx2Hi,Y \ = sin(xRotation) LDA P \ AND #%11111110 \ with bit 0 clear (positive) STA mx2Lo,Y LDA S \ Set (mx1Hi mx1Lo) = (S R) STA mx1Hi,Y \ = -cos(xRotation) LDA R \ AND #%11111110 \ with bit 0 clear (positive) STA mx1Lo,Y JMP axis4 \ Jump to axis4 to return from the subroutine .axis1 \ If we get here then K has: \ \ * Bit 6 clear \ * Bit 7 set \ \ so this is the third quadrant of the axis' rotation LDA Q \ Set (mx2Hi mx2Lo) = -(Q P) STA mx2Hi,Y \ LDA P \ with bit 0 set (negative) ORA #1 STA mx2Lo,Y LDA S \ Set (mx1Hi mx1Lo) = -(S R) STA mx1Hi,Y \ LDA R \ ORA #1 \ with bit 0 set (negative) STA mx1Lo,Y BNE axis4 \ Jump to axis4 to return from the subroutine (this BNE \ is effectively a JMP as A is never zero) .axis2 \ If we get here then K has bit 6 set BMI axis3 \ If bit 7 of K is set, jump to axis3 \ If we get here then K has: \ \ * Bit 6 set \ * Bit 7 clear \ \ so this is the second quadrant of the axis' rotation LDA S \ Set (mx2Hi mx2Lo) = (S R) STA mx2Hi,Y \ LDA R \ with bit 0 clear (positive) AND #%11111110 STA mx2Lo,Y LDA Q \ Set (mx1Hi mx1Lo) = -(Q P) STA mx1Hi,Y \ LDA P \ with bit 0 set (negative) ORA #1 STA mx1Lo,Y BNE axis4 \ Jump to axis4 to return from the subroutine (this BNE \ is effectively a JMP as A is never zero) .axis3 \ If we get here then K has: \ \ * Bit 6 set \ * Bit 7 set \ \ so this is the fourth quadrant of the axis' rotation LDA S \ Set (mx2Hi mx2Lo) = -(S R) STA mx2Hi,Y \ LDA R \ with bit 0 set (negative) ORA #1 STA mx2Lo,Y LDA Q \ Set (mx1Hi mx1Lo) = (Q P) STA mx1Hi,Y \ LDA P \ with bit 0 clear (positive) AND #%11111110 STA mx1Lo,Y .axis4 RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: Sine16Bit \ Type: Subroutine \ Category: Maths \ Summary: Calculate the sine of a 16-bit number \ Deep dive: Trigonometry \ \ ------------------------------------------------------------------------------ \ \ Sets (A Y) = sin(X W) \ \ where (X W) is a 16-bit angle, with 0 to 65535 representing a quarter circle. \ \ The sine lookup table contains 256 entries that represent a quarter circle, so \ we work out the result by first looking up the sine for the high byte X, and \ then interpolating W/256 of the way between the results for X and X + 1. It \ might help to think of X being an integer (0 to 255) and W being a fraction \ (0 to 255) and we're trying to map X.W onto the sine table by finding the \ entry for X and doing linear interpolation between X and X + 1 for the \ fractional amount. \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ (X W) The 16-bit angle, representing a quarter-circle in the \ range 0 to 255 \ \ ------------------------------------------------------------------------------ \ \ Returns: \ \ (A Y) sin(X W) \ \ (X W) Unchanged \ \ ****************************************************************************** .Sine16Bit STX H \ Store X in H for later SEC \ Set (A I) = sin(X + 1) - sin(X) LDA sinLo+1,X \ SBC sinLo,X \ starting with the low bytes STA I LDA sinHi+1,X \ And then the high bytes SBC sinHi,X \ Let's call this dX, the difference between sin(X + 1) \ sin(X) and, so (A I) = dX LSR A \ The maximum differential value we can have is 402, so ROR I \ we halve (A I) to fit the result into one byte, like LDX I \ this: \ \ (A I) = dX / 2 \ \ and then setting X to the low byte in I, so: \ \ X = dX / 2 LDY W \ Set Y = W JSR Multiply8x8 \ Set (A V) = X * Y \ = (dX / 2) * W LDX H \ Set X to the original argument we stored above ASL V \ Set (A V) = (A V) * 2 ROL A \ = (dX / 2) * W * 2 \ = dX * W \ \ and put bit 7 of (A V) into the C flag, so in effect \ we have: \ \ (C A V) = dX * W PHP \ Store the C flag on the stack CLC \ Set (A Y) = (C A) + sin(X) ADC sinLo,X \ = (dX * W / 256) + sin(X) TAY \ \ starting with the low bytes LDA #0 \ And then adding the carry to the high byte, so now we ADC sinHi,X \ have the interim result for (0 A) + sin(X) PLP \ And finally adding C (which we retrieve from the ADC #0 \ stack) to give the final result for (C A) + sin(X) \ So at this point we have: \ \ (A Y) = sin(X) + (dX * W / 256) \ \ which is the result we want, as it takes sin(X) and \ adds on W/256 of the difference between sin(X) and \ sin(X + 1) RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: Multiply4x16 \ Type: Subroutine \ Category: Maths \ Summary: Multiply a 4-bit and a 16-bit number \ Deep dive: Times tables and nibble arithmetic \ \ ------------------------------------------------------------------------------ \ \ This routine multiplies a 4-bit number by a 16-bit number, using the lookup \ table at timesTable for fast results. It does the following calculation: \ \ (G W) = V * (S R) >> 8 \ \ If bit 7 of K is set, the routine returns double this amount. \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ (S R) A 16-bit number \ \ V A 4-bit number (0 to 15), can be positive or negative \ (i.e. bit 7 contains the sign, bits 0-3 the magnitude) \ \ K Bit 7 determines whether to multiply the result by 2: \ \ * If bit 7 of K = 0, calculate (G W) = V * (S R) >> 8 \ \ * If bit 7 of K = 1, calculate (G W) = V * (S R) >> 7 \ \ ****************************************************************************** .Multiply4x16 LDX S \ Set X = S = %SSSSssss LDY V \ Set Y = V = %VVVVvvvv LDA shift4Left,Y \ Set A = V << 4 \ = %vvvv0000 ORA shift4Right,X \ Set Y = A OR (S >> 4) TAY \ = %vvvv0000 OR %0000SSSS \ = %vvvvSSSS AND #%11110000 \ Set U = (A AND %11110000) OR (X AND %00001111) ORA lowNibble,X \ = (%vvvvSSSS AND %11110000) OR (X AND %00001111) STA U \ = %vvvv0000 OR (%SSSSssss AND %00001111) \ = %vvvvssss LDX R \ Set X = R = %RRRRrrrr AND #%11110000 \ Set X = (A AND %1111000) OR (X >> 4) ORA shift4Right,X \ = (%vvvvssss AND %11110000) OR (%RRRRrrrr >> 4) TAX \ = %vvvv0000 OR %0000RRRR \ = %vvvvRRRR LDA timesTable,X \ Set X = %vvvv * %RRRR TAX \ \ Call this %XXXXxxxx STX T \ Set T = %XXXXxxxx LDA timesTable,Y \ Set Y = %vvvv * %SSSS TAY \ \ Call this %YYYYyyyy LDA shift4Right,X \ Set A = (X >> 4) OR (Y << 4) ORA shift4Left,Y \ = %0000XXXX OR %yyyy0000 \ = %yyyyXXXX CLC \ Set W = A + (%vvvv * %ssss) LDX U \ = %yyyyXXXX + (%vvvv * %ssss) ADC timesTable,X STA W LDA shift4Right,Y \ Set G = (Y >> 4) + carry ADC #0 \ = %0000YYYY + carry STA G \ So (G W) = %YYYYyyyyXXXX + (%vvvv * %ssss) \ = %YYYYyyyy0000 + %XXXX + (%vvvv * %ssss) \ = %vvvv * %SSSS << 4 \ + %vvvv * %RRRR >> 4 \ + %vvvv * %ssss \ = %vvvv * (%SSSS << 4 + %ssss + %RRRR >> 4) \ = %vvvv * (%SSSSssss + %RRRR >> 4) \ = %vvvv * (%SSSSssss + %RRRRrrrr >> 8) \ = %vvvv * (S R) >> 8 \ = V * (S R) >> 8 BIT K \ If bit 7 of K is clear, jump to mulp2 to skip the BPL mulp2 \ following and apply the correct sign to the result LDX R \ Set X = R = %RRRRrrrr LDA V \ Set Y = (V AND %00001111) OR (X << 3) AND #%00001111 \ = %0000vvvv OR %rrrr0000 ORA shift4Left,X \ = %rrrrvvvv TAY LDX T \ Set X = T = %XXXXxxxx LDA shift4Left,X \ Set A = (X << 4) + (%rrrr * %vvvv) CLC \ = %xxxx0000 + (%rrrr * %vvvv) ADC timesTable,Y \ = %vvvv * %RRRR << 4 + %rrrr * %vvvv \ = %vvvv * (%RRRR << 4 + %rrrr) \ = %vvvv * %RRRRrrrr \ = V * R BCC mulp1 \ If the addition didn't overflow, i.e. V * R < 256, \ jump to mulp1 to skip the following INC W \ Set (G W) = (G W) + 1 BNE mulp1 \ INC G \ to round the result up, as the low bytes of the \ multiplication produced a carry .mulp1 ASL A \ Set (G W A) = (G W A) * 2 ROL W \ ROL G \ so the result is doubled when bit 7 of K is set .mulp2 LDA V \ If V is positive, skip the following BPL mulp3 LDA #0 \ V is negative, so we now negate (G W) by subtracting SEC \ it from 0, first the low byte and then the high byte SBC W STA W LDA #0 SBC G STA G .mulp3 RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: SetObjPointCoords (Part 1 of 2) \ Type: Subroutine \ Category: 3D geometry \ Summary: Calculate the coordinate for a point within an object \ Deep dive: 3D objects \ Rotating and translating points in 3D space \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ GG The ID of the point to process and update \ \ matrixNumber The matrix to use in the calculation: \ \ * 0 = matrix 1 \ \ * 9 = matrix 2 \ \ * 18 = matrix 3 \ \ * 27 = matrix 4 \ \ objectAnchorPoint Point ID of the anchor point to which we add the final \ result to \ \ ------------------------------------------------------------------------------ \ \ Returns: \ \ xPointHi etc. Point GG is set to the point's coordinate in 3D space \ \ showLine If the calculation overflows, then the coordinate does \ not fit within the boundaries of Aviator's 3D world, so \ bit 6 is set to indicate that the line containing this \ point should not be shown \ \ xTemp1Hi etc. The rotated vector from the anchor point to the object \ point \ \ ****************************************************************************** .SetObjPointCoords LDX GG \ Set X to the point ID whose coordinates we want to \ calculate LDY xObjectPoint,X \ Set PP to the point's anchor-relative x-coordinate STY PP \ from xObjectPoint LDY yObjectPoint,X \ Set QQ to the point's anchor-relative y-coordinate STY QQ \ from yObjectPoint LDY zObjectPoint,X \ Set RR (and Y) to the point's anchor-relative STY RR \ z-coordinate from zObjectPoint, which also contains \ the scale factor in bits 4 to 7 LDA shift4Right,Y \ Set UU (and A) to Y >> 4, to extract the scale factor STA UU \ from bits 4 to 7 of the zObjectPoint entry CMP #9 \ If the scale factor in A >= 9, set bit 7 of K so the ROR K \ result of the call to Multiply4x16 below is doubled, \ i.e. (G W) is doubled. This gives us an extra factor \ of 2 on top of the maximum 2^8 factor we would get by \ left-shifting the result (see part 2 for the scaling \ code) LDX #5 \ We now zero (xTemp1, yTemp1, zTemp1), which is stored \ in six bytes to give us three 16-bit coordinate values \ (from xTemp1Lo to zTemp1Hi), so set a counter in X to \ count the bytes LDA #0 \ Set A = 0 to use as our zero .objp1 STA xTemp1Lo,X \ Zero the X-th byte of the six-byte coordinate block \ between xTemp1Lo and zTemp1Hi DEX \ Decrement the loop counter BPL objp1 \ Loop back until we have zeroed all six bytes \ We now do the matrix multiplication: \ \ [ xTemp1 ] [ m0 m1 m2 ] [ xObjectPoint ] \ [ yTemp1 ] = [ m3 m4 m5 ] x [ yObjectPoint ] \ [ zTemp1 ] [ m6 m7 m8 ] [ zObjectPoint ] \ \ We do this in three loops of three, using an outer and \ inner loop. We set two loop counters, VV and X, for \ the outer and inner loops respectively. They both \ iterate through 2, 1 and 0, with VV iterating through \ zTemp1, yTemp1 and xTemp1, and X iterating through \ zObjectPoint, yObjectPoint and xObjectPoint \ \ We also iterate a counter in P for the matrix entries, \ which counts down from m8 to m0, decreasing by 1 on \ each iteration \ \ All the iterators count backwards, so the calculations \ in order are: \ \ * zTemp1 += zObjectPoint * m8 \ * zTemp1 += yObjectPoint * m7 \ * zTemp1 += xObjectPoint * m6 \ \ * yTemp1 += zObjectPoint * m5 \ * yTemp1 += yObjectPoint * m4 \ * yTemp1 += xObjectPoint * m3 \ \ * xTemp1 += zObjectPoint * m2 \ * xTemp1 += yObjectPoint * m1 \ * xTemp1 += xObjectPoint * m0 \ \ Or, to switch it around the other way and plug in the \ initial value of (xTemp1, yTemp1, zTemp1) = (0, 0, 0), \ we get: \ \ * xTemp1 = m0 * xObjectPoint + m1 * yObjectPoint \ + m2 * zObjectPoint \ \ * yTemp1 = m3 * xObjectPoint + m4 * yObjectPoint \ + m5 * zObjectPoint \ \ * zTemp1 = m6 * xObjectPoint + m7 * yObjectPoint \ + m8 * zObjectPoint \ \ which gives us the matrix multiplication that we want \ to calculate LDA matrixNumber \ Set P = matrixNumber + 8 CLC \ ADC #8 \ In the following loop, P counts down from m8 to m0, STA P \ which we implement as an index that counts down from \ matrixNumber + 8 to matrixNumber, so that it iterates \ through the correct matrix table \ \ This works because matrixNumber contains 0 for matrix \ 1, 9 for matrix 2 and so on, and each matrix table, \ such as matrix1Lo, contains 9 entries LDA #2 \ Set VV = 2, to act as our outer loop counter that STA VV \ iterates through zTemp1, yTemp1 and xTemp1 .objp2 LDX #2 \ Set X = 2, to act as our inner loop counter that \ iterates through zObjectPoint, yObjectPoint and \ xObjectPoint (i.e. RR, QQ and PP) .objp3 LDY P \ Set Y = P, which is the number of the matrix element \ to multiply next LDA PP,X \ Set A = PP, QQ or RR (when X = 0, 1 or 2), which is \ the correct zObjectPoint, yObjectPoint or xObjectPoint \ to multiply next STA I \ Store A in I (this doesn't appear to be used) AND #%00001111 \ Set V = bits 0-3 of A, which removes the scale factor STA V \ in the case of zObjectPoint (the other points always \ fit into bits 0 to 3) BEQ objp5 \ If V = 0, jump to objp5 to move on to the next loop, \ as we already know the result of V * (S R) will be \ zero LDA matrix1Hi,Y \ Set S = P-th entry of matrix1Hi STA S LDA matrix1Lo,Y \ Set R = P-th entry of matrix1Lo STA R \ \ so now (S R) is the 16-bit matrix element that we want \ to multiply AND #1 \ If bit 0 of R is clear, then the matrix entry is BEQ objp4 \ positive, skip the following three instructions LDA V \ Bit 0 of R is set, which means this matrix entry is EOR #%10000000 \ negative, so set bit 7 of V to make it negative, to STA V \ give the result of the multiplication below the \ correct sign .objp4 STX Q \ Store the loop counter in X, so we can retrieve it \ after the call to Multiply4x16 JSR Multiply4x16 \ Call Multiply4x16 to calculate: \ \ (G W) = V * (S R) >> 8 if bit 7 of K = 0 \ \ (G W) = V * (S R) >> 7 if bit 7 of K = 1 \ \ K is only set to 1 if the scale factor is 9, in which \ case we effectively do one of the factors of 2 at this \ point (and the other 8 in part 2) LDX Q \ Restore the value of X LDY VV \ Fetch the outer loop counter from VV, which points to \ the relevant xTemp1, yTemp1 or zTemp1 coordinate LDA W \ Add (G W) to the xTemp1 coordinate, starting with the CLC \ low bytes ADC xTemp1Lo,Y STA xTemp1Lo,Y LDA G \ And then the high bytes, so we have the following (if ADC xTemp1Hi,Y \ we are working with xTemp1 and m0, for example): STA xTemp1Hi,Y \ \ xTemp1 += (G W) \ += V * (S R) \ += xObjectPoint * m0 \ \ which is the result we want for this element of the \ matrix multiplication .objp5 LDA P \ If P = matrixNumber then we have done all nine CMP matrixNumber \ calculations, so jump down to objp6 to apply the BEQ objp6 \ correct scale factor DEC P \ Otherwise we have more calculations to do, so \ decrement P to point to the next matrix entry DEX \ Decrement the inner loop counter to work our way \ through zObjectPoint, yObjectPoint and xObjectPoint BPL objp3 \ Loop back until we have worked through all three \ anchor-relative points DEC VV \ Decrement the outer loop counter to work our way \ through zTemp1, yTemp1 and xTemp1 JMP objp2 \ Jump back to objp2 to do the next calculation \ ****************************************************************************** \ \ Name: SetObjPointCoords (Part 2 of 2) \ Type: Subroutine \ Category: 3D geometry \ Summary: Apply the correct scale factor to the matrix multiplication \ Deep dive: 3D objects \ Rotating and translating points in 3D space \ \ ****************************************************************************** .objp6 \ We now want to apply the scale factor that we \ extracted into UU above. The scale factor is given as \ a power of 2, so a scale factor of n means we scale \ the result by 2^n, which we can do by shifting left \ by n places (where n is in the range 0 to 8) \ \ Note that n can also be 9, in which case we already \ doubled the result in part 1, so by this point we only \ need to shift a maximum of 8 places \ \ Note also that we are scaling up 16-bit numbers, so \ in order to capture these scaled-up values, we add a \ third byte to the significant end, giving us a 24-bit \ number to shift, and we take the top two bytes as our \ final result, discarding the least significant byte \ \ This means the result we end up with is divided by \ another 256 but fits into two bytes LDX #2 \ Set X = 2 to act as a loop counter, to iterate through \ 2, 1 and 0, which we use to work through zTemp1, \ yTemp1 and xTemp1, scaling each one separately \ \ The comments below refer to xTemp1, for simplicity .objp7 LDY UU \ Set Y = UU = %0000ZZZZ, which is our scale factor BEQ objp10 \ If UU = 0, then the scale factor is 2^0, or 1, so \ there is no scaling to do, so jump to objp10 to drop \ the least significant byte without any shifting CPY #8 \ If Y >= 8 then we can skip doing 8 shifts as the BCS objp9 \ result is already correct. To see this, consider the \ process below, which creates (0 xTemp1Hi xTemp1Lo) and \ shifts it left by the number of places, which would be \ this if Y = 8: \ \ (0 xTemp1Hi xTemp1Lo) << 8 = (xTemp1Hi xTemp1Lo 0) \ \ from which we would then drop the least significant \ byte to give (xTemp1Hi xTemp1Lo)... which is what we \ already have, so when Y >= 8 we can simply jump to \ objp9 to move on to the next axis \ \ (As noted above, if Y = 9 then we already doubled the \ result in part 1, so the above approach works for both \ Y = 8 and 9) LDA xTemp1Lo,X \ Set P to the low byte of xTemp1 STA P LDA #0 \ Set R = 0 to act as the new highest byte in our 24-bit STA R \ number LDA xTemp1Hi,X \ Set A to the high byte of xTemp1, so we now have the \ following 24-bit number: \ \ (R A P) = (0 xTemp1Hi xTemp1Lo) BPL objp8 \ If the high byte of xTemp1 is positive, skip the \ following instruction DEC R \ xTemp1Hi is negative, so decrement R to &FF so (R A P) \ is a correctly signed 24-bit negative number \ We now shift (R A P) left by Y places (where Y is in \ the range 1 to 7) .objp8 ASL P \ Set (R A P) = (R A P) << 1 ROL A ROL R DEY \ Decrement the shift counter in Y BNE objp8 \ Loop back to objp8 until we have shifted (R A P) left \ by Y places STA xTemp1Lo,X \ Set xTemp1 = (R A) LDA R \ STA xTemp1Hi,X \ so we drop the least significant byte, as discussed \ above .objp9 DEX \ Decrement the counter in X so we work through zTemp1, \ yTemp1 and xTemp1 BPL objp7 \ Loop back to objp7 until we have processed all three \ axes BMI objp12 \ Jump to objp12 to finish off (this BMI is effectively \ a JMP as X is now negative) .objp10 \ If we get here then the scale factor is 0, so we can \ simply drop the least significant byte without any \ shifting, as discussed above LDA #0 \ Set (R A) = (0 xTemp1Hi) STA R LDA xTemp1Hi,X BPL objp11 \ If xTemp1Hi is positive, skip the next instruction DEC R \ xTemp1Hi is negative, so decrement R to &FF so (R A) \ is a 16-bit negative number .objp11 STA xTemp1Lo,X \ Set xTemp1 = (R A) LDA R STA xTemp1Hi,X JMP objp9 \ Jump back to objp9 to move on to the next axis (i.e. \ yTemp1 or zTemp1) .objp12 \ Our vector in (xTemp1, yTemp1, zTemp1) is now scaled \ properly, so it's time for the addition: \ \ [ xPoint ] [ xAnchor ] [ xTemp1 ] \ [ yPoint ] = [ yAnchor ] + [ yTemp1 ] \ [ zPoint ] [ zAnchor ] [ zTemp1 ] LDX GG \ Set X to the point ID whose coordinates we want to \ calculate, so the original point is updated with the \ final result LDY objectAnchorPoint \ Set Y to the point ID of the object's anchor point \ Fall through into AddTempToPoint to add the anchor \ point to the (xTemp1, yTemp1, zTemp1) vector and store \ the result in (xPoint, yPoint, zPoint) \ ****************************************************************************** \ \ Name: AddTempToPoint (Part 1 of 2) \ Type: Subroutine \ Category: 3D geometry \ Summary: Add the xTemp1 vector to a point, store the result in another \ point, and set the result to hidden if it overflows \ \ ------------------------------------------------------------------------------ \ \ Set point X to the sum of the xTemp1 vector and point Y. In other words, add \ the following: \ \ (xTemp1 yTemp1 zTemp1) + point Y's coordinate in (xPoint, yPoint, zPoint) \ \ and store the results in point X's coordinate in (xPoint, yPoint, zPoint). \ \ We also set showLine to "hidden" if the addition overflows in any of the axes. \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ X The ID of the point in which to store the result \ \ Y The ID of the point to add to the xTemp1 vector \ \ ------------------------------------------------------------------------------ \ \ Returns: \ \ showLine If the calculation overflows, then the coordinate does \ not fit within the boundaries of Aviator's 3D world, so \ bit 6 is set to indicate that the line containing this \ point should not be shown \ \ ****************************************************************************** .AddTempToPoint LDA xTemp1Lo \ Set point X's x-coordinate to the following: CLC \ ADC xPointLo,Y \ (xTemp1Hi xTemp1Lo) + (xPointHi+Y xPointLo+Y) STA xPointLo,X \ LDA xTemp1Hi \ i.e. we add xTemp1 and point Y's x-coordinate ADC xPointHi,Y STA xPointHi,X PHP \ Store the flags for the x-axis addition on the stack, \ so we can check them in part 2 CLC \ Set point X's y-coordinate to the following: LDA yTemp1Lo \ ADC yPointLo,Y \ (yTemp1Hi yTemp1Lo) + (yPointHi+Y yPointLo+Y) STA yPointLo,X \ LDA yTemp1Hi \ i.e. we add yTemp1 and point Y's y-coordinate ADC yPointHi,Y STA yPointHi,X PHP \ Store the flags for the y-axis addition on the stack, \ so we can check them in part 2 CLC \ Set point X's z-coordinate to the following: LDA zTemp1Lo \ ADC zPointLo,Y \ (zTemp1Hi zTemp1Lo) + (zPointHi+Y zPointLo+Y) STA zPointLo,X \ LDA zTemp1Hi \ i.e. we add zTemp1 and point Y's z-coordinate ADC zPointHi,Y STA zPointHi,X JMP addv1 \ Jump to part 2 to set showLine according to the \ overflow flag from each of the results (so if any axes \ overflow, we hide the line containing the point) NOP \ This instruction is not used; perhaps at one point the \ JMP above was a JSR instruction, and this was an RTS? \ ****************************************************************************** \ \ Name: Add16x16Bit0 \ Type: Subroutine \ Category: Maths \ Summary: Add two 16-bit numbers that have their signs in bit 0 \ \ ------------------------------------------------------------------------------ \ \ This routine adds two 16-bit numbers, both of which have their signs in bit 0 \ of the low byte. It calculates: \ \ (S R) = (H G) + (J I) \ \ The result in (S R) has the sign in bit 0 of the low byte. \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ (H G) A signed 16-bit number, with the sign in bit 0 of G \ \ (J I) A signed 16-bit number, with the sign in bit 0 of I \ \ ------------------------------------------------------------------------------ \ \ Returns: \ \ (S R) The result of the multiplication, with the sign in bit 0 \ of R \ \ K The sign of the result (in bit 0) \ \ ****************************************************************************** .Add16x16Bit0 LDA G \ Set K to the sign of G (from bit 0) AND #1 STA K EOR I \ If G and I have different sign bits, then this will AND #1 \ set A to 1, in which case jump to abit2 BNE abit2 \ If we get here then the two arguments (H G) and (J I) \ have the same sign, which is stored in K LDA G \ Set (S R) = (H G) + (J I) CLC \ ADC I \ starting with the low bytes, setting bit 0 of the AND #%11111110 \ result to the sign in K (as the result of adding two ORA K \ numbers with sign K will have sign K) STA R LDA H \ Then adding the high bytes ADC J STA S BCC abit1 \ If the addition didn't overflow, jump to abit1 to \ return from the subroutine, as the result is correct LDA #%11111111 \ The addition overflowed, so we return the maximum STA S \ value in (R S), which has every bit set except for the LDA #%11111110 \ sign bit, which is set to K ORA K STA R .abit1 RTS \ Return from the subroutine .abit2 \ If we get here then the two arguments (H G) and (J I) \ have different signs, with the sign of (H G) stored in \ K LDA G \ Set (S R) = (H G) - (J I) SEC \ SBC I \ starting with the low bytes STA R LDA H \ And then the high bytes SBC J STA S BCC abit3 \ If the subtraction underflowed, jump to abit3 \ Otherwise the result is correct apart from the sign, \ and the sign of the result should be that of (H G), as \ (H G) is the larger value (i.e. it has the highest \ magnitude), as otherwise the subtraction would \ underflow LDA R \ Set the sign bit of (S R) to K, as that contains the AND #%11111110 \ sign of (H G) ORA K STA R RTS \ Return from the subroutine .abit3 \ If we get here then the (H G) - (J I) subtraction \ underflowed, so (J I) is the larger value (i.e. it has \ the highest magnitude) \ \ We therefore need to negate the subtraction result to \ get (J I) - (H G), and the sign of the final result \ should be given the sign of (J I), as that's the \ dominant part... and as K contains the sign of (H G) \ and we know (H G) and (J I) have opposite signs, we \ should therefore give the result the opposite sign to \ the sign in K LDA #0 \ Negate the (S R) to get the correct result, SEC \ SBC R \ (S R) = 0 - (S R) AND #%11111110 \ ORA K \ while also setting the sign in bit 0 to the opposite EOR #1 \ of K STA R LDA #0 SBC S STA S RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: SetMatrices \ Type: Subroutine \ Category: 3D geometry \ Summary: Set up matrices 1 to 4 \ Deep dive: Rotation matrices \ \ ------------------------------------------------------------------------------ \ \ This routine populates the four matrix tables with 16-bit values. \ \ Matrix 1 is the most complex matrix, and contains the rotation matrix for \ rotating a point around the origin by the plane's roll, then pitch, then yaw. \ It is constructed from the coordinates produced by the ProjectAxisAngle \ routine as follows: \ \ [ (mx2 * my2 * mz2) -mx1 * mz2 (mx2 * my1 * mz2) ] \ [ + (my1 * mz1) - (my2 * mz1) ] \ [ ] \ [ -(mx2 * my2 * mz1) mx1 * mz1 -(mx2 * my1 * mz1) ] \ [ + (my1 * mz2) - (my2 * mz2) ] \ [ ] \ [ mx1 * my2 mx2 mx1 * my1 ] \ \ Matrix 2 is the transpose of matrix 1: \ \ [ m0 m3 m6 ] \ [ m1 m4 m7 ] \ [ m2 m5 m8 ] \ \ and therefore represents the inverse transformation of matrix 1. \ \ Matrix 3 is as follows: \ \ [ mz1 -mz2 0 ] \ [ mz2 mz1 0 ] \ [ 0 0 1 ] \ \ This is a rotation matrix that rotates through the plane's roll angle. The \ zeroes and 1 are hardcoded in memory, with the latter being stored as &FFFE = \ %11111111 11111110, with the sign in bit 0. \ \ Matrix 4 contains an interim step from the calculation of matrix 1, and \ contains the rotation matrix for the roll-and-pitch rotation (i.e. without any \ yaw). It looks like this: \ \ [ mz1 -mx1 * mz2 mx2 * mz2 ] \ [ mz2 mx1 * mz1 -mx2 * mz1 ] \ [ 0 mx2 mx1 ] \ \ The m6 entry is not set by any code, but it contains a zero in the source code \ and stays that way. \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ matrixNumber The routine is only ever called with matrixNumber = 0, \ so it only ever writes to matrices 1 to 4 - presumably \ the need for this argument was dropped at some point \ \ matrixAxis The routine is only ever called with matrixAxis = 0, \ so it only ever reads from mx1 and mx2 - presumably \ the need for this argument was dropped at some point \ \ ****************************************************************************** .SetMatrices LDX matrixAxis \ Set X to the matrix axis (this routine is only ever \ called with a matrixAxis of 0, so this sets X = 0 \ We first set up a load of temporary variables that we \ can use to populate the various matrices, storing them \ in the matrix 1 variables (we will populate matrix 1 \ properly after we have populated matrices 2, 3 and 4) LDA mx1Lo,X \ Set (J I) = -(mx1Hi mx1Lo) EOR #1 \ = -mx1 STA I \ LDA mx1Hi,X \ by flipping the sign in bit 0 STA J LDX #5 \ Set m1 = (J I) * mz2 / 256 LDY #1 \ = -mx1 * mz2 / 256 JSR SetMatrixEntry \ \ Also set (S R) = mz2 LDX #1 \ Set m3 = (S R) * my1 / 256 LDY #3 \ = mz2 * my1 / 256 JSR SetMatrixEntry2 LDX #3 \ Set m2 = (S R) * mx2 / 256 LDY #2 \ = mz2 * mx2 / 256 JSR SetMatrixEntry2 LDX #4 \ Set m0 = (S R) * my2 / 256 LDY #0 \ = mz2 * my2 / 256 JSR SetMatrixEntry2 \ \ Also set (J I) = my2 LDX #0 \ Set m6 = (J I) * mx1 / 256 LDY #6 \ = my2 * mx1 / 256 JSR SetMatrixEntry \ \ Also set (S R) = mx1 LDX #1 \ Set m8 = (S R) * my1 / 256 LDY #8 \ = mx1 * my1 / 256 JSR SetMatrixEntry2 LDX #2 \ Set m4 = (S R) * mz1 / 256 LDY #4 \ = mx1 * mz1 / 256 JSR SetMatrixEntry2 \ \ Also set (J I) = mz1 LDX #3 \ Set m5 = (J I) * -mx2 / 256 LDY #5 \ = mz1 * -mx2 / 256 JSR SetMatrixEntry3 \ \ Also set (H G) = m5 \ So we now have the following set of values (stripping \ out the division by 256 and reordering the \ multiplications for clarity): \ \ m0 = my2 * mz2 \ m1 = -mx1 * mz2 \ m2 = mx2 * mz2 \ m3 = my1 * mz2 \ m4 = mx1 * mz1 \ m5 = -mx2 * mz1 \ m6 = mx1 * my2 \ m8 = mx1 * my1 \ \ Note that we didn't set m7 in the above, and also note \ that in the following, we update some of these values, \ so, for example, m0 and m2 change part way through the \ calculation process (which I have pointed out) \ \ We now work our way through the matrices as follows: \ \ * Populate matrix 3 (which is hardcoded apart from \ the 2x2 matrix in the top-left corner) \ \ * Populate matrix 4 (except m6, which is hardcoded) \ \ * Populate matrix 1 (by overwriting some of the \ m0-m8 variables we just set, but keeping others) \ \ * Set matrix 2 to the transpose of matrix 1 LDX matrixAxis \ Set X = 0 LDY matrixNumber \ Set Y = 0 LDA mz1Lo,X \ Set m0 in matrix 3 = mz1 STA matrix3Lo,Y \ m4 in matrix 3 = mz1 STA matrix3Lo+4,Y \ m0 in matrix 4 = mz1 STA matrix4Lo,Y LDA mz1Hi,X STA matrix3Hi,Y STA matrix3Hi+4,Y STA matrix4Hi,Y LDA mz2Lo,X \ Set m3 in matrix 3 = mz2 STA matrix3Lo+3,Y \ m3 in matrix 4 = mz2 STA matrix4Lo+3,Y \ m1 in matrix 3 = -mz2 EOR #1 STA matrix3Lo+1,Y LDA mz2Hi,X STA matrix3Hi+1,Y STA matrix3Hi+3,Y STA matrix4Hi+3,Y LDA mx1Lo,X \ Set m8 in matrix 4 = mx1 STA matrix4Lo+8,Y LDA mx1Hi,X STA matrix4Hi+8,Y LDA mx2Lo,X \ Set m7 in matrix 4 = mx2 STA matrix4Lo+7,Y LDA mx2Hi,X STA matrix4Hi+7,Y \ The following loop does the following: \ \ * Set m1 in matrix 4 = m1 in matrix 1 = -mx1 * mz2 \ * Set m2 in matrix 4 = m2 in matrix 1 = mx2 * mz2 \ * Set m4 in matrix 4 = m4 in matrix 1 = mx1 * mz1 \ * Set m5 in matrix 4 = m5 in matrix 1 = -mx2 * mz1 LDY #5 \ Set a loop counter in Y to count through 5, 4, 2, 1 .smat1 CPY #3 \ If Y = 3 then jump to smat2 to skip this iteration BEQ smat2 LDA matrix1Lo,Y \ Set mY in matrix 4 = mY in matrix 1 STA matrix4Lo,Y LDA matrix1Hi,Y STA matrix4Hi,Y .smat2 DEY \ Decrement the loop counter BNE smat1 \ Loop back until we have copied all four entries from \ matrix 1 to matrix 4 LDA G \ Set (J I) = (H G) STA I \ = m5 LDA H \ = -mx2 * mz1 STA J LDX matrixAxis \ Set X = 0 LDA my2Lo,X \ Set (S R) = my2 STA R LDA my2Hi,X STA S JSR Multiply16x16Bit0 \ Set (H G) = (J I) * (S R) >> 16 \ = -(mx2 * mz1 * my2) >> 16 LDY matrixNumber \ Set Y = 0 LDA matrix1Lo+3,Y \ Set (J I) = m3 STA I \ = my1 * mz2 LDA matrix1Hi+3,Y STA J JSR Add16x16Bit0 \ Set (S R) = (H G) + (J I) \ = -(mx2 * mz1 * my2) + (my1 * mz2) LDA R \ Set m3 = (S R) STA matrix1Lo+3,Y LDA S STA matrix1Hi+3,Y LDA matrix1Lo+5,Y \ Set (J I) = m5 STA I \ = -mx2 * mz1 LDA matrix1Hi+5,Y STA J LDX matrixAxis \ Set X = 0 LDA my1Lo,X \ Set (S R) = my1 STA R LDA my1Hi,X STA S JSR Multiply16x16Bit0 \ Set (H G) = (J I) * (S R) >> 16 \ = -(mx2 * mz1 * my1) >> 16 LDY matrixNumber \ Set Y = 0 LDA matrix1Lo,Y \ Set (J I) = -m0 EOR #1 \ = -my2 * mz2 STA I LDA matrix1Hi,Y STA J JSR Add16x16Bit0 \ Set (S R) = (H G) + (J I) \ = -(mx2 * mz1 * my1) - (my2 * mz2) LDA R \ Set m5 = (S R) STA matrix1Lo+5,Y LDA S STA matrix1Hi+5,Y LDX matrixAxis \ Set X = 0 LDA my1Lo,X \ Set (S R) = my1 STA R LDA my1Hi,X STA S LDX #2 \ Set m0 = (S R) * mz1 / 256 LDY #0 \ = my1 * mz1 / 256 JSR SetMatrixEntry2 \ \ Also set (J I) = mz1 LDX matrixAxis \ Set X = 0 LDA my2Lo,X \ Set (S R) = -my2 EOR #1 STA R LDA my2Hi,X STA S JSR Multiply16x16Bit0 \ Set (H G) = (J I) * (S R) >> 16 \ = mz1 * -my2 >> 16 LDY matrixNumber \ Set Y = 0 LDA matrix1Lo+2,Y \ Set (J I) = m2 STA I \ = mx2 * mz2 STA xTemp1Lo \ LDA matrix1Hi+2,Y \ Also set xTemp1 = m2 STA J \ = mx2 * mz2 STA xTemp1Hi LDA G \ Set m2 = (H G) STA matrix1Lo+2,Y \ = -my2 * mz1 LDA H STA matrix1Hi+2,Y LDA R \ Set (S R) = -(S R) EOR #1 \ = my2 STA R JSR Multiply16x16Bit0 \ Set (H G) = (J I) * (S R) >> 16 \ = mx2 * mz2 * my2 >> 16 LDY matrixNumber \ Set Y = 0 LDA matrix1Lo,Y \ Set (J I) = m0 STA I \ = my1 * mz1 LDA matrix1Hi,Y \ STA J \ (note that we updated m0 above, so this is different \ to the first value we gave m0) JSR Add16x16Bit0 \ Set (S R) = (H G) + (J I) \ = (mx2 * mz2 * my2) + (my1 * mz1) LDA R \ Set m0 = (S R) STA matrix1Lo,Y LDA S STA matrix1Hi,Y LDA xTemp1Lo \ Set (S R) = xTemp1 STA R \ = mx2 * mz2 LDA xTemp1Hi STA S LDX matrixAxis \ Set X = 0 LDA my1Lo,X \ Set (J I) = my1 STA I LDA my1Hi,X STA J JSR Multiply16x16Bit0 \ Set (H G) = (J I) * (S R) >> 16 \ = my1 * mx2 * mz2 >> 16 LDY matrixNumber \ Set Y = 0 LDA matrix1Lo+2,Y \ Set (J I) = m2 STA I \ = -my2 * mz1 LDA matrix1Hi+2,Y \ STA J \ (note that we updated m2 above, so this is different \ to the first value we gave m2) JSR Add16x16Bit0 \ Set (S R) = (H G) + (J I) \ = (my1 * mx2 * mz2) - (my2 * mz1) LDA R \ Set m2 = (S R) STA matrix1Lo+2,Y \ = (my1 * mx2 * mz2) - (my2 * mz1) LDA S STA matrix1Hi+2,Y LDX matrixAxis \ Set X = 0 LDA mx2Lo,X \ Set m7 = mx2 STA matrix1Lo+7,Y LDA mx2Hi,X STA matrix1Hi+7,X \ We now set matrix 2 to the transpose of matrix 1, so \ if matrix 1 looks like this: \ \ [ m0 m1 m2 ] \ [ m3 m4 m5 ] \ [ m6 m7 m8 ] \ \ then matrix 2 looks like this: \ \ [ m0 m3 m6 ] \ [ m1 m4 m7 ] \ [ m2 m5 m8 ] \ \ This sets matrix 2 to the inverse of matrix 1 LDA #2 \ We transpose the matrix by looping through each column STA T \ in matrix 1 and copy it into the corresponding column \ in matrix 2, so set a loop counter in T for each row LDY matrixNumber \ Set Y = 0 to loop through the start of each row in \ matrix 1, so it iterates through 0, 3 and 6 LDX matrixNumber \ Set X = 0 to loop through the top of each column in \ matrix 2, so it iterates through 0, 1 and 2 .smat3 \ The following blocks iterate through the nine copies \ (three in each loop) as follows: LDA matrix1Lo,Y \ Set m0 in matrix 2 = m0 in matrix 1 STA matrix2Lo,X \ m1 = m3 LDA matrix1Hi,Y \ m2 = m6 STA matrix2Hi,X LDA matrix1Lo+1,Y \ Set m3 in matrix 2 = m1 in matrix 1 STA matrix2Lo+3,X \ m4 = m4 LDA matrix1Hi+1,Y \ m5 = m7 STA matrix2Hi+3,X LDA matrix1Lo+2,Y \ Set m6 in matrix 2 = m2 in matrix 1 STA matrix2Lo+6,X \ m7 = m5 LDA matrix1Hi+2,Y \ m8 = m8 STA matrix2Hi+6,X INY \ Set Y = Y + 3 INY \ INY \ to point to the start of the next row in matrix 1 INX \ Set X = X + 1 \ \ to point to the top of the next column in matrix 2 DEC T \ Decrement the loop counter BPL smat3 \ Loop back until we have copied all nine entries from \ matrix 1 to matrix 2 RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: SetMatrixEntry \ Type: Subroutine \ Category: 3D geometry \ Summary: Calculate a matrix entry \ \ ------------------------------------------------------------------------------ \ \ This routine reads one of the projected coordinate values that we calculated \ in ProjectAxisAngle (in mx1 to mz2), performs a multiplication, and writes the \ result to the specified entry in matrix 1. \ \ If mRead is the matrix entry in X (from mx1 to mz2) and mWrite is the matrix \ entry in Y (from m0 to m8), then it calculates the following: \ \ mWrite = (J I) * mRead >> 16 \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ X The matrix entry to read (mRead): \ \ * 0 = mx1 \ \ * 1 = my1 \ \ * 2 = mz1 \ \ * 3 = mx2 \ \ * 4 = my2 \ \ * 5 = mz2 \ \ Y The matrix entry to write (mWrite) in matrix 1: \ \ * 0 = m0 \ \ * 1 = m1 \ \ * 2 = m2 \ \ ... \ \ * 7 = m7 \ \ * 8 = m8 \ \ matrixNumber The routine is only ever called with matrixNumber = 0, \ so it only ever writes to matrix 1, but calling the \ routine with a different value would allow us to write \ to a different matrix \ \ matrixAxis The routine is only ever called with matrixAxis = 0, \ which has no effect on the calculation, but calling the \ routine with a non-zero value of matrixAxis would allow \ different mRead values to be used, i.e. matrixAxis+X \ \ ------------------------------------------------------------------------------ \ \ Returns: \ \ mWrite Set to (J I) * mRead >> 16 \ \ (S R) The value of mRead \ \ (H G) The value written to mWrite \ \ ------------------------------------------------------------------------------ \ \ Other entry points: \ \ SetMatrixEntry2 Set mWrite = (S R) * mRead >> 16 and (J I) = mRead \ \ SetMatrixEntry3 Set mWrite = (J I) * -mRead >> 16 and (S R) = -mRead \ \ ****************************************************************************** .SetMatrixEntry TXA \ Set X = matrixAxis + X CLC \ ADC matrixAxis \ This has no effect when matrixAxis = 0, which is the TAX \ only value that this routine is called with LDA mx1Lo,X \ Set R = the low byte from the matrix entry to read STA R .smen1 LDA mx1Hi,X \ Set (S R) = the matrix entry to read STA S JMP smen2 \ Jump down to smen2 to do the calculation .SetMatrixEntry3 TXA \ Set X = matrixAxis + X CLC \ ADC matrixAxis \ This has no effect when matrixAxis = 0, which is the TAX \ only value that this routine is called with LDA mx1Lo,X \ Set R = the low byte from the matrix entry to read, EOR #1 \ with the sign in bit 0 flipped STA R JMP smen1 \ Jump up to smen1 to set the high byte .SetMatrixEntry2 TXA \ Set X = matrixAxis + X CLC \ ADC matrixAxis \ This has no effect when matrixAxis = 0, which is the TAX \ only value that this routine is called with LDA mx1Lo,X \ Set (J I) = the matrix entry to read STA I LDA mx1Hi,X STA J .smen2 TYA \ Set N = Y + matrixNumber CLC \ ADC matrixNumber \ This sets N = Y when matrixNumber = 0, which is the STA N \ only value that this routine is called with JSR Multiply16x16Bit0 \ Set (H G) = (J I) * (S R) >> 16 LDY N \ Fetch the index of the matrix entry to write that we \ stored in N above LDA G \ Store (H G) in the matrix entry to write STA matrix1Lo,Y LDA H STA matrix1Hi,Y RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: Multiply16x16 \ Type: Subroutine \ Category: Maths \ Summary: Multiply two unsigned 16-bit numbers \ Deep dive: Times tables and nibble arithmetic \ \ ------------------------------------------------------------------------------ \ \ This routine multiplies two unsigned 16-bit numbers: \ \ (H A) = (J I) * (S R) >> 16 \ \ The fractional part of the result is in W, so this is the full result, though \ in practice W is ignored: \ \ (H A W) = (J I) * (S R) >> 8 \ \ The fractional part is rounded up or down, to the nearest integer, by adding \ 128 at mult1. This part of the code is modified by the ApplyAerodynamics \ routine to toggle this rounding behaviour. \ \ If the C flag is set on return, then the result in H needs to be incremented, \ so technically the result is: \ \ (H+C A) = (J I) * (S R) >> 16 \ \ The multiplication is done using the following algorithm: \ \ (J I) * (S R) = (J << 8 + I) * (S << 8 + R) \ = (J << 8 * S << 8) + (J << 8 * R) \ + (I * S << 8) \ + (I * R) \ = (J * S) << 16 + (J * R) << 8 \ + (I * S) << 8 \ + (I * R) \ \ This returns a fractional result with the lowest byte containing the fraction. \ The routine doesn't care about the very lowest byte, so it actually calculates \ the following, effectively shifting the above to the right by 8 places and \ dropping the (I * R) part: \ \ (J I) * (S R) = (J * S) << 8 + (J * R) + (I * S) \ \ before adding 128 to round the result up or down. \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ (S R) An unsigned 16-bit number \ \ (J I) An unsigned 16-bit number \ \ ------------------------------------------------------------------------------ \ \ Returns: \ \ (H A) The result of the multiplication \ \ C flag If set, H should be incremented by the caller to get the \ correct result \ \ ****************************************************************************** .Multiply16x16 LDX J \ Set (A V) = X * Y LDY R \ = J * R JSR Multiply8x8 STA G \ Set (G A) = (A V) LDA V \ = J * R CLC \ Clear the C flag for the following addition .mult1 ADC #128 \ Set (G W) = (G A) + 128 STA W \ \ starting with the low bytes \ \ The ADC #128 instruction gets modified by the \ ApplyAerodynamics routine as follows: \ \ * ADC #0 disables the rounding \ \ * ADC #128 enables the rounding BCC mult2 \ And then the high bytes, so we now have: INC G \ \ (G W) = (J * R) + 128 .mult2 LDX S \ Set (A V) = X * Y LDY J \ = S * J JSR Multiply8x8 STA H \ Set (H A) = (A V) LDA V \ = S * J \ We now do the following addition: \ \ (H G W) = (H A) << 8 + (G W) \ = (H A 0) + (G W) \ \ We don't need to do the lowest byte, as W + 0 is just \ W again, so we can just do the following: \ \ (H G) = (H A) + G \ \ and then keep W as the lowest significant byte of the \ result CLC \ Set (H G) = (H A) + G ADC G \ STA G \ starting with the low bytes BCC mult3 \ And then the high bytes, so we now have: INC H \ \ (H G) = (H A) + G \ \ which is the same as: \ \ (H G W) = (H A) << 8 + (G W) \ = (S * J) << 8 + (J * R) + 128 .mult3 LDX S \ Set (A V) = X * Y LDY I \ = S * I JSR Multiply8x8 STA T \ Set (T A) = (A V) LDA V \ = S * I \ We now do the following addition: \ \ (H A W) = (T A) + (H G W) \ \ though we don't actually do the highest byte, but \ instead return the C flag depending on whether the \ addition of the middle bytes overflowed (in which case \ the highest byte in H needs incrementing, a task we \ leave to the caller) CLC \ Set (A W) = (T A) + (G W) ADC W \ STA W \ starting with the low bytes LDA T \ And then the high bytes, so we now have: ADC G \ \ (H A W) = (T A) + (H G W) \ = (S * I) + (S * J) << 8 + (J * R) + 128 \ \ which is the result we need. The C flag is set if the \ last addition overflowed, in which case H needs to be \ incremented by the caller to get the final result RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: Multiply16x16Bit0 \ Type: Subroutine \ Category: Maths \ Summary: Multiply two 16-bit numbers that have their signs in bit 0 \ \ ------------------------------------------------------------------------------ \ \ This routine multiplies two 16-bit numbers, both of which have their signs in \ bit 0 of the low byte. It calculates: \ \ (H G) = (J I) * (S R) >> 16 \ \ The result in (H G) has the sign in bit 0 of the low byte. \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ (J I) A signed 16-bit number, with the sign in bit 0 of I \ \ (S R) A signed 16-bit number, with the sign in bit 0 of R \ \ ------------------------------------------------------------------------------ \ \ Returns: \ \ (H G) The result of the multiplication, with the sign in bit 0 \ of G \ \ K The sign of the result (in bit 0) \ \ ****************************************************************************** .Multiply16x16Bit0 LDA R \ Extract the sign of (J I) * (S R) from bit 0 of I and EOR I \ bit 0 of R, and store the result in K AND #%00000001 STA K JSR Multiply16x16 \ Calculate (H A) = (S R) * (J I) >> 16 \ \ and set the C flag if we need to increment H AND #%11111110 \ Set bit 0 of A to the sign we stored in K above, so ORA K \ (H A) has the correct sign of the multiplication STA G \ Set (H G) = (H A) \ = (S R) * (J I) >> 16 BCC mbit1 \ Increment the top byte in H if required INC H .mbit1 RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: SetPointCoords \ Type: Subroutine \ Category: 3D geometry \ Summary: Calculate the coordinates for a point \ Deep dive: Lines and points \ Rotating and translating points in 3D space \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ GG The ID of the point to process \ \ matrixNumber The matrix to use in the calculation: \ \ * 0 = matrix 1 \ \ * 9 = matrix 2 \ \ * 18 = matrix 3 \ \ * 27 = matrix 4 \ \ xPointHi etc. The point's coordinates before rotation \ \ ------------------------------------------------------------------------------ \ \ Returns: \ \ xPointHi etc. The point's coordinates after rotation \ \ xTemp1Hi etc. The point's coordinates after rotation \ \ ****************************************************************************** .SetPointCoords LDX GG \ Set X to the point ID whose coordinates we want to \ calculate LDA xPointLo,X \ Set (SS PP) to the point's x-coordinate STA PP LDA xPointHi,X STA SS LDA yPointLo,X \ Set (TT QQ) to the point's y-coordinate STA QQ LDA yPointHi,X STA TT LDA zPointLo,X \ Set (UU RR) to the point's z-coordinate STA RR LDA zPointHi,X STA UU LDX #5 \ We now zero (xTemp1, yTemp1, zTemp1), which is stored \ in six bytes to give us three 16-bit coordinate values \ (from xTemp1Lo to zTemp1Hi), so set a counter in X to \ count the bytes LDA #0 \ Set A = 0 to use as our zero .pcrd1 STA xTemp1Lo,X \ Zero the X-th byte of the six-byte coordinate block \ between xTemp1Lo and zTemp1Hi DEX \ Decrement the loop counter BPL pcrd1 \ Loop back until we have zeroed all six bytes \ We now do the matrix multiplication: \ \ [ xTemp1 ] [ m0 m1 m2 ] [ xPoint ] \ [ yTemp1 ] = [ m3 m4 m5 ] x [ yPoint ] \ [ zTemp1 ] [ m6 m7 m8 ] [ zPoint ] \ \ We do this in three loops of three, using an outer and \ inner loop. We set two loop counters, VV and X, for \ the outer and inner loops respectively. They both \ iterate through 2, 1 and 0, with VV iterating through \ zTemp1, yTemp1 and xTemp1, and X iterating through \ zPoint, yPoint and xPoint \ \ We also iterate a counter in P for the matrix entries, \ which counts down from m8 to m0, decreasing by 1 on \ each iteration \ \ All the iterators count backwards, so the calculations \ in order are: \ \ * zTemp1 += zPoint * m8 \ * zTemp1 += yPoint * m7 \ * zTemp1 += xPoint * m6 \ \ * yTemp1 += zPoint * m5 \ * yTemp1 += yPoint * m4 \ * yTemp1 += xPoint * m3 \ \ * xTemp1 += zPoint * m2 \ * xTemp1 += yPoint * m1 \ * xTemp1 += xPoint * m0 \ \ Or, to switch it around the other way and plug in the \ initial value of (xTemp1, yTemp1, zTemp1) = (0, 0, 0), \ we get: \ \ * xTemp1 = m0 * xPoint + m1 * yPoint + m2 * zPoint \ \ * yTemp1 = m3 * xPoint + m4 * yPoint + m5 * zPoint \ \ * zTemp1 = m6 * xPoint + m7 * yPoint + m8 * zPoint \ \ which gives us the matrix multiplication that we want \ to calculate LDA matrixNumber \ Set P = matrixNumber + 8 CLC \ ADC #8 \ In the following loop, P counts down from m8 to m0, STA P \ which we implement as an index that counts down from \ matrixNumber + 8 to matrixNumber, so that it iterates \ through the correct matrix table \ \ This works because matrixNumber contains 0 for matrix \ 1, 9 for matrix 2 and so on, and each matrix table, \ such as matrix1Lo, contains 9 entries LDA #2 \ Set VV = 2, to act as our outer loop counter that STA VV \ iterates through zTemp1, yTemp1 and xTemp1 .pcrd2 LDX #2 \ Set X = 2, to act as our inner loop counter that \ iterates through zPoint, yPoint and xPoint .pcrd3 LDY P \ Set Y = P, which is the number of the matrix element \ to multiply next LDA matrix1Hi,Y \ Set S = P-th entry of matrix1Hi STA S BNE pcrd4 LDA matrix1Lo,Y \ If the P-th entry of matrix1Lo is < 5, jump to pcrd5 CMP #5 \ to move on to the next loop BCC pcrd5 .pcrd4 LDA matrix1Lo,Y \ Set R = P-th entry of matrix1Lo STA R \ \ so now (S R) is the 16-bit matrix element that we want \ to multiply LDA PP,X \ Set I = PP, QQ or RR (when X = 0, 1 or 2), which is STA I \ the correct zPointLo, yPointLo or xPointLo to multiply \ next LDA SS,X \ Set J = SS, TT or UU (when X = 0, 1 or 2), which is STA J \ the correct zPointHi, yPointHi or xPointHi to multiply \ next so now (J I) is the 16-bit point coordinate that \ we want to multiply LDA #0 \ Set K = 0, so Multiply16x16Mix doesn't negate the STA K \ result STX Q \ Store the loop counter in X, so we can retrieve it \ after the call to Multiply16x16Mix JSR Multiply16x16Mix \ Call Multiply16x16Mix to calculate: \ \ (H G) = (J I) * (S R) >> 16 LDX Q \ Restore the value of X LDY VV \ Fetch the outer loop counter from VV, which points to \ the relevant xTemp1, yTemp1 or zTemp1 coordinate LDA G \ Add (H G) to the xTemp1 coordinate, starting with the CLC \ low bytes ADC xTemp1Lo,Y STA xTemp1Lo,Y LDA H \ And then the high bytes, so we have the following (if ADC xTemp1Hi,Y \ we are working with xTemp1 and m0, for example): STA xTemp1Hi,Y \ \ xTemp1 += (H G) \ += (J I) * (S R) \ += xPoint * m0 \ \ which is the result we want for this element of the \ matrix multiplication BVC pcrd5 LDA #%01000000 \ The addition overflowed for this axis, so set bit 6 of STA showLine \ showLine so the line containing this point is marked \ as being hidden .pcrd5 LDY P \ If P = matrixNumber then we have done all nine CPY matrixNumber \ calculations, so jump down to objp6 to update the BEQ pcrd6 \ point's coordinates with the result DEC P \ Otherwise we have more calculations to do, so \ decrement P to point to the next matrix entry DEX \ Decrement the inner loop counter to work our way \ through zPoint, yPoint and xPoint BPL pcrd3 \ Loop back until we have worked through all three \ anchor-relative points DEC VV \ Decrement the outer loop counter to work our way \ through zTemp1, yTemp1 and xTemp1 JMP pcrd2 \ Jump back to objp2 to do the next calculation .pcrd6 LDX GG \ Set X to the point ID whose coordinates we want to \ calculate, so the original point is updated with the \ final result \ Fall through into CopyTempToPoint to copy the result \ from (xTemp1, yTemp1, zTemp1) to (xPoint, yPoint, \ zPoint) \ ****************************************************************************** \ \ Name: CopyTempToPoint \ Type: Subroutine \ Category: Utility routines \ Summary: Set a specified point to (xTemp1, yTemp1, zTemp1) \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ X The ID of the point to set to (xTemp1, yTemp1, zTemp1) \ \ ****************************************************************************** .CopyTempToPoint LDA xTemp1Lo \ Set point X's x-coordinate to (xTemp1Hi xTemp1Lo) STA xPointLo,X LDA xTemp1Hi STA xPointHi,X LDA yTemp1Lo \ Set point X's y-coordinate to (yTemp1Hi yTemp1Lo) STA yPointLo,X LDA yTemp1Hi STA yPointHi,X LDA zTemp1Lo \ Set point X's z-coordinate to (zTemp1Hi zTemp1Lo) STA zPointLo,X LDA zTemp1Hi STA zPointHi,X RTS \ Return from the subroutine EQUB &00, &49, &60 \ These bytes appear to be unused, and simply repeat the \ last three bytes from above (i.e. the last two bytes \ of STA zPointHi,X and the RTS instruction) \ ****************************************************************************** \ \ Name: UpdateIndicator (Part 1 of 15) \ Type: Subroutine \ Category: Dashboard \ Summary: Update a single indicator on the dashboard \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ X Indicator number: \ \ * 0 = Compass \ \ * 1 = Airspeed indicator \ \ * 2 = Altimeter \ Small "hour" hand, whole dial = 10,000 feet \ \ * 3 = Altimeter \ Large "minute" hand, whole dial = 1,000 feet \ \ * 4 = Vertical speed indicator \ \ * 5 = Turn indicator \ Bottom part of the slip-and-turn indicator \ \ * 6 = Slip indicator \ Top part of the slip-and-turn indicator \ \ * 7 = Artificial horizon \ \ * 8 or 10 = Joystick position display \ \ * 9 = Rudder indicator \ \ * 11 = Thrust indicator \ \ ****************************************************************************** .UpdateIndicator STX WW \ Set WW to the value in X, so we can refer to it later \ if we overwrite the value in X CPX #0 \ If X = 0, jump down to uind1 to update indicator 0 BEQ uind1 CPX #2 \ If X < 2 (i.e. X = 1), jump down to uind2 to update BCC uind2 \ indicator 1 BEQ uind4 \ If X = 2, jump down to uind4 to update indicator 2 JMP uind7 \ X > 2, so jump down to uind7 to check for more values \ of X \ ****************************************************************************** \ \ Name: UpdateIndicator (Part 2 of 15) \ Type: Subroutine \ Category: Dashboard \ Summary: Calculations for the compass (indicator 0) \ Deep dive: Hard-coded division in the dashboard routines \ \ ------------------------------------------------------------------------------ \ \ This section takes the compass heading from yRotationHi and reduces it to \ the range 0 to 73, before passing it to the DrawIndicatorHand to update the \ on-screen compass. \ \ ****************************************************************************** .uind1 \ If we get here then the indicator number in X is 0 LDA yRotationHi \ Set T = yRotationHi STA T \ We now calculate A = T * n / 256 with a hardcoded n, \ using unrolled shift-and-add multiplication \ We don't need an LDA T instruction as A already \ contains the same value as T LSR A \ Bit 0 of n is 0 LSR A \ Bit 1 of n is 0 CLC \ Bit 2 of n is 1 ADC T ROR A LSR A \ Bit 3 of n is 0 LSR A \ Bit 4 of n is 0 CLC \ Bit 5 of n is 1 ADC T ROR A LSR A \ Bit 6 of n is 0 \ Bit 7 of n is 0 and the final right shift is missing \ So by now, A is in the range 0 to 73 - here's why: \ \ From the above, n = %00100100 (36), so we just \ calculated: \ \ A = (T * n / 256) << 1 \ = (T * 36 / 256) << 1 \ = T * 72 / 256 \ \ which takes the compass heading in the range 0 to 255 \ and reduces it to the range 0 to 73 JMP DrawIndicatorHand \ Apply min and max limits to the value in A and update \ the indicator on-screen, returning from the subroutine \ using a tail call \ ****************************************************************************** \ \ Name: UpdateIndicator (Part 3 of 15) \ Type: Subroutine \ Category: Dashboard \ Summary: Calculations for the airspeed indicator (indicator 1) \ \ ------------------------------------------------------------------------------ \ \ This section takes the forward airspeed from (zVelocityPHi zVelocityPLo) and \ reduces it to match the scale of the indicator, which is represented by a \ value of 9 (for 50 mph) to 74 (for 400 mph). The airspeed value can be outside \ these limits, but these examples show the scale factor. \ \ It then passes this value to the DrawIndicatorHand to update the on-screen \ airspeed indicator. \ \ ****************************************************************************** .uind2 \ If we get here then the indicator number in X is 1 LDA zVelocityPHi \ If the high byte of the forward airspeed in zVelocityP BPL uind3 \ is positive, jump down to uind3 to skip the following LDA #0 \ The airspeed is negative, so set A to 0 and jump to JMP DrawIndicatorHand \ DrawIndicatorHand to zero the indicator on-screen, \ returning from the subroutine using a tail call .uind3 LDA zVelocityPLo \ Set A = (zVelocityPHi zVelocityPLo) * 2 / 256 ASL A LDA zVelocityPHi ROL A \ So by now, A matches the range of the airspeed \ indicator - here's why: \ \ The indicatorBase for this indicator is 48 and the \ indicatorMin/Max range shown on the dial is 57 to 122, \ which represents 50 to 400 mph (according to the game \ manual) \ \ So after this scaling, a result in A of 9 represents \ 50 mph (as 48 + 9 = 57), and a result in A of 74 \ represents 400 mph (as 48 + 74 = 122), for example \ \ These values correspond to the following 16-bit values \ of (zVelocityPHi zVelocityPLo): \ \ * If A = 9, then airspeed = (00000100 10100000), \ which is 1184, or 50 mph \ \ * If A = 74, then airspeed = (00100101 00000000), \ which is 9472, or 400 mph \ \ The plane can go faster or slower than in these \ examples, but the dial only shows speeds between 50mph \ and 400 mph, so higher or lower speeds will be clipped JMP DrawIndicatorHand \ Apply min and max limits to the value in A and update \ the indicator on-screen, returning from the subroutine \ using a tail call \ ****************************************************************************** \ \ Name: UpdateIndicator (Part 4 of 15) \ Type: Subroutine \ Category: Dashboard \ Summary: Calculations for the altimeter's small "hour" hand (indicator 2) \ Deep dive: Hard-coded division in the dashboard routines \ \ ------------------------------------------------------------------------------ \ \ This section takes the altitude from (yPlaneHi yPlaneLo) and reduces it to \ the range 0 to 254, before passing it to the DrawIndicatorHand to update the \ small hand of the on-screen altimeter. \ \ It also sets altitudeMinutes to the low byte of the altitude, reduced to the \ range 0 to 104, so it can be used in part 5 to update the large hand of the \ on-screen altimeter. \ \ ****************************************************************************** .uind4 \ If we get here then the indicator number in X is 2 LDA yPlaneLo \ Set (A R) = (yPlaneHi yPlaneLo) STA R LDA yPlaneHi LSR A \ Set (S R) = (A R) / 4 ROR R \ = (yPlaneHi yPlaneLo) / 4 LSR A \ = altitude ROR R \ STA S \ so (S R) is the altitude in feet, as the value stored \ in (yPlaneHi yPlaneLo) is the actual altitude x 4 LDA #0 \ Set T = 0 STA T \ We now calculate A = R * n / 256 with a hardcoded n, \ using unrolled shift-and-add multiplication LDA R \ Set A = R LSR A \ Bit 0 of n is 0 LSR A \ Bit 1 of n is 0 CLC \ Bit 2 of n is 1 ADC R ROR A LSR A \ Bit 3 of n is 0 CLC \ Bit 4 of n is 1 ADC R ROR A CLC \ Bit 5 of n is 1 ADC R ROR A LSR A \ Bit 6 of n is 0 \ Bit 7 of n is 0 and the final right shift is missing \ From the above, n = %000110100 (52), so we just \ calculated: \ \ A = (R * n / 256) << 1 \ = (R * 52 / 256) << 1 \ = R * 104 / 256 \ \ which is the low byte of the altitude in (S R), \ reduced to a range of 0 to 104 to represent the whole \ dial's range of 0 to 1,000 feet STA altitudeMinutes \ Store the result in altitudeMinutes, so we can draw \ the altimeter's minute hand in indicator 3 \ We now calculate A = S * n / 256 with a hardcoded n, \ using unrolled shift-and-add multiplication and \ keeping the overspill from the result LDA S \ Set A = S LSR A \ Bit 0 of n is 0 ROR T LSR A \ Bit 1 of n is 0 ROR T CLC \ Bit 2 of n is 1 ADC S ROR A ROR T LSR A \ Bit 3 of n is 0 ROR T CLC \ Bit 4 of n is 1 ADC S ROR A ROR T CLC \ Bit 5 of n is 1 ADC S ROR A ROR T LSR A \ Bit 6 of n is 0 ROR T \ Bit 7 of n is 0 and the final right shift is missing \ From the above, n = %00110100 (52), so we just \ calculated: \ \ A = (S * n / 256) << 1 \ = (S * 52 / 256) << 1 \ = S * 104 / 256 \ \ and T contains the overspill from the result STA U \ Set U = S * 104 / 256 LDA T \ Set (U A) = (U altitudeMinutes) + (0 T) CLC \ ADC altitudeMinutes \ by adding the low bytes BCC uind5 \ And, if the addition overflowed, incrementing the high INC U \ byte in U \ So we have just calculated: \ \ (U A) = (U altitudeMinutes) + (0 T) \ \ and we already know that: \ \ U = S * 104 / 256 \ \ altitudeMinutes = R * 104 / 256 \ \ so plugging these into the above, we get: \ \ (U A) = (U altitudeMinutes) + (0 T) \ = (S*104 R*104) / 256 + (0 T) \ = (S R) * 104 / 256 .uind5 LSR U \ Set (U A) = (U A) / 16 ROR A \ = ((S R) * 104 / 256) / 16 LSR U \ = (S R) * 6.5 / 256 ROR A \ LSR U ROR A LSR U ROR A \ So by now, A is in the range 0 to 254 - here's why: \ \ The maximum altitude that the altimeter can show is \ 10,000 feet (after which it just wraps around), so the \ final result of all these calculations is that the \ altitude in (S R) has been reduced from a range of 0 \ to 10,000 to a range of 0 to 254 in (U A), as: \ \ 10000 * 6.5 / 256 = 254 \ \ and that value is in A alone, as (U A) < 255, so U = 0 JMP DrawIndicatorHand \ Apply min and max limits to the value in A and update \ the indicator on-screen, returning from the subroutine \ using a tail call \ ****************************************************************************** \ \ Name: UpdateIndicator (Part 5 of 15) \ Type: Subroutine \ Category: Dashboard \ Summary: Calculations for the altimeter's "minute" hand (indicator 3) \ \ ****************************************************************************** .uind6 \ If we get here then the indicator number in X is 3 LDA altitudeMinutes \ Fetch the value of the altimeter's minute hand that we \ calculated in part 4 JMP DrawIndicatorHand \ Apply min and max limits to the value in A and update \ the indicator on-screen, returning from the subroutine \ using a tail call \ ****************************************************************************** \ \ Name: UpdateIndicator (Part 6 of 15) \ Type: Subroutine \ Category: Dashboard \ Summary: Logic for checking which indicator to update \ \ ****************************************************************************** .uind7 \ If we get here then X > 2 CPX #4 \ If X < 4 (i.e. X = 3), jump up to uind6 to update BCC uind6 \ indicator 3 BEQ uind8 \ If X = 4, jump down to uind8 to update indicator 4 JMP uind13 \ X > 4, so jump down to uind13 to check for more values \ of X \ ****************************************************************************** \ \ Name: UpdateIndicator (Part 7 of 15) \ Type: Subroutine \ Category: Dashboard \ Summary: Calculations for the vertical speed indicator (indicator 4) \ Deep dive: Hard-coded division in the dashboard routines \ \ ------------------------------------------------------------------------------ \ \ This section takes the vertical speed from (yVelocityTop yVelocityHi) and \ reduces it to the range -40 to +40, before passing it to the DrawIndicatorHand \ routine to update the on-screen vertical speed indicator. \ \ ****************************************************************************** .uind8 \ If we get here then the indicator number in X is 4 LDA yVelocityHi \ Set (A T) = (yVelocityTop yVelocityHi) STA T \ = yVelocity LDA yVelocityTop BPL uind9 \ If the vertical speed is positive, jump down to uind9 LDA #0 \ The vertical speed is negative, so we make it positive SEC \ by calculating: SBC T \ STA T \ (A T) = (0 0) - (A T) \ \ starting with the low bytes LDA #0 \ And then the high bytes SBC yVelocityTop .uind9 \ By this point, (A T) = |yVelocity| LSR A \ Set (A T) = (A T) / 8 ROR T \ = |yVelocity| / 8 LSR A ROR T LSR A ROR T CMP #0 \ If A = 0, so (A T) = (0 T) = T, so jump to uind10 to BEQ uind10 \ skip the following as T contains the correct value of \ |yVelocity| / 8 LDA #255 \ A is non-zero, which means that (A T) > 255, so set STA T \ T = 255 so that T has a maximum value of 255 .uind10 \ At this point, T contains |yVelocity| / 8, capped to a \ maximum value of 255 \ We now calculate A = T * n / 256 with a hardcoded n, \ using unrolled shift-and-add multiplication LDA T \ Set A = T LSR A \ Bit 0 of n is 0 CLC \ Bit 1 of n is 1 ADC T ROR A LSR A \ Bit 2 of n is 0 LSR A \ Bit 3 of n is 0 LSR A \ Bit 4 of n is 0 CLC \ Bit 5 of n is 1 ADC T ROR A LSR A \ Bit 6 of n is 0 \ Bit 7 of n is 0 and the final right shift is missing \ From the above, n = %00100010 (34), so we just \ calculated: \ \ A = (T * n / 256) << 1 \ = (T * 34 / 256) << 1 \ = T * 68 / 256 \ \ which takes |yVelocity| / 8 in the range 0 to 255 \ and reduces it to the range 0 to 68 CMP #40 \ If A < 40, jump to uind11 to skip the following BCC uind11 \ instruction LDA #40 \ Set A = 40, so A has a maximum of 40 and is now in the \ range 0 to 40 .uind11 BIT yVelocityTop \ If the top byte in yVelocityTop is positive (and BPL uind12 \ therefore so is the vertical speed), jump to uind12 to \ skip the following STA T \ Negate the value in A by calculating: LDA #0 \ SEC \ A = 0 - A SBC T \ So by now, A is in the range -40 to +40 \ \ In terms of the original value of yVelocity, this \ means that: \ \ yVelocity / 8 * (68 / 256) = A \ \ so: \ \ yVelocity = A * (256 / 68) * 8 \ = A * 2048 / 68 \ \ If A is 40 then this shows as a vertical speed of \ 4000 feet per minute on the indicator, so if v is the \ vertical speed in feet per minute, A = v / 100, and: \ \ yVelocity = A * 2048 / 68 \ = (v / 100) * 2048 / 68 \ = v * 2048 / 6800 \ = v * 128 / 425 \ \ so yVelocity is stored as 128 / 425 * the vertical \ speed in feet per minute .uind12 JMP DrawIndicatorHand \ Apply min and max limits to the value in A and update \ the indicator on-screen, returning from the subroutine \ using a tail call \ ****************************************************************************** \ \ Name: UpdateIndicator (Part 8 of 15) \ Type: Subroutine \ Category: Dashboard \ Summary: Logic for checking which indicator to update \ \ ****************************************************************************** .uind13 \ If we get here then X > 4 CPX #6 \ If X < 6 (i.e. X = 5), jump down to uind14 to update BCC uind14 \ indicator 5 BEQ uind19 \ If X = 6, jump down to uind19 to update indicator 6 JMP uind23 \ X > 6, so jump down to uind23 to check for more values \ of X \ ****************************************************************************** \ \ Name: UpdateIndicator (Part 9 of 15) \ Type: Subroutine \ Category: Dashboard \ Summary: Calculations for the turn indicator (indicator 5), the bottom part \ of the slip-and-turn indicator \ Deep dive: Hard-coded division in the dashboard routines \ \ ------------------------------------------------------------------------------ \ \ This section takes the turn rate around the y-axis (i.e. the yaw rate) from \ (yTurnTop yTurnHi) and reduces it to the range -19 to +19, before passing it \ to the DrawIndicatorHand to update the bottom part of the slip-and-turn \ indicator. \ \ ****************************************************************************** .uind14 \ If we get here then the indicator number in X is 5 LDA yTurnHi \ Set (A T) = (yTurnTop yTurnHi) STA T \ = yTurn LDA yTurnTop BPL uind15 \ If the turn rate is positive, jump down to uind9 LDA #0 \ The turn rate is negative, so we make it positive SEC \ by calculating: SBC T \ STA T \ (A T) = (0 0) - (A T) \ \ starting with the low bytes LDA #0 \ And then the high bytes SBC yTurnTop .uind15 \ By this point, (A T) = |yTurn| BNE uind16 \ If the high byte in A is non-zero, this means that \ (A T) > 255, so skip the following three instructions \ to set A to the maximum value of 140 LDA T \ A is 0, so set A = T, so A now contains the correct \ value of |yTurn| CMP #140 \ If T < 140, jump to uind17 to skip the following two BCC uind17 \ instructions .uind16 LDA #140 \ Set T = 140, so T is always a maximum value of 140 STA T .uind17 \ At this point, T contains |yTurn|, capped to a maximum \ value of 140 \ We now calculate A = T * n / 256 with a hardcoded n, \ using unrolled shift-and-add multiplication \ We don't need an LDA T instruction as A already \ contains the same value as T LSR A \ Bit 0 of n is 0 CLC \ Bit 1 of n is 1 ADC T ROR A LSR A \ Bit 2 of n is 0 LSR A \ Bit 3 of n is 0 CLC \ Bit 4 of n is 1 ADC T ROR A LSR A \ Bit 5 of n is 0 LSR A \ Bit 6 of n is 0 \ Bit 7 of n is 0 and the final right shift is missing \ From the above, n = %00010010 (18), so we just \ calculated: \ \ A = (T * n / 256) << 1 \ = (T * 18 / 256) << 1 \ = T * 36 / 256 \ \ which takes |yTurn| in the range 0 to 140 and reduces \ it to the range 0 to 19 BIT yTurnTop \ If the top byte in yTurnTop is negative (and therefore BMI uind18 \ so is the turn rate), jump to uind18 to skip the \ following STA T \ Negate the value in A by calculating: LDA #0 \ SEC \ A = 0 - A SBC T .uind18 \ So by now, A is in the range -19 to +19 \ \ The maximum turn rate shown on the indicator is \ 4 x 180 degrees per minute, which is shown when \ T = 140, so yTurn is stored as around 35 * the turn \ rate, as 140 / 4 = 35 JMP DrawIndicatorHand \ Apply min and max limits to the value in A and update \ the indicator on-screen, returning from the subroutine \ using a tail call \ ****************************************************************************** \ \ Name: UpdateIndicator (Part 10 of 15) \ Type: Subroutine \ Category: Dashboard \ Summary: Calculations for the slip indicator (indicator 6), the top part of \ the slip-and-turn indicator \ \ ****************************************************************************** .uind19 \ If we get here then the indicator number in X is 6 LDA slipRate \ Set A = slipRate JMP DrawIndicatorHand \ Apply min and max limits to the value in A and update \ the indicator on-screen, returning from the subroutine \ using a tail call \ ****************************************************************************** \ \ Name: UpdateIndicator (Part 11 of 15) \ Type: Subroutine \ Category: Dashboard \ Summary: Calculations for the artificial horizon (indicator 7) \ \ ------------------------------------------------------------------------------ \ \ Calculations for the artificial horizon's line vector are all performed in \ the ArtificialHorizon routine, which calculates the starting coordinates and \ deltas for the vector line. This routine simply takes those results, sets the \ direction bits for the line, and moves the starting point so the line is \ centred on the artificial horizon indicator. \ \ ****************************************************************************** .uind20 \ If we get here then the indicator number in X is 7 LDY #0 \ Set Y = 0, to use as an argument to ArtificialHorizon STY K \ Set K = 0, to use as an argument to ArtificialHorizon STY R \ Set R = 0, to use as the first guess for the direction \ of the horizon line (we will change it below if \ required) JSR ArtificialHorizon \ Call ArtificialHorizon with K = 0, Y = 0 to calculate \ the x-coordinate of the line's starting point in A CLC \ Clear the C flag (this appears to have no effect) STA S \ Set H = S, so this sets the x-coordinate of the line's \ starting point LDY #3 \ Set Y = 3 JSR ArtificialHorizon \ Call ArtificialHorizon with K = 0, Y = 3 to calculate \ the y-coordinate of the line's starting point in A STA H \ Set H = A, so this sets the y-coordinate of the line's \ starting point LDY #0 \ Set Y = 0, to use as an argument to ArtificialHorizon LDA #1 \ Set K = 1, to use as an argument to ArtificialHorizon STA K JSR ArtificialHorizon \ Call ArtificialHorizon with K = 1, Y = 0 to calculate \ the x-delta of the line SEC \ Set A = A - S SBC S \ = A - x-coordinate of start BPL uind21 \ If A is positive, the sign of the x-delta is correct, \ so jump to uind21 STA T \ The returned x-delta is negative, so store it in T so \ we can negate it below LDA #%10000000 \ Set bit 7 of R to indicate that the x-delta for the STA R \ line is negative and the y-delta is positive LDA #0 \ Set A = 0 - T SEC \ SBC T \ so the x-delta in A is now positive .uind21 CLC \ Set W = A + 1 ADC #1 \ STA W \ so this sets the line's x-delta, making sure the line \ is at least one pixel wide LDY #3 \ Set Y = 3, to use as an argument to ArtificialHorizon JSR ArtificialHorizon \ Call ArtificialHorizon with K = 1, Y = 3 to calculate \ the y-delta of the line SEC \ Set A = A - H SBC H \ = A - y-coordinate of start BPL uind22 \ If A is positive, the sign of the y-delta is correct, \ so jump to uind22 STA T \ The returned y-delta is negative, so store it in T so \ we can negate it below LDA #%01000000 \ Set bit 6 of R to indicate that the y-delta is ORA R \ negative, making sure to leave bit 7 as it is STA R LDA #0 \ Set A = A - T SEC \ SBC T \ so the y-delta in A is now positive .uind22 CLC \ Set G = A + 1 ADC #1 \ STA G \ so this sets the line's y-delta, making sure the line \ is at least 1 pixel tall LDA S \ Set S = S + 53 CLC \ ADC #53 \ so the start x-coordinate is moved to be relative to STA S \ the centre of the artificial horizon indicator, which \ is at (53, 160 + 29) LDA H \ Set H = H - 29 CLC \ ADC #227 \ so the start y-coordinate is moved to be relative to STA H \ the centre of the artificial horizon indicator, which \ is at (53, 160 + 29) JMP DrawIndicatorLine \ Draw the new artificial horizon, returning from the \ subroutine using a tail call \ ****************************************************************************** \ \ Name: UpdateIndicator (Part 12 of 15) \ Type: Subroutine \ Category: Dashboard \ Summary: Logic for checking which indicator to update \ \ ****************************************************************************** .uind23 \ If we get here then X > 6 CPX #7 \ If X = 7, jump up to uind20 to update indicator 7 BEQ uind20 CPX #9 \ If X < 9 (i.e. X = 8), jump down to uind25 to update BCC uind25 \ indicator 8 BEQ uind24 \ If X = 9, jump down to uind24 to update indicator 9 CPX #11 \ If X < 11 (i.e. X = 10), jump down to uind25 to update BCC uind25 \ indicator 10 BEQ uind26 \ If X = 11, jump down to uind26 to update indicator 11 \ If we get here then X > 11, which should not happen, \ but if it does, we simply do nothing RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: UpdateIndicator (Part 13 of 15) \ Type: Subroutine \ Category: Dashboard \ Summary: Calculations for the rudder indicator (indicator 9) \ \ ------------------------------------------------------------------------------ \ \ This section takes the rudder position from rudderPosition and reduces it to \ the range -8 to +8, before passing it to DrawIndicatorBar to update the rudder \ indicator. \ \ ****************************************************************************** .uind24 \ If we get here then the indicator number in X is 9 LDX #1 \ Set X = 1 so the current value of the indicator gets \ stored in yJoyCoord+1 in DrawIndicatorBar LDA #128 \ Set S = 128, to denote that when we fall through into STA S \ DrawIndicatorBar below, joyCoord is the y-coordinate, \ so we draw the indicator's vertical bar from point \ (H + W, joyCoord) LDA #80 \ Set W = 80 to use as the centre y-coordinate for the STA W \ rudder indicator (i.e. the centre bar) LDA rudderPosition \ Set A = rudderPosition SEC \ Set the C flag so the following call to ScaleSigned \ divides the rudder value by 16 JSR ScaleSigned \ Scale the value in A down by a factor of 16, retaining \ the sign and being sensitive to small values STA H \ Store the scaled value in H, which has now been \ reduced from the range -128 to 127 down to -8 to +8, \ to use as the x-coordinate offset from the centre of \ the indicator in DrawIndicatorBar LDY #163 \ Set Y = 163 to use as the y-coordinate of the top of \ the vertical bar LDA #11 \ Set A = 11 to set the height of the vertical bar at 11 \ pixels BNE DrawIndicatorBar \ Jump to DrawIndicatorBar to update indicator 9 by \ drawing a vertical bar of height 11 pixels with \ the top at (80 + H, 163) \ ****************************************************************************** \ \ Name: UpdateIndicator (Part 14 of 15) \ Type: Subroutine \ Category: Dashboard \ Summary: Calculations for the joystick position display (indicator 8 or 10) \ \ ------------------------------------------------------------------------------ \ \ This section takes the joystick position from aileronPosition and \ elevatorPosition and reduces it to -8 to +8 (for the x-position in \ aileronPosition) and to -32 to +32 (for the y-position in elevatorPosition), \ before passing it to the DrawJoystickCross to update the cross in the joystick \ position display. The x-position is reduced more because the joystick position \ display is taller than it is wide. \ \ Note that this indicator has two IDs, 8 and 10, so that it gets updated twice \ as often by UpdateDashboard. \ \ ****************************************************************************** .uind25 \ If we get here then the indicator number in X is 8 or \ 10 LDA #128 \ Redraw the existing cross on the joystick position JSR DrawJoystickCross \ display using EOR logic, which removes it LDA #%00100010 \ Redraw the joystick position display's x-axis, in case STA row24_char18_7 \ the old cross was overwriting the axis STA row24_char21_7 LDA #%01000100 STA row24_char19_7 LDA #%10011001 \ Redraw the joystick position display's y-axis, in case STA row24_char20_7 \ the old cross was overwriting the axis LDA #%10001000 STA row21_char20_7 STA row22_char20_7 STA row23_char20_7 STA row25_char20_7 STA row26_char20_7 STA row27_char20_7 LDA aileronPosition \ Set A = aileronPosition, the x-position of the \ joystick SEC \ Set the C flag so the following call to ScaleSigned \ divides the joystick x-position by 16 JSR ScaleSigned \ Scale the value in A down by a factor of 16, retaining \ the sign and being sensitive to small values STA xJoyCoord \ Store the scaled value of A in xJoyCoord, so we can \ pass it to DrawJoystickCross below to draw the new \ cross, but also so we can erase this cross when we \ need to update the indicator in the future LDA elevatorPosition \ Set A = elevatorPosition, the y-position of the \ joystick CLC \ Clear the C flag so the following call to ScaleSigned \ divides the joystick y-position by 4 JSR ScaleSigned \ Scale the value in A down by a factor of 4, retaining \ the sign and being sensitive to small values EOR #&FF \ Set A = -A using two's complement CLC \ ADC #1 \ This flips the sign of the y-position, because the \ joystick value will be high when we pull up, but this \ corresponds to moving the stick down, which should be \ shown lower down the indicator STA yJoyCoord \ Store the scaled value of A in yJoyCoord, so we can \ pass it to DrawJoystickCross below to draw the new \ cross, but also so we can erase this cross when we \ need to update the indicator in the future LDA #0 \ Draw a new cross on the joystick position display JSR DrawJoystickCross RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: UpdateIndicator (Part 15 of 15) \ Type: Subroutine \ Category: Dashboard \ Summary: Calculations for the thrust indicator (indicator 11) \ \ ------------------------------------------------------------------------------ \ \ This section takes the thrust from (thrustHi thrustLo) and reduces it to the \ range 0 to 60, before falling through into DrawIndicatorBar to update the \ rudder indicator. \ \ ****************************************************************************** .uind26 \ If we get here then the indicator number in X is 11 LDA #128 \ Set S = 128, to denote that when we fall through into STA S \ DrawIndicatorBar below, joyCoord is the y-coordinate, \ so we draw the indicator's vertical bar from point \ (H + W, joyCoord) LDA #125 \ Set W = 125 to use as the centre y-coordinate for the STA W \ thrust indicator (i.e. the left end of the bar, as we \ are only showing positive thrust values) LDA thrustHi \ Set (R A) = (thrustHi thrustLo) STA R \ = Thrust LDA thrustLo LDX #3 \ Set X = 3 to act as a shift counter in the following \ loop, where we right shift (R A) four times .uind27 LSR R \ Set (R A) = (R A) / 2 ROR A \ = Thrust / 2 DEX \ Decrement the shift counter BPL uind27 \ Loop back until we have shifted right by 4 places, so \ we now have: \ \ (R A) = (R A) / 8 \ = Thrust / 8 \ \ We now ignore the high byte in R, so presumably it is \ zero STA R \ Set A = A + A / 2 LSR A \ = 1.5 * A ADC R \ = 1.5 * (Thrust / 8), rounded up \ = Thrust * 3 / 16 LSR A \ Set A = A / 4 LSR A \ = Thrust * 3 / 64 STA H \ Store the scaled value in H, which has now been \ reduced from the range 0 to 1280 down to 0 to 60, \ to use as the x-coordinate offset from the left end of \ the indicator in DrawIndicatorBar LDX #3 \ Set X = 3 so the current value of the indicator gets \ stored in yJoyCoord+3 in DrawIndicatorBar LDY #243 \ Set Y = 243 to use as the y-coordinate of the top of \ the vertical bar LDA #7 \ Set A = 7 to set the height of the vertical bar at 7 \ pixels \ Fall through into DrawIndicatorBar to update indicator \ 11 by drawing a vertical bar of height 7 pixels with \ the top at (125 + H, 243) \ ****************************************************************************** \ \ Name: DrawIndicatorBar \ Type: Subroutine \ Category: Drawing lines \ Summary: Draw a vertical bar on indicator 9 (rudder) or 11 (thrust) \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ A The height of the vertical bar in pixels \ \ S Defines the starting coordinate for the line: \ \ * 0 = (joyCoord, H + W) \ \ * 128 = (H + W, joyCoord) \ \ As all the vertical bar indicators are horizontal in \ Aviator, only the second option is used \ \ X The offset from yJoyCoord where we store the indicator \ value, so it can be erased when the bar needs to move: \ \ * 1 = rudder indicator (indicator 9) \ \ * 3 = thrust indicator (indicator 11) \ \ W The x-coordinate of the centre point of the indicator \ \ Y The y-coordinate of the top of the bar \ \ ****************************************************************************** .DrawIndicatorBar STA U \ Set U = A, so the line is A pixels tall LDA #1 \ Set T = 1, so the line is 1 pixel wide STA T STY joyCoord \ Set joyCoord = Y, the y-coordinate of the top of the \ bar LDA yJoyCoord,X \ Set G = the X-th byte of yJoyCoord, which contains the STA G \ x-coordinate of the current bar, so we can erase it LDA H \ Store H in the X-th byte of yJoyCoord, so the next STA yJoyCoord,X \ time we call this routine, we can use this to erase \ the bar we are about to draw JSR EraseOrthoLine \ Erase the current vertical bar at x-coordinate G LDA #0 \ Set N = 0 to switch the drawing mode to OR logic, so STA N \ the bar gets drawn on-screen JSR DrawOrthoLine \ Draw a new vertical bar at x-coordinate H RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: DrawIndicatorHand \ Type: Subroutine \ Category: Drawing lines \ Summary: Apply min and max limits to an indicator value and draw a hand \ on the indicator \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ A The value to show on the indicator \ \ X The indicator number (0-6) \ \ ****************************************************************************** .DrawIndicatorHand \ When we get here, the values of A are set as follows: \ \ * X = 0: Compass A = 0 to 73 \ * X = 1: Airspeed A = 9 to 74 \ * X = 2: Altimeter small hand A = 0 to 254 \ * X = 3: Altimeter large hand A = 0 to 104 \ * X = 4: Vertical speed A = -40 to 40 \ * X = 5: Turn A = -19 to 19 \ * X = 6: Slip A = ??? to ??? CLC \ Set A = A + the X-th byte of indicatorBase ADC indicatorBase,X \ \ This adds the relevant indicator's base value from the \ indicatorBase table to give the following: \ \ * Compass +0 A = 0 to 73 \ * Airspeed +48 A = 57 to 122 \ * Altimeter small hand +0 A = 0 to 254 \ * Altimeter large hand +0 A = 0 to 104 \ * Vertical speed +67 A = 27 to 107 \ * Turn +53 A = 34 to 72 \ * Slip +106 A = ??? to ??? CMP indicatorMin,X \ If A >= the X-th byte of indicatorMin, jump to dinh1 BCS dinh1 \ to skip the following LDA indicatorMin,X \ Set A to the X-th byte of indicatorMin, so A is at \ least the relevant value in indicatorMin BCC dinh2 \ Jump to dinh2 to skip the following, as we don't need \ to check the maximum limit (this BCC is effectively a \ JMP as we passed through the BCS above) .dinh1 \ If we get here then A >= the X-th byte of indicatorMin CMP indicatorMax,X \ If A >= the X-th byte of indicatorMax, jump to dinh2 BCC dinh2 \ to skip the following LDA indicatorMax,X \ Set A to the X-th byte of indicatorMax, so A is no \ more than the relevant value in indicatorMax .dinh2 \ A is now clipped to this indicator's range as given in \ the indicatorMin and indicatorMax tables, so the final \ ranges are: \ \ * Compass A = 0 to 73 \ * Airspeed A = 57 to 122 \ * Altimeter small hand A = 0 to 254 \ * Altimeter large hand A = 0 to 104 \ * Vertical speed A = 30 to 104 \ * Turn A = 33 to 72 \ * Slip A = 91 to 120 STA H \ Store the clipped indicator value in H JSR GetHandVector \ Calculate the vector for drawing the new dial hand \ with value A, returning the result in W, G and R \ Fall through into DrawIndicatorLine to draw the new \ hand on the indicator \ ****************************************************************************** \ \ Name: DrawIndicatorLine \ Type: Subroutine \ Category: Drawing lines \ Summary: Draw a line on indicators 0 to 7, i.e. a dial hand (0-6) or an \ artificial horizon (7) \ Deep dive: Line buffers \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ S Start point x-coordinate (artificial horizon only) \ \ H Start point y-coordinate (artificial horizon only) \ \ W Magnitude of x-coordinate of line's vector |x-delta| \ \ G Magnitude of y-coordinate of line's vector |y-delta| \ \ R Direction of vector (T, U): \ \ * Bit 7 is the direction of the x-delta \ \ * Bit 6 is the direction of the y-delta \ \ Direction is like a clock, so positive (clear) is up and \ right \ \ X The indicator number (0-7) \ \ WW The indicator number (0-7) \ \ ****************************************************************************** .DrawIndicatorLine LDA indicatorLineI,X \ Set I = x-coordinate of the starting point of the STA I \ current line LDA indicatorLineJ,X \ Set J = y-coordinate of the starting point of the STA J \ current line LDA indicatorLineT,X \ Set T = x-delta of the current line STA T LDA indicatorLineU,X \ Set U = y-delta of the current line STA U LDA indicatorLineV,X \ Set V = direction of the current line STA V LDA #128 \ Set N = 128 so the call to DrawVectorLine erases the STA N \ current line JSR DrawVectorLine \ Erase a line from (I, J) as a vector (T, U) with \ direction V LDX WW \ If this is not indicator 7, jump to dinl2 CPX #7 BNE dinl2 \ If we get here then this is the artificial horizon \ (indicator 7) LDA #%11111111 \ Set A to the pixel byte for four white pixels, to use \ for the bottom row of pixels in the centre block of \ the artificial horizon's centre line LDY #2 \ We want to redraw the three pixel rows in the centre \ block of the artificial horizon's centre line, which \ from bottom to top contain 3 pixels, 1 pixel and 1 \ pixel, so set a counter in Y for 3 bytes .dinl1 STA row23_char13_2,Y \ Redraw the Y-th pixel row in the centre block LDA #%01000100 \ The top two pixel rows of the centre block contain the \ vertical mark at the centre of the indicator, so set A \ to the appropriate single-pixel byte DEY \ Decrement the counter to move up to the next pixel row BPL dinl1 \ Loop back until we have redrawn all three pixel rows \ in the centre block LDA #%00110011 \ Redraw the two-pixels at the left end of the STA row23_char12_4 \ artificial horizon's centre line LDA #%10001000 \ Redraw the single pixel at the right end of the STA row23_char14_4 \ artificial horizon's centre line LDA S \ Fetch the x-coordinate of the starting point of the \ new line from S STA I \ Set I = the x-coordinate of the starting point of the \ new line to draw STA indicatorLineI,X \ Store the x-coordinate in indicatorLineI, so we can \ use it to erase the line later LDA H \ Set A = the y-coordinate of the starting point of the \ new line to draw STA indicatorLineJ,X \ Store the y-coordinate in indicatorLineJ, so we can \ use it to erase the line later BNE dinl3 \ Jump to dinl3 to draw the new line (this BNE is \ effectively a JMP as A is never zero) .dinl2 \ If we get here then this is indicator 0-6, so it's a \ hand-based dial LDA indicatorLineI,X \ Set I = x-coordinate of starting point of hand, which STA I \ is a fixed value for hand-based dials LDA indicatorLineJ,X \ Set J = y-coordinate of starting point of hand, which \ is a fixed value for hand-based dials .dinl3 STA J \ Store A in J as the y-coordinate of the starting \ point of the new line to draw LDA W \ Fetch the x-delta of the new line from W STA T \ Set T = the x-delta of the new line STA indicatorLineT,X \ Store the x-delta in indicatorLineT, so we can use it \ to erase the line later LDA G \ Fetch the y-delta of the new line from G STA U \ Set U = the y-delta of the new line STA indicatorLineU,X \ Store the y-delta in indicatorLineU, so we can use it \ to erase the line later LDA R \ Fetch the direction of the new line from R STA V \ Set V = the direction of the new line STA indicatorLineV,X \ Store the direction in indicatorLineV, so we can use \ it to erase the line later LDA #0 \ Set N = 0 so the call to DrawVectorLine draws the new STA N \ line JSR DrawVectorLine \ Draw a line from (I, J) as a vector (T, U) with \ direction V RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: DrawJoystickCross \ Type: Subroutine \ Category: Drawing lines \ Summary: Draw a cross in the joystick position display (indicator 8 or 10) \ \ ------------------------------------------------------------------------------ \ \ This routine draws a cross, relative to a centre point of (80, 216), and with \ a 3-pixel horizontal bar and a 5-pixel vertical bar. \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ A Drawing mode: \ \ * 0 = Draw (using OR logic) \ \ * 128 = Erase (using EOR logic) \ \ xJoyCoord The current joystick x-coordinate \ \ yJoyCoord The current joystick y-coordinate \ \ ****************************************************************************** .DrawJoystickCross STA N \ Store the drawing mode in N \ First we draw the 3-pixel horizontal line right from \ (79 + x, 216 + y) LDA yJoyCoord \ Set H = yJoyCoord STA H LDA xJoyCoord \ Set joyCoord = xJoyCoord + 79 CLC \ ADC #79 \ to get the x-coordinate of the left end of the STA joyCoord \ horizontal line at 79 + x LDA #216 \ Set W = 216, the y-coordinate of the centre point, so STA W \ we draw the line from a y-coordinate of 216 + y LDA #0 \ Set S = 0, to denote that joyCoord is the STA S \ x-coordinate, so we draw the line from point \ (joyCoord, H + W) LDA #3 \ Set T = 3, so we draw a horizontal 3-pixel line STA T LDA #1 \ Set U = 1, so the line is 1 pixel high STA U JSR DrawOrthoLine \ Draw the horizontal part of the cross, starting from \ (79 + x, 216 + y) and drawing to the right for three \ pixels \ Now we draw the 5-pixel vertical line down from \ (80 + x, 214 + y) LDA xJoyCoord \ Set H = xJoyCoord STA H LDA yJoyCoord \ Set joyCoord = yJoyCoord + 214 CLC \ ADC #214 \ to get the y-coordinate of the top end of the vertical STA joyCoord \ line at 214 + y LDA #80 \ Set W = 80, the x-coordinate of the centre point, so STA W \ we draw the line from an x-coordinate of 80 + x LDA #128 \ Set S = 128, to denote that joyCoord is the STA S \ y-coordinate, so we draw the line from point \ (H + W, joyCoord) LDA #1 \ Set T = 1, so the line is 1 pixel wide STA T LDA #5 \ Set U = 5, so we draw a vertical 5-pixel line STA U JSR DrawOrthoLine \ Draw the vertical part of the cross, starting from \ (80 + x, 214 + y) and drawing down for five pixels RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: GetHandVector \ Type: Subroutine \ Category: Dashboard \ Summary: Vector line calculation for a hand on indicators 0 to 6 \ Deep dive: Clock hands and dial indicators \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ A The value to show as a hand on the dial indicator \ \ ------------------------------------------------------------------------------ \ \ Returns: \ \ W Magnitude of x-coordinate of line's vector |x-delta| \ \ G Magnitude of y-coordinate of line's vector |y-delta| \ \ R Direction of vector (T, U): \ \ * Bit 7 is the direction of the x-delta \ \ * Bit 6 is the direction of the y-delta \ \ Direction is like a clock, so positive (clear) is up and \ right, so this means the following: \ \ * x-delta +ve, y-delta +ve: 12 to 3 o'clock \ \ * x-delta +ve, y-delta -ve: 3 to 6 o'clock \ \ * x-delta -ve, y-delta -ve: 6 to 9 o'clock \ \ * x-delta -ve, y-delta +ve: 9 to 12 o'clock \ \ ****************************************************************************** .GetHandVector LDY #0 \ Set R = 0, to use as the first guess for the direction STY R \ of the hand on the dial (we will change it below if \ required) LDY dialQuadrant,X \ Set Y = the dialQuadrant value for this indicator, \ which is the size of a quarter of the dial in terms of \ the value in A INY \ Set K = Y + 1 STY K \ = dialQuadrant + 1 \ \ Doing this enables us to subtract this value below, \ leaving the result in a suitable state for negating \ using two's complement (see dhvc2) SEC \ Set the C flag for the subtraction we are about to do .dhvc1 SBC K \ Set A = A - K \ = A - (dialQuadrant + 1) \ \ so we have subtracted a quadrant's worth of value from \ the value we want to show in A BCS dhvc2 \ If the subtraction didn't underflow, jump to dhvc2 to \ skip the following two instructions \ The subtraction underflowed, so we know that the hand \ is in the first quadrant, i.e. 3 to 6 o'clock ADC K \ Reverse the subtraction by adding K to A, so A is now \ back to its original value JMP dhvc6 \ Jump down to dhvc5 to calculate the hand's vector, \ with R set to 0 to indicate that both the x-delta and \ y-delta for the line are positive .dhvc2 SBC dialQuadrant,X \ Subtract a second quadrant's worth, so: \ \ A = A - (dialQuadrant + 1) - dialQuadrant \ = A - 2 * dialQuadrant - 1 \ \ If we want to negate this value below, then we can do \ this using two's complement by simply inverting all \ the bits, as we have already subtracted 1, and we can \ negate by either inverting-and-adding-1, or by \ subtracting-1-and-inverting (as they are equivalent) BCS dhvc3 \ If the subtraction didn't underflow, jump to dhvc3 to \ skip the following three instructions \ The subtraction underflowed, so we know that the hand \ is in the second quadrant, i.e. 6 to 9 o'clock LDY #%01000000 \ Set bit 6 of R to indicate that the x-delta for the STY R \ line is positive and the y-delta is negative BNE dhvc5 \ Jump down to dhvc5 to negate A before calculating the \ hand's vector (this BNE is effectively a JMP as A is \ never zero) .dhvc3 SBC K \ Subtract a third quadrant's worth BCS dhvc4 \ If the subtraction didn't underflow, jump to dhvc4 to \ skip the following four instructions \ The subtraction underflowed, so we know that the hand \ is in the third quadrant on the dial ADC K \ Reverse the subtraction by adding K to A, so A is now \ back to its original value LDY #%11000000 \ Set bits 7 and 6 of R to indicate that both the STY R \ x-delta and y-delta for the line are negative BNE dhvc6 \ Jump down to dhvc5 to calculate the hand's vector \ (this BNE is effectively a JMP as A is never zero) .dhvc4 SBC dialQuadrant,X \ Subtract a fourth quadrant's worth BCS dhvc1 \ If the subtraction didn't underflow, jump to dhvc1 to \ start the subtraction process again, as we have now \ subtracted a whole dial's worth and need to keep going \ The subtraction underflowed, so we know that the hand \ is in the fourth quadrant on the dial LDY #%10000000 \ Set bit 7 of R to indicate that the x-delta for the STY R \ line is negative and the y-delta is positive .dhvc5 \ If we get here then the hand is either in the second \ or fourth quadrant on the dial EOR #&FF \ Invert the value of A (i.e. negate it) using two's \ complement, which works because we can negate a number \ by subtracting 1 and then inverting, and we \ effectively subtracted 1 in the above by using K .dhvc6 \ By the time we get here, the direction of the new \ dial hand is in R, the value of A has been reduced \ into negative territory by repeated subtraction, and \ it has been switched to be positive if the quadrant is \ bottom-right or top-left, i.e. 3 to 6 o'clock or \ 9 to 12 o'clock) \ \ We now use this value of A as our value of x in the \ above calculation STA S \ Store the reduced value of A in S \ We now calculate y-delta, where y = w - x or y = x - w \ depending on the quadrant. We already set the sign of \ x correctly with the above EOR, so if we calculate \ y = w - x, it will be correct LDA dialQuadrant,X \ Set A = the dialQuadrant value for this indicator, \ which is the size of a quarter of the dial in terms of \ the value in A. This is what we use for w in the above SEC \ Set A = A - S SBC S \ = w - x \ \ so A contains our y-delta \ We now cap the y-delta, which has the effect of \ blunting the points of our diamond CMP yDeltaMax,X \ If A < yDeltaMax for this indicator, jump to dhvc7 to BCC dhvc7 \ skip the following instruction LDA yDeltaMax,X \ Set A = yDeltaMax, so A is never greater than the \ yDeltaMax value for this indicator .dhvc7 CLC \ Set G = A + 1 ADC #1 \ = y-delta + 1 STA G \ \ so G contains the y-delta, plus 1 to ensure it is \ non-zero LDA S \ Fetch the reduced value of A that we stored in S we \ above, which we know is our x-delta CLC \ Set A = (A + 1) / 2 ADC #1 \ LSR A \ because mode 5 pixels are twice as wide as they are \ high, so this scales the x-delta to be a pixel value CMP xDeltaMax,X \ If A < xDeltaMax for this indicator, jump to dhvc8 to BCC dhvc8 \ skip the following instruction LDA xDeltaMax,X \ Set A = xDeltaMax, so A is never greater than the \ xDeltaMax value for this indicator .dhvc8 CLC \ Set W = A + 1 ADC #1 \ = x-delta + 1 STA W \ \ so W contains the y-delta, plus 1 to ensure it is \ non-zero RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: DrawVectorLine (Part 1 of 3) \ Type: Subroutine \ Category: Drawing lines \ Summary: Draw a line: set up pixel bytes and slope variables \ Deep dive: Line buffers \ \ ------------------------------------------------------------------------------ \ \ Draw/erase a line from (I, J) as a vector (T, U) with direction V. \ \ This routine uses Bresenham's algorithm to draw the line, by working along the \ longer axis of the line vector, one pixel at a time, plotting a pixel after \ each step. All the while we keep a cumulative tally of fractional pixel counts \ along the shorter axis (known as the "slope error"), and move one pixel along \ the shorter axis when the tally reaches a multiple of the axis length. \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ I Start point x-coordinate \ \ J Start point y-coordinate, relative to the bottom of the \ canopy/top of the dashboard. Specifically: \ \ * J = 0 to 151 are all within the bounds of the canopy \ \ * The bottom pixel inside the canopy is 0, while the \ top pixel just below the top canopy edge is 151 \ \ * J = -1 to -96 are all on the dashboard \ \ * The white horizontal edge at the bottom of the \ canopy/top of the dashboard is -1 \ \ * The bottom row of pixels on-screen is -96 \ \ N Drawing mode: \ \ * Bit 7 clear = draw \ \ * Bit 7 set = erase \ \ T Magnitude of x-coordinate of line's vector |x-delta| \ Horizontal width/length of line when V = 0 \ \ U Magnitude of y-coordinate of line's vector |y-delta| \ Vertical width/length of line when V = 0 \ \ V Direction of vector (T, U): \ \ * Bit 7 is the direction of the x-delta \ \ * Bit 6 is the direction of the y-delta \ \ Direction is like a clock, so positive (clear) is up and \ right \ \ ****************************************************************************** .DrawVectorLine LDY #3 \ Set Y = 3 to use as a byte counter in the following \ loop, so it writes four bytes LDA #%00010001 \ Set A = %00010001, the pixel pattern for pixel 0 in \ white .dvec1 STA RR,Y \ Set the Y-th byte of RR to A ASL A \ Set A = A * 2 DEY \ Decrement the byte counter BPL dvec1 \ Loop back until we have updated RR to RR+3 as \ follows: \ \ RR = %10001000 = pixel 3 in white \ RR+1 = %01000100 = pixel 2 in white \ RR+2 = %00100010 = pixel 1 in white \ RR+3 = %00010001 = pixel 0 in white LDA #0 \ Set QQ = 0 STA QQ STA PP \ Set PP = 0 LDA T \ If T < U, jump down to dvec2 to skip the following CMP U \ two instructions BCC dvec2 \ If we get here then T >= U, so the line is a shallow \ horizontal slope STA VV \ Set VV = T, the length of the longer axis BCS dvec11 \ Jump down to dvec11 to start drawing the line (this \ BCS is effectively a JMP as we just passed through a \ BCC) .dvec2 \ If we get here then T < U, so the line is a steep \ vertical slope LDA U \ Set VV = U, the length of the longer axis STA VV STA PP \ Set PP = U BCC dvec11 \ Jump down to dvec11 to start drawing the line (this \ BCC is effectively a JMP as we got here by taking a \ BCC) \ ****************************************************************************** \ \ Name: DrawVectorLine (Part 2 of 3) \ Type: Subroutine \ Category: Drawing lines \ Summary: Calculate the coordinates of the next pixel as we step along the \ line by one pixel \ \ ****************************************************************************** .dvec3 \ If we get here then we need to step along the x-axis LDA QQ \ Set A = QQ + U CLC \ ADC U \ so A contains the cumulative step along the y-axis \ (the shorter axis) CMP T \ If A < T, then we haven't yet reached a full step of BCC dvec5 \ length T along the y-axis, so we don't change the \ y-coordinate and instead jump to dvec5 to do the step \ along the x-axis by one pixel \ We now need to step along the y-axis by one pixel as \ the cumulative step has just crossed over into a new \ multiple of T SBC T \ Set A = A - T \ \ so we keep the cumulative step within the bounds of a \ single byte (as we are only interested in when it \ crosses the boundary into a new multiple of T) \ We now move one pixel along the y-axis in the \ direction given in V BIT V \ If bit 6 of V is clear, jump to dvec4 to step along BVC dvec4 \ the y-axis in a positive direction DEC J \ Bit 6 of V is set, so decrement the y-coordinate in \ J so we move along the y-axis in a negative direction BVS dvec5 \ Jump to dvec5 to do the step along the x-axis by one \ pixel (this BVS is effectively a JMP as we know the V \ flag is set) .dvec4 INC J \ Bit 6 of V is clear, so increment the y-coordinate in \ J so we move along the y-axis in a positive direction .dvec5 STA QQ \ Store the updated fractional value in QQ \ We now move one pixel along the x-axis in the \ direction given in V BIT V \ If bit 7 of V is clear, jump to dvec6 to step along BPL dvec6 \ the x-axis in a positive direction DEC I \ Bit 7 of V is set, so decrement the x-coordinate in \ I so we move along the x-axis in a negative direction JMP dvec11 \ Now that we have moved (I, J) to the next pixel in the \ line, jump to dvec11 to plot the next pixel .dvec6 INC I \ Bit 7 of V is clear, so increment the x-coordinate in \ I so we move along the x-axis in a positive direction JMP dvec11 \ Now that we have moved (I, J) to the next pixel in the \ line, jump to dvec11 to plot the next pixel .dvec7 \ We jump here when we need to calculate the coordinates \ of the next pixel in the line when stepping along the \ longer delta axis one pixel at a time LDA PP \ If PP = 0 then this is a shallow horizontal slope, so BEQ dvec3 \ jump up to dvec3 step along the x-axis \ If we get here then this is a steep vertical line, so \ we need to step along the y-axis LDA QQ \ Set A = QQ + T CLC \ ADC T \ so A contains the cumulative step along the x-axis \ (the shorter axis) CMP U \ If A < U, then we haven't yet reached a full step of BCC dvec9 \ length U along the x-axis, so we don't change the \ x-coordinate and instead jump to dvec9 to do the step \ along the y-axis by one pixel \ We now need to step along the x-axis by one pixel as \ the cumulative step has just crossed over into a new \ multiple of U SBC U \ Set A = A - U \ \ so we keep the cumulative step within the bounds of a \ single byte (as we are only interested in when it \ crosses the boundary into a new multiple of U) \ We now move one pixel along the x-axis in the \ direction given in V BIT V \ If bit 7 of V is clear, jump to dvec8 to step along BPL dvec8 \ the x-axis in a positive direction DEC I \ Bit 7 of V is set, so decrement the x-coordinate in \ I so we move along the x-axis in a negative direction, \ i.e. to the left JMP dvec9 \ Jump to dvec9 to do the step along the y-axis by one \ pixel .dvec8 INC I \ Bit 7 of V is clear, so increment the x-coordinate in \ I so we move along the x-axis in a positive direction, \ i.e. to the right .dvec9 STA QQ \ Store the updated fractional value in QQ \ We now move one pixel along the y-axis in the \ direction given in V BIT V \ If bit 6 of V is clear, jump to dvec10 to step along BVC dvec10 \ the y-axis in a positive direction, i.e. up the screen DEC J \ Bit 6 of V is set, so decrement the y-coordinate in \ J so we move along the y-axis in a negative direction, \ i.e. down the screen BVS dvec11 \ Now that we have moved (I, J) to the next pixel in the \ line, jump to dvec11 to plot the next pixel (this BVS \ is effectively a JMP as we know the V flag is set) .dvec10 INC J \ Bit 6 of V is clear, so increment the y-coordinate in \ J so we move along the y-axis in a positive direction \ Now that we have moved (I, J) to the next pixel in the \ line, we fall through into part 3 to plot that pixel \ ****************************************************************************** \ \ Name: DrawVectorLine (Part 3 of 3) \ Type: Subroutine \ Category: Drawing lines \ Summary: Plot a pixel at (I, J) \ \ ****************************************************************************** .dvec11 \ When we first arrive here: \ \ * QQ = 0, where we will tally up the fractional part \ of the move along the shortest axis \ * RR = pixel byte table \ * VV = longest side of delta triangle \ * PP = 0 (shallow horizontal) \ VV (steep vertical) \ \ so we now draw a line from (I, J), moving one pixel \ at a time along the longest side of the delta triangle LDA I \ Set X = I / 4 LSR A \ LSR A \ so X is the number of the character block containing TAX \ pixel (I, J), as each character block is 4 pixels wide LDA J \ Set Y = J / 8 LSR A \ LSR A \ so Y is the number of the character row containing LSR A \ pixel (I, J), as each character row is 8 pixels high TAY LDA yLookupLo,Y \ Set P = Y-th byte of yLookupLo CLC \ + X-th byte of xLookupLo ADC xLookupLo,X \ = LO(screen address) + LO(X * 8) STA P LDA yLookupHi,Y \ Set Q = Y-th byte of yLookupHi ADC xLookupHi,X \ + X-th byte of xLookupHi STA Q \ = HI(screen address) + HI(X * 8) \ So (Q P) is the screen address of the pixel row \ containing pixel (I, J), out by 8 bytes for each row \ above or below the top of the dashboard LDA #159 \ Set Y = 159 - J SEC \ SBC J \ so Y is the number of pixels that (I, J) is above TAY \ (+ve) or below (-ve) the top of the dashboard, where a \ value of 0 is the bottom pixel inside the canopy, and \ a value of -1 is the white horizontal edge at the \ bottom of the canopy LDA I \ Set X = bits 0 and 1 of I AND #%00000011 \ = I mod 4 TAX \ = pixel number within the 4-pixel byte BIT N \ If bit 7 of N is set, jump to dvec12 to erase the line BMI dvec12 \ with EOR logic instead of drawing it with OR logic LDA RR,X \ Fetch the X-th byte of RR, which is a pixel byte with \ the X-th pixel set to white ORA (P),Y \ OR it with (Q P) + Y, which is the screen address of \ the pixel row containing (I, J) \ \ This will keep all pixels the same except the X-th \ pixel, which is set to white, so this will plot a \ pixel at (I, J) when stored in screen memory JMP dvec13 \ Jump to dvec13 to skip the following three \ instructions .dvec12 LDA RR,X \ Fetch the X-th byte of RR, which is a pixel byte with \ the X-th pixel set to white EOR #%11111111 \ Invert all the bits, so A is now a pixel byte that is \ all white except for the X-th pixel, which is black AND (P),Y \ AND it with (Q P) + Y, which is the screen address of \ pixel (I, J) \ \ This will keep all pixels the same except the X-th \ pixel, which is set to black, so this will erase the \ pixel at (I, J) when stored in screen memory .dvec13 STA (P),Y \ Store the byte in A in screen memory at (Q P) + Y, \ which sets all four pixels to the pixel pattern in A, \ which either draws or erases the pixel at (I, J) DEC VV \ Decrement VV to step one pixel along the longer axis BNE dvec7 \ If VV is non-zero, jump up to dvec7 to calculate the \ coordinate of the next pixel in the line RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: ArtificialHorizon \ Type: Subroutine \ Category: Dashboard \ Summary: Vector line calculation for the artificial horizon on indicator 7 \ \ ------------------------------------------------------------------------------ \ \ The commentary in this routine is a work in progress. \ \ matrix4Lo, matrix4Lo+2, matrix4Lo+3, matrix4Lo+4 are only used to provide \ signs in bit 0: \ Negative if bit 0 is set, positive if clear \ \ matrix4Hi, matrix4Hi+3 provide values for T calculations \ matrix4Hi+2, matrix4Hi+4 provide values for U calculations \ \ matrix4Hi+3 = current roll orientation, 0-&FF for 0 to 45 degrees \ matrix4Lo+3 = direction of roll \ \ Y = 0, K = 0: \ T = (matrix4Lo matrix4Hi) / 4 \ U = -(matrix4Lo+2 matrix4Hi+2) / 4 \ Return (T + U) / 8 with the sign bits retained = x-coord of start \ \ Y = 0, K = 1: \ T = -(matrix4Lo matrix4Hi) / 4 \ U = -(matrix4Lo+2 matrix4Hi+2) / 4 \ Return (T + U) / 8 with the sign bits retained = y-coord of start \ \ Y = 3, K = 0: \ T = (matrix4Lo+3 matrix4Hi+3) / 4 \ U = -(matrix4Lo+4 matrix4Hi+4) / 4 \ Return (T + U) / 8 with the sign bits retained = x-delta \ \ Y = 3, K = 1: \ T = -(matrix4Lo+3 matrix4Hi+3) / 4 \ U = -(matrix4Lo+4 matrix4Hi+4) / 4 \ Return (T + U) / 8 with the sign bits retained = y-delta \ \ The line is returned relative to the origin (0, 0), so that's as if the centre \ of the artificial horizon indicator were at (0, 0). This means that the deltas \ that are calculated are the equivalent to the end point of the line. The line \ itself gets moved to the location of the on-screen indicator in part 11 of \ UpdateIndicator. \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ K The axis to calculate: \ \ * 0 = x-axis \ \ * 1 = y-axis \ \ Y The value to calculate: \ \ * 0 = coordinate of starting point \ \ * 3 = deltas (i.e. coordinates of end point) \ \ ------------------------------------------------------------------------------ \ \ Returns: \ \ A Depending on the values of K and Y: \ \ * K = 0, Y = 0: returns the x-coordinate of the \ artificial horizon's starting point \ \ * K = 0, Y = 3: returns the y-coordinate of the \ artificial horizon's starting point \ \ * K = 1, Y = 0: returns the x-delta of the artificial \ horizon \ \ * K = 1, Y = 3: returns the y-delta of the artificial \ horizon \ \ ****************************************************************************** .ArtificialHorizon LDA matrix4Hi,Y \ Set A = matrix4Hi (Y = 0) or matrix4Hi+3 (Y = 3) LSR A \ Set A = A / 4 LSR A CPY #0 \ If Y = 3, halve A again, so A = A / 8 BNE arhi1 LSR A .arhi1 STA T \ Set T = A, so T = A / 4 or A / 8 LDA matrix4Lo,Y \ Set A = matrix4Lo (Y = 0) or matrix4Lo+3 (Y = 3) EOR K \ If K = 1, flip bit 0 of A AND #1 \ If bit 0 of A is zero, jump to arhi2 to skip the BEQ arhi2 \ following LDA #0 \ Set T = 0 - T SEC SBC T STA T .arhi2 LDA matrix4Hi+2,Y \ Set A = matrix4Hi+2 (Y = 0) or matrix4Hi+4 (Y = 3) LSR A \ Set A = A / 4 LSR A CPY #0 \ If Y = 3, halve A again, so A = A / 8 BNE arhi3 LSR A .arhi3 STA U \ Set U = A, so U = A / 4 or A / 8 LDA matrix4Lo+2,Y \ Set A = matrix4Lo+2 (Y = 0) or matrix4Lo+4 (Y = 3) CPY #0 \ If Y = 0, flip bit 0 of A BNE arhi4 EOR #1 .arhi4 AND #1 \ If bit 0 of A is zero, jump to arhi5 to skip the BEQ arhi5 \ following LDA #0 \ Set U = 0 - U SEC SBC U STA U .arhi5 CLC \ A = T + U LDA T ADC U BMI arhi6 LSR A \ A = A / 8 LSR A LSR A ADC #0 \ Round up the A/8 division RTS \ Return from the subroutine .arhi6 SEC \ A = A / 8 + with bits 5-7 set ROR A SEC ROR A SEC ROR A ADC #0 \ Round up the A / 8 division RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: DrawOrthoLine \ Type: Subroutine \ Category: Drawing lines \ Summary: Draw an orthogonal line (i.e. vertical or horizontal) \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ S Defines the starting coordinate for the line: \ \ * 0 = (joyCoord, H + W) \ \ * 128 = (H + W, joyCoord) \ \ H + W Coordinate of the start of the line (it doesn't matter \ how this value is split between H and W as only the sum \ is used) \ \ joyCoord Coordinate of the start of the line \ \ T Horizontal width/length of line \ \ U Vertical width/length of line \ \ N Drawing mode: \ \ * 0 = Draw (using OR logic) \ \ * 128 = Erase (using EOR logic) \ \ ------------------------------------------------------------------------------ \ \ Other entry points: \ \ EraseOrthoLine Use the value of G instead of H (so the coordinate is \ G + W) and always use EOR Logic to draw the line (which \ will erase it if it is already on-screen) \ \ ****************************************************************************** .DrawOrthoLine LDA H \ Set A = H JMP dort1 \ Jump to dort1 to draw the orthogonal line and skip the \ code for the EraseOrthoLine entry point .EraseOrthoLine LDA #128 \ Set N = 128 so the line is drawn with EOR logic, which STA N \ erases the line if it is already on-screen LDA G \ Set A = G .dort1 CLC \ Set A = A + W ADC W BIT S \ If bit 7 of S is set, jump down to dort2 BMI dort2 STA J \ Set J = A LDA joyCoord \ Set I = joyCoord STA I \ We now have (I, J) = (joyCoord, A + W) JMP dort3 \ Jump down to dort3 .dort2 STA I \ Set I = A LDA joyCoord \ Set J = joyCoord STA J \ We now have (I, J) = (A + W, joyCoord) .dort3 LDA #0 \ Set V = 0 so the line is drawn in a positive direction STA V \ for both axes JSR DrawVectorLine \ Draw/erase a line from (I, J) as a vector (T, U) with \ direction V RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: ScaleSigned \ Type: Subroutine \ Category: Maths \ Summary: Scale an indicator value by 4 or 16, retaining the sign and adding \ sensitivity for smaller values \ \ ------------------------------------------------------------------------------ \ \ This routine is used to scale the values for the following indicators: \ \ * The joystick position display (indicator 8 or 10), where the x-coordinate \ is scaled by 16 and the y-coordinate by 4, as the display is taller than \ it is wide \ \ * Rudder (indicator 9), which is divided by 16 \ \ When scaling down by a factor of 16, some smaller values scale to 1 instead of \ 0 (specifically, 4 to 7), and in all cases the final scaling is rounded up, so \ this routine shows small deviations on the rudder and joystick indicators that \ otherwise wouldn't register. \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ A The value to scale \ \ C flag Determines the scale factor: \ \ * C flag set = divide A by 16 \ \ * C flag clear = divide A by 4 \ \ ****************************************************************************** .ScaleSigned PHP \ Store the flags on the stack, so we can check later \ what their values were on entry BPL scsi1 \ If A is positive, jump to scsi1 to skip the following \ three instructions EOR #&FF \ Set A = -A using two's complement, so A is positive CLC ADC #1 .scsi1 \ By this point, A = |A| LSR A \ Set A = |A| / 2 PLP \ Restore the flags from the stack, leaving them on the PHP \ stack for later BCC scsi3 \ If the C flag is clear, jump to scsi3 so we only \ divide the original value by 4 \ If we get here then the C flag was set on entry, so we \ want to divide A by 16 using four shifts in total LSR A \ Set A = A / 2 \ = |A| / 4 CMP #1 \ If A <> 1, skip the following instruction BNE scsi2 LDA #2 \ A = 1 (so the original |A| was in the range 4 to 7), \ so set A = 2, which will give us an end result of 1 \ \ In other words, this scales smaller values to 1 that \ would otherwise scale to 0, like this: \ \ * 0 to 3 scale down to 0 \ * 4 to 23 scale down to 1 \ * 24 to 39 scale down to 2 \ * 40 to 55 scale down to 3 \ \ and so on .scsi2 LSR A \ Set A = A / 2 \ = |A| / 8 .scsi3 LSR A \ Set A = A / 2 \ \ so this is: \ \ * |A| / 16 if the C flag was set on entry \ * |A| / 4 if the C flag was clear on entry ADC #0 \ Increment A if the value of A before the LSR was odd \ (so the result of the last division gets rounded up) \ \ This works because the LSR will set the C flag if bit \ 0 of A was set before the shift, so A gets bumped up \ by 1 by the ADC PLP \ Restore the flags from the stack BPL scsi4 \ If the N flag is clear, then the result already has \ the correct sign (positive), so jump to scsi4 to \ return from the subroutine EOR #&FF \ Set A = -A using two's complement, so A is now CLC \ negative and the sign matches the original value of A ADC #1 \ on entry into the subroutine .scsi4 RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: UpdateDashboard \ Type: Subroutine \ Category: Dashboard \ Summary: Update two indicators on the dashboard, one from 0-6, one from \ 7-11, cycling through them with each subsequent call \ \ ------------------------------------------------------------------------------ \ \ Each call to this routine updates two indicators, one from the range 0 to 6, \ and the other from 7 to 11, with subsequent calls working their way through \ the ranges in order (so the first call updates indicators 0 and 7, the second \ call updates indicators 1 and 8, and so on). When we reach the end of a range, \ we wrap round to the start again. \ \ Note that the joystick position display has two numbers, 8 and 10, so it gets \ updated at twice the rate of the other indicators. \ \ Also, because the first group covers seven indicators (0 to 6) and the second \ group covers five (7 to 11), the indicators in the second group are updated \ more frequently than those in the first group. \ \ ------------------------------------------------------------------------------ \ \ Other entry points: \ \ UpdateDash7To11 Update the next indicator in the range 7 to 11 \ \ ****************************************************************************** .UpdateDashboard LDX indicator0To6 \ Increment the indicator number to point to the next INX \ indicator within the range 0 to 6 CPX #7 \ If X < 7, skip the following instruction BCC udas1 LDX #0 \ Set X = 0, so X steps through the range 0 to 6 with \ each subsequent call to UpdateDashboard .udas1 STX indicator0To6 \ Store the updated indicator number JSR UpdateIndicator \ Update indicator X (0 to 6) .UpdateDash7To11 LDX indicator7To11 \ Increment the indicator number to point to the next INX \ indicator within the range 7 to 11 CPX #11 \ If X < 11, skip the following instruction BCC udas2 LDX #7 \ Set X = 7, so X steps through the range 7 to 11 with \ each subsequent call to UpdateDashboard .udas2 STX indicator7To11 \ Store the updated indicator number JSR UpdateIndicator \ Update indicator X (7 to 11) LDA #%01110111 \ Redraw the small horizontal line in the centre of the STA row28_char26_5 \ slip-and-turn indicator RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: UpdateFlightModel (Part 1 of 4) \ Type: Subroutine \ Category: Flight model \ Summary: Apply any axis control key presses to the current axis values \ Deep dive: The key logger \ \ ****************************************************************************** .UpdateFlightModel LDX #2 \ We start with the aileron, rudder and elevator key \ pairs, whose values are stored in these key logger \ offsets in (keyLoggerHi keyLoggerLo): \ \ * 2 = aileron \ * 1 = rudder \ * 0 = elevator \ \ So we set a counter in X to count down through these \ index values, from 2 to 1 to 0 .umod1 CLC \ Clear the C flag for the addition below LDA keyLoggerLo,X \ Fetch the low byte for this key pair, which will be 1 \ if a key is being pressed, or 0 if no key is pressed BEQ umod4 \ If A = 0 then neither key in this key pair is being \ pressed, so jump down to umod4 ADC axisKeyUsage,X \ Add the low value to the corresponding variable for STA axisKeyUsage,X \ this key pair in axisKeyUsage, so we increment the \ value every time a key from this pair is used LDA keyLoggerHi,X \ Fetch the high byte for this key pair, which will be \ +1 or -1 if a key is being pressed, or 0 if no key is \ pressed (so it contains the sign of the key logger \ value) STA P \ Store the value in P so we can check its sign below ADC elevatorPosition,X \ Set A = A + one of the following axis values: \ \ * aileronPosition if this is the aileron key pair \ * rudderPosition if this is the rudder key pair \ * elevatorPosition if this is the elevator key pair \ \ so the relevant value is increased or decreased by 1 \ according to the key press LDY axisChangeRate,X \ If this key pair's axisChangeRate value is already BEQ umod2 \ zero, skip the following instruction DEC axisChangeRate,X \ Decrement this key pair's axisChangeRate value .umod2 BNE umod3 \ If this key pair's axisChangeRate value is non-zero, \ skip the following \ If we get here, then this key pair's axisChangeRate \ value has been reduced down to zero by repeated calls \ to UpdateFlightModel with the key being held down, so \ the relevant control is now fully engaged and we bump \ up the rate of change by another 3 in the relevant \ direction CLC \ Set A = A + 3 ADC #3 BIT P \ If P is positive (so we are increasing the relevant BPL umod3 \ axis value), skip the following instruction ADC #250 \ P is negative (so we are decreasing the relevant \ axis value), so set A = A - 6, giving a net change of \ setting A = A - 3 .umod3 TAY \ If the adjusted axis value is positive, jump down to BPL umod5 \ umod5 to check the maximum allowed value \ If we get here then the adjusted axis value is \ negative, so now we check against the minimum allowed \ value CMP #140 \ If A >= -116, the value is within limits, so jump to BCS umod6 \ umod6 to store it LDA #140 \ Set A = -116 as the minimum allowed value for the axis BNE umod6 \ value and jump to umod6 to store it .umod4 \ If we get here then neither key was pressed from this \ key pair LDA #6 \ Set the axisChangeRate value for this key pair to 6, STA axisChangeRate,X \ so the rate of change goes back to 1 until we fully \ engage the control once again BNE umod7 \ Jump down to umod7 to move on to the next key pair \ (this BNE is effectively a JMP as A is never zero) .umod5 \ If we get here then the adjusted axis value is \ positive, so now we check against the maximum allowed \ value CMP #119 \ If A < 119, the value is within limits, so jump to BCC umod6 \ umod6 to store it LDA #118 \ Set A = 118 as the maximum allowed value for the axis \ value .umod6 STA elevatorPosition,X \ Store the adjusted axis value in the relevant axis \ variable: \ \ * aileronPosition if this is the aileron key pair \ * rudderPosition if this is the rudder key pair \ * elevatorPosition if this is the elevator key pair .umod7 DEX \ Decrement the key pair index BPL umod1 \ Loop back to process the next key pair until we have \ done all three axes of movement (aileron roll, rudder \ yaw and elevator pitch) JSR ReadJoystick \ Read the joystick axes and fire button and update the \ aileron, elevator and fire key values accordingly \ ****************************************************************************** \ \ Name: UpdateFlightModel (Part 2 of 4) \ Type: Subroutine \ Category: Flight model \ Summary: Apply any throttle key presses to the current thrust value \ Deep dive: The key logger \ \ ****************************************************************************** \ Now we process the thrust keys CLC \ Clear the C flag for the addition below LDA keyLoggerLo+3 \ Fetch the low byte for the throttle key BEQ umod11 \ If A = 0 then neither key in this key pair is being \ pressed, so jump down to umod11 to skip the throttle \ calculations below \ We now want to add the key logger value to the current \ thrust value, which we do like this: \ \ (Y X) = (keyLoggerHi+3 keyLoggerLo+3) \ + (thrustHi thrustLo) ADC thrustLo \ We start by adding the low bytes TAX LDA keyLoggerHi+3 \ And then add the high bytes, so now (Y X) contains the ADC thrustHi \ updated thrust value TAY BMI umod8 \ If the result is negative, jump to umod8 to set both X \ and Y to zero, so the minimum thrust value is zero CPY #5 \ If the high byte in Y < 5, then the result is within BCC umod10 \ bounds, so jump to umod10 to store the new thrust \ value LDY #5 \ Set Y = 5 and jump to umod9 to set X to 0, so the BNE umod9 \ maximum thrust value is (5 0), or 1280 (this BNE is \ effectively a JMP, as Y is never zero) .umod8 LDY #0 \ If we get here then the new thrust in (Y X) turned out \ to be negative, so we set Y = 0 and then X = 0 to zero \ the thrust .umod9 LDX #0 \ Zero the low byte of the new thrust as we are either \ at the maximum value of (5 0) or the minimum value of \ (0 0) .umod10 STX thrustLo \ Store the thrust value in (thrustHi thrustLo) to the STY thrustHi \ updated thrust value in (Y X) LDX #11 \ Update the thrust indicator JSR UpdateIndicator \ ****************************************************************************** \ \ Name: UpdateFlightModel (Part 3 of 4) \ Type: Subroutine \ Category: Flight model \ Summary: Process the undercarriage, brake, flaps and fire keys \ Deep dive: The key logger \ \ ****************************************************************************** .umod11 LDX #4 \ Process the undercarriage or brake keys, if pressed JSR ProcessOtherKeys BEQ umod13 \ If neither are being pressed, jump to umod13 to check \ for the next set of key presses BMI umod12 \ If the brake key is being pressed, then the returned \ value is 128, so jump to umod12 JSR IndicatorU \ The undercarriage key is being pressed, so update the \ undercarriage indicator JMP umod13 \ Jump to umod13 to check for the next set of key \ presses .umod12 JSR IndicatorB \ The brake key is being pressed, so update the brakes \ indicator .umod13 LDX #5 \ Process the flaps or fire keys, if pressed JSR ProcessOtherKeys BEQ umod15 \ If neither are being pressed, jump to umod15 to check \ for the next set of key presses BMI umod14 \ If the fire key is being pressed, then the returned \ value is 128, so jump to umod14 JSR IndicatorF \ The flaps key is being pressed, so update the flaps \ indicator JMP umod15 \ Jump to umod15 to check for the next set of key \ presses .umod14 JSR FireGuns \ The fire key is being pressed, so call FireGuns \ ****************************************************************************** \ \ Name: UpdateFlightModel (Part 4 of 4) \ Type: Subroutine \ Category: Flight model \ Summary: Set up matrices, apply the flight model and update the dashboard \ \ ****************************************************************************** .umod15 LDY #2 \ We now set up the matrices, starting with the \ projected rotation angles, which we populate one axis \ at a time, so set a counter in Y to work through the \ three axes .umod16 STY matrixAxis \ Set matrixAxis to the current axis in Y, to pass to \ ProjectAxisAngle JSR ProjectAxisAngle \ Convert the rotation angles of the plane in axis Y to \ coordinates that we can use to populate the matrices \ in the call to SetMatrices LDY matrixAxis \ Restore the axis counter that we stored above DEY \ Decrement the axis counter in Y BPL umod16 \ Loop back until we have processed all three axes LDA #0 \ Set matrixNumber = 0 to pass to SetMatrices, so we STA matrixNumber \ set the values for matrices 1 to 4 STA matrixAxis \ Set matrixAxis = 0 to pass to SetMatrices, so we set \ the three standard axes in matrices 1 to 4 JSR SetMatrices \ Set up the four rotation matrices JSR ApplyFlightModel \ Apply the flight model to our plane JSR UpdateDashboard \ Update the next two indicators in the ranges 0 to 6 \ and 7 to 11 JSR UpdateDash7To11 \ Update the next indicator in the range 7 to 11 RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: ProcessOtherKeys \ Type: Subroutine \ Category: Keyboard \ Summary: Apply the undercarriage, brakes, flaps and fire keys \ Deep dive: The key logger \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ X The offset within the key logger for the keys to check: \ \ * 4 = "U" or "B" (undercarriage, brakes) \ \ * 5 = "F" or SHIFT (flaps, fire) \ \ ------------------------------------------------------------------------------ \ \ Returns: \ \ A Returns the status of the relevant key presses: \ \ * 0 = neither key in the pair is being pressed, or a \ key is still being held down from a previous \ call to the routine, so it has already been \ processed \ \ * 1 = One of "U" and "F" (undercarriage, flaps) is \ being pressed \ \ * 128 = One of "B" and SHIFT (brakes, fire) is being \ pressed \ \ ****************************************************************************** .ProcessOtherKeys LDA keyLoggerLo,X \ Fetch the low byte for this key pair BNE poth2 \ If A is non-zero then a key from this key pair is \ being pressed, so jump down to poth2 to process the \ key press STA pressingUFBS-4,X \ Zero the relevant value in pressingUFBS (for U) or \ pressingUFBS+1 (for F) to indicate that the first key \ from this pair is not being held down STA pressingUFBS-4+3,X \ Zero the relevant value in pressingUFBS+3 (for B) or \ pressingUFBS+4 (for SHIFT) to indicate that the second \ key from this pair is not being held down .poth1 LDA #0 \ Set A = 0 as the return value RTS \ Return from the subroutine .poth2 TAY \ Copy the low value into Y, so we have: \ \ * Y = 4 if "U" is being pressed (undercarriage) \ * Y = 5 if "F" is being pressed (flaps) \ * Y = 7 if "B" is being pressed (brakes) \ * Y = 8 if SHIFT is being pressed (fire) LDA pressingUFBS-4,Y \ Fetch the relevant value from pressingUFBS to see if BNE poth1 \ the key press has already been processed, and if it is \ non-zero, this indicates that it is still being held \ down from a previous visit to this routine, so jump to \ poth1 to return a value of 0, so we can ignore the key \ press LDA ucStatus-4,Y \ Flip the value of the relevant status byte, as EOR #1 \ follows: STA ucStatus-4,Y \ \ * Flip ucStatus if "U" is being pressed \ * Flip flapsStatus if "F" is being pressed \ * Flip brakesStatus if "B" is being pressed \ * Flip brakesStatus+1 if SHIFT is being pressed \ (which has no effect) LDA #1 \ Set the relevant value in pressingUFBS to denote that STA pressingUFBS-4,Y \ this key is being pressed, so if we revisit this \ routine before the key is released, we don't keep on \ flipping the status byte CPY #7 \ If Y < 7, i.e. U or F are being pressed, jump to poth3 BCC poth3 \ to return a result of 1 LDA #128 \ Y >= 7, i.e. B or SHIFT are being pressed, so set \ A = 128 as the return value RTS \ Return from the subroutine .poth3 LDA #1 \ Set A = 1 as the return value RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: IndicatorU \ Type: Subroutine \ Category: Dashboard \ Summary: Update the undercarriage indicator ("U") and related variables \ \ ****************************************************************************** .IndicatorU LDA forceFactor+5 \ Set A to the force factor for zLiftDrag LDY ucStatus \ If ucStatus is non-zero then the undercarriage is BNE indu1 \ down, so jump to indu1 \ If we get here then the undercarriage is up SEC \ Set A = A - 10 SBC #10 \ \ so having the undercarriage up reduces drag LDX #5 \ Set X = 5 to store in yLandingGear below, as the \ vertical distance between the cockpit and the bottom \ of the plane LDY #%01010101 \ Set Y to a four-pixel block with pixels 0 and 2 in \ white, to act as the centre of the undercarriage \ indicator when turned off BNE indu2 \ Jump to indu2 to update the indicator (this BNE is \ effectively a JMP as Y is never zero) .indu1 \ If we get here then the undercarriage is down LDY onGround \ If onGround is non-zero, then we are on the ground, so BNE indu4 \ jump to indu4 to set the undercarriage to up (because \ once we have landed with the undercarriage up, we \ can't put it down again) CLC \ Set A = A + 10 ADC #10 \ \ so having the undercarriage down increases drag LDX #10 \ Set X = 10 to store in in yLandingGear below, as the \ vertical distance between the cockpit and the bottom \ of the plane LDY #%01110111 \ Set Y to a four-pixel block with pixels 0, 1 and 2 in \ white, to act as the centre of the undercarriage \ indicator when turned on .indu2 STA forceFactor+5 \ Store A in the force factor for zLiftDrag, so it is \ incremented by 10 when the undercarriage is down, and \ reduced by 10 when the undercarriage is up, i.e. \ having the undercarriage down increases drag STX yLandingGear \ Store X in yLandingGear, so the vertical distance \ between the cockpit and the bottom of the plane is 5 \ if the undercarriage is up, or 10 if it is down TYA \ Set A to the pixel pattern in Y LDX #2 \ Set X = 2 to use as a pixel row counter for the three \ pixel rows in the undercarriage indicator .indu3 STA row30_char32_2,X \ Update pixel row X of the undercarriage indicator to \ the pixel pattern in A DEX \ Decrement the byte counter to the pixel row above BPL indu3 \ Loop back to update the next row of the indicator RTS \ Return from the subroutine .indu4 LDA #0 \ Set ucStatus = 0 to set the undercarriage to up, STA ucStatus \ because once we have landed with the undercarriage up, \ the belly of the plane is on the ground and we can't \ put the undercarriage down again RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: IndicatorF \ Type: Subroutine \ Category: Dashboard \ Summary: Update the flaps indicator ("F") and related variables \ \ ****************************************************************************** .IndicatorF LDA forceFactor+5 \ Set A to the force factor for zLiftDrag LDY flapsStatus \ If flapsStatus is non-zero then the flaps are on, so BNE indf1 \ jump to indf1 \ If we get here then the flaps are off SEC \ Set A = A - 200 SBC #200 \ \ so having the flaps off reduces drag LDX #0 \ Set X = 0 to use as the force factor for yFlapsLift \ below LDY #%01000100 \ Set Y to a four-pixel block with pixel 2 in white, to \ act as the centre of the flaps indicator when turned \ off BNE indf2 \ Jump to indf2 to update the indicator (this BNE is \ effectively a JMP as Y is never zero) .indf1 \ If we get here then the flaps are on CLC \ Set A = A + 200 ADC #200 \ \ so having the flaps on increases drag LDX #152 \ Set X = 152 to use as the force factor for yFlapsLift \ below LDY #%11001100 \ Set Y to a four-pixel block with pixels 1 and 2 in \ white, to act as the centre of the flaps indicator \ when turned on .indf2 STA forceFactor+5 \ Store A in the force factor for zLiftDrag, so it is \ incremented by 200 when the flaps are on, and reduced \ by 200 when the flaps are off, i.e. having the flaps \ on increases drag STX forceFactor+7 \ Store X in the force factor for yFlapsLift, so it is 0 \ if the flaps are off and 152 if they are on, i.e. \ having the flaps on increases the vertical lift TYA \ Set A to the pixel pattern in Y LDX #2 \ Set X = 2 to use as a pixel row counter for the three \ pixel rows in the flaps indicator .indf3 STA row30_char35_2,X \ Update pixel row X of the flaps indicator to the pixel \ pattern in A DEX \ Decrement the byte counter to the pixel row above BPL indf3 \ Loop back to update the next row of the indicator RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: FireGuns \ Type: Subroutine \ Category: The Theme \ Summary: Create the bullet objects and send them on their way \ Deep dive: Adding bullets to the world \ \ ------------------------------------------------------------------------------ \ \ Other entry points: \ \ FireGuns-1 Contains an RTS \ \ ****************************************************************************** .FireGuns LDA firingStatus \ If either firingStatus or hitTimer are non-zero, then ORA hitTimer \ there are either bullets still in the air, or we only BNE FireGuns-1 \ just hit an alien. In either case we can't fire the \ guns, so return from the subroutine (as FireGuns-1 \ contains an RTS) LDX #228 \ Set point 228 to (0, 0, zVelocityPHi + 200) JSR SetPointToOrigin \ \ starting with the zeroes LDA zVelocityPHi \ And then setting the low byte of the z-coordinate CLC ADC #200 STA zPointLo+228 LDA #&FF \ Set the point with ID 95 to (&FFF6, &FFFC, &FF14) LDX #95 \ = (-10, -4, -236) JSR SetPoint \ \ which is the vector from the plane back to object 12 \ at the trailing end of the left bullet's trail \ \ We start with the high bytes LDA #&14 \ And then set the low bytes STA zPointLo+95 LDA #&F6 STA xPointLo+95 LDA #&FC STA yPointLo+95 LDA #228 \ Set GG to point ID 228, to pass to the call to STA GG \ SetPointCoords LDA #9 \ Set the matrix number so the call to SetPointCoords STA matrixNumber \ uses matrix 2 in the calculation, so it rotates the \ point from the plane's frame of reference to the \ outside world's frame of reference (so we spawn the \ bullets using coordinates that are relative to the \ plane, but calculate them in the following as \ world-relative coordinates, so they are then \ independent of the plane's future movement) STA firingStatus \ Set firingStatus = 9, which is a non-zero value, to \ indicate that there are bullets are in the air (the \ value 9 isn't significant beyond the fact that it is \ non-zero) JSR SetPointCoords \ Calculate the coordinates for point 228, which also \ returns the coordinates in (xTemp1, yTemp1, zTemp1) LDX #229 \ We now copy the coordinates from (xTemp1, yTemp1, \ zTemp1) to points 229, 230 and 231, so we set a \ counter in X for the point IDs .fire1 JSR CopyTempToPoint \ Copy from (xTemp1, yTemp1, zTemp1) into the \ coordinates for point X INX \ Increment the point ID CPX #232 \ Loop back until we have copied (xTemp1, yTemp1, BNE fire1 \ zTemp1) into points 229, 230 and 231 LDA #95 \ Set GG to point ID 95, to pass to the call to STA GG \ SetPointCoords JSR SetPointCoords \ Calculate the coordinates for point 95 LDX #LO(xPlaneLo) \ Set X so the call to CopyWorkToPoint copies the \ coordinates from (xPlane, yPlane, zPlane) LDY #96 \ Set Y so the call to CopyPointToWork copies the \ coordinates to point 96 JSR CopyWorkToPoint \ Copy the coordinates from (xPlane, yPlane, zPlane) \ to point 96 LDY #12 \ We now set the object coordinates for objects 12 to 15 \ to the coordinates in point 96, so set Y as the object \ ID, starting with 12 LDX #96 \ And set X as the point ID to add in the call to \ AddPointToObject .fire2 JSR SetObjectToOrigin \ Set the object coordinates for object Y to (0, 0, 0) JSR AddPointToObject \ Add the vector in point 96 to the object coordinates, \ so this sets the location of the object to that of \ point 96, i.e. the coordinates of the plane INY \ Increment the object ID CPY #16 \ Loop back until we have set objects 12 to 15 to the BNE fire2 \ coordinates in point 96, so they are all now at the \ plane's coordinates LDY #12 \ Add the vector in point 95 to the object coordinates LDX #95 \ for object 12 JSR AddPointToObject STX objectAnchorPoint \ Store the vector in point 95 as the object anchor \ point, so the following calculations use this as the \ anchor point \ \ This means that the following coordinate calculations \ will return the vector from the plane's location to \ each point, as we are telling SetObjPointCoords to \ take the anchor point vector (from the plane to point \ 95) and add on the vector of the object point (which \ is the interior vector of the point within the object) LDA #96 \ Calculate the coordinates for object point 96 with STA GG \ anchor point 95 JSR SetObjPointCoords LDA #97 \ Calculate the coordinates for object point 97 with STA GG \ anchor point 95, which also sets (xTemp1 yTemp1 JSR SetObjPointCoords \ zTemp1) to the rotated vector from the anchor point \ (point 95) to point 97 LDX #98 \ Set point 98's coordinates to point 96's coordinates + LDY #96 \ (xTemp1 yTemp1 zTemp1), the latter containing the JSR AddTempToPoint \ vector from point 95 to point 97 \ Finally, we add the following vectors to the object \ points for objects 13 to 15: \ \ * Move object 13 by the vector in point 96 \ \ * Move object 14 by the vector in point 97 \ \ * Move object 15 by the vector in point 98 LDY #15 \ Set Y as the object ID that gets moved in the call to \ AddPointToObject LDX #98 \ Set X as the point ID to add to the object coordinates \ in the call to AddPointToObject .fire3 JSR AddPointToObject \ Add the vector in point X to the object coordinates \ for point Y DEX \ Decrement the point ID DEY \ Decrement the object ID CPY #12 \ Loop back until we have added points 96 to 98 to BNE fire3 \ objects 13 to 15 RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: IndicatorB \ Type: Subroutine \ Category: Dashboard \ Summary: Update the brakes indicator ("B") \ \ ****************************************************************************** .IndicatorB LDA #%01110111 \ Set A to a four-pixel block with pixels 0, 1 and 2 in \ white, to act as the centre of the brakes indicator \ when turned on LDX brakesStatus \ If brakesStatus is non-zero then the brakes are on, so BNE indb1 \ jump to indb1 LDA #%01010101 \ Set A to a four-pixel block with pixels 0 and 2 in \ white, to act as the centre of the brakes indicator \ when turned off .indb1 LDX #2 \ Set X = 2 to use as a pixel row counter for the three \ pixel rows in the brakes indicator .indb2 STA row30_char37_2,X \ Update pixel row X of the brakes indicator to the \ pixel pattern in A DEX \ Decrement the byte counter to the pixel row above BPL indb2 \ Loop back to update the next row of the indicator RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: IndicatorT \ Type: Subroutine \ Category: Dashboard \ Summary: Update the Theme indicator ("T") \ \ ****************************************************************************** .IndicatorT LDA #%01110111 \ Set A to a four-pixel block with pixels 0, 1 and 2 in \ white, to act as the centre of the Theme indicator \ when turned on LDX themeStatus \ If themeStatus is positive then the Theme is enabled, BPL indt1 \ so jump to indt1 LDA #%01010101 \ Set A to a four-pixel block with pixels 0 and 2 in \ white, to act as the centre of the Theme indicator \ when turned off .indt1 LDX #2 \ Set X = 2 to use as a pixel row counter for the three \ pixel rows in the Theme indicator .indt2 STA row30_char0_2,X \ Update pixel row X of the Theme indicator to the pixel \ pattern in A DEX \ Decrement the byte counter to the pixel row above BPL indt2 \ Loop back to update the next row of the indicator RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: ScanKeyboard \ Type: Subroutine \ Category: Keyboard \ Summary: Scan the keyboard for a specific key \ Deep dive: The key logger \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ X The internal key number of the key to scan for \ \ ------------------------------------------------------------------------------ \ \ Returns: \ \ Z flag If set (BEQ) then the key is being pressed, if clear \ (BNE) then it is not being pressed \ \ ****************************************************************************** .ScanKeyboard LDA #129 \ Call OSBYTE with A = 129, X = key number and Y = &FF LDY #&FF \ to scan the keyboard for the key in X, returning the JSR OSBYTE \ following in both X and Y: \ \ * 0 = the key is not being pressed \ \ * &FF = the key is being pressed CPX #&FF \ Set the Z flag depending on whether the key is being \ pressed RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: UpdateKeyLogger \ Type: Subroutine \ Category: Keyboard \ Summary: Scan the keyboard for keys in the two key tables and update the \ key logger \ Deep dive: The key logger \ \ ------------------------------------------------------------------------------ \ \ This routine updates the value in the key logger, which is stored in \ (keyLoggerHi keyLoggerLo). If a key is pressed, then the corresponding 16-bit \ value in the key logger is set to the corresponding value from the KeyTable \ tables, which are stored at (keyTable1Hi keyTable1Lo) and (keyTable2Hi \ keyTable2Lo). \ \ ****************************************************************************** .UpdateKeyLogger LDA #5 \ Set V = 5 to act as an offset as we work our way STA V \ through the six keys in keyTable1 .klog1 LDY V \ Set Y = the offset of the key we are processing LDX keyTable1,Y \ Scan the keyboard to see if the Y-th key in keyTable1 JSR ScanKeyboard \ is being pressed BNE klog2 \ If the key is not being pressed, jump down to klog2 to \ check the Y-th key in keyTable1 LDX V \ Set X = the offset of the key we are processing LDY keyTable1Lo,X \ Fetch the key logger value for this key press into LDA keyTable1Hi,X \ (A Y) JMP klog4 \ Jump down to klog4 to store (A Y) in the key logger .klog2 LDY V \ Set Y = the offset of the key we are processing LDX keyTable2,Y \ Scan the keyboard to see if the Y-th key in keyTable2 JSR ScanKeyboard \ is being pressed BNE klog3 \ If the key is not being pressed, jump down to klog3 to \ store 0 in the key logger LDX V \ Set X = the offset of the key we are processing LDY keyTable2Lo,X \ Fetch the key logger value for this key press into LDA keyTable2Hi,X \ (A Y) JMP klog4 \ Jump down to klog4 to store (A Y) in the key logger .klog3 LDA #0 \ Set A = 0 LDX V \ Set X = the offset of the key we are processing TAY \ Set Y = 0, so the key logger value in (A Y) is 0 .klog4 STA keyLoggerHi,X \ Store the high byte of the key logger value in (A Y) \ in the X-th byte of keyLoggerHi TYA \ Store the low byte of the key logger value in (A Y) STA keyLoggerLo,X \ in the X-th byte of keyLoggerLo DEC V \ Decrement the offset to point to the next key to \ process BPL klog1 \ Loop back until we have processed all six key pairs RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: AddPointToObject \ Type: Subroutine \ Category: 3D geometry \ Summary: Add a point vector to an object's coordinates \ \ ------------------------------------------------------------------------------ \ \ This routine adds a point vector in (xPoint, yPoint, zPoint) to the object \ coordinates in (xObject, yObject, zObject), storing the result in the object \ coordinates. In other words, this moves an object by the xPoint vector. \ \ This is called MOBJ or UOBJ in the original source code. \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ X The ID of the point vector to add to the object \ \ Y The ID of the object to update \ \ ****************************************************************************** .AddPointToObject LDA xObjectLo,Y \ Set object Y's x-coordinate to the following: CLC \ ADC xPointLo,X \ (xObjectHi+Y xObjectLo+Y) + (xPointHi+X xPointLo+X) STA xObjectLo,Y \ LDA xObjectHi,Y \ i.e. we add object Y's x-coordinate and point X's ADC xPointHi,X \ x-coordinate STA xObjectHi,Y LDA yObjectLo,Y \ Set object Y's y-coordinate to the following: CLC \ ADC yPointLo,X \ (yObjectHi+Y yObjectLo+Y) + (yPointHi+X yPointLo+X) STA yObjectLo,Y \ LDA yObjectHi,Y \ i.e. we add object Y's y-coordinate and point X's ADC yPointHi,X \ y-coordinate STA yObjectHi,Y LDA zObjectLo,Y \ Set object Y's z-coordinate to the following: CLC \ ADC zPointLo,X \ (zObjectHi+Y zObjectLo+Y) + (zPointHi+X zPointLo+X) STA zObjectLo,Y \ LDA zObjectHi,Y \ i.e. we add object Y's z-coordinate and point X's ADC zPointHi,X \ z-coordinate STA zObjectHi,Y RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: SetObjectToOrigin \ Type: Subroutine \ Category: 3D geometry \ Summary: Set an object's coordinates to (0, 0, 0) \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ Y The ID of the object to set to (0, 0, 0) \ \ ****************************************************************************** .SetObjectToOrigin LDA #0 \ Zero the object's x-coordinate STA xObjectLo,Y STA xObjectHi,Y STA yObjectLo,Y \ Zero the object's y-coordinate STA yObjectHi,Y STA zObjectLo,Y \ Zero the object's z-coordinate STA zObjectHi,Y RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: ReadADCChannel \ Type: Subroutine \ Category: Keyboard \ Summary: Read the joystick position from one of the ADC channels \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ X The ADC channel to read: \ \ * 1 = joystick X \ \ * 2 = joystick Y \ \ ------------------------------------------------------------------------------ \ \ Returns: \ \ A The joystick position, inverted and clipped to the range \ -118 to +116 \ \ ****************************************************************************** .ReadADCChannel LDA #128 \ Call OSBYTE with A = 128 to fetch the 16-bit value JSR OSBYTE \ from ADC channel X, returning (Y X), i.e. the high \ byte in Y and the low byte in X \ \ * Channel 1 is the x-axis: 0 = right, 65520 = left \ \ * Channel 2 is the y-axis: 0 = down, 65520 = up TYA \ Copy Y to A, so the result is now in (A X) CMP #247 \ If A < 247, jump to radc1 to skip the next instruction BCC radc1 LDA #246 \ Set A = 246, so A now has a maximum value of 246 .radc1 CMP #12 \ If A >= 12, jump to radc2 to skip the next instruction BCS radc2 LDA #12 \ Set A = 12, so A now has a minimum value of 12 .radc2 \ By the time we get here, A is in the range 12 to 246 SEC \ Set A = A - 128, so A is now in the range -116 to +118 SBC #128 EOR #&FF \ Negate A using two's complement, so A is now in the CLC \ range -118 to +116 ADC #1 RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: ResetVariables \ Type: Subroutine \ Category: Setup \ Summary: Reset most variables to prepare for a new flight \ \ ****************************************************************************** .ResetVariables LDX #0 \ Set A = 0 to use as our zero value TXA \ Set X = 0 to use as a counter for zeroing 256 bytes in \ the rset1 loop STA alienSlot \ Set alienSlot = 0 to clear out the first alien slot \ (we clear out the other three slots below) STA forceFactor+7 \ Set the force factor for yFlapsLift = 0 STA hitTimer \ Set hitTimer = 0 to cancel any alien explosions STA randomNumbers \ Set randomNumbers = 0 to reset the pointer for the \ list of random numbers STA scoreLo \ Set (scoreHi scoreLo) = 0 to reset the current score STA scoreHi .rset1 \ This loop zeroes the whole page at pointStatus, which \ zeroes both pointStatus and objectStatus, resetting \ all the point and object statuses STA pointStatus,X \ Zero the X-th byte of pointStatus DEX \ Decrement the byte counter BNE rset1 \ Loop back until we have zeroed the whole page \ We now zero all the workspace variables from xTurnHi \ to yPlaneHi, so their default values are all zero \ unless they are explicitly set below LDX #255 \ Set X = 255 to use as a counter for zeroing 255 bytes \ in the rset2 loop STA relatedPoints \ Set relatedPoints = 0 to reset the relatedPoints list STA mainLoopCounter \ Set mainLoopCounter = 0 to reset the main loop counter .rset2 STA xTurnHi-1,X \ Zero the X-1-th byte of the workspace variables DEX \ Decrement the byte counter BNE rset2 \ Loop back until we have zeroed from xTurnHi to \ yPlaneHi \ We now zero the eight bytes at alienState to reset the \ state of the aliens LDX #7 \ Set X = 7 to use as a counter for zeroing eight bytes \ in the following loop .rset3 STA alienState,X \ Zero the X-th byte of alienState DEX \ Decrement the byte counter BPL rset3 \ Loop back until we have zeroed alienState to \ alienState+7 LDA #&48 \ Set (zPlaneHi zPlaneLo) = &485C STA zPlaneHi LDA #&5C STA zPlaneLo LDA #&C6 \ Set (xPlaneHi xPlaneLo) = &C6E5 STA xPlaneHi LDA #&E5 STA xPlaneLo LDA #&0A \ Set (yPlaneHi yPlaneLo) = &000A STA yPlaneLo STA alienSpeed \ Set alienSpeed = 10, the movement speed for the first \ wave of aliens LDA #242 \ Set the force factor for zLiftDrag = 242, which is STA forceFactor+5 \ quickly adjusted by +10 for the undercarriage being \ down and -200 for the flaps being off, giving a \ starting value of 52 when we are sitting on the runway LDA #1 \ Set ucStatus = 1, so the undercarriage is down STA ucStatus STA brakesStatus \ Set brakesStatus = 1, so the brakes are on STA landingStatus \ Set landingStatus = 1, so we do all landing tasks JSR IndicatorU \ Update the undercarriage indicator LDA #1 \ Set onGround = 1, so we start on the ground STA onGround LDA #47 \ Set lineBuffer2Count = 47, so line buffer 2 is empty STA lineBuffer2Count LDA #255 \ Set themeStatus = 255, so the Theme is disabled STA themeStatus STA lineBuffer1Count \ Set lineBuffer1Count = 255, so line buffer 1 is empty \ We now zero the eight bytes at alienObjectId, so no \ objects are associated with any aliens LDX #7 \ Set X = 7 to use as a counter for zeroing eight bytes \ in the following loop STX xRotationHi \ Set xRotationHi = 7, so the plane tilts backwards by \ 7/256 = 9.84 degrees when its wheels are on the ground .rset4 STA alienObjectId,X \ Zero the X-th byte of alienObjectId DEX \ Decrement the byte counter BPL rset4 \ Loop back until we have zeroed all 8 alienObjectId \ bytes \ We now zero alienSlot+1 to alienSlot+3 to clear out \ the rest of the alien slots (we already zeroed \ alienSlot above) LDX #2 \ Set X = 2 to use as a counter for zeroing three bytes \ in the following loop .rset5 STA alienSlot+1,X \ Zero the X-th byte of alienSlot+1 DEX \ Decrement the byte counter BPL rset5 \ Loop back until we have zeroed all three bytes JSR IndicatorT \ Update the Theme indicator LDX #11 \ Update the thrust indicator JSR UpdateIndicator LDA #65 \ Set fuelLevel = 65, to indicate a full tank STA fuelLevel \ We now drain the fuel tank one point at a time, \ updating the fuel gauge as we go so the fuel gauge \ gets cleared down to empty at the same time as \ the value in fuelLevel .rset6 DEC fuelLevel \ Decrement the counter in fuelLevel JSR UpdateFuelGauge \ Update the fuel gauge LDA fuelLevel \ Loop back until fuelLevel = 0, by which point we have BNE rset6 \ reset the fuel tanks and cleared the fuel gauge \ Fall through into ResetRadar to reset the radar \ display \ ****************************************************************************** \ \ Name: ResetRadar \ Type: Subroutine \ Category: Dashboard \ Summary: Reset the radar display \ \ ****************************************************************************** .ResetRadar LDA #80 \ Set xPointLo = 80, so we don't draw a new alien on the STA xPointLo \ radar (as this coordinate is off the radar) LDA #1 \ Set alien = 1, so we remove the alien from the radar STA alien \ rather than the runway when we call DrawRadarBlip STA xPointHi \ Set xPointHi = 1, so the value in xPointLo is treated \ as positive JSR DrawRadarBlip \ Remove the current dot from the radar, but don't draw \ a new one, as xPointLo is off-radar LDY #33 \ Reset object 33's coordinates (the flying alien) to JSR SetObjectToOrigin \ (0, 0, 0) RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: StartGame \ Type: Subroutine \ Category: Setup \ Summary: Reset the high score, set up the gunfire sound envelope and start \ a new game \ \ ****************************************************************************** .StartGame LDA #0 \ Set the high score in (highScoreHi highScoreLo) to 0 STA highScoreHi STA highScoreLo LDA #14 \ Call DefineEnvelope with A = 14 to set up the second JSR DefineEnvelope \ sound envelope \ Fall through into NewGame to start a new game \ ****************************************************************************** \ \ Name: NewGame \ Type: Subroutine \ Category: Setup \ Summary: Start a new game \ \ ****************************************************************************** .NewGame JSR ClearCanopy \ Clear the canopy to black, leaving the canopy edges \ alone JSR ResetVariables \ Reset most variables to prepare for a new flight JSR UpdateKeyLogger \ Scan for key presses and update the key logger JSR UpdateFlightModel \ Process any key presses in the key logger and update \ the matrices and flight model JSR ResetLineLists \ Reset the line lists at linesToShow and linesToHide, \ which will populate them with the correct lines to \ show for the starting point on the runway JSR IndicatorF \ Update the flaps indicator JSR IndicatorB \ Update the brakes indicator LDA #%01000000 \ Set the 6522 User VIA auxiliary control register STA VIA+&6B \ (SHEILA &6B) to %01000000 to disable the shift \ register LDA #234 \ Set 6522 User VIA T1C-L timer 1 high-order counter STA VIA+&65 \ (SHEILA &65) to 234 to start the T1 counter \ counting down at a rate of 1 MHz \ ****************************************************************************** \ \ Name: MainLoop (Part 1 of 15) \ Type: Subroutine \ Category: Main loop \ Summary: Start the main loop by processing gunfire and bullets \ Deep dive: Program flow of the main game loop \ \ ****************************************************************************** .MainLoop LDA linesToHideEnd \ Store the index of the end of the linesToHide list in STA previousListEnd \ previousListEnd so we can check it at the end of the \ main loop JSR SpawnAlien \ If the Theme is enabled and the current wave does not \ yet have eight aliens in it, spawn a new alien JSR UpdateKeyLogger \ Scan for key presses and update the key logger LDA firingStatus \ If firingStatus is non-zero, then we have already BNE main2 \ fired our gun and the bullets are still in the air, so \ jump to main2 to skip firing bullets, as we can't fire \ any more bullets until the current ones expire JSR UpdateFlightModel \ Process any key presses in the key logger and update \ the matrices and flight model LDA firingStatus \ If the call to UpdateFlightModel has left firingStatus BEQ main3 \ set to zero, then the fire key is not being pressed, \ so jump to main3 to skip firing bullets \ The call to UpdateFlightModel had changed firingStatus \ from zero to non-zero, which means the fire key is \ being pressed, so we now need to add two bullets to \ the scene LDA #2 \ Set gunSoundCounter = 2, so we make two firing sounds STA gunSoundCounter \ below, one for each bullet LDY #33 \ We now copy the status bytes for objects 30 to 33 (the \ four alien objects), copying the four bytes between \ objectStatus+30 and objectStatus+33 into the four \ bytes at alienStatus, so set up a counter in Y that \ can also act as the offset .main1 LDA objectStatus,Y \ Copy the Y-th byte of objectStatus to alienStatus-30, STA alienStatus-30,Y \ to give this: \ \ objectStatus+30 -> alienStatus \ objectStatus+31 -> alienStatus+1 \ objectStatus+32 -> alienStatus+2 \ objectStatus+33 -> alienStatus+3 DEY \ Decrement the loop counter CPY #30 \ Loop back until we have copied all four bytes BCS main1 \ We now add the bullet lines (line IDs 60 and 61) to \ the linesToShow list, so they get displayed LDY linesToShowEnd \ Set Y to the first free entry at the end of the \ linesToShow list LDA #60 \ Append line 60 to the end of the linesToShow list STA linesToShow,Y INY \ Increment Y to point to the next free entry in the \ list LDA #61 \ Append line 61 to the end of the linesToShow list STA linesToShow,Y INY \ Increment Y to point to the next free entry in the \ list STY linesToShowEnd \ Update linesToShowEnd with the updated index of the \ next free entry, which is two more than it was before \ we added the bullet lines JMP main3 \ Skip the following instruction, as we have already \ processed the key logger .main2 JSR UpdateFlightModel \ Process any key presses in the key logger and update \ the matrices and flight model \ ****************************************************************************** \ \ Name: MainLoop (Part 2 of 15) \ Type: Subroutine \ Category: Main loop \ Summary: Reset object statuses and related points \ Deep dive: Program flow of the main game loop \ \ ****************************************************************************** .main3 \ We now want to zero the 40 bytes in objectStatus, so \ that all objects are marked as unprocessed, ready to \ be processed again in this iteration of the main loop LDX #19 \ We do this as two blocks of 20 bytes, so set a counter \ in X to use in the loop below LDA #0 \ Set showRunwayDashes = 0 to reset the dashes down the STA showRunwayDashes \ middle of the runway to be visible STA relatedPoints \ Set relatedPoints = 0 to reset the relatedPoints list, \ so we can build a new list of related object points \ in this iteration of the main loop .main4 STA objectStatus,X \ Zero the X-th byte of objectStatus STA objectStatus+20,X \ Zero the X-th byte of objectStatus+20 DEX \ Decrement the loop counter BPL main4 \ Loop back until we have zeroed all 40 bytes \ ****************************************************************************** \ \ Name: MainLoop (Part 3 of 15) \ Type: Subroutine \ Category: Main loop \ Summary: Make the sound of firing, if appropriate \ Deep dive: Program flow of the main game loop \ \ ****************************************************************************** LDA firingStatus \ If firingStatus is zero then there are no bullets in BEQ main5 \ the air, so jump to main5 to skip updating the bullet \ positions JSR UpdateBullets \ Update the bullet positions LDA gunSoundCounter \ If gunSoundCounter = 0 then we don't have any gun BEQ main5 \ firing sounds to make, so jump to main5 to skip the \ gun sounds code DEC gunSoundCounter \ Decrement the sound counter in gunSoundCounter LDA #6 \ Make sound #6, the sound of our guns firing JSR MakeSound \ ****************************************************************************** \ \ Name: MainLoop (Part 4 of 15) \ Type: Subroutine \ Category: Main loop \ Summary: Check whether aliens have invaded Acornsville \ Deep dive: Program flow of the main game loop \ \ ****************************************************************************** .main5 LDA themeStatus \ If bit 7 of themeStatus is set, then the Theme is not BMI main6 \ enabled, so jump to main6 JSR AlienInAcornsville \ Check to see whether an alien has reached Acornsville \ and terminate the main loop if it has \ ****************************************************************************** \ \ Name: MainLoop (Part 5 of 15) \ Type: Subroutine \ Category: Main loop \ Summary: Update lines, check flying skills, increment main loop counter, \ update the radar \ Deep dive: Program flow of the main game loop \ \ ****************************************************************************** .main6 JSR UpdateLinesToShow \ Update the linesToShow list, moving any lines that \ aren't visible into the linesToHide list JSR ExplodeAlien \ If an alien has been hit, process its explosion, with \ all the convulsions that entails LDY #2 \ Check to see if we are flying under the suspension JSR CheckFlyingSkills \ bridge and award points if we are LDY #34 \ Check to see if we are flying down the main street of JSR CheckFlyingSkills \ Acornsville and award points if we are INC mainLoopCounter \ Increment the main loop counter LDA mainLoopCounter \ If (loop counter + 4) mod 8 <> 0, jump to main7, so CLC \ we only do the following once every 8 iterations of ADC #4 \ the main loop, when the loop counter is 4, 12, 20 and AND #7 \ so on BNE main7 LDY #1 \ Update the runway on the radar JSR UpdateRadarBlip LDX alienToMove \ Set X to the number of the alien whose turn it is to \ move towards Acornsville in this iteration of the main \ loop, which we set in UpdateAliens BMI main7 \ If X is negative, then there is no alien to move, so \ jump to main7 to skip the following LDY #33 \ Set Y = 33 so the call to UpdateRadarBlip updates the \ alien on the radar LDA alienState,X \ If the moving alien's state is < 27, skip the CMP #27 \ following instruction as the alien is not flying BCC main7 JSR UpdateRadarBlip \ The moving alien's state is >= 27, which means it is \ either flying to Acornsville or is in the final \ descent stage, so update the alien on the radar \ ****************************************************************************** \ \ Name: MainLoop (Part 6 of 15) \ Type: Subroutine \ Category: Main loop \ Summary: Check whether any aliens have been hit \ Deep dive: Program flow of the main game loop \ \ ****************************************************************************** .main7 LDA themeStatus \ If themeStatus is non-zero then either the Theme is BNE main12 \ not enabled, or it is enabled but we haven't yet added \ all eight aliens to the current wave, so jump to \ main12 as we only move aliens when the whole wave has \ arrived LDA firingStatus \ If firingStatus is zero then there are no bullets in BEQ main11 \ the air, so jump to main11, as we only need to check \ whether an alien is hit when there are bullets around LDA #33 \ We now loop through objects 33 down to 30, which are STA objectId \ all the alien objects, so we can check whether any of \ them have been hit, so set a loop counter in objectId .main8 LDY objectId \ Set Y to the object ID of the alien to check LDA alienStatus-30,Y \ We copied the object status bytes for all four alien BPL main9 \ objects into alienStatus in part 1, so this checks \ whether bit 7 of the alien's object status byte is \ clear \ \ If it is clear, then this object is not visible, so \ we skip the following three instructions and move on \ to the next alien, as we can't hit an alien that we \ can't see JSR CheckIfAlienIsHit \ This alien is visible, so check to see whether it has \ been hit, and if so, initiate the explosion LDA hitTimer \ If hitTimer is non-zero, then we just hit an alien, so BNE main10 \ jump to main10 to skip checking the rest of the aliens .main9 DEC objectId \ Decrement the loop counter to the next alien LDA objectId \ Loop back to check the next alien, until we have CMP #30 \ done all of them (from object 33 down to object 30) BCS main8 BCC main11 \ Jump to main11 (this BCC is effectively a JMP as we \ just passed through a BCS) .main10 STA distanceFromHit \ Store the value of hitTimer (which will be 27 as we \ just hit an alien) in distanceFromHit, so we are far \ enough away to avoid any turbulence, for now (as \ turbulence only kicks in when distanceFromHit < 16) LDA #0 \ Set firingStatus = 0 to indicate that there are no STA firingStatus \ longer any bullets are in the air, as they are now \ embedded in an unfortunate alien .main11 JSR UpdateAliens \ Update the aliens' statuses \ ****************************************************************************** \ \ Name: MainLoop (Part 7 of 15) \ Type: Subroutine \ Category: Main loop \ Summary: Process the terminate key \ Deep dive: Program flow of the main game loop \ \ ****************************************************************************** .main12 LDA onGround \ If onGround is zero, we are in the air and can't BEQ main13 \ terminate the game with the right arrow key, so jump \ to main13 to skip the following LDX #&86 \ Scan the keyboard to see if the right arrow is being JSR ScanKeyboard \ pressed BNE main13 \ If the right arrow is not being pressed, jump to \ main13 JSR TerminateGame \ The right arrow is being pressed, which is the key to \ terminate the game, so call TerminateGame to do \ exactly that JMP NewGame \ Jump to NewGame to start a new game \ ****************************************************************************** \ \ Name: MainLoop (Part 8 of 15) \ Type: Subroutine \ Category: Main loop \ Summary: If we fire the guns on the runway, enable the Theme \ Deep dive: Program flow of the main game loop \ \ ****************************************************************************** .main13 LDA landingStatus \ If landingStatus = 0, jump to main17 to skip the BEQ main17 \ following (enabling the Theme, filling up with fuel, \ awarding points for landing) BMI main16 \ If bit 7 of landingStatus is set, jump to main16 to \ skip the following (enabling the Theme, filling up \ with fuel) as we are on the ground following an \ emergency landing LDA firingStatus \ If firingStatus is zero then there are no bullets in BEQ main14 \ the air, so jump to main14 LDA themeStatus \ If themeStatus is positive then the Theme is already BPL main14 \ enabled, so jump to main14 \ If we get here then there are bullets in the air and \ the Theme is not enabled, so we now need to enable the \ Theme (as it is triggered by us firing bullets when \ we are stationary and on the runway with the brakes \ on) LDA #8 \ Set themeStatus = 8 to enable the Theme and initiate STA themeStatus \ a new wave of aliens JSR IndicatorT \ Update the Theme indicator \ ****************************************************************************** \ \ Name: MainLoop (Part 9 of 15) \ Type: Subroutine \ Category: Main loop \ Summary: Fill up the tank if the engine is switched off, and process the \ volume keys \ Deep dive: Program flow of the main game loop \ \ ****************************************************************************** .main14 LDA engineStatus \ If the engine is on, jump to main15 to skip the BNE main15 \ following instruction JSR FillUpFuelTank \ Fill up the fuel tank by one unit .main15 JSR ProcessVolumeKeys \ Check the volume keys and adjust the sound volume \ accordingly \ ****************************************************************************** \ \ Name: MainLoop (Part 10 of 15) \ Type: Subroutine \ Category: Main loop \ Summary: Award points for a successful landing \ Deep dive: Program flow of the main game loop \ Take-offs and landings \ \ ****************************************************************************** .main16 LDA reached512ft \ If we have not yet reached an altitude of 512 feet BEQ main17 \ since taking off, reached512ft will be zero, so jump \ to main17 to skip the following, as we are not \ eligible for the landing points LDX #0 \ Set reached512ft = 0 to reset the 512 feet counter, STX reached512ft \ ready for the next landing attempt LDA #&15 \ We have successfully landed the plane without JSR ScorePoints \ crashing, so add 150 points to the score and make a \ beep by calling ScorePoints with (X A) = &0015 \ ****************************************************************************** \ \ Name: MainLoop (Part 11 of 15) \ Type: Subroutine \ Category: Main loop \ Summary: Process engine start and stop \ Deep dive: Program flow of the main game loop \ \ ****************************************************************************** .main17 JSR UpdateFuelGauge \ Update the fuel gauge LDX #&DC \ Scan the keyboard to see if "T" is being pressed JSR ScanKeyboard BNE main18 \ If "T" is not being pressed, jump to main18 LDA pressingT \ If pressingT is non-zero, then we are still pressing BNE main19 \ "T" having already toggled the engine, so jump down \ to main19 so we don't keep switching the engine on and \ off by accident LDA propellorStatus \ If propellorStatus is non-zero, then the propellor is BNE main20 \ broken and we can't turn on the engine, so jump to \ main20 to skip the following \ At this point, we know that "T" is being pressed, \ pressingT is zero (so we haven't yet acted on the key \ press), and propellorStatus is zero (so the propellor \ is working), so now we toggle the engine status to \ switch it on or off LDA engineStatus \ Fetch the value of engineStatus and invert bit 0 so it EOR #1 \ changes to the opposite state JSR SetEngine \ Set the engine status to the value in A LDA #1 \ Set A = 1 to use as the new value of pressingT below, BNE main19 \ so that holding down "T" won't keep toggling the \ engine status .main18 LDA #0 \ "T" is not being pressed, so set A = 0 to use as the \ new value of pressingT .main19 STA pressingT \ Set pressingT = A, so we don't try toggling the engine \ again until we release the "T" key \ ****************************************************************************** \ \ Name: MainLoop (Part 12 of 15) \ Type: Subroutine \ Category: Main loop \ Summary: Spend at least 9 centiseconds processing lines from the \ linesToHide list \ Deep dive: Program flow of the main game loop \ Scheduling tasks in the main loop \ \ ****************************************************************************** .main20 LDA relatedPoints \ If the relatedPoints list contains 35 or more entries, CMP #35 \ jump to main21 to skip the following three BCS main21 \ instructions JSR ProcessLinesToHide \ Process three lines from the linesToHide list, if JSR ProcessLinesToHide \ there are any unprocessed lines there JSR ProcessLinesToHide .main21 LDX #&70 \ Call OSWORD with A = 1 and (Y X) = &0070, which reads LDY #&00 \ the system clock and writes the result into the five LDA #1 \ bytes from &0070 to &0074 (P, Q, R, S and T). For the JSR OSWORD \ purposes of the call to CheckTimePassed, P is set to \ the least significant byte of the time, which \ increments 100 times a second JSR CheckTimePassed \ If fewer than 9 centiseconds have passed since the BCC main20 \ first time we were here on this iteration of the main \ loop, then we haven't yet spent enough time processing \ lines from the linesToHide list, so jump back to \ main20 to do a few more \ ****************************************************************************** \ \ Name: MainLoop (Part 13 of 15) \ Type: Subroutine \ Category: Main loop \ Summary: Process more lines and update the view out of the canopy \ Deep dive: Program flow of the main game loop \ \ ****************************************************************************** JSR ProcessLinesToShow \ Process the lines in the linesToShow list JSR DrawCanopyView \ Update the main view out of the canopy JSR SetRandomNumber \ Add a new random number to the end of the \ randomNumbers list \ ****************************************************************************** \ \ Name: MainLoop (Part 14 of 15) \ Type: Subroutine \ Category: Main loop \ Summary: Handle the score display \ Deep dive: Program flow of the main game loop \ \ ****************************************************************************** LDA scoreDisplayTimer \ If scoreDisplayTimer = 0, jump to main22 to skip the BEQ main22 \ following three instructions, as we are not currently \ displaying the score CMP #220 \ If scoreDisplayTimer <> 220, jump down to main23 to BNE main23 \ decrement the timer and keep displaying the score BEQ main24 \ If scoreDisplayTimer = 220, it's time to remove the \ score from the screen, so jump down to main24 .main22 LDX #&C8 \ Scan the keyboard to see if "P" is being pressed JSR ScanKeyboard BNE main25 \ If "P" is not being pressed, jump to main25 to \ continue with the main loop .main23 \ If we get here then either we just pressed "P" or the \ score is already being displayed, so in either case we \ should update the timer and display the score DEC scoreDisplayTimer \ Decrement the score timer JSR DisplayScore \ Display the score, so we show it for the first time if \ we just pressed "P", or update it if it changes while \ being displayed JMP main25 \ Jump down to main25 to continue with the main loop .main24 JSR RemoveScore \ Remove the score from the screen LDA #0 \ Set scoreDisplayTimer = 0 as we are no longer showing STA scoreDisplayTimer \ the score on-screen \ ****************************************************************************** \ \ Name: MainLoop (Part 15 of 15) \ Type: Subroutine \ Category: Main loop \ Summary: Update the status of any new line points \ Deep dive: Program flow of the main game loop \ \ ------------------------------------------------------------------------------ \ \ If we have added any lines to the linesToHide list during the main loop (and \ therefore incremented linesToHideEnd), we reset the point status bytes for the \ line's start and end points, so they are no longer flagged as having had their \ visibility checked already. \ \ ****************************************************************************** .main25 LDX previousListEnd \ If the value of linesToHideEnd has changed since the CPX linesToHideEnd \ start of the main loop - in other words, it is not the BNE main26 \ same as the value we stored in previousListEnd - then \ this means the value of linesToHideEnd has changed \ during this iteration of the main loop, so jump down \ to main26 to process the new additions to the \ linesToHide list JMP MainLoop \ Otherwise we are done, so jump back up to the top of \ the main loop for the next iteration .main26 \ The first time we get here, X contains the size of the \ linesToHide list as it was when we started this \ iteration of the main loop, and we have added new \ lines to the list, so now we need to process those \ new lines INX \ Increment X to point to the next line, which will be \ the first new one to process LDY linesToHide,X \ Set Y to this line's ID from linesToHide STX previousListEnd \ Update the value of previousListEnd, so if we revisit \ the main26 loop, we will move on to the next line \ Now to process the line LDX lineStartPointId,Y \ Set X to the ID of this line's start point LDA #0 \ Zero the start point's status byte, so it is no longer STA pointStatus,X \ marked as having had its coordinates and visibility \ calculated LDX lineEndPointId,Y \ Set X to the ID of this line's end point STA pointStatus,X \ Zero the end point's status byte, so it is no longer \ marked as having had its coordinates and visibility \ calculated JMP main25 \ Jump back to main25 to repeat this process until we \ have zeroed all the new additions to linesToHide \ ****************************************************************************** \ \ Name: UpdateLinesToShow \ Type: Subroutine \ Category: Visibility \ Summary: Update the linesToShow list, moving any lines that aren't visible \ into the linesToHide list \ Deep dive: Visibility checks \ \ ****************************************************************************** .UpdateLinesToShow LDX linesToShowEnd \ If the linesToShow list is empty, jump to upll5 to BEQ upll5 \ return from the subroutine, as there is nothing to \ process LDA #255 \ Set linesToShowPointer = 255, to use as a pointer STA linesToShowPointer \ to the end of the new linesToShow list as we build it \ up in-situ LDA #0 \ Set lineCounter = 0, to use as a line counter as we STA lineCounter \ loop through the linesToShow list .upll1 LDX lineCounter \ Set lineId to the ID of the next line in the LDA linesToShow,X \ linesToShow list STA lineId LDA #1 \ Set HH = 1, so in the following call to ProcessLine STA HH \ we do not check the line's distance against \ maxLineDistance in the visibility checks JSR ProcessLine \ Check whether this line is visible, returning the \ result in showLine LDA lineId \ If lineId = 0, then this is the runway, so skip the BEQ upll2 \ following instruction to keep the line in the list LDX showLine \ If showLine is non-zero then this line is not visible, BNE upll3 \ so jump to upll3 .upll2 \ If we get here then we want to keep this line in the \ linesToShow list INC linesToShowPointer \ Increment the linesToShowPointer pointer to point to \ the next free slot in the new list we are creating LDX linesToShowPointer \ Store the line ID at the end of the new list we are STA linesToShow,X \ creating JMP upll4 \ Jump down to upll4 to move on to the next line in the \ list .upll3 \ If we get here then we do not want to keep this line \ in the linesToShow list, so we need to move it to the \ linesToHide list INC linesToHideEnd \ Increment linesToHideEnd to point to the next free \ entry at the end of the linesToHide list LDX linesToHideEnd \ Add the line ID to the end of the linesToHide list STA linesToHide,X .upll4 INC lineCounter \ Increment the loop counter to point to the next line \ in the linesToShow list LDA lineCounter \ Loop back to process the next line from linesToShow CMP linesToShowEnd \ until we have worked our way to the end BCC upll1 LDA linesToShowPointer \ Set linesToShowEnd = linesToShowPointer + 1 ADC #0 \ STA linesToShowEnd \ so it points to the index of the first empty entry in \ the newly processed linesToShow list .upll5 RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: ProcessLinesToShow \ Type: Subroutine \ Category: Visibility \ Summary: Process the linesToShow list, projecting all the lines onto the \ screen and moving any that aren't visible to the linesToHide list \ Deep dive: Visibility checks \ \ ****************************************************************************** .ProcessLinesToShow LDA linesToShowEnd \ If the linesToShow list is empty, jump to show5 to BEQ show5 \ clear down the relatedPoints list, as there are no \ visible lines at all LDA #255 \ Set linesToShowPointer = 255, to use as a pointer STA linesToShowPointer \ to the end of the new linesToShow list as we build it \ up in-situ LDA #0 \ Set lineCounter = 0, to use as a line counter as we STA lineCounter \ loop through the linesToShow list .show1 LDX lineCounter \ Set lineId to the ID of the next line in the LDY linesToShow,X \ linesToShow list STY lineId LDX lineStartPointId,Y \ Set GG to the point ID of the line's start point, to STX GG \ pass to the ProjectPoint routine STX L \ Set L to the point ID of the line's start point LDX lineEndPointId,Y \ Set M to the point ID of the line's end point STX M JSR ProjectPoint \ Project the line's end point onto the screen, putting \ the screen coordinates into (xPoint, yPoint) and \ setting the point's status byte accordingly LDA M \ Set GG to the point ID of the line's start point, to STA GG \ pass to the ProjectPoint routine JSR ProjectPoint \ Project the line's start point onto the screen, \ putting the screen coordinates into (xPoint, yPoint) \ and setting the point's status byte accordingly LDX L \ Set startStatus to the status byte of the line's start LDA pointStatus,X \ point STA startStatus LDX M \ Set N to the status byte of the line's end point LDA pointStatus,X STA N AND startStatus \ Set A as follows: AND #%00110000 \ \ * Bit 4 is set in A if bit 4 is set in both points, \ so both the start and end points (in 3D space) \ have |yPoint| * 2 >= |zPoint| \ \ * Bit 5 is set in A if bit 5 is set in both points, \ so both the start and end points (in 3D space) \ have |xPoint| >= |zPoint| \ \ so A is in the form %00xx0000 BEQ show2 \ If both bits 4 and 5 are clear in the above, jump to \ show2 to keep the line in the linesToShow list LSR A \ Shift bits 4 and 5 down into bits 2 and 3 and store LSR A \ the result in T, so: STA T \ \ * Bit 2 is set in T if bit 4 is set in both points \ * Bit 3 is set in T if bit 5 is set in both points \ \ and the rest of the bits will be 0, so T is in the \ form %0000xx00 LDA N \ Set A = end point status EOR startStatus \ EOR start point status EOR #&FF \ EOR %11111111 AND T \ AND T \ \ Because T is in the form %0000xx00, we are only \ interested in the calculation for bits 2 and 3, as all \ other bits will be zeroed by the AND T. Just taking \ bit 2 we get the following (the numbers on the right \ show the calculation in progress): \ \ A = bit 2 of end point \ EOR bit 2 of start point <- 0 if bits are same \ EOR 1 <- 1 if bits are same \ AND bit 2 of T <- 1 if set \ \ So bit 2 of A will be set only when both of the \ following are true: \ \ * Bit 2 is the same in both points \ * Bit 2 of T is set \ \ which expands to the following: \ \ * Bit 2 is the same in both points \ * Bit 4 is set in both points \ \ and means: \ \ * yPoint in both points has the same sign \ * |yPoint| * 2 >= |zPoint| for both points \ \ Similarly, bit 3 of A will be set only when both of \ the following are true: \ \ * Bit 3 is the same in both points \ * Bit 5 is set in both points \ \ which means: \ \ * xPoint in both points has the same sign \ * |xPoint| >= |zPoint| for both points BNE show3 \ If A is non-zero, then this means that at least one of \ the above is true for this line. Let's see what that \ means. If both of the following are true: \ \ * yPoint in both points has the same sign \ * |yPoint| * 2 >= |zPoint| for both points \ \ then: \ \ * The first one means that either both ends of the \ line are above us, or both are below us, so the \ line isn't crossing our field of view from top to \ bottom \ \ * The second one means that both points are above or \ both points are below the 2y = z line (or, more \ accurately, the 2y = z plane). In terms of 3D \ space, this means that for us to look at these \ points from our plane seat, we would need to raise \ or lower our gaze by more than 22.5 degrees \ \ If both of these are true, then the line isn't \ crossing our field of view, and it's too far above or \ below us to be seen... or in other words, it isn't \ visible \ \ The xPoint rules are similar. If both of these are \ true: \ \ * xPoint in both points has the same sign \ * |xPoint| >= |zPoint| for both points \ \ then: \ \ * The first one means that either both ends of the \ line are to the left of us, or both are to the \ right of us, so the line isn't crossing our field \ of view from left to right \ \ * The second one means that both points are to the \ left of or both points are to the right of the \ x = z line (or, more accurately, the x = z plane). \ In terms of 3D space, this means that for us to \ look at these points from our plane seat, we would \ need to look left or right by more than 45 degrees \ \ If both of these are true, then the line isn't \ crossing our field of view, and it's too far to the \ left or right of us to be seen... or in other words, \ it isn't visible \ \ So if A is non-zero, this means that the line is not \ visible so we jump to show3 to add this line to the \ linesToHide list .show2 \ If we get here then we want to keep this line in the \ linesToShow list INC linesToShowPointer \ Increment the linesToShowPointer pointer to point to \ the next free slot in the new list we are creating LDX linesToShowPointer \ Store the line ID at the end of the new list we are LDA lineId \ creating STA linesToShow,X JMP show4 \ Jump to show4 to move on to the next line .show3 LDA lineId \ If the line Id is 0 then it's the horizon, so jump up BEQ show2 \ to show2 add it to the linesToShow list, as we don't \ want to hide the horizon INC linesToHideEnd \ Increment linesToHideEnd to point to the next free \ entry at the end of the linesToHide list LDX linesToHideEnd \ Add the line ID in A to the end of the linesToHide STA linesToHide,X \ list .show4 INC lineCounter \ Increment the loop counter to point to the next line \ in the linesToShow list LDA lineCounter \ Loop back to process the next line from linesToShow CMP linesToShowEnd \ until we have worked our way to the end BCC show1 LDA linesToShowPointer \ Set linesToShowEnd = linesToShowPointer + 1 ADC #0 \ STA linesToShowEnd \ so it points to the index of the first empty entry in \ the newly processed linesToShow list .show5 LDX relatedPoints \ If the relatedPoints list is empty, jump to show7 to BEQ show7 \ return from the subroutine \ Otherwise we now reset the point status bytes for all \ the points mentioned in the relatedPoints list, so \ they are no longer marked as having their coordinates \ and visibility calculated LDA #0 \ Set A = 0 to use for resetting the status bytes .show6 LDY relatedPoints,X \ Zero the status byte for the X-th entry in the list STA pointStatus,Y DEX \ Decrement the loop counter BNE show6 \ Loop back until we have reset all the .show7 RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: ProcessLinesToHide \ Type: Subroutine \ Category: Visibility \ Summary: Process an unprocessed line from the linesToHide list \ Deep dive: Visibility checks \ \ ------------------------------------------------------------------------------ \ \ This routine is called from the main loop, but only when there are fewer than \ 35 related points, and only if there is enough time (the routine is called in \ batches of three, until we have spent 9 centiseconds processing the lines to \ hide list). \ \ It pulls a line ID from the end of the linesToHide list, and checks the line's \ visibility. If it is visible, then it moves the line to the linesToShow list \ so it gets shown on-screen, but if it is not visible, it sticks the line on \ the end of the linesToHide list and moves on to the next. In this way the game \ is constantly checking hidden lines to see if they are visible, so when we fly \ near enough to a line that it should be shown, this is the routine that shows \ it. (And, if that line is part of an object, then other lines in the object \ will also be shown, via the related points list.) \ \ ****************************************************************************** .ProcessLinesToHide LDA linesToHidePointer \ Set A to the position within the linesToHide list up \ to which we have processed lines CMP linesToHideEnd \ If we have processed all the way to the end of the BEQ ProcessLine-1 \ list, then linesToHidePointer = linesToHideEnd, so \ return from the subroutine (as ProcessLine-1 \ contains an RTS) CLC \ Otherwise we have lines on the end of the linesToHide ADC #1 \ list that we have not yet processed, so increment STA linesToHidePointer \ the linesToHidePointer, as we are about to process a \ line TAX \ Set lineId to the line's ID from the linesToHide list LDA linesToHide,X STA lineId CMP #60 \ If lineId = 60, jump back to ProcessLinesToHide to BEQ ProcessLinesToHide \ pick another line CMP #61 \ If lineId = 61, jump back to ProcessLinesToHide to BEQ ProcessLinesToHide \ pick another line \ Fall through into ShowOrHideLine to process the line \ and add it to the linesToShow or linesToHide list \ ****************************************************************************** \ \ Name: ShowOrHideLine \ Type: Subroutine \ Category: Visibility \ Summary: Process a line, working out its visibility and adding it to the \ linesToShow or linesToHide list \ Deep dive: Visibility checks \ \ ------------------------------------------------------------------------------ \ \ Check the visibility of a line and either add it to the linesToShow list \ or the linesToHide list, depending on whether it is visible. \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ lineId The line ID to process \ \ ****************************************************************************** .ShowOrHideLine LDA #0 \ Set HH = 0, so in the following call to ProcessLine STA HH \ we check the line's distance against maxLineDistance \ in the visibility checks JSR ProcessLine \ Check whether this line is visible, returning the \ result in showLine LDA lineId \ Fetch the line ID into A LDX showLine \ If showLine = 0 then the line is visible, so jump down BEQ shli1 \ to shli1 to add the line to the linesToShow list \ The value of showLine is non-zero, so the line is not \ visible and we now add it to the linesToHide list INC linesToHideEnd \ Increment linesToHideEnd to point to the next free \ entry at the end of the linesToHide list LDX linesToHideEnd \ Add the line's ID to the end of the linesToHide list STA linesToHide,X RTS \ Return from the subroutine .shli1 \ The value of showLine is zero, so the line is visible \ and we now add it to the linesToShow list INC linesToShowEnd \ Increment linesToShowEnd so it still points to the \ first empty entry in the linesToShow list after we \ add this line INC linesToShowPointer \ Increment linesToShowPointer as we have already \ processed the line, so we set the progress pointer to \ be after this line LDX linesToShowPointer \ Add the line's ID to the end of the linesToShow list STA linesToShow,X RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: ProcessLine (Part 1 of 7) \ Type: Subroutine \ Category: Visibility \ Summary: Process a line, rotating and transforming it to the correct \ coordinates and calculating its visibility \ Deep dive: Visibility checks \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ lineId The line ID of the line to process \ \ HH Determines whether we check against the line's maximum \ visible distance during the visibility checks: \ \ * 0 = check the line's distance against the maximum \ visible distance in maxLineDistance \ \ * Non-zero = skip the distance check \ \ ------------------------------------------------------------------------------ \ \ Returns: \ \ showLine Whether this line is visible: \ \ * 0 = line is visible \ \ * Non-zero = line is not visible \ \ ------------------------------------------------------------------------------ \ \ Other entry points: \ \ ProcessLine-1 Contains an RTS \ \ ****************************************************************************** .ProcessLine LDA #0 \ Set showLine = 0 as a starting point for the return STA showLine \ value (so we start out by assuming the line is \ visible, and change this if we find that it isn't) STA isObject \ Set isObject = 0, which we will set to a non-zero \ object ID below if we end up processing an object LDX lineId \ Set X = lineId, so X contains the ID of the line we \ want to check for visibility LDY lineEndPointId,X \ Set M to the point ID of the line's end point STY M LDY lineStartPointId,X \ Set L to the point ID for the line's start point STY L CPX #12 \ If lineId >= 12, then this is not the horizon or a BCS plin2 \ runway line, so jump to plin2 to process the line CPX #0 \ If lineId is not zero, then this is not the horizon, BNE plin1 \ so it must be a runway line, so jump to plin1 to \ process it \ If we get here then lineId is 0, so this is the \ horizon JSR ProcessHorizonLine \ Process the horizon and set showLine accordingly RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: ProcessLine (Part 2 of 7) \ Type: Subroutine \ Category: Visibility \ Summary: Process runway lines \ Deep dive: Visibility checks \ \ ****************************************************************************** .plin1 \ If we get here then lineId is in the range 1 to 11, \ so this is a runway line JSR ProcessRunwayLine \ Process the runway line and set showLine accordingly JMP plin19 \ Jump down to plin19 to check the line's z-coordinates \ and return the final visibility result \ ****************************************************************************** \ \ Name: ProcessLine (Part 3 of 7) \ Type: Subroutine \ Category: Visibility \ Summary: If a line is part of a multi-point object, extract the other \ points in the line so we can check them all \ Deep dive: Visibility checks \ \ ****************************************************************************** .plin2 \ If we get here, lineId >= 12 and Y contains the point \ ID for the line's start point \ \ We now run through the following process twice, first \ for the line's start point, and then for the line's \ end point LDA #2 \ Set pointCount = 2 to act as a counter so we can check STA pointCount \ the start point first, and then the end point .plin3 LDA pointStatus,Y \ If bit 7 of the point's status byte is clear, then the BPL plin4 \ point has not yet had its coordinates and visibility \ calculated, so skip the following instruction to move \ on to those calculations JMP plin17 \ Bit 7 of the point's status byte is set, which means \ we have already calculated this point's coordinates \ and visibility, so jump to plin17 to do the final \ distance and z-coordinate tests .plin4 \ We get here if we haven't already calculated the \ coordinates and visibility for the point whose ID is \ in Y, so that's what we do now TYA \ Store the point ID of this point on the stack and in PHA \ pointId. We are about to check whether this point is STA pointId \ part of a multi-point object, and if so we're going to \ add the IDs of all the other points in the object to \ the stack and then work our way through the stacked \ values, processing each of them in turn \ \ Adding the starting point ID to the stack and to \ pointId at the same time lets us use this ID as a \ backstop - in other words, we'll know we have \ processed all the other point that we added to the \ stack when we pull a value off the stack that matches \ the value of pointId (see parts 5 and 6 to see this in \ action) .plin5 LDA objectPoints,Y \ Fetch this point's entry from objectPoints, which \ will tell us if this point is related to any other \ points as part of a multi-point object CMP #40 \ If object ID < 40 then this point does not link to BCC plin8 \ another point, so it's the last point in a linked \ object - i.e. the ID of the object itself - so jump to \ plin8 to process the point and any others we already \ put on the stack \ If we get here then this point links to another point \ in objectPoints, so we now follow the links and add \ all of the point IDs to the stack and to the \ relatedPoints list, looping back until we reach the \ last point, at which point we jump to plin8 with a \ stack full of points SEC \ Subtract 40 from A to get the point ID of the new SBC #40 \ point to check STA objectAnchorPoint \ Store the new point's ID as the object's anchor point, \ so it contains the ID of the last point before the \ object ID at the end of the linked list of points TAY \ Copy the new point's ID into Y so we can use it as an \ index into pointStatus LDA pointStatus,Y \ If bit 7 of the new point's status byte is set, then BMI plin14 \ we have already calculated the coordinates and \ visibility for this new point, which means we have \ also done the rest of the points in the linked object, \ so jump down to plin14 to check the new points we \ added to the stack TYA \ Set A = the new point's ID PHA \ Store the new point's ID on the stack LDX relatedPoints \ If relatedPoints >= 49, then the relatedPoints list is CPX #49 \ full, so jump to plin11 to set this line as not being BCS plin11 \ visible INC relatedPoints \ Increment the value in relatedPoints, which contains \ the size of the list, as we are about to add a new \ entry to the end of the list LDX relatedPoints \ Add A, the new point's ID, to the end of the STA relatedPoints,X \ relatedPoints list BNE plin5 \ Jump back to plin5 to recurse through the new point \ (this BNE is effectively a JMP as A is never zero, \ because objectPoints does not contain a value of 40) \ ****************************************************************************** \ \ Name: ProcessLine (Part 4 of 7) \ Type: Subroutine \ Category: Visibility \ Summary: Process bullet lines \ Deep dive: Visibility checks \ \ ****************************************************************************** .plin6 \ If we get here then the point is not part of a linked \ object, and object ID is in the range 12 to 15, so \ this is a bullet line PLA \ Pull the point ID from the stack and store it in GG STA GG LDA objectStatus,Y \ If bit 7 of the object's status byte is set, jump to BMI plin7 \ plin16 via plin7 \ If we get here then bit 7 of the object's status byte \ is clear LDA #%10000000 \ Set showLine to say that the line is not in view STA showLine RTS \ Return from the subroutine .plin7 JMP plin16 \ Jump down to plin16 with the bullet point ID in GG, to \ check distance and z-coordinates and return the final \ result \ ****************************************************************************** \ \ Name: ProcessLine (Part 5 of 7) \ Type: Subroutine \ Category: Visibility \ Summary: Calculate the object's coordinates and visibility \ Deep dive: Visibility checks \ \ ****************************************************************************** .plin8 \ By the time we get here, Y contains the last entry \ from objectPoints for this multi-point object (so it \ contains the object ID), and any previous points from \ the objectPoints entry are on the stack TAY \ Store the object ID in objectId STY objectId CMP #16 \ If the object ID >= 16, jump to plin9 to skip the BCS plin9 \ following CMP #12 \ If the object ID >= 12, then the object ID is in the BCS plin6 \ range 12 to 15, which means it's a bullet line, so \ jump up to plin6 to work out its visibility .plin9 LDA objectStatus,Y \ Fetch the object's status byte AND #%01000000 \ If bit 6 of the object's status byte is set then we BNE plin10 \ have already set this object's coordinates in this \ iteration of the main loop, so skip the following JSR SetObjectCoords \ Calculate the object's coordinates and visibility, \ updating the object's status byte with the results \ and setting isObject to the object ID .plin10 LDY objectId \ Fetch the object ID from objectId LDA objectStatus,Y \ If bit 7 of the object's status byte is set, then the BMI plin13 \ object is visible, so jump to plin13 to set the anchor \ point and work our way through the points on the stack .plin11 LDA #%10000000 \ Set showLine to say that the line is not in view STA showLine .plin12 PLA \ Pull numbers off the stack until we reach the first CMP pointId \ number that we put on, which will match pointId, so BNE plin12 \ by the time we are done we have removed all the IDs \ we added to the stack during the routine RTS \ Return from the subroutine .plin13 TYA \ Set objectAnchorPoint = object ID + 216 CLC \ ADC #216 \ so it contains the point ID associated with the anchor STA objectAnchorPoint \ point of this object \ ****************************************************************************** \ \ Name: ProcessLine (Part 6 of 7) \ Type: Subroutine \ Category: Visibility \ Summary: Check the visibility of all the object's points on the stack \ Deep dive: Visibility checks \ \ ****************************************************************************** .plin14 \ We jump straight here if we are working through a \ linked object and come across a point with bit 7 set \ in the point's status byte (which means we have \ already processed the rest of the points in the \ linked object), otherwise we got here after processing \ the object in the previous part \ We now loop through any points we have added to the \ stack and process them all. If we find any that are \ not visible, then we return the result that the whole \ line is not visible PLA \ Pull the next point ID from the stack CMP pointId \ If the point ID matches pointId, then we have no other BEQ plin15 \ points on the stack to process, so jump down to plin15 \ to calculate its visibility STA GG \ Store the point ID in GG so its coordinates get \ calculated in the call to SetObjPointCoords LDA #0 \ Set matrixAxis = 0 (this has no effect, as this value STA matrixAxis \ is only used when setting the matrices, so this is \ presumably left over from a version of the code that \ supported multiple sets of matrices) STA matrixNumber \ Set the matrix number so the call to SetObjPointCoords \ uses matrix 1 in the calculation, which will rotate \ the point by the plane's pitch, roll and yaw angles, \ transforming it from the outside world's frame of \ reference to the plane's frame of reference JSR SetObjPointCoords \ Calculate the coordinates for this object point LDA showLine \ If showLine is non-zero, then the point is not BNE plin12 \ visible, so jump to plin12 to clear down the stack and \ return from the subroutine LDY GG \ Set objectAnchorPoint = the point ID in GG, so it STY objectAnchorPoint \ contains the ID of the last point before the object ID \ at the end of the linked list of points (as the last \ ID, the object ID, is processed by plin15 below) LDA #%10000000 \ Set bit 7 of the point's status byte, to indicate that ORA pointStatus,Y \ the point has now had its coordinates and visibility STA pointStatus,Y \ calculated BNE plin14 \ Jump to plin14 (this BNE is effectively a JMP as A is \ never zero) .plin15 STA GG \ Store the point ID in GG LDA #0 \ Set matrixAxis = 0 (this has no effect, as this value STA matrixAxis \ is only used when setting the matrices, so this is \ presumably left over from a version of the code that \ supported multiple sets of matrices) STA matrixNumber \ Set the matrix number so the call to SetObjPointCoords \ uses matrix 1 in the calculation, which will rotate \ the point by the plane's pitch, roll and yaw angles, \ transforming it from the outside world's frame of \ reference to the plane's frame of reference JSR SetObjPointCoords \ Calculate the coordinates for this object point LDA showLine \ If showLine is non-zero, then the line is not visible, BNE plin20 \ so jump to plin20 to return from the subroutine \ ****************************************************************************** \ \ Name: ProcessLine (Part 7 of 7) \ Type: Subroutine \ Category: Visibility \ Summary: Check distance and z-coordinates and return the final result \ Deep dive: Visibility checks \ \ ****************************************************************************** .plin16 LDY GG \ Set Y to the point ID, which we stored in GG before \ jumping here or falling through from above .plin17 \ We jump straight here if line ID >= 12 and bit 7 of \ the point's status byte is set (which means we have \ already processed this point) LDA HH \ If HH is non-zero, jump to plin18 to move on to the BNE plin18 \ end point and then check the z-coordinates LDX lineId \ Check whether this line ID is close enough to be JSR CheckLineDistance \ visible (so it gets hidden if it is further away than \ the visibility distance for this line) STA showLine \ The above call returns 1 if the line is too far away, \ or the previous value of showLine if it is close \ enough to be shown, so store the updated response in \ showLine BNE plin20 \ If the response is non-zero then the line is not \ visible and showLine contains a non-zero response, so \ jump to plin20 to return from the subroutine \ Otherwise the line is close enough to be visible and \ has passed all the other visibility checks so far, so \ now we check the z-coordinates .plin18 LDA #%10000000 \ Set bit 7 of the point's status byte, to indicate that ORA pointStatus,Y \ the point has now had its coordinates and visibility STA pointStatus,Y \ calculated DEC pointCount \ Decrement pointCount so we check the end point next BEQ plin19 \ If pointCount = 0 then we have checked both the start \ and end point, so jump to plin19 to check the line's \ z-coordinates \ If we get here then we have checked the start point, \ so we now loop back to check the end point LDY M \ Set M to the point ID for the line's end point, so \ when we run through the process above, we do it for \ the end point instead of the start point JMP plin3 \ Jump back to plin3 to check the visibility of the end \ point .plin19 LDY M \ If the end point's z-coordinate is positive, jump to LDA zPointHi,Y \ plin20 to return from the subroutine BPL plin20 LDY L \ If the start point's z-coordinate is positive, jump to LDA zPointHi,Y \ plin20 to return from the subroutine BPL plin20 \ If we get here then both the start and end point have \ negative z-coordinates, so they are both behind us and \ the line is therefore not visible LDA showLine \ Set bit 7 of showLine to indicate that the line is not ORA #%10000000 \ visible STA showLine LDY isObject \ If we processed an object above, then its ID will be \ in isObject, so fetch this into Y for the following \ call JSR NextObjectGroup \ If we just processed an object in an object group, \ i.e. isObject is 6, 7, 8 or 9, then increment the \ object's group number .plin20 RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: SetObjectCoords (Part 1 of 11) \ Type: Subroutine \ Category: 3D geometry \ Summary: Calculate the coordinates for an object's location \ Deep dive: 3D objects \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ objectId The ID of the object to process (1 to 39) \ \ GG For bullets only (12, 13, 14 or 15), the point ID for \ the bullet's anchor point \ \ ------------------------------------------------------------------------------ \ \ Returns: \ \ objectStatus The object's status byte: bits 6 and 7 affected \ \ Flags Set according to the object's updated status byte \ \ isObject The object ID, i.e. a non-zero value so callers of this \ routine will know we just processed an object \ \ xObjectHi etc. Set to the object's coordinates, relative to the plane \ \ xPointHi etc. The object's anchor point: \ \ * For bullets, point GG is updated with the bullet's \ anchor point \ \ * For other objects, point objectId+216 is updated \ with the object's anchor point \ \ ****************************************************************************** .SetObjectCoords LDY objectId \ Set Y to the object ID STY isObject \ Store the object ID in isObject, so callers of this \ routine will know we just processed an object TYA \ Set N = 216 + object ID CLC \ ADC #216 \ so N is in the range 217 to 255 and is the point ID STA N \ that we use for storing the object's anchor point LDA #1 \ Set K = 1, so the CheckObjDistance routine returns STA K \ the correct 0 or 1 value when checking an object's \ distance, which is the default behaviour (we set K to \ 0 below for bullets only, as we always want them to \ remain visible at distance) \ We now check for some specific objects: \ \ * Object groups, i.e. trees (6, 7), hills (8, 9) \ * Bullets (12, 13, 14 or 15) \ * Feeding aliens (object group 30) \ * Flying aliens (31, 32 or 33) \ \ so we can pre-process them before moving on to the \ main processing routine in part 8 CPY #12 \ If the object ID < 12, jump to objc1 to check the next BCC objc1 \ range CPY #16 \ If the object ID >= 16, jump to objc1 to check the BCS objc1 \ next range \ ****************************************************************************** \ \ Name: SetObjectCoords (Part 2 of 11) \ Type: Subroutine \ Category: 3D geometry \ Summary: Pre-process the bullets (objects 12, 13, 14 or 15) \ Deep dive: 3D objects \ \ ****************************************************************************** \ If we get here then the object ID is 12, 13, 14 or 15, \ so this is a bullet object \ \ There are two bullet trails, with objects 13 and 15 at \ the head of the trail (i.e. the bullets), and objects \ 12 and 14 at the back of the bullet trail LDA #0 \ Set K = 0, so the CheckObjDistance routine always STA K \ returns 0, which means the bullet trails never get \ too far away to be hidden LDA yObjectHi,Y \ If the object's y-coordinate is positive, then the BPL objc3 \ bullet is above ground, so jump to objc3 to check the \ next range and then process the bullet TYA \ If bit 0 of the object ID is set, i.e. the object ID AND #1 \ is 13 or 15, jump to objc3 to check the next range BNE objc3 \ and then process the bullet (so objects 13 and 15 can \ be below ground while 12 and 14 are above ground, in \ which case the bullets are still in play) JMP objc10 \ The object ID is 12 or 14 (the back end of the bullet \ trail) and the object is below ground level, so jump \ to objc10 to tidy up and return from the subroutine, \ as the bullets have hit the ground \ ****************************************************************************** \ \ Name: SetObjectCoords (Part 3 of 11) \ Type: Subroutine \ Category: 3D geometry \ Summary: Logic for checking which objects to pre-process \ Deep dive: 3D objects \ \ ****************************************************************************** .objc1 \ If we get here then this is not a bullet LDA N \ Set GG = N STA GG \ = 216 + object ID \ \ so GG contains the point ID that we use for storing \ the object's anchor point CPY #6 \ If the object ID < 6, jump to objc3 to check the next BCC objc3 \ range CPY #10 \ If the object ID >= 10, jump to objc3 to check the BCS objc3 \ next range \ Otherwise the object ID is 6, 7, 8 or 9, so fall \ through into part 4 to pre-process the object \ ****************************************************************************** \ \ Name: SetObjectCoords (Part 4 of 11) \ Type: Subroutine \ Category: 3D geometry \ Summary: Pre-process the object groups (objects 6, 7, 8 or 9) \ Deep dive: 3D objects \ \ ****************************************************************************** \ If we get here then the object ID is 6, 7, 8 or 9, so \ this is an object group (e.g. a tree) LDA #8 \ Set objCount = 8 to act as a counter in the loop STA objCount \ below, so we work through all 8 objects in this object \ group .objc2 LDX objectGroup-6,Y \ Set X = 0, 8, 16 or 24, for Y = 6, 7, 8 or 9 \ \ (or 1, 9, 17, 25 etc., depending on the current group) LDA xGroupObjectHi,X \ Set the object's x-coordinate to the X-th entry in STA xObjectHi,Y \ xGroupObjectHi, which contains the high byte of the \ group object's x-coordinate LDA zGroupObjectHi,X \ Set the object's z-coordinate to the X-th entry in STA zObjectHi,Y \ zGroupObjectHi, which contains the high byte of the \ group object's z-coordinate JMP objc9 \ Jump to objc9 to process this object \ ****************************************************************************** \ \ Name: SetObjectCoords (Part 5 of 11) \ Type: Subroutine \ Category: 3D geometry \ Summary: Logic for checking which objects to pre-process \ Deep dive: 3D objects \ \ ****************************************************************************** .objc3 CPY #34 \ If the object ID >= 34, jump to objc9 to process the BCS objc9 \ object CPY #30 \ If the object ID < 30, jump to objc9 to process the BCC objc9 \ object BNE objc6 \ If the object ID <> 30, i.e. 31, 32 or 33, jump to \ objc6 to pre-process the object \ Otherwise the object ID is 30, so fall through into \ part 6 to pre-process object 30 \ ****************************************************************************** \ \ Name: SetObjectCoords (Part 6 of 11) \ Type: Subroutine \ Category: 3D geometry \ Summary: Pre-process dormant aliens (object group 30) \ Deep dive: 3D objects \ \ ****************************************************************************** \ If we get here then the object ID is 30 LDA themeStatus \ If themeStatus is non-zero then either the Theme is BNE objc8 \ not enabled, or it is enabled but we haven't yet added \ all eight aliens to the current wave, so jump to \ objc8 to return from the subroutine LDA #8 \ Set objCount = 8 to act as a counter in the loop STA objCount \ below, so we work through all eight aliens in this \ group \ As we loop through the following, alienSlot gets \ incremented so we work our way through all the aliens \ in the slot .objc4 LDX alienSlot \ Fetch the contents of the slot for alien 30, so X now \ contains the number of the alien in that slot (0 to 7) LDA alienState,X \ If the alien's state is non-zero, jump to objc11 via BNE objc7 \ objc7 to move on to the next alien in the group, as \ the alien is feeding LDA alienObjectId,X \ Set A to the object ID for the alien BPL objc5 \ If A is positive, jump to objc5... which has no \ effect, as that's the next instruction anyway .objc5 TAX \ Copy the alien's x-coordinate to the current object's LDA xObjectHi,X \ x-coordinate STA xObjectHi,Y LDA zObjectHi,X \ Copy the alien's z-coordinate to the current object's STA zObjectHi,Y \ x-coordinate JMP objc9 \ Jump to objc9 to process this object, after which we \ return to the above from part 10 to process the next \ alien (with alienSlot incremented) \ ****************************************************************************** \ \ Name: SetObjectCoords (Part 7 of 11) \ Type: Subroutine \ Category: 3D geometry \ Summary: Pre-process feeding and flying aliens (objects 31, 32 and 33) \ Deep dive: 3D objects \ \ ****************************************************************************** .objc6 \ If we get here then the object ID is 31, 32 or 33 LDX alienSlot-30,Y \ If the slot for alien Y contains a negative number, BMI objc7 \ then the slot is empty, so jump to objc11 via objc7 \ to return from the subroutine LDA alienState,X \ If the alien's state is >= 27, then it is heading for CMP #27 \ Acornsville, so jump to objc9 to process this object BCS objc9 LDA alienObjectId,X \ Set A to the object ID for this alien BPL objc5 \ If A is positive, jump to objc5 to set the object \ coordinates and process this object .objc7 JMP objc11 \ Jump to objc11 to move on to the next alien in the \ group (for object 30) or return from the subroutine \ (for objects 31 to 33) .objc8 JMP objc12 \ Jump to objc12 to return from the subroutine \ ****************************************************************************** \ \ Name: SetObjectCoords (Part 8 of 11) \ Type: Subroutine \ Category: 3D geometry \ Summary: Process the object \ Deep dive: 3D objects \ \ ****************************************************************************** .objc9 \ By this point we have pre-processed any special \ objects, so now to process it \ We now translate the base location of the object, \ in (xObject, yObject, zObject), by subtracting the \ current plane coordinates (xPlane, altitude, zPlane) \ to give the vector from the plane's location to the \ object's location LDX GG \ Set X to the value of GG: \ \ * For bullets, this is the value of GG that is \ passed to the routine, i.e. 98 \ \ * For other objects, this is 216 + object ID, so \ points 216 to 255 contain the calculated \ coordinates for objects 0 to 39 \ \ So in the following, we set the coordinates of the \ point whose ID is in GG, i.e. point GG \ We do the following calculation with 24-bit values, \ so we can do the visibility checks. This means we \ calculate: \ \ xPoint = xObject - xPlane \ yPoint = yObject - yPlane \ zPoint = zObject - zPlane \ \ but in each case, the values have three bytes. To take \ the x-axis calculation: \ \ xPoint is (A xPointHi xPointLo) \ xObject is (0 xObjectHi xObjectLo) \ xPlane is (xPlaneTop xPlaneHi xPlaneLo) \ \ and we pass the top byte of the result, in A, to the \ CheckObjDistance routine, so it can be used in the \ visibility check (a high value in A means the object \ is a very long way away) SEC \ Set the x-coordinate of point GG: LDA xObjectLo,Y \ SBC xPlaneLo \ xPoint = xObject - xPlane STA xPointLo,X \ \ starting with the low bytes LDA xObjectHi,Y \ And then the high bytes SBC xPlaneHi STA xPointHi,X STA T \ Set T = xPointHi to pass to CheckObjDistance LDA #0 \ And then the top bytes SBC xPlaneTop JSR CheckObjDistance \ Check whether the object is close enough to be visible \ in the direction of the x-axis BNE objc10 \ If it is too far away to be visible, jump to objc10 to \ either move on to the next object in the group, or \ tidy up and return from the subroutine SEC \ Set the y-coordinate of point GG: LDA yObjectLo,Y \ SBC yPlaneLo \ yPoint = yObject - yPlane STA yPointLo,X \ \ starting with the low bytes LDA yObjectHi,Y \ And then the high bytes SBC yPlaneHi STA yPointHi,X STA T \ Set T = yPointHi to pass to CheckObjDistance LDA #0 \ And then the top bytes SBC yPlaneTop JSR CheckObjDistance \ Check whether the object is close enough to be visible \ in the direction of the y-axis BNE objc10 \ If it is too far away to be visible, jump to objc10 to \ either move on to the next object in the group, or \ tidy up and return from the subroutine SEC \ Set the z-coordinate of point GG: LDA zObjectLo,Y \ SBC zPlaneLo \ zPoint = zObject - zPlane STA zPointLo,X \ \ starting with the low bytes LDA zObjectHi,Y \ And then the high bytes SBC zPlaneHi STA zPointHi,X STA T \ Set T = zPointHi to pass to CheckObjDistance LDA #0 \ And then the top bytes SBC zPlaneTop JSR CheckObjDistance \ Check whether the object is close enough to be visible \ in the direction of the z-axis BNE objc10 \ If it is too far away to be visible, jump to objc10 to \ either move on to the next object in the group, or \ tidy up and return from the subroutine LDA #0 \ Set the matrix number so the call to SetPointCoords STA matrixNumber \ uses matrix 1 in the calculation, which will rotate \ the point by the plane's pitch, roll and yaw angles, \ transforming it from the outside world's frame of \ reference to the plane's frame of reference JSR SetPointCoords \ Rotate the coordinates for point GG: \ \ * For bullets, this is point 98 \ \ * For other objects, this is 216 + object ID, so \ points 216 to 255 will contain the calculated \ coordinates for objects 0 to 39 \ \ and update the coordinates in (xPoint, yPoint, zPoint) LDY objectId \ Fetch the object ID into Y LDA showLine \ If showLine is non-zero, which means the line is not BNE objc12 \ visible, jump to objc12 to return from the subroutine \ without setting bit 7 of the object's status byte LDA #%11000000 \ Set A = %11000000 to set bits 6 and 7 of the object's \ status byte, to denote that the object has had its \ coordinates calculated, and that it is visible BNE objc13 \ Jump to objc13 to set the object's status byte and \ return from the subroutine (this BNE is effectively a \ JMP as A is never zero) \ ****************************************************************************** \ \ Name: SetObjectCoords (Part 9 of 11) \ Type: Subroutine \ Category: 3D geometry \ Summary: Process the next object in the group, if applicable, or return \ from the subroutine if not \ Deep dive: 3D objects \ \ ****************************************************************************** .objc10 \ If we get here then we are done processing this \ object, so now we check whether there are other \ objects to process: \ \ * If the object is in a group (i.e. the object ID \ is 6, 7, 8 or 9) and there are other objects to \ process in the group, we loop back to process them \ \ * If this is object 30, then we check whether we \ have processed all 8 items in its group, and if \ not, we loop back to process the next one (see \ part 10) \ \ * If this object is not caught by the above, then \ we finally return from the subroutine (see part \ 11) JSR NextObjectGroup \ If this object is in an object group, i.e. Y is 6, 7, \ 8 or 9, then increment the object's group number BCC objc11 \ If the C flag is clear then this object is not part of \ an object group, so jump to objc11 to skip the \ following \ This object is part of an object group, so now we move \ onto the next object in the group DEC objCount \ Decrement the loop counter to move on to the next \ object in the group BEQ objc12 \ If we have processed all 8 items in the group, jump \ to objc12 to return from the subroutine JMP objc2 \ Otherwise loop back to objc2 to process the next item \ in the group \ ****************************************************************************** \ \ Name: SetObjectCoords (Part 10 of 11) \ Type: Subroutine \ Category: 3D geometry \ Summary: Process the next dormant alien (object group 30) \ Deep dive: 3D objects \ \ ****************************************************************************** .objc11 CPY #30 \ If the object ID <> 30, jump to objc12 to return from BNE objc12 \ the subroutine LDA alienSlot \ Increment alienSlot to iterate through values 0 to 7 CLC \ and round again ADC #1 AND #7 STA alienSlot DEC objCount \ Decrement the loop counter to move on to the next \ alien in the group BEQ objc12 \ If we have processed all 8 items in the group, jump \ to objc12 to return from the subroutine JMP objc4 \ Otherwise loop back to objc4 to process the next item \ in the group \ ****************************************************************************** \ \ Name: SetObjectCoords (Part 11 of 11) \ Type: Subroutine \ Category: 3D geometry \ Summary: Update the object status and return from the subroutine \ Deep dive: 3D objects \ \ ****************************************************************************** .objc12 LDA #%01000000 \ Set A = %01000000 to set bit 6 of the object's status \ byte, to denote that the object has had its \ coordinates calculated .objc13 ORA objectStatus,Y \ Set the bits of the object's status byte as per the STA objectStatus,Y \ value in A RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: CheckObjDistance \ Type: Subroutine \ Category: Visibility \ Summary: Check whether an object is within the visible distance for that \ object, along just one axis \ Deep dive: Visibility checks \ \ ------------------------------------------------------------------------------ \ \ We call this routine by passing in the distance from the plane to the object, \ along the axis we are checking. We pass it as the top two bytes of a 24-bit \ number, (A T x), containing the object's coordinate in the relevant axis. We \ can ignore the low byte as it doesn't affect object visibility. \ \ First we check the top byte in A to make sure it is either 0 or -1, as \ otherwise the distance is definitely too far for the object to be visible. \ \ Assuming the top byte is within range, we then check the high byte in T \ against the maximum visible distance for the object in question. The object \ is visible only if |T| < the object's maxObjDistance value, otherwise it is \ too far away to be seen. \ \ When called with K = 0, this routine will always return 0 to indicate that the \ object is visible. This is used by the bullets, to ensure that they remain \ visible, whatever their distance from the plane. \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ Y The object ID of the object to check \ \ A The top byte of the object's coordinates in the axis we \ want to check (i.e. the byte above xPointHi, yPointHi or \ zPointHi) \ \ T The high byte the object's coordinates in the axis we \ want to check (i.e. xPointHi, yPointHi or zPointHi) \ \ K The non-zero return value (see below), so setting this \ to 0 will ensure that the object is always reported as \ being visible \ \ ------------------------------------------------------------------------------ \ \ Returns: \ \ A Contains the result as follows: \ \ * 0 if the object is close enough to be visible \ \ * K if the object is too far away to be visible \ \ ****************************************************************************** .CheckObjDistance BPL objd1 \ If A is positive, jump to objd1 CMP #&FF \ If A <> -1, jump to objd3 as the object is too far BNE objd3 \ away to be visible \ If we get here, then A = -1, so now we need to check \ the middle byte against the object's maximum visible \ distance, though we need to negate the middle byte \ first, as the coordinate is negative and the values in \ maxObjDistance are positive LDA T \ Set A = ~T to negate it EOR #&FF JMP objd2 \ Jump to objd2 to check against the object's maximum \ visible distance .objd1 \ If we get here, then A is positive BNE objd3 \ If A is non-zero, jump to objd3 as the object is too \ far away to be visible \ If we get here then A = 0, so now we need to check the \ middle byte against the object's maximum visible \ distance LDA T \ Set A = T so we check the middle byte in the following .objd2 CMP maxObjDistance,Y \ If A >= the object's maximum visible distance, then BCS objd3 \ the object is too far away to be visible, so jump to \ objd3 \ If we get here then we want to return a "visible" \ result LDA #0 \ Set A = 0 as the return value RTS \ Return from the subroutine .objd3 \ If we get here then we want to return a "not visible" \ result LDA K \ Set A = K as the return value RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: ResetLineLists \ Type: Subroutine \ Category: Visibility \ Summary: Reset the line lists at linesToShow and linesToHide \ \ ****************************************************************************** .ResetLineLists LDA #%10000000 \ Set colourLogic = %10000000 STA colourLogic LDA #%00001111 \ Set colourCycle = %00001111, so we show colour 1 and STA colourCycle \ hide colour 2 in the canopy view JSR ModifyDrawRoutine \ Modify the drawing routines so we draw canopy lines in \ colour 1 (as colourLogic = %10000000) LDA #0 \ Set lineId = 0 to act as a loop counter below STA lineId STA linesToShowEnd \ Set linesToShowEnd = 0 to reset the linesToShow list LDA #255 \ Set linesToHideEnd = 255 to reset the linesToHide list STA linesToHideEnd STA linesToShowPointer \ Set linesToShowPointer = 255 to reset the progress \ pointer for the linesToShow list STA linesToHidePointer \ Set linesToHidePointer = 255 to reset the progress \ pointer for the linesToHide list .rell1 JSR ShowOrHideLine \ Process the line with ID lineId, adding it to either \ the linesToShow or the linesToHide list INC lineId \ Increment lineId to move on to the next line LDA lineId \ Loop back until we have processed all 193 lines CMP numberOfLines BCC rell1 LDX #3 \ Set logical colour 3 to white so the dashboard display JSR SetColourToWhite \ shows up in white \ Fall through into FlipColours to flip the values of \ colourCycle and colourLogic to cycle to the next \ colour state, so we end up with drawing routines that \ draw in colour 1, while colourLogic = %01000000 and \ colourCycle = %11110000 \ ****************************************************************************** \ \ Name: FlipColours \ Type: Subroutine \ Category: Graphics \ Summary: Flip the values of colourCycle and colourLogic to cycle to the \ next colour state \ \ ------------------------------------------------------------------------------ \ \ This routine flips the colourCycle and colourLogic variables between these two \ states: \ \ * colourLogic = %10000000 \ colourCycle = %00001111 = show colour 1, hide colour 2 \ \ * colourLogic = %01000000 \ colourCycle = %11110000 = show colour 2, hide colour 1 \ \ The routine only checks the value of colourCycle, so if colourLogic is \ %00000000 on entry, it will be set to one of %10000000 and %01000000, so we \ can use this routine to set the correct state after erasing lines during the \ colourLogic %00000000 phase. \ \ ****************************************************************************** .FlipColours LDX #%00001111 \ Set the values of X and Y to use if bit 7 of LDY #%10000000 \ colourCycle is set, i.e. %11110000 LDA colourCycle \ If bit 7 of colourCycle is set, i.e. %11110000, jump BMI flip1 \ down to flip1 LDX #%11110000 \ Set X and Y for when bit 7 of colourCycle is clear, LDY #%01000000 \ i.e. %00001111 .flip1 STX colourCycle \ Store X in colourCycle, so colourCycle is now: \ \ * %11110000 if the old colourCycle was %00001111 \ * %00001111 if the old colourCycle was %11110000 STY colourLogic \ Store Y in colourLogic, so colourLogic is now: \ \ * %01000000 if the old colourCycle was %00001111 \ * %10000000 if the old colourCycle was %11110000 RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: SetColourToWhite \ Type: Subroutine \ Category: Graphics \ Summary: Set a logical colour to white \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ X The logical colour to set to white \ \ ****************************************************************************** .SetColourToWhite LDY #7 \ Set Y = 7 so we set the logical colour to physical \ colour 7 (white) BNE SetLogicalColour \ Jump to SetLogicalColour to set the logical colour in \ X to white (this BNE is effectively a JMP as Y is \ never zero \ ****************************************************************************** \ \ Name: SetColourToBlack \ Type: Subroutine \ Category: Graphics \ Summary: Set a logical colour to black \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ X The logical colour to set to black \ \ ****************************************************************************** .SetColourToBlack LDY #0 \ Set Y = 0 so we set the logical colour to physical \ colour 7 (black) \ Fall through into SetLogicalColour to set the logical \ colour in X to black \ ****************************************************************************** \ \ Name: SetLogicalColour \ Type: Subroutine \ Category: Graphics \ Summary: Set a logical colour to a physical colour \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ X The logical colour to set \ \ Y The physical colour to map to the logical colour \ \ ****************************************************************************** .SetLogicalColour LDA #19 \ Start a VDU 19 command, which sets a logical colour to JSR OSWRCH \ a physical colour using the following format: \ \ VDU 19, logical, physical, 0, 0, 0 TXA \ Write the value in X, which is the logical colour JSR OSWRCH TYA \ Copy the physical colour from Y to A LDX #3 \ Set a counter in X to write the next four values, so \ the following loop writes: \ \ physical, 0, 0, 0 .setl1 JSR OSWRCH \ Write the value in A LDA #0 \ Set A to 0 to write the three zeroes DEX \ Decrement the loop counter BPL setl1 \ Loop back until we have written the whole VDU command RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: DrawCanopyView \ Type: Subroutine \ Category: Graphics \ Summary: Draw the main view out of the canopy \ Deep dive: Flicker-free animation through colour cycling \ \ ****************************************************************************** .DrawCanopyView JSR ModifyDrawRoutine \ Modify the drawing routines to use the current colour \ cycle LDA linesToShowEnd \ If linesToShowEnd is non-zero, jump to view1 to skip BNE view1 \ the following instruction, as there are lines in the \ linesToShow list that we need to draw BEQ view6 \ linesToShowEnd is zero, which means the linesToShow \ list is empty, so there is nothing to draw, so we \ jump down to view6 to flip the colours (this BEQ is \ effectively a JMP as we know A is zero) .view1 LDA #0 \ Set lineCounter = 0 to act as a counter in the STA lineCounter \ following loop, where we loop through the linesToShow \ list .view2 TAX \ Set lineId to the next line ID from the linesToShow LDA linesToShow,X \ list STA lineId BNE view3 \ If lineId is non-zero, jump to view3, as this is not \ the horizon JSR DrawHalfHorizon \ The line ID is 0, which is the horizon, so draw half \ of the horizon line (as only half of the horizon is \ stored in line 0) LDA lineId \ Retrieve the line's ID, as it will have been corrupted \ by the call to DrawHalfHorizon, and fall through into \ view3 to draw the other half of the horizon .view3 TAX \ Set M to the point ID for the line's end point LDY lineEndPointId,X STY M LDA #0 \ Zero the end point's status byte, so it is no longer STA pointStatus,Y \ marked as having had its coordinates and visibility \ calculated LDY lineStartPointId,X \ Set L to the point ID for the line's start point STY L STA pointStatus,Y \ Zero the start point's status byte, so it is no longer \ marked as having had its coordinates and visibility \ calculated JSR DrawClippedLine \ Draw the line, clipping it to the canopy view if \ required INC lineCounter \ Increment the loop counter LDA lineCounter \ Loop back to draw the next line from the linesToShow CMP linesToShowEnd \ list BCC view2 JSR DrawGunSights \ Draw the gun sights, if shown \ We now flip the screens between the old screen (which \ is being shown in white) and the new one (which we \ just drew in black) LDA colourCycle \ If bit 7 of colourCycle is set, i.e. %11110000, jump BMI view4 \ down to view4 to hide colour 1 and show colour 2 \ If we get here then colourCycle is %00001111 LDX #2 \ Set logical colour 2 to black, to hide the old canopy JSR SetColourToBlack \ view in colour 2 LDX #1 \ Set logical colour 1 to white, to show the new canopy JSR SetColourToWhite \ view that we just drew in colour 1 JMP view5 \ Now that we have cycled the colours, jump down to \ view5 .view4 \ If we get here then colourCycle is %11110000 LDX #1 \ Set logical colour 1 to black, to hide the old canopy JSR SetColourToBlack \ view in colour 1 LDX #2 \ Set logical colour 2 to white, to show the new JSR SetColourToWhite \ view that we just drew in colour 2 .view5 JSR EraseCanopyLines \ Erase the lines that are now hidden, and which are \ stored in the relevant line buffer .view6 JSR FlipColours \ Flip the values of colourCycle and colourLogic to \ cycle to the next colour state RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: ProcessHorizonLine \ Type: Subroutine \ Category: Visibility \ Summary: Calculate coordinates for the horizon's start and end points \ Deep dive: Visibility checks \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ L The point ID of the horizon line's start point \ \ M The point ID of the horizon line's end point \ \ ****************************************************************************** .ProcessHorizonLine LDX M \ Set the end point to (0, 0, 0) JSR SetPointToOrigin LDX L \ Set X = the point ID of the horizon line's start point JSR SetPointToOrigin \ Set the start point to (0, 0, 0) LDA #&F0 \ Set the x-coordinate of the horizon line's start point STA xPointHi,X \ to &F000 by setting the high byte STX GG \ Set GG = the point ID of the horizon line's start \ point \ We now do the following loop twice, once for the \ horizon's start point, and again for the end point .phor1 BIT xRotationHi \ If bit 7 of xRotation is set, jump to phor3 BMI phor3 BVS phor4 \ If bit 6 of xRotation is set (so bit 7 is clear \ and bit 6 is set), jump to phor4 .phor2 \ We get here if one of the following is true: \ \ * Bit 7 of zRotationHi is clear and bit 6 is clear \ * Bit 7 of zRotationHi is set and bit 6 is set \ \ either of which means the plane is the correct way up LDA #40 \ Set A = 40 to set as point's z-coordinate, so we \ draw the horizon in front of the plane (which is the \ direction we are looking) BNE phor5 \ Jump to phor5 to set this as the point's z-coordinate \ (this BNE is effectively a JMP as A is never zero) .phor3 BVS phor2 \ If bit 6 of xRotation is set (so both bits 6 and 7 \ are set), jump to phor2 .phor4 \ We get here if one of the following is true: \ \ * Bit 7 of zRotationHi is clear and bit 6 is set \ * Bit 7 of zRotationHi is set and bit 6 is clear \ \ either of which means the plane is upside down LDA #216 \ Set A = -40 to use as the point's z-coordinate, so \ we draw the horizon behind the plane (which is the \ direction we are looking) .phor5 STA zPointHi,X \ Set the z-coordinate of the horizon line's start (or \ end) point to A LDA #%10000000 \ Set bit 7 of point X's status byte, to indicate that ORA pointStatus,X \ the point's coordinates and visibility have been STA pointStatus,X \ calculated LDA #27 \ Set the matrix number so the call to SetPointCoords STA matrixNumber \ uses matrix 4 in the calculation, so it rotates the \ point by the roll and pitch angles, but not the yaw, \ as rotating a horizon by a yaw angle has no effect JSR SetPointCoords \ Calculate the coordinates for point GG, which will \ be the start or end point of the horizon CPX M \ If we just calculated the coordinates for the horizon BEQ phor6 \ line's end point, then we have now done both points, \ so jump to phor6 to return from the subroutine LDX M \ Set GG and X to the point ID for the horizon line's STX GG \ end point BNE phor1 \ Loop back to phor1 to calculate the coordinates for \ the end point (this BNE is effectively a JMP as X is \ never zero) .phor6 RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: UpdateRadarBlip \ Type: Subroutine \ Category: Dashboard \ Summary: Update a blip on the radar (runway or alien) \ \ ------------------------------------------------------------------------------ \ \ This routine calculates the screen coordinates for a blip on the radar, which \ it then passes to DrawRadarBlip to update the blip. \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ Y The item to update on the radar: \ \ * 1 = update the runway \ \ * 33 = update the alien \ \ ****************************************************************************** .UpdateRadarBlip LDX #0 \ Set X = 0 act as an offset in the loop below, so we \ iterate through the x, y and z axes STX alien \ Set alien = 0, to indicate that we should draw the \ runway for when we fall through into DrawRadarBlip \ below CPY #33 \ If Y = 33, skip the following two instructions BNE blip1 LDA #1 \ Y = 33, which is the alien, so set alien = 1, to STA alien \ indicate that we should draw the alien for when we \ fall through into DrawRadarBlip below \ We now loop through the x-, y- and z-axes to do the \ following, where the object is either the runway or \ the alien: \ \ xTemp2 = xObject - xPlane \ yTemp2 = yObject - yPlane \ zTemp2 = zObject - zPlane \ \ so (xTemp2 yTemp2 zTemp2) contains the vector from the \ plane to the object, which is the same as the vector \ from the centre of the radar to the blip \ \ Note that we only bother with the top and high bytes \ of the calculation (where top, high and low are the \ bytes in a 24-bit number), as the radar isn't accurate \ enough to show the low byte, so we can just ignore it \ \ As the object coordinates don't have a top byte, we \ use 0 rather than the non-existent xObjectTop \ \ The loop comments are for the xTemp2 iteration .blip1 LDA xObjectHi,Y \ Set (xTemp2Hi xTemp2Lo) to the following: SEC \ SBC xPlaneHi,X \ (0 xObjectHi) - (xPlaneTop xPlaneHi) STA xTemp2Lo,X \ \ starting with the top bytes LDA #0 \ and then the high bytes (we don't bother with the low SBC xPlaneTop,X \ bytes) STA xTemp2Hi,X TYA \ Point Y to the next axis (xObject, yObject, zObject) CLC ADC #nextAxis TAY INX \ Increment X to move on to the next axis CPX #3 \ Loop back until we have done all 3 axes BNE blip1 \ We now want to calculate the coordinates for this \ vector when rotated correctly, so we first set up \ the coordinates, and then rotate them LDX #LO(xTemp2Lo) \ Set X so the call to CopyWorkToPoint copies the \ coordinates from (xTemp2, yTemp2, zTemp2) LDY #0 \ Set Y so the call to CopyWorkToPoint copies the \ coordinates to point 0 STY GG \ Set GG = 0 JSR CopyWorkToPoint \ Copy the coordinates from (xTemp2, yTemp2, zTemp2) \ to point 0 LDA #0 \ Set the matrix number so the call to SetPointCoords STA matrixNumber \ uses matrix 1 in the calculation, which will rotate \ the point by the plane's pitch, roll and yaw angles, \ transforming it from the outside world's frame of \ reference to the plane's frame of reference JSR SetPointCoords \ Calculate the coordinates for point 0 \ We now take the rotated x- and z-coordinates and \ scale them down so they work as screen coordinates \ within the range of the radar display (we can ignore \ the y-coordinate, as the radar is a top-down display \ that ignores altitude) \ \ Specifically, we do this by dividing the z-coordinate \ by 8, and the x-coordinate by 16, and then using the \ low bytes of the result as the radar coordinates \ (along the sign bit from the high byte of the result) \ \ For the z-coordinate, his reduces the value from the \ range -256 to +256 down to -32 to +32, which maps onto \ the four character rows above and below the centre of \ the radar (each of which contains eight pixels, so the \ vertical range of the radar is -32 to +32 pixels, as \ 8 * 4 = 32) \ \ For the x-coordinate, we halve it again as mode 5 \ pixels are twice as wide as they are high LDA xPointHi \ Set R = xPointHi so we can shift xPoint below without STA R \ affecting the value of xPointHi LDA zPointHi \ Set S = zPointHi so we can shift zPoint below without STA S \ affecting the value of zPointHi LDX #3 \ We now want to shift the point values right by 3 \ places, so set a shift counter in X .blip2 LSR R \ Set (R xPointLo) = (R xPointLo) >> 1 ROR xPointLo \ = xPoint / 2 LSR S \ Set (S zPointLo) = (S zPointLo) >> 1 ROR zPointLo \ = zPoint / 2 DEX \ Decrement the shift counter BPL blip2 \ Loop back until we have shifted right three times \ Because mode 5 pixels are twice as wide as they are \ high, we need to halve the x-coordinate one more time \ to get the correct result for the pixel x-coordinate LSR R \ Set (R A) = (R xPointLo) >> 1 LDA xPointLo ROR A ADC #0 \ Add bit 0 of the original value to round up the STA xPointLo \ division and store the result in xPointLo \ We now fall through into DrawRadarBlip to erase and \ redraw the blip on the radar at the coordinates in \ (xPointLo, zPointLo) \ ****************************************************************************** \ \ Name: DrawRadarBlip \ Type: Subroutine \ Category: Dashboard \ Summary: Draw a blip on the radar (runway or alien) \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ xPointLo The radar x-coordinate of the blip to update \ \ xPointHi The sign of the x-coordinate in bit 7 \ \ zPointLo The radar y-coordinate of the blip to update (we use the \ real-world z-coordinate, as the radar is a top-down \ view) \ \ zPointHi The sign of the y-coordinate in bit 7 \ \ alien The blip to update on the radar: \ \ * 0 = update the runway \ \ * 1 = update the alien \ \ ****************************************************************************** .DrawRadarBlip LDX alien \ Set X = alien to point to the blip to update on the \ radar (0 for the runway, 1 for the alien) LDA xRadarBuffer,X \ Set I = the X-th byte of xRadarBuffer, which contains STA I \ the x-coordinate of the current line or dot on the \ radar from a previous call to DrawRadarBlip LDA yRadarBuffer,X \ Set J = the X-th byte of yRadarBuffer, which contains STA J \ the y-coordinate of the current line or dot on the \ radar from a previous call to DrawRadarBlip LDA #128 \ Set N = 128 so the call to DrawVectorLine erases the STA N \ current line LDA previousCompass \ Set A = previousCompass, so it contains the value of A \ from the last call to GetRadarVector, i.e. for the \ current line on the radar JSR GetRadarVector \ Calculate the vector for the current line on the radar JSR DrawVectorLine \ Erase a line from (I, J) as a vector (T, U) with \ direction V LDX alien \ If alien is non-zero then we just erased a dot from BNE drbl1 \ the radar, so jump to drbl1 as we don't need to redraw \ the cross at the centre of the radar \ If we get here then we just erased a line from the \ radar, which may have corrupted the cross in the \ centre, so we redraw it LDA #%10001000 \ Redraw the top-but-one pixel of the cross (though, STA row25_char35_6 \ oddly, not the very top pixel) STA row26_char35_0 \ Redraw the bottom two pixels of the cross STA row26_char35_1 LDA #%00010001 \ Redraw the left pixel of the cross STA row25_char34_7 LDA #%11001100 \ Redraw the centre and right pixels of the cross STA row25_char35_7 .drbl1 \ Now to calculate the position of the new line or dot \ to draw on the radar LDA xPointLo \ Set A = xPointLo, the x-coordinate of the alien or \ runway BIT xPointHi \ If the high byte in xPointHi is positive, jump to BPL drbl2 \ drbl2 to skip the following three instructions EOR #&FF \ Otherwise negate A using two's complement, so A is CLC \ positive, i.e. A = |xPointLo| ADC #1 .drbl2 CMP #13 \ If A >= 13, jump to drbl4 to return from the BCS drbl4 \ subroutine, as the item is off the side of the radar LDA zPointLo \ Set A = zPointLo, the y-coordinate of the alien or \ runway BIT zPointHi \ If the high byte in zPointHi is positive, jump to BPL drbl3 \ drbl3 to skip the following three instructions EOR #&FF \ Otherwise negate A using two's complement, so A is CLC \ positive, i.e. A = |zPointLo| ADC #1 .drbl3 CMP #26 \ If A >= 26, jump to drbl4 to return from the BCS drbl4 \ subroutine, as the item is off the top or bottom of \ the radar LDA xPointLo \ Set I = xPointLo + 140 CLC \ ADC #140 \ to move the coordinate onto the radar, whose centre STA I \ cross on-screen is at (140, 207) STA xRadarBuffer,X \ Store the x-coordinate in the relevant byte of \ xRadarBuffer, so we can easily erase this item from \ the radar when we want to move it again LDA zPointLo \ Set J = zPointLo + 208 CLC \ ADC #208 \ to move the coordinate onto the radar, whose centre STA J \ cross on-screen is at (140, 207) STA yRadarBuffer,X \ Store the x-coordinate in the relevant byte of \ yRadarBuffer, so we can easily erase this item from \ the radar when we want to move it again LDA #0 \ Set N = 0 so the call to DrawVectorLine draws the STA N \ new line LDA yRotationHi \ Set A to the current compass heading, for use in the \ call to GetRadarVector if this is the runway (if this \ is an alien, this value is ignored) JSR GetRadarVector \ Calculate the vector for the new line on the radar JSR DrawVectorLine \ Draw a line from (I, J) as a vector (T, U) with \ direction V .drbl4 RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: AlienInAcornsville \ Type: Subroutine \ Category: The Theme \ Summary: Move an alien towards Acornsville and check whether it has reached \ it yet (and if so, end the game) \ Deep dive: Aliens attack Acornsville! \ \ ****************************************************************************** .AlienInAcornsville LDY alienToMove \ Set Y to the number of the alien whose turn it is to \ move towards Acornsville in this iteration of the main \ loop, which we set in UpdateAliens BMI acrn1 \ If Y is negative, then there is no alien to move, so \ jump to acrn1 to return from the subroutine LDA alienState,Y \ If the alien's state is 27, then it is heading for the CMP #27 \ town, so jump to acrn7 if this is not the case BNE acrn7 \ The alien is heading towards the town. This happens \ in three stages: first, the alien takes off; then it \ flies towards the town; and finally it descends. When \ the alien's state is 27, it is working through the \ first two stages, so that's what we do now LDA yObjectHi+33 \ If the high byte of the alien's y-coordinate >= 12, CMP #12 \ then the alien is already flying high, so jump to BCS acrn2 \ acrn2 to move it closer to Acornsville LDA yObjectLo+33 \ Otherwise add 10 to the alien's yObject coordinate, ADC #10 \ so that it rises into the air, starting with the low STA yObjectLo+33 \ bytes BCC acrn1 \ If the addition of the low bytes overflowed, increment INC yObjectHi+33 \ the high byte .acrn1 RTS \ Return from the subroutine .acrn2 \ If we get here then the alien's yObjectHi coordinate \ is 12 or more, so it's already flying high LDX #0 \ Set X = 0, to use in the following to check either the \ x-coordinate (X = 0) or the z-coordinate (X = 80) STX T \ Set T = 0, which we use to record whether we move the \ alien along the x-axis or z-axis (or both) .acrn3 \ We either do the following with X = 0 or X = 80, which \ either checks the x-coordinate or the z-coordinate \ (as zObjectHi - xObjectHi is 80) \ \ The comments are for when X = 0, which checks the \ x-coordinate LDA xObjectHi+33,X \ If the high byte of the alien's x-coordinate is zero, BEQ acrn5 \ then we have already reached the right longitude for \ Acornsville, so jump to acrn5 LDA xObjectLo+33,X \ Subtract alienSpeed from the alien's x-coordinate, SEC \ starting with the low bytes, so the alien moves SBC alienSpeed \ towards Acornsville along the x-axis at the correct STA xObjectLo+33,X \ speed (which increases with later waves) BCS acrn4 \ If the subtraction of the low bytes underflowed, DEC xObjectHi+33,X \ decrement the high byte .acrn4 LDA #1 \ Set T = 1, to record the fact that we moved the alien STA T .acrn5 CPX #80 \ If X = 80 then we have checked both axes, so jump to BEQ acrn6 \ acrn6 to check the alien's altitude LDX #80 \ Otherwise jump back to acrn3 with X = 80 to check the BNE acrn3 \ alien's latitude in its z-coordinate (this BNE is \ effectively a JMP as X is never zero) .acrn6 LDA T \ If T <> 0 then we moved the alien along at least one BNE acrn1 \ of the axes, so jump to acrn1 to return from the \ subroutine as the alien is still in the process of \ flying to Acornsville LDA #28 \ Set the alien's state to 28, so the next time we call STA alienState,Y \ this routine, we jump straight to the following .acrn7 CMP #28 \ If the alien's state is not 28, then it isn't BNE acrn1 \ attacking the town, so jump to acrn1 to return from \ the subroutine \ The alien is heading towards the town. This happens \ in three stages: first, the alien takes off; then it \ flies towards the town; and finally it descends. When \ the alien's state is 28, it is on the final stage, so \ that's what we do now LDA hitTimer \ If hitTimer is non-zero, jump to acrn1 to return from BNE acrn1 \ the subroutine, so the alien doesn't move if we \ recently hit one of its comrades LDA yObjectLo+33 \ Subtract 10 from the alien's yObject coordinate, so SEC \ that it descends towards the town, starting with the SBC #10 \ low bytes STA yObjectLo+33 BCS acrn8 \ If the subtraction of the low bytes underflowed, DEC yObjectHi+33 \ decrement the high byte .acrn8 LDA yObjectHi+33 \ If the high byte of the alien's y-coordinate is zero, BNE acrn1 \ then it is still too high to attack, so jump to acrn1 \ to return from the subroutine LDA yObjectLo+33 \ If the low byte of the alien's y-coordinate is 10 or CMP #10 \ more, then it is still too high to attack, so jump to BCS acrn1 \ acrn1 to return from the subroutine JSR PrintTooLate \ Otherwise the alien is close enough to the town to \ wreak havoc... which means it's game over, so print \ the "TOO LATE!" message in the middle of the screen LDA #90 \ Delay for 90^3 loop iterations JSR Delay JSR TerminateGame \ Terminate the game TSX \ Remove the two bytes from the top of the stack that INX \ were put there by the JSR that called this routine, so INX \ we can jump to NewGame without leaving any trace on TXS \ the stack JMP NewGame \ Jump to NewGame to start a new game \ ****************************************************************************** \ \ Name: GetRadarVector \ Type: Subroutine \ Category: Dashboard \ Summary: Calculate the radar line vector for a line (the runway) or a dot \ (an alien) \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ A For the runway line only, this is either the current \ compass direction from yRotationHi (if we are drawing \ the radar line), or a previous compass direction (if we \ are erasing the existing radar line) \ \ X The type of radar line to calculate: \ \ * 0 = runway line \ \ * Non-zero = alien (shown as a dot) \ \ ------------------------------------------------------------------------------ \ \ Returns: \ \ T Magnitude of x-coordinate of line's vector |x-delta| \ \ U Magnitude of y-coordinate of line's vector |y-delta| \ \ V The direction of the line (runway), or an arbitrary \ direction for the dot, because it doesn't matter (alien) \ \ * Bit 7 is the direction of the x-delta \ \ * Bit 6 is the direction of the y-delta \ \ Direction is like a clock, so positive (clear) is up and \ right \ \ ****************************************************************************** .GetRadarVector CPX #0 \ If X = 0, jump to rvec1 to calculate the line vector BEQ rvec1 \ for the runway \ Otherwise we return the deltas for a dot, T = U = 1, \ and don't worry about setting the direction in V as \ it gets ignored when drawing dots LDY #1 \ Set Y = 1 to return as the y-delta in U BNE rvec2 \ Jump down to rvec2 to set X = 1 and return from the \ subroutine (this BNE is effectively a JMP as Y is \ never zero) .rvec1 \ We now want to calculate the vector for the runway \ line on the radar \ \ The runway runs north-south, so our compass direction \ in A lets us work out the direction of the runway line \ on the radar. The value of our compass in A is: \ \ * 0 = north \ * 64 = east \ * 128 = south \ * 192 = west STA previousCompass \ Store A in previousCompass, so we can pass it to this \ routine again when we want to erase the line we are \ about to draw CLC \ Set A = A + 16 ADC #16 \ \ This rotates the compass needle clockwise by 16, or \ 22.5 degrees, so if the needle is just anticlockwise \ of a cardinal compass point (e.g. NNW, ENE) it will be \ bumped along to the quadrant and will inherit the \ correct direction bit in bit 7. At the cardinal \ points, A now contains the following: \ \ * 16 = north -> bit 7 clear \ * 80 = east -> bit 7 clear \ * 144 = south -> bit 7 set \ * 208 = west -> bit 7 set \ \ So bit 7 contains the direction of the needle along \ the x-axis, with 0 to the right and 1 to the left ASL A \ Shift bit 7 of the result into the C flag, which we \ will use below as the direction of the line's x-delta STA P \ Store the result in P, so: \ \ P = (A + 16) << 1 \ \ which looks like this: \ \ * %00100000 = north \ * %10100000 = east \ * %00100000 = south \ * %10100000 = west \ \ We use this below to work out the line's vector PHP \ Store the flags on the stack, in particular the C \ flag from the above operation, which gives us the \ direction of the line's x-delta ROR A \ Shift the C flag back into bit 7 of A, so bit 7 once \ again contains the direction of the x-delta SEC \ Set A = A - 64 SBC #64 \ \ This rotates the compass needle anticlockwise by 64, \ or 90 degrees, which will change bit 7 from being the \ direction of the x-delta to the being the direction of \ the y-delta PLP \ Retrieve the C flag from above, which contains the \ direction of the x-delta ROR A \ Shift the direction of the x-delta into bit 7 of A, \ and at the same time shift the y-delta from bit 7 to \ bit 6 EOR #%11000000 \ Reverse both deltas by flipping bits 6 and 7 of A \ (I am not sure why this is done) STA V \ Store the result in V to set the direction of the line \ vector \ Above, we stored a value in P like this: \ \ * %00100000 = north \ * %10100000 = east \ * %00100000 = south \ * %10100000 = west \ \ We can use this below to work out the vector of the \ line to show, as follows: \ \ * A set bit 6 means the line is between the cardinal \ points, e.g. northeast, southwest and so on, so \ the line is diagonal \ \ * A set bit 7 means the line is generally east-west, \ which is horizontal \ \ * A clear bit 7 means the line is generally north- \ south, which is vertical \ \ We use this to set the vector in T and U to the \ following: \ \ * Diagonal: x-delta = 2, y-delta = 4 \ \ * Horizontal: x-delta = 2, y-delta = 1 \ \ * Vertical: x-delta = 1, y-delta = 4 \ \ The y-delta is twice the x-delta because the pixels \ in mode 5 are twice as wide as they are tall LDX #2 \ Set X = 2 to return as the x-delta in T for a diagonal LDY #4 \ Set Y = 4 to return as the y-delta in U for a diagonal BIT P \ If bit 6 of P is set, jump to rvec3 to return these BVS rvec3 \ values as the deltas (i.e. 2 and 4), as the runway \ line is diagonal BPL rvec2 \ If bit 7 of P is clear, jump to rvec2 to return an \ x-delta of 1 and a y-delta of 4, as the runway line is \ vertical LDY #1 \ Otherwise set Y = 1 and jump to rvec3 to return an BNE rvec3 \ x-delta of 2 and a y-delta of 1, as the runway line is \ horizontal .rvec2 LDX #1 \ Set X = 1 to return as the x-delta in T .rvec3 STX T \ Return X as the x-delta in T STY U \ Return Y as the y-delta in U RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: colour1L2R \ Type: Variable \ Category: Drawing lines \ Summary: Pixel bytes for drawing canopy lines left to right in colour 1 \ \ ****************************************************************************** .colour1L2R EQUB %00001000 \ Colour 1, drawing a line left to right EQUB %00001100 EQUB %00001110 EQUB %00001111 EQUB %00000100 EQUB %00000110 EQUB %00000111 EQUB %00000010 EQUB %00000011 EQUB %00000001 \ ****************************************************************************** \ \ Name: colour1R2L \ Type: Variable \ Category: Drawing lines \ Summary: Pixel bytes for drawing canopy lines right to left in colour 1 \ \ ****************************************************************************** .colour1R2L EQUB %00000001 \ Colour 1, drawing a line right to left EQUB %00000011 EQUB %00000111 EQUB %00001111 EQUB %00000010 EQUB %00000110 EQUB %00001110 EQUB %00000100 EQUB %00001100 EQUB %00001000 \ ****************************************************************************** \ \ Name: colour2L2R \ Type: Variable \ Category: Drawing lines \ Summary: Pixel bytes for drawing canopy lines left to right in colour 2 \ \ ****************************************************************************** .colour2L2R EQUB %10000000 \ Colour 2, drawing a line left to right EQUB %11000000 EQUB %11100000 EQUB %11110000 EQUB %01000000 EQUB %01100000 EQUB %01110000 EQUB %00100000 EQUB %00110000 EQUB %00010000 \ ****************************************************************************** \ \ Name: colour2R2L \ Type: Variable \ Category: Drawing lines \ Summary: Pixel bytes for drawing canopy lines right to left in colour 2 \ \ ****************************************************************************** .colour2R2L EQUB %00010000 \ Colour 2, drawing a line right to left EQUB %00110000 EQUB %01110000 EQUB %11110000 EQUB %00100000 EQUB %01100000 EQUB %11100000 EQUB %01000000 EQUB %11000000 EQUB %10000000 \ ****************************************************************************** \ \ Name: colour1Row \ Type: Variable \ Category: Drawing lines \ Summary: Pixel bytes for erasing canopy lines in colour 1 \ \ ****************************************************************************** .colour1Row EQUB %00001111 \ Solid colour 1 EQUB %00001111 EQUB %00001111 EQUB %00001111 EQUB %00001111 EQUB %00001111 EQUB %00001111 EQUB %00001111 EQUB %00001111 EQUB %00001111 \ ****************************************************************************** \ \ Name: colour2Row \ Type: Variable \ Category: Drawing lines \ Summary: Pixel bytes for erasing canopy lines in colour 2 \ \ ****************************************************************************** .colour2Row EQUB %11110000 \ Solid colour 2 EQUB %11110000 EQUB %11110000 EQUB %11110000 EQUB %11110000 EQUB %11110000 EQUB %11110000 EQUB %11110000 EQUB %11110000 EQUB %11110000 \ ****************************************************************************** \ \ Name: previousCompass \ Type: Variable \ Category: Dashboard \ Summary: Stores the value of the compass heading when we draw the runway \ on the radar, so we can erase the line later \ \ ****************************************************************************** .previousCompass EQUB &20 \ ****************************************************************************** \ \ Name: previousTime \ Type: Variable \ Category: Main loop \ Summary: Stores the current time (low byte only), so we can process lines \ in a timely fashion during the main loop \ \ ****************************************************************************** .previousTime EQUB &20 EQUB &20, &20 \ These bytes appear to be unused \ ****************************************************************************** \ \ Name: ClearCanopy \ Type: Subroutine \ Category: Graphics \ Summary: Clear the canopy to black, leaving the canopy edges alone \ \ ****************************************************************************** .ClearCanopy LDX #0 \ Set X = 0 so we clear the canopy to black \ Fall through into FillCanopy to fill the canopy with \ black, leaving the top and side edges alone \ ****************************************************************************** \ \ Name: FillCanopy \ Type: Subroutine \ Category: Graphics \ Summary: Fill the canopy with a specified colour, leaving the canopy edges \ alone \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ X The colour to fill the canopy with \ \ ****************************************************************************** .FillCanopy TXA \ Copy the colour into X LDX #19 \ Set R = 19, so we clear 19 character rows (the whole STX R \ canopy view except for the top row containing the \ canopy edge) LDY #HI(row1_char1_0) \ Set (Y X) to the screen address for row 1, character LDX #LO(row1_char1_0) \ block 1 \ Fall through into FillCanopyRows to fill the canopy \ view, from the top row to the bottom, avoiding the top \ and side edges \ ****************************************************************************** \ \ Name: FillCanopyRows \ Type: Subroutine \ Category: Graphics \ Summary: Fill multiple screen rows with a particular colour, avoiding the \ canopy edges \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ A The colour to fill the canopy with \ \ (Y X) The screen address to start filling from \ \ R The number of character rows to fill \ \ ****************************************************************************** .FillCanopyRows STY Q \ Set (Q P) = (Y X), so (Q P) is now the screen address STX P \ we want to start filling from STA S \ Store the value we want to store into S .fill1 LDY #0 \ Set a byte counter in Y LDA S \ Fetch the value of A that we stored in S above .fill2 STA (P),Y \ Set the Y-th byte of (Q P) to A, which sets 4 pixels \ to the pixel pattern in S DEY \ Decrement the byte counter BNE fill2 \ Loop back until we have set 256 bytes, starting at \ (Q P), to the value in A LDY #47 \ Set a byte counter in Y for 47 bytes INC Q \ Set (Q P) = (Q P) + 256 \ \ so it points to the next byte to fill after the 256 \ bytes we just did .fill3 STA (P),Y \ Set the Y-th byte of (Q P) to A, which sets 4 pixels \ to the pixel pattern in S DEY \ Decrement the byte counter BPL fill3 \ Loop back until we have set 47 bytes, starting at \ (Q P), to the value in A LDA P \ Set (Q P) = (Q P) + 64 CLC \ ADC #64 \ starting with the low bytes STA P BCC fill4 \ If the above addition didn't overflow, skip the next \ instruction INC Q \ The above addition overflowed, so increment the high \ byte of (Q P) to point to the next page in memory \ So now (Q P) is 320 greater than at the start, so it \ points to the next character row in screen memory .fill4 DEC R \ Decrement the row counter in R BNE fill1 \ Loop back until we have updated R rows RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: Delay \ Type: Subroutine \ Category: Utility routines \ Summary: Delay for a specified number of loops \ \ ------------------------------------------------------------------------------ \ \ This routine performs A^3 loop iterations, to create a delay. \ \ Fragments of the original source for this routine appear in the game code, as \ follows: \ \ .dlp2 STA&76 \ .dl ... \ ... :BNE dlp2 \ DEC&74:BNE dlp1 \ rts \ \ ****************************************************************************** .Delay STA T \ Set T as the counter for the outer loop .dely1 STA U \ Set U as the counter for the middle loop .dely2 STA V \ Set V as the counter for the inner loop .dely3 DEC V \ Loop around for A iterations in the inner loop BNE dely3 DEC U \ Loop around for A iterations in the middle loop BNE dely2 DEC T \ Loop around for A iterations in the outer loop BNE dely1 RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: UpdateBullets \ Type: Subroutine \ Category: The Theme \ Summary: Move the bullets through the air \ \ ------------------------------------------------------------------------------ \ \ This is called UBUL in the original source code. \ \ ****************************************************************************** .UpdateBullets LDY #15 \ The bullets are made up of objects 12 to 15, so set a STY objectId \ counter in objectId to count through all four LDA #98 \ Objects 12 to 15 are made up of points 95 to 98, so STA GG \ set a counter in GG to count through all four .ubul1 TYA \ Set X = Y + 216 CLC \ ADC #216 \ so X will be 228 to 231 for objects 12 to 15, which is TAX \ the ID of each bullet object's velocity vector, as set \ up in the FireGuns routine JSR AddPointToObject \ Add the vector in point X to the object coordinates \ for object Y (in other words, add the velocity vector \ to object Y's coordinates, which moves the bullet in \ space by the correct amount) LDA #0 \ Set showLine = 0 as a starting point for the line's STA showLine \ visibility (so we start out by assuming the line is \ visible, and change this in the following call to \ SetObjectCoords if we find that it isn't) JSR SetObjectCoords \ Calculate the object's coordinates and visibilty, \ updating the object's status byte with the results BPL ubul2 \ If bit 7 of the object's updated status byte is clear, \ then the bullets have hit the ground, so jump to ubul2 \ to remove the bullets LDY GG \ Set Y to the point ID that we're checking LDX #60 \ Set X to line ID 60 for the call to CheckLineDistance JSR CheckLineDistance \ Check whether point GG on line 60 is within the \ visible distance for the line BEQ ubul3 \ If the result is 0, then the point is visible, so \ jump to ubul3 to move onto the next bullet point .ubul2 \ If we get here then this bullet point has either hit \ the ground, or it is too far away to be seen any more, \ so it's time to remove the bullets LDA #0 \ Set firingStatus = 0 to indicate that there are no STA firingStatus \ bullets are in the air .ubul3 DEC GG \ Decrement the point counter to move on to the next \ point from 98 down to 95 DEC objectId \ Decrement the object counter to move on to the next \ object from 15 down to 12 LDY objectId \ Loop back until we have processed all four objects CPY #12 \ and points BCS ubul1 RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: SpawnAlien \ Type: Subroutine \ Category: The Theme \ Summary: If the Theme is enabled and the current wave does not yet have \ eight aliens in it, spawn a new alien \ Deep dive: Alien feeding and growth patterns \ \ ------------------------------------------------------------------------------ \ \ This routine tries to add a new alien to the current wave. It sets the alien's \ state to 0, and chooses a random object ID from the range 16 to 29 for the \ alien. If this object ID is already in use by an alien that we've already \ spawned for this wave, then it aborts the spawning, otherwise it allocates the \ object ID to the new alien's alienObjectId entry and our new alien is spawned. \ \ This means that aliens only ever spawn in the fixed locations for objects 16 \ to 29, which can be seen in the xObjectLo table. \ \ This is called SUTR in the original source code. \ \ ****************************************************************************** .SpawnAlien LDX themeStatus \ If bit 7 of themeStatus is set, then the Theme is not BMI spaw3 \ enabled, so jump to spaw3 to return from the \ subroutine BEQ spaw3 \ If themeStatus is zero, then we have already spawned \ all eight aliens in this wave, so return from the \ subroutine LDA onGround \ If onGround is non-zero, then we are on the ground, so BNE spaw3 \ jump to spaw3 to return from the subroutine \ If we get here, themeStatus is in the range 1 to 8 and \ we are not on the ground, so the Theme has started but \ we haven't yet spawned all eight aliens in the current \ wave, so we need to spawn another alien (specifically \ the alien whose ID is in themeStatus) STA alienState-1,X \ We know A is 0, as we just passed through a BNE above, \ so this zeroes the state of the alien whose number is \ in themeStatus, so it starts its feeding routine from \ scratch \ We now need to allocate an object ID to this alien, \ from the range 16 to 29, making sure we don't reuse \ any objects that are currently in use by other aliens \ in the wave 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 AND #15 \ Reduce the random number to the range 0 to 15 CMP #14 \ If the random number is >= 14 (12.5% chance), jump to BCS spaw3 \ spaw3 to return from the subroutine ORA #16 \ Increase the random number to the range 16 to 29, to \ give us a potential object ID DEC themeStatus \ We are spawning a new alien, so decrement the counter \ in themeStatus so it reflects the number of aliens \ that still have to spawn to complete this wave LDX #8 \ We now need to confirm that this object ID isn't being \ used by any other aliens that we've already spawned in \ this wave, so set X to act as a loop counter, going \ from 8 down to themeStatus (so this works through the \ IDs of the aliens that we have already spawned in this \ wave, as we start spawning at ID 8 and work our way \ down to 1) .spaw1 DEX \ Decrement the loop counter CPX themeStatus \ If X <> themeStatus, jump to spaw2 to check this BNE spaw2 \ alien's object ID STA alienObjectId,X \ Otherwise we have run through all the other aliens in \ this wave and none of them are using this object ID, \ so set this as the object ID for this alien RTS \ Return from the subroutine .spaw2 CMP alienObjectId,X \ If the object ID for the X-th alien does not match our BNE spaw1 \ random number, then jump back to spaw1 to move on to \ the next alien INC themeStatus \ Otherwise we've found an alien that is already using \ the object ID we generated above, so we give up on \ spawning this alien, incrementing the alien counter \ in themeStatus so that it's back to the original value \ (so we can have another go at spawning an alien on the \ next call to SpawnAlien) .spaw3 RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: UpdateAliens (Part 1 of 5) \ Type: Subroutine \ Category: The Theme \ Summary: Update the aliens so they progress through their feeding or attack \ cycles \ Deep dive: Scheduling tasks in the main loop \ Alien feeding and growth patterns \ \ ------------------------------------------------------------------------------ \ \ We start by incrementing the state of any non-dormant aliens, so they slowly \ move through their feeding or take-off cycles. \ \ ****************************************************************************** .UpdateAliens LDA mainLoopCounter \ If the mainLoopCounter is a multiple of 128, jump to AND #127 \ upal3 to move on to part 2 (so we only do this part BNE upal3 \ on 2 out of every 256 iterations round the main loop) LDX #7 \ We now progress the state of each alien, from alien 7 \ down to alien 0, so set a loop counter in X .upal1 LDA alienState,X \ If the state of alien X is zero, jump to upal2 BEQ upal2 \ to move on to the next alien CMP #22 \ If the state of alien X is 22, jump to upal2 BEQ upal2 \ to move on to the next alien CMP #27 \ If the state of alien X is 27 or higher, jump BCS upal2 \ to upal2 to move on to the next alien INC alienState,X \ If we get here then the state of alien X is not 0, 22 \ or >= 27, i.e. it's 1-21 or 23-26, so increment the \ state to progress through the feeding and take-off \ stages .upal2 DEX \ Decrement the alien number BPL upal1 \ Loop back until we have progressed all eight aliens \ to the next state \ ****************************************************************************** \ \ Name: UpdateAliens (Part 2 of 5) \ Type: Subroutine \ Category: The Theme \ Summary: Manage alien slots 31 and 32, and if there's a vacancy, wake up a \ dormant alien, move it into the slot and start its feeding cycle \ Deep dive: Alien feeding and growth patterns \ \ ------------------------------------------------------------------------------ \ \ Look at alien slots 31 and 32, and for each one: \ \ * If the alien in the slot is in state 24, remove it from the slot \ \ * Otherwise check to see if the alien in the slot has been destroyed, and if \ so, empty the slot \ \ * If the slot is not empty, search the aliens for one is dormant (i.e. in \ state 0), isn't already in the other slot, and hasn't been destroyed, and \ put that into the slot, bumping its state up to 1 and setting the size of \ the alien to the correct size for the first feeding stage \ \ ****************************************************************************** .upal3 LDY #31 \ We run the following outer loop twice, once for alien \ slot 31 and again for alien slot 32, so set a counter \ in Y to iterate through 31 and 32 .upal4 LDX alienSlot-30,Y \ If alien slot Y contains a negative number, then the BMI upal6 \ slot is already empty, so jump to upal6 LDA alienState,X \ If the state of the alien in slot Y is 24, then skip CMP #24 \ the following two instructions BEQ upal5 LDA alienObjectId,X \ If the object ID of the alien in slot Y is positive, BPL upal11 \ jump to upal11 to move on to the next alien .upal5 \ If we get here then alien slot Y contains an alien \ (with ID 0 to 7), and either the alien's state is 24, \ or the alien's object ID is negative (which means it \ has been destroyed) LDA #254 \ Clear out slot Y by setting it to a negative number STA alienSlot-30,Y .upal6 \ We now run through aliens 7 to 0, until we find one \ with a positive object ID and a state of 0, and which \ is not already in the other slot. When we find it, \ we insert it into the slot, bump its state up to 1 \ and stop looking LDX #7 \ Set a loop counter in X to contain the alien's number .upal7 LDA alienObjectId,X \ If the alien's object ID is negative, jump to upal10 BMI upal10 \ to move on to the next alien, as this alien has been \ destroyed LDA alienState,X \ If the alien's state is non-zero, jump to upal10 to BNE upal10 \ move on to the next alien CPY #31 \ If we are processing alien slot 31 in the outer loop, BEQ upal8 \ skip the following two instructions CPX alienSlot+1 \ Compare the alien number with the contents of alien JMP upal9 \ slot 31 and skip the following instruction .upal8 CPX alienSlot+2 \ Compare the alien number with the contents of alien \ slot 32 .upal9 BEQ upal10 \ If the current alien number matches the contents of \ slot Y, jump to upal10 to move on to the next alien TXA \ Insert this alien number into alien slot Y STA alienSlot-30,Y STX U \ Store the alien number in U LDA #%10000000 \ Set bit 7 of A so the call to ResizeFeedingAlien sets \ the size of the alien to the first feeding stage JSR ResizeFeedingAlien \ Resize the alien to the first feeding stage LDX U \ Retrieve the alien number from U LDA #1 \ Set the alien's state to 1 STA alienState,X BNE upal11 \ Jump to upal11 to terminate the inner loop (this BNE \ is effectively a JMP as A is never zero) .upal10 DEX \ Decrement the inner loop alien counter BPL upal7 \ Loop back until we have worked our way through aliens \ 0 to 7 .upal11 INY \ Increment the alien counter CPY #33 \ Loop back until we have processed both alien slots 31 BNE upal4 \ and 32 \ ****************************************************************************** \ \ Name: UpdateAliens (Part 3 of 5) \ Type: Subroutine \ Category: The Theme \ Summary: If alien slot 33 is free and there's an alien waiting to take off, \ promote it into slot 33 \ Deep dive: Alien feeding and growth patterns \ \ ------------------------------------------------------------------------------ \ \ Look at alien slot 33, and: \ \ * If the alien in slot 33 has been destroyed, clear the slot \ \ * If slot 33 is clear, then check slots 31 and 32 to see if either of then \ contains an alien in state 22 (i.e. an alien that has finished feeding and \ is ready to take off), and if so, move that alien into slot 33, promoting \ it to state 23 in the process \ \ ****************************************************************************** \ At this point Y = 33, so we now process alien slot 33 LDX alienSlot-30,Y \ If alien slot 33 contains a negative number, then the BMI upal12 \ slot is already empty, so jump to upal12 to move on to \ skip the following LDA alienObjectId,X \ If the object ID of the alien in slot 33 is positive, BPL upal15 \ then we already have an alien moving towards the town, \ so jump to upal15 to skip the STA alienSlot-30,Y \ Clear out slot 33 by setting it to a negative number \ (we know A is negative because we just passed through \ a BPL instruction) .upal12 LDY #31 \ We now work our way through alien slots 31 and 32, \ so set a counter in Y for the slot number .upal13 LDX alienSlot-30,Y \ If alien slot Y contains a negative number, then the BMI upal14 \ slot is empty, so jump to upal14 to skip the following LDA alienState,X \ If the alien's state is not 22, jump to upal14 to skip CMP #22 \ the following BNE upal14 STX alienToMove \ Set this alien as the one to move towards Acornsville \ in this iteration of the main loop by setting \ alienToMove to the alien number in X LDA #23 \ Set the alien's state to 23 STA alienState,X BNE upal15 \ Jump to upal15 to exit the loop (this BNE is \ effectively a JMP as A is never zero) .upal14 INY \ Increment the counter to point to the next alien slot CPY #33 \ Loop back until we have done alien slots 31 and 32 BNE upal13 \ ****************************************************************************** \ \ Name: UpdateAliens (Part 4 of 5) \ Type: Subroutine \ Category: The Theme \ Summary: When an alien reaches the next feeding stage, double its size \ Deep dive: Scheduling tasks in the main loop \ Alien feeding and growth patterns \ \ ****************************************************************************** .upal15 LDA mainLoopCounter \ If the mainLoopCounter is a multiple of 128, jump to AND #127 \ upal19 to skip the following (so we only do this part BNE upal19 \ on 2 out of every 256 iterations round the main loop, \ just like part 1) LDY #31 \ We now work our way through alien slots 31 and 32, \ so set a counter in Y for the slot number .upal16 LDX alienSlot-30,Y \ If alien slot Y contains a negative number, then the BMI upal17 \ slot is empty, so jump to upal17 to move on to the \ next slot LDA alienState,X \ If the alien's state is < 5, jump to upal17 to move on CMP #5 \ to the next slot, as the alien is still in feeding BCC upal17 \ stage 1 and doesn't need resizing CMP #20 \ If the alien's state is >= 20, jump to upal17 to move BCS upal17 \ on to the next slot, as the alien has already reached \ the last feeding stage and can't get any bigger AND #%00000011 \ Check whether the feeding state is in the form BNE upal17 \ %xxxxxx00, and if not, jump to upal17 to move on to \ the next slot \ If we get here, then the alien's state is between 6 \ and 19, and matches %xxxxxx00, so it's one of these: \ \ * 8 = %00001000 \ * 12 = %00001100 \ * 16 = %00010000 \ \ These are the points in the feeding stage when the \ size of the alien doubles JSR ResizeFeedingAlien \ Double the size of the feeding alien to move it onto \ the next feeding stage .upal17 INY \ Increment the counter to point to the next alien slot CPY #33 \ Loop back until we have done alien slots 31 and 32 BNE upal16 \ ****************************************************************************** \ \ Name: UpdateAliens (Part 5 of 5) \ Type: Subroutine \ Category: The Theme \ Summary: Check whether the whole wave has been destroyed, and award points \ accordingly \ Deep dive: Alien feeding and growth patterns \ \ ****************************************************************************** LDX #7 \ We now work through all the aliens, from alien 7 down \ to alien 0, to check whether they have all been \ destroyed (i.e. whether they all have a negative \ object ID), so set a loop counter in X .upal18 LDA alienObjectId,X \ If the X-th alien object ID is positive, then that BPL upal19 \ alien is still at large, so jump to upal19 to return \ from the subroutine, as we haven't destroyed all eight \ aliens in this wave DEX \ Decrement the loop counter to point to the next alien BPL upal18 \ Loop back until we have checked all eight aliens \ If we get here then all eight aliens have negative \ object IDs, so they have all been destroyed LDA #8 \ Set themeStatus = 8 to initiate a brand new wave of STA themeStatus \ aliens LDX #&00 \ Add 500 points to the score and make a beep by calling LDA #&50 \ ScorePoints with (X A) = &0050 JSR ScorePoints \ \ This is the score for completing a wave in the Theme LDA alienSpeed \ Set A = alienSpeed + 4 CLC ADC #4 CMP #19 \ If A >= 19, skip the following instruction, so BCS upal19 \ alienSpeed has a maximum value of 22 (as alienSpeed \ starts with a value of 10, and increases by 4 with \ each wave until it is >= 19, which happens when it \ reaches 22) STA alienSpeed \ Update alienSpeed with the faster speed so the next \ wave has faster-moving aliens .upal19 RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: ResizeFeedingAlien \ Type: Subroutine \ Category: The Theme \ Summary: Change the size of an alien so it grows bigger as it feeds \ Deep dive: Alien feeding and growth patterns \ \ ------------------------------------------------------------------------------ \ \ This routine grows the alien in slot 31 or 32 by scaling the z-coordinates for \ the alien's four object points. Because object points are stored with the \ scale factor in bits 4 to 7, we can double the size of the alien by adding \ 16 to each z-coordinate, thus doubling the scale factor. \ \ If bit 7 of A is set, then this routine resets the size to the smallest \ feeding stage, which has a scale factor of 32 (i.e. 2^2). \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ A Determines how the alien is resized: \ \ * If bit 7 is clear, double the size of the alien \ \ * If bit 7 is set, reset the alien to stage 1 \ \ Y The number of the alien slot to process (31 or 32), \ which determines which object points have their \ z-coordinates updated in the zObjectPoint table: \ \ * Resize object points 183 to 186 for alien slot 31 \ \ * Resize object points 188 to 191 for alien slot 32 \ \ ------------------------------------------------------------------------------ \ \ Returns: \ \ zObjectPoint Updated z-coordinates for the relevant object points, \ scaled as required \ \ Y Y is unchanged \ \ ****************************************************************************** .ResizeFeedingAlien STA K \ Store the A argument in K so we can retrieve it below STY T \ Store Y in T so we can ensure it is unchanged by the \ routine LDX #3 \ We do the following loop four times, to update the \ alien's four object ID z-coordinates, so set a loop \ counter in X LDA #186 \ Set A to the object point ID to update for slot 31, so \ we update points 183 to 186 in the loop CPY #31 \ If this is alien slot 31, skip the following BEQ size1 \ instruction LDA #191 \ Set A to the object point ID to update for slot 32, so \ we update points 188 to 191 in the loop .size1 TAY \ Copy the object point ID we just set in A into Y, so \ we can use it as an index .size2 LDA zObjectPoint,Y \ Set A to the z-coordinate of the object point CLC \ Add 16 to the z-coordinate, which doubles the scale ADC #16 \ factor in bits 4 to 7 BIT K \ If bit 7 of K is clear, skip the following two BPL size3 \ instructions, as we are done AND #15 \ Otherwise bit 7 of S is set, so we need to reset the ORA #32 \ scale to the first feeding stage, which we can do like \ this: \ \ z = 32 + (z + 16) MOD 16 \ \ This gives us a result with 32 in bits 4 to 7 (i.e. a \ scale factor of 2^2) while retaining the value in bits \ 0 to 3 .size3 STA zObjectPoint,Y \ Store the updated z-coordinate DEY \ Decrement the object point ID DEX \ Decrement the loop counter BPL size2 \ Loop back until we have updated three slots LDY T \ Restore Y from T, so it doesn't get changed by the \ routine RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: CheckIfAlienIsHit (Part 1 of 2) \ Type: Subroutine \ Category: The Theme \ Summary: Extract the alien's feeding stage, ready for the hit calculations \ in part 2 \ Deep dive: Detecting alien hits \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ Y The object ID of the alien to check (30 to 33) \ \ objectId The object ID of the alien to check (30 to 33) \ \ ****************************************************************************** .CheckIfAlienIsHit LDA #%01111101 \ Set QQ = %01111101, to hold the y-axis dimension of STA QQ \ the alien's weak spot (let's call this y-size), which \ we will scale down to match the alien's current size LDA #%01000000 \ Set Q = %01000000, to hold the low byte of the offset STA Q \ of the alien's weak spot from the alien's anchor point \ (the high byte is always 5), which we will scale down \ to match the alien's current size LDA #%10100000 \ Set RR = %10100000, to hold the x-axis dimension of STA RR \ the alien's weak spot (let's call this x-size), which \ we will scale down to match the alien's current size STA PP \ Set PP = %10100000, to hold the z-axis dimension of \ the alien's weak spot (let's call this z-size), which \ we will scale down to match the alien's current size CPY #31 \ If Y >= 31, jump to ahit1 BCS ahit1 \ If we get here then Y = 30, so this is a dormant alien LDA #4 \ Set A = 4, to set as the feeding stage in ahit5 (stage \ 4 is the dormant phase) LDX #3 \ Set X = 3, so ahit4 shifts right by 3 places BNE ahit4 \ Jump to ahit4 (this BNE is effectively a JMP as X is \ never zero) .ahit1 LDA #0 \ Set A = 0, so if this is the flying alien (Y = 33), we \ set the feeding stage to 0 in ahit5 (stage 0 is for \ fully fed aliens, and only fully fed aliens can fly) CPY #32 \ If Y >= 32, jump to ahit2 BCS ahit2 \ If we get here then Y = 31, so this is a feeding alien LDX #183 \ Set X = 183, to use as the object point ID BNE ahit3 \ Jump to ahit3 (this BNE is effectively a JMP as X is \ never zero) .ahit2 BNE ahit5 \ If Y <> 32, then Y = 33 and this is the flying alien, \ so jump to ahit5 to store the feeding stage and move \ on to the next part \ If we get here then Y = 32, so this is a feeding alien LDX #188 \ Set X = 188, to use as the object point ID .ahit3 \ If we get here then Y = 31 or 32 and X is the ID of \ the first object point for this alien, so now we \ extract the alien's current feeding stage from the \ scale of the object point' z-coordinate, which gets \ updated in ResizeFeedingAlien as the alien grows \ \ Note that the feeding stage goes from 4 (dormant) down \ to 0 (fully fed) as the alien feeds and grows bigger, \ so we need to invert the scale of the z-coordinate \ to get the correct value for the feeding stage LDA zObjectPoint,X \ Set A to the object point's z-coordinate EOR #%01110000 \ The z-coordinate contains scale information in bits 4 \ to 7, with the smallest feeding alien having a scale \ factor of 2^2, and the largest having a scale factor \ of 2^5 (there are four different sizes of feeding \ alien) \ \ This means that even at the largest scale, bit 7 of \ the scale will still be zero, so to invert the scale \ we only need to flip bits 4 to 6, as then: \ \ * If scale is %0010 (2^2), result is %0101 (5) \ * If scale is %0011 (2^3), result is %0100 (4) \ * If scale is %0100 (2^4), result is %0011 (3) \ * If scale is %0101 (2^5), result is %0010 (2) \ \ so this EOR inverts the alien's scale factor to the \ range 2 to 5, if we only consider the top nibble LSR A \ We now shift the top nibble down, so A contains the LSR A \ inverted scale factor (i.e. A is in the range 2 to 5) LSR A LSR A TAX \ Finally, we subtract 2 from the inverted scale factor, DEX \ so A is now in the range 0 to 3 and reflects the four DEX \ feeding stages: \ \ * 0 = large feeding alien (fully fed) \ * 1 = medium feeding alien \ * 2 = small feeding alien \ * 3 = smallest feeding alien TXA \ Copy the feeding stage into X BEQ ahit5 \ If X = 0, the alien is at its largest size, so jump to \ ahit5 to store the feeding stage and move on to the \ next part \ Otherwise we scale QQ, RR, PP and QQ right by the \ number of places given in X .ahit4 \ This loop shifts the following to the right by X \ places, where X > 0 LSR QQ \ Shift QQ, RR, PP and QQ right by one place LSR RR LSR PP LSR Q DEX \ Decrement the shift counter in X BNE ahit4 \ Loop back until we have shifted right by X places \ By the time we get here, the variables are set as \ follows: \ \ * Fully fed (stage 0) or flying alien: \ \ Q = %01000000, so (5 Q) = &540 = 1360 \ RR = %10100000, so x-size = 160 \ QQ = %01111101, so y-size = 125 \ PP = %10100000, so z-size = 160 \ \ * Medium feeding alien (stage 1): \ \ Q = %00100000, so (5 Q) = &520 = 1312 \ RR = %01010000, so x-size = 80 \ QQ = %00111110, so y-size = 62 \ PP = %01010000, so z-size = 80 \ \ * Small feeding alien (stage 2): \ \ Q = %00010000, so (5 Q) = &510 = 1296 \ RR = %00101000, so x-size = 40 \ QQ = %00011111, so y-size = 31 \ PP = %00101000, so z-size = 40 \ \ * Smallest feeding alien (stage 3) or dormant alien: \ \ Q = %00001000, so (5 Q) = &508 = 1288 \ RR = %00010100, so x-size = 20 \ QQ = %00001111, so y-size = 15 \ PP = %00010100, so z-size = 20 .ahit5 STA feedingStage \ Store the alien's feeding stage in feedingStage \ ****************************************************************************** \ \ Name: CheckIfAlienIsHit (Part 2 of 2) \ Type: Subroutine \ Category: The Theme \ Summary: Check to see whether the alien has been hit, and if so, initiate \ the explosion \ Deep dive: Detecting alien hits \ \ ****************************************************************************** LDA #0 \ Set hitTimer = 0 so the default result is a miss STA hitTimer LDY objectId \ Set Y to the object ID of the alien JSR GetAlienWeakSpot \ Calculate the coordinate of the alien's weak spot as \ follows: \ \ (I+2 W+2) = xObject + (5 Q) \ \ (I+1 W+1) = yObject \ \ (I W) = zObject + (5 Q) \ \ So for the alien object, the weak spot is at a point \ coordinate of ((5 Q), 0, (5 Q)) within the object LDX #228 \ Set VV to iterate through the following in the outer STX VV \ loop below (i.e. from ahit6 to the end): \ \ * 228 = the trailing end of the left bullet trail \ \ * 230 = the trailing end of the right bullet trail .ahit6 LDX VV \ Set X to denote the trailing end of the bullet trail \ that we are analysing (228 or 230) LDA #31 \ Set WW to act as a loop counter for when we check STA WW \ along the length of the bullet trail for a hit, moving \ 1/32 of the way for each of the 32 iterations LDY #0 \ Set Y to loop through the three axes .ahit7 STY Q \ Store the axis counter in Q so we can retrieve it \ below JSR GetTrailVectorStep \ Set (A V R) to 1/32 of the vector for the specified \ bullet trail LDY Q \ Restore the axis counter into Y STA xTemp2Hi,Y \ Set (xTemp2Hi xTemp2Lo xTemp1Lo) = (A V R) LDA V STA xTemp2Lo,Y LDA R STA xTemp1Lo,Y INY \ Increment the loop counter to move on to the next axis CPY #3 \ Loop back until we have calculated the bullet trail BNE ahit7 \ vector LDX #LO(xTemp2Lo) \ Set X so the call to CopyWorkToPoint copies the \ coordinates from (xTemp2, yTemp2, zTemp2) LDY #0 \ Set Y so the call to CopyWorkToPoint copies the \ coordinates to point 0 JSR CopyWorkToPoint \ Copy the coordinates from (xTemp2, yTemp2, zTemp2) \ to point 0 \ Point 0 now contains the vector of the bullet trail \ for the bullet specified in VV, with the lowest byte \ dropped, so each axis contains (A V) from the above LDY VV \ Set Y to the bullet that we are analysing (228 or 230) JSR CheckAlienWeakSpot \ Check whether this end of the bullet trail has hit the \ alien's weak spot, and if it has, set up the explosion \ and return from the subroutine LDY VV \ Set Y to the bullet that we are analysing (228 or 230) LDX #216 \ Set X to iterate through the coordinate offsets for \ object 0 .ahit8 TYA \ Point Y to the next axis (xObject, yObject, zObject) CLC \ ADC #nextAxis \ The first iteration of this loop has Y = 228 or 230, TAY \ so this bumps Y onto 268 or 260, which gets truncated \ in the 8-bit register to 12 or 14, so this moves Y \ through the xObject, yObject and zObject values for \ object 12 (when the initial value of Y = 228) or \ object 14 (when the initial value of Y = 230) TXA \ Point X to the next axis (xObject, yObject, zObject) CLC \ ADC #nextAxis \ The first iteration of this loop has X = 216, so this TAX \ bumps X onto 256, which gets truncated in the 8-bit \ register to 0, so this moves X through the xObject, \ yObject and zObject values for object 0 LDA xObjectLo,Y \ Copy the coordinate from the bullet object to object 0 STA xObjectLo,X CPX #200 \ Loop back until we have copied all six coordinates BNE ahit8 \ By this point, object 0 is at the same coordinate as \ the bullet we are checking, and we now work our way \ along the trail vector, adding the 1/32 vector that \ we calculated above for each of the 32 iterations of \ the following loop .ahit9 LDY #2 \ Set a counter in Y to iterate through the three axes \ (the comments below are for the x-axis calculation) LDX #nextAxis*2 \ Set the index in X to point to the z-coordinate for \ object 0, which we decrement by nextAxis over each \ iteration to work through the three axes .ahit10 \ Above we set (xTemp2Hi xTemp2Lo xTemp1Lo) to the 1/32 \ vector, but we only copied (xTemp2Hi xTemp2Lo) into \ point 0, and ignored the fractional part, so we add \ that part to the object 0 coordinate first LDA xTemp1Lo,Y \ Set xTemp2Hi = xTemp2Hi + xTemp1Lo CLC \ ADC xTemp2Hi,Y \ to work out when the fractional part cumulatively STA xTemp2Hi,Y \ adds up to an integer (in other words, we store the \ cumulative sum of the fractional part from xTemp1Lo in \ xTemp2Hi) BCC ahit11 \ If the addition didn't overflow, jump to ahit11 to \ skip incrementing the object coordinate \ Otherwise the cumulative sum of the fractional part \ just reached an integer, so we need to add an extra \ integer (1) to the object 0 coordinate INC xObjectLo,X \ Increment the object coordinate for object 0 in BNE ahit11 \ (xObjectHi xObjectLo), starting with the low byte INC xObjectHi,X \ and incrementing the high byte if the low byte \ overflows .ahit11 TXA \ Point X to the next axis (zObject, yObject, xObject) SEC SBC #nextAxis TAX DEY \ Decrement the axis counter BPL ahit10 \ Loop back until we have processed all three axes \ We have now moved object 0 along the trail by adding \ the fractional part of the 1/32 vector xTemp1Lo, so \ now we can add the rest of the vector which we stored \ in point 0 above, to move along the trail by exactly \ 1/32 of the trail vector LDX #0 \ Move object 0 by the vector in point 0, which we set LDY #0 \ to the vector of the bullet trail above JSR AddPointToObject \ We now check whether this point along the bullet trail \ is in the alien's weak spot LDY #216 \ Check whether object 0 has hit the alien's weak JSR CheckAlienWeakSpot \ spot, and if it has, set up the explosion and return \ from the subroutine DEC WW \ Decrement the counter we set in WW so we repeat this \ process 32 times BPL ahit9 \ Loop back until we have stepped all the way along the \ trail vector, from the back end of the trail all the \ way to the bullet at the front, in steps of 1/32, \ checking at each point whether it is in the weak spot \ If we get here then we have now checked the entire \ trail of the left bullet for a hit, so we repeat the \ whole thing for the right bullet trail LDA VV \ Set VV = VV + 2 CLC \ ADC #2 \ so VV now points to the next bullet trail (i.e. 230) STA VV CMP #232 \ If VV = 232 then we have now processed both bullet BEQ ahit12 \ trails, so jump to ahit12 to return from the \ subroutine JMP ahit6 \ Otherwise loop back to ahit6 to process the next \ bullet trail .ahit12 RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: GetAlienWeakSpot \ Type: Subroutine \ Category: The Theme \ Summary: Calculate the coordinates of an alien's weak spot \ Deep dive: Detecting alien hits \ \ ------------------------------------------------------------------------------ \ \ The alien's weak spot is calculated as follows: \ \ * x-coordinate = (xObjectHi xObjectLo) + (5 Q) \ \ * y-coordinate = (yObjectHi yObjectLo) \ \ * z-coordinate = (zObjectHi zObjectLo) + (5 Q) \ \ where Q is the low byte of the amount to add, depending on the feeding state \ of the alien. \ \ This is called STIP in the original source code. \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ Y The object ID of the alien (30 to 33) \ \ Q The low byte of the amount to add \ \ ------------------------------------------------------------------------------ \ \ Returns: \ \ (I+2 W+2) The x-coordinate of the alien's weak spot \ \ (I+1 W+1) The y-coordinate of the alien's weak spot \ \ (I W) The z-coordinate of the alien's weak spot \ \ ****************************************************************************** .GetAlienWeakSpot LDX #2 \ Set a counter in X to iterate through 2, 1, 0, which \ has the following effect: \ \ When X = 2: \ \ * (I+2 W+2) = (xObjectHi xObjectLo) + (5 Q) \ \ When X = 1: \ \ * (I+1 W+1) = (yObjectHi yObjectLo) \ \ When X = 0: \ \ * (I W) = (zObjectHi zObjectLo) + (5 Q) \ \ note that .weak1 LDA xObjectLo,Y \ Set (I+X W+X) = (xObjectHi xObjectLo) + (5 Q) CLC \ ADC Q \ starting with the low bytes STA W,X LDA xObjectHi,Y \ And then the high bytes ADC #5 STA I,X .weak2 TYA \ Point Y to the next axis (xObject, yObject, zObject) CLC ADC #nextAxis TAY DEX \ Decrement the loop counter BPL weak3 \ If we haven't yet done all three calculations, jump \ to weak3 RTS \ Return from the subroutine .weak3 BEQ weak1 \ If X = 0, jump up to weak1 to add (5 Q) LDA xObjectLo,Y \ If we get here then X = 1, so do the calculation STA W,X \ without adding (5 Q) LDA xObjectHi,Y STA I,X JMP weak2 \ Jump back to weak2 to move on to X = 0 \ ****************************************************************************** \ \ Name: CheckAlienWeakSpot \ Type: Subroutine \ Category: The Theme \ Summary: Check whether an object is close enough to an alien's weak spot to \ be hitting it \ Deep dive: Detecting alien hits \ \ ------------------------------------------------------------------------------ \ \ If the object is hitting the alien's weak spot, the routine returns to the \ caller of the caller - in other words, it returns from the original call to \ the CheckIfAlienIsHit routine, in part 6 of the main loop. \ \ This is called HITS in the original source code. \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ Y The bullet trail to be calculated: \ \ * 216 = object 0, which we set to points along the \ bullet trail at 1/32 intervals \ \ * 228 = the trailing end of the left bullet trail \ \ * 230 = the trailing end of the right bullet trail \ \ ****************************************************************************** .CheckAlienWeakSpot LDX #2 \ Set a counter in X to iterate through the three axes .spot1 TYA \ Point Y to the next axis (xObject, yObject, zObject) CLC \ ADC #nextAxis \ The routine is called is with Y = 216, 228 or 230, so TAY \ this bumps X onto 256, 268 or 260, which gets \ truncated in the 8-bit register to 0, 12 or 14, so \ this moves X through the xObject, yObject and zObject \ values for object 0 (when called with X = 216), object \ 12 (when called with X = 228) or object 14 (when \ called with X = 230) \ \ Objects 12 and 14 are the trailing ends of the two \ bullet trails, so the following checks whether the \ trailing end is within the weak spot, rather than the \ bullets themselves LDA xObjectLo,Y \ Set (A T) = (xObjectHi xObjectLo) - (I+X W+X) SEC \ SBC W,X \ starting with the low bytes STA T LDA xObjectHi,Y \ And then the high bytes SBC I,X \ So we now have: \ \ (A T) = xObject - (I W) \ \ where xObject is the coordinate of the bullet, and \ (I W) is the coordinate of the alien's weak spot \ \ In other words, (A T) is the distance between the \ bullet and the alien's weak spot BNE spot2 \ If the high byte in A is non-zero, then the bullet is \ too far from the weak spot to do any damage, so jump \ to spot2 to return from the subroutine LDA T \ If the low byte in T is >= the corresponding value in CMP PP,X \ PP, QQ or RR (for the z, y and x-axes respectively), BCS spot2 \ then the bullet is too far away from the weak spot to \ do any damage, so jump to spot2 to return from the \ subroutine DEX \ Otherwise the bullet is close enough to the weak spot \ to cause damage in this axis, so decrement X to point \ to the next axis BPL spot1 \ Loop back until we have checked all three axes \ If we get here then the bullet is close enough to the \ weak spot in all three axes, so we have a hit LDA objectId \ Store the object ID of the hit alien in hitObjectId STA hitObjectId TSX \ Remove two bytes from the top of the stack, so the INX \ RTS below returns us to the JSR CheckIfAlienIsHit in INX \ part 6 of the main loop TXS LDA #27 \ Set the hitTimer to 27 to start the explosion counter STA hitTimer .spot2 RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: GetTrailVectorStep \ Type: Subroutine \ Category: The Theme \ Summary: Calculate 1/32 of the vector for a bullet trail \ Deep dive: Detecting alien hits \ \ ------------------------------------------------------------------------------ \ \ Calculates one axis of the vector for the 1/32 of the specified bullet trail \ and returns it in (A V R). \ \ This is called ADIF in the original source code. \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ X The bullet trail to be calculated: \ \ * 228 = left bullet trail \ \ * 230 = right bullet trail \ \ ------------------------------------------------------------------------------ \ \ Returns: \ \ X Updated to point to the next axis, so the first call \ will return the x-axis of the vector, the second the \ y-axis, and the third the z-axis \ \ (A V R) One axis of the vector for the specified bullet trail, \ as a signed 24-bit number and divided by 32 \ \ ****************************************************************************** .GetTrailVectorStep LDA #0 \ Set P = 0, to feed bits into the top bit of (A V R) in STA P \ the final stage below STA R \ Set R = 0, for use in constructing (A V R) TXA \ Point X to the next axis (xObject, yObject, zObject) CLC \ ADC #nextAxis \ The first time that the routine is called is with TAX \ X = 228 or 230, so this bumps X onto 268 or 260, which \ gets truncated in the 8-bit register to 12 or 14, so \ this moves X through the xObject, yObject and zObject \ values for object 12 (when first called with X = 228) \ or object 14 (when first called with X = 230) \ We now subtract the following object coordinates: \ \ * If first called with X = 228, we calculate object \ 13 - object 12 (i.e. the left bullet minus the \ back end of the left bullet trail) \ \ * If first called with X = 230, we calculate object \ 15 - object 13 (i.e. the right bullet minus the \ back end of the right bullet trail) \ \ In each case we end up with the vector of the relevant \ bullet trail for axis Y in (A V) LDA xObjectLo+1,X \ Set (A V) = xObject+1 - xObject SEC \ SBC xObjectLo,X \ starting with the low bytes STA V LDA xObjectHi+1,X \ And then the high bytes SBC xObjectHi,X BPL bulv1 \ If (A V) is negative, decrement P to &FF, so it is DEC P \ full of bits of the correct polarity to shift into bit \ 7 of A in (A V R) .bulv1 \ We now have the number (A V 0) in (A V R), plus a byte \ P made up of the correct sign bits, so the final stage \ is to divide this by 32 by shifting right LDY #4 \ We want to shift the result right by five places to \ divide the result by 32, so set a shift counter in Y .bulv2 LSR P \ Shift (A V R) right by one place, shifting one of the ROR A \ sign bits from P into bit 7 of A ROR V ROR R DEY \ Decrement the shift counter BPL bulv2 \ Loop back until we have done all five shifts, so we \ now have: \ \ (A V R) = (A V R) / 32 RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: ShowUpsideDownBar \ Type: Subroutine \ Category: Dashboard \ Summary: Show or hide the upside down bar in the artificial horizon \ indicator \ \ ------------------------------------------------------------------------------ \ \ This routine shows the upside down bar in the artificial horizon if bit 6 and \ bit 7 of zRotationHi are different. \ \ This is how it works. The value of zRotationHi contains the following: \ \ * 0 to 63 (bit 6 clear, bit 7 clear) \ * 64 to 127 (bit 6 set, bit 7 clear) \ * 128 to 191 (bit 6 clear, bit 7 set) \ * 192 to 255 (bit 6 set, bit 7 set) \ \ The zRotation angle determines how the plane is rotated around the z-axis. The \ z-axis points into the screen, so rotating the plane around this axis is the \ same as rolling the plane. A zRotation of 0 is a horizontal plane, and as the \ angle increases, the plane rolls to the right. If we consider the plane doing \ a full 360-degree roll, then for the first quarter the plane is still upright, \ for the second and third quarters it is upside down, and then for the final \ quarter it is upright again. \ \ You will notice that bits 6 and 7 differ in the second and third quarters in \ the list above, so if bits 6 and 7 of zRotationHi are different, then the \ plane is upside down, and we should show a bar at the bottom of the artificial \ horizon indicator. \ \ ****************************************************************************** .ShowUpsideDownBar LDA zRotationHi \ If bit 7 of zRotationHi is clear, skip the following BPL upsi1 \ instruction EOR #%11000000 \ Flip bits 6 and 7 of A, making bit 7 clear and bit 6 \ flipped .upsi1 \ When we get here, we know bit 7 of A is clear AND #%11000000 \ Extract bits 6 and 7 of A BEQ upsi2 \ If bit 6 of A is clear (we already know bit 7 is \ clear), then the plane is the correct way up, so skip \ the following instruction \ We get here if one of the following is true: \ \ * Bit 7 of zRotationHi is clear and bit 6 is set \ * Bit 7 of zRotationHi is set and bit 6 is clear \ \ either of which means the plane is upside down LDA #%00001110 \ Bit 6 is set, so set A to a three-pixel horizontal \ line to show in the indicator .upsi2 STA row25_char13_1 \ Set the line at the bottom of the artificial horizon \ indicator to the pixel pattern in A (0 if the plane is \ the right way up, %00001110 if it is upside down) RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: ProcessRunwayLine (Part 1 of 5) \ Type: Subroutine \ Category: Visibility \ Summary: Calculate coordinates and visibility for a runway line \ Deep dive: Visibility checks \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ lineId The line ID to process (1 to 11): \ \ * 1 to 4 are the runway outline \ \ * 5 to 11 are the dashes down the middle of the runway \ \ ------------------------------------------------------------------------------ \ \ Returns: \ \ showLine Whether this line is visible: \ \ * 0 = line is visible \ \ * Non-zero = line is not visible \ \ ****************************************************************************** .ProcessRunwayLine LDA objectStatus+1 \ Fetch the status byte for object 1, the runway object BEQ prun3 \ If object 1's status byte is zero, then we have not \ yet calculated the runway object's coordinates and \ visibility, so jump to prun3 to do this BMI prun1 \ If bit 7 of object 1's status byte is set, then we \ have already decided that the runway object is \ visible, so jump to prun1 to set the line's visibility \ accordingly JMP prun9 \ If we get here then we have already decided that the \ runway object is hidden, so jump to prun9 to set the \ line's visibility as hidden .prun1 LDA lineId \ If lineId < 5, then the line ID is in the range 1 to CMP #5 \ 4, so it's part of the runway outline, so jump to BCC prun2 \ prun2 to return from the subroutine as we always show \ the outline when the runway is visible LDA showRunwayDashes \ The line we are checking is one of the dashes down the STA showLine \ middle of the runway, so set the line's visibility to \ the value of showRunwayDashes, which contains the \ visibility of the runway dashes .prun2 RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: ProcessRunwayLine (Part 2 of 5) \ Type: Subroutine \ Category: Visibility \ Summary: Calculate coordinates and visibility for the runway outline \ \ ****************************************************************************** .prun3 LDA #0 \ Set the matrix number so the call to SetPointCoords STA matrixNumber \ and SetObjPointCoords use 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 LDA #1 \ Set objectId = 1 to denote the runway object STA objectId JSR SetObjectCoords \ Calculate the runway object's coordinates and \ visibility, updating the object's status byte with the \ results and setting point 217 to the object's anchor \ point BPL prun9 \ If bit 7 of the runway object's updated status byte is \ clear, then the runway object is not visible, so jump \ to prun9 to set the line's visibility accordingly \ The runway object is visible, so now we work out the \ coordinates of the runway outline, which is a \ rectangle whose corners are points 1 to 4 LDA #0 \ We start by zeroing the (xTemp1 yTemp1 zTemp1) vector, \ which is stored in six bytes to give us three 16-bit \ coordinate values (from xTemp1Lo to zTemp1Hi), so \ first we set A = 0 to use as our zero LDX #5 \ Set a counter in X to count the six bytes .prun4 STA xTemp1Lo,X \ Zero the X-th byte of the six-byte coordinate block \ between xTemp1Lo and zTemp1Hi DEX \ Decrement the loop counter BPL prun4 \ Loop back until we have zeroed all six bytes \ We now calculate the coordinates for points 1 to 4, \ starting with point 1 LDY #217 \ Set Y so the call to AddTempToPoint adds point 217 to \ the xTemp1 vector STY objectAnchorPoint \ The anchor point of the runway is point 217, so set \ objectAnchorPoint to this point for the call to \ SetObjPointCoords LDX #1 \ Set X so the call to AddTempToPoint stores the result \ in point ID 1 JSR AddTempToPoint \ Add point 217 to the (xTemp1 yTemp1 zTemp1) point and \ store the result in (xPoint, yPoint, zPoint) for \ point 1 \ \ This simply sets point ID 1 to the object's anchor \ point, as we just zeroed (xTemp1 yTemp1 zTemp1), so \ point1 of the runway outline is now calculated LDA #2 \ Calculate the coordinates for object point 2 with STA GG \ anchor point 217, so point 2 is now calculated JSR SetObjPointCoords \ \ This also sets (xTemp1 yTemp1 zTemp1) to the vector \ from the anchor point to point 2, which we now copy \ into xTemp2 by copying the following values: \ \ xTemp1Lo, yTemp1Lo, zTemp1Lo \ xTemp1Hi, yTemp1Hi, zTemp1Hi \ \ into the following locations: \ \ xTemp2Hi, yTemp2Hi, zTemp2Hi \ xTemp2Top, yTemp2Top, zTemp2Top \ \ so (xTemp2Top xTemp2Hi) etc. contain the vector from \ the anchor point to the point 2 LDX #5 \ Set a counter for six bytes .prun5 LDA xTemp1Lo,X \ Copy the X-th byte of xTemp1Lo to the X-th byte of STA xTemp2Hi,X \ xTemp2Hi DEX \ Decrement the loop counter BPL prun5 \ Loop back until we have copied all six bytes \ By now we have coordinates for points 1 and 2, plus \ the vector from the anchor point to point 2 (which \ is the same as the vector from point 1 to point 2, \ as point 1 is the runway's anchor point) LDA #4 \ Calculate the coordinates for object point 4 with STA GG \ anchor point 217, so point 4 is now calculated JSR SetObjPointCoords \ \ This also sets (xTemp1 yTemp1 zTemp1) to the vector \ from the anchor point (point 1) to point 4 \ By now we have coordinates for points 1, 2 and 4, and \ we could just call SetObjPointCoords for point 3, but \ we can save a bit of time as the runway outline is a \ rectangle, so we already have all the data we need to \ calculate the coordinates for point 3 \ \ The runway outline looks like this: \ \ 2 3 \ +-------+ \ | : | \ | : | \ | : | \ | : | \ | : | \ +-------+ \ 1 4 \ \ We know the vector from point 1 to point 4 - it's in \ the (xTemp1 yTemp1 zTemp1) vector - and we know the \ coordinates of point 2, so we can calculate the \ coordinates of point 3 by simply adding the vector to \ the coordinates of point 2, which is what we do next LDY #2 \ Set point 3's coordinates to point 2's coordinates + LDX #3 \ (xTemp1 yTemp1 zTemp1), so point 3 is now calculated JSR AddTempToPoint LDA showLine \ If showLine is zero, then all four coordinates fit BEQ prun6 \ within the boundaries of the world, so jump to prun6 \ to keep going LDA #%01000000 \ At least one of the calculations above overflowed, so STA objectStatus+1 \ at least one coordinate is outside the boundaries of \ the world, so set bit 6 of the status byte for the \ runway object to indicate that we have now calculated \ the runway object's coordinates and visibility, and \ clear bit 7 to indicate that the runway is not visible RTS \ Return from the subroutine .prun6 LDX #4 \ Set the status byte for points 1 to 4 to indicate JSR SetPointVisibility \ that their coordinates and visibility have been \ calculated \ ****************************************************************************** \ \ Name: ProcessRunwayLine (Part 3 of 5) \ Type: Subroutine \ Category: Visibility \ Summary: Calculate visibility for the runway dashes \ \ ****************************************************************************** LDY #1 \ Set Y = 1 so we check point ID 1 in the call to \ CheckLineDistance (point ID 1 is the anchor point of \ the runway) LDX #10 \ Set X = 10 so we check the distance against that for \ line ID 10, which is one of the dashes in the middle \ of the runway .prun7 JSR CheckLineDistance \ Check whether point Y on line X is within the visible STA showLine \ distance for the line and store the result in showLine BNE prun8 \ If the point is too far away to be visible, then all \ the dashes are too far away to be seen, so jump to \ prun8 to return the relevant result CPY #3 \ If Y = 3, then we have checked both point 1 and 3 and BEQ prun12 \ both are close enough to be visible, so jump to prun12 \ to finish off the processing LDY #3 \ Otherwise set Y = 3 and jump back prun7 to check point BNE prun7 \ 3 as well (point 3 is the corner of the runway outline \ opposite the anchor point) .prun8 LDA #%10000000 \ Set showRunwayDashes to indicate that the dashes down STA showRunwayDashes \ the middle of the runway are not visible LDA lineId \ If lineId < 5, then the line ID is in the range 1 to CMP #5 \ 4, so it's part of the runway outline, so jump to BCC prun10 \ prun10 to return the result that the line is visible, \ as we always show the outline when the runway is \ visible, even when the runway dashes are hidden .prun9 LDA #%10000000 \ Set A to indicate the line is not visible, and jump to BNE prun11 \ prun11 to return this as the value of showLine (this \ BNE is effectively a JMP as A is never zero) .prun10 LDA #0 \ Set A to indicate the line is visible, so we can \ return this as the value of showLine .prun11 STA showLine \ Set the line's visibility to the value in A, to return \ as our result RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: ProcessRunwayLine (Part 4 of 5) \ Type: Subroutine \ Category: Visibility \ Summary: Construct the dashes down the middle of the runway \ \ ****************************************************************************** .prun12 \ By the time we get here, (xTemp1 yTemp1 zTemp1) \ contains the vector from the anchor point (point 1) to \ point 4, which is the vector from one side of the \ runway to the other \ \ We now want to halve the (xTemp1 yTemp1 zTemp1) \ vector, to give the vector from one side of the runway \ to the line of dashes down the middle \ \ As a reminder, the runway outline looks like this: \ \ 2 3 \ +-------+ \ | : | \ | : | \ | : | \ | : | \ | : | \ +-------+ \ 1 4 \ \ so we now calculate (xTemp1 yTemp1 zTemp1) to be the \ vector from the left edge of the runway to the dashes \ in the middle LDX #2 \ Set a counter in X to work through the three axes of \ the (xTemp1 yTemp1 zTemp1) vector (the comments below \ cover the iteration for the x-axis) .prun13 CLC \ Clear the C flag, to use when (xTemp1Hi xTemp1Lo) is \ positive LDA xTemp1Hi,X \ If xTemp1Hi is positive, skip the next instruction BPL prun14 SEC \ Set the C flag, to use when (xTemp1Hi xTemp1Lo) is \ negative .prun14 ROR A \ Shift (xTemp1Hi xTemp1Lo) to the right by one place, STA xTemp1Hi,X \ inserting the C flag into the top bit of xTemp1Hi, LDA xTemp1Lo,X \ which ensures that we retain the same sign ROR A STA xTemp1Lo,X DEX \ Decrement the loop counter to move to the next axis BPL prun13 \ Loop back until we have halved xTemp1, yTemp1 and \ zTemp1 \ (xTemp1 yTemp1 zTemp1) now contains the vector from \ one side of the runway to the line of dashes down the \ middle LDY #2 \ Set point 21's coordinates to point 2's coordinates + LDX #21 \ (xTemp1 yTemp1 zTemp1) JSR AddTempToPoint LDY #1 \ Set point 5's coordinates to point 1's coordinates + LDX #5 \ (xTemp1 yTemp1 zTemp1) JSR AddTempToPoint \ So we've now calculated points 5 and 21 as follows: \ \ 2 21 3 \ +-------+ \ | : | \ | : | \ | : | \ | : | \ | : | \ +-------+ \ 1 5 4 \ \ Next, we make a backup of the (xTemp1 yTemp1 zTemp1) \ vector in (xDashesVector yDashesVector zDashesVector) LDX #5 \ Set a counter for six bytes .prun15 LDA xTemp1Lo,X \ Copy the X-th byte of xTemp1Lo to the X-th byte of STA xDashesVectorLo,X \ xDashesVectorLo DEX \ Decrement the loop counter BPL prun15 \ Loop back until we have copied all six bytes \ So (xDashesVector yDashesVector zDashesVector) now \ contains the vector from one side of the runway to the \ line of dashes down the middle \ We now zero variables T, U, V, W, G and H LDX #5 \ Set a counter for six bytes LDA #0 \ Set A = 0 so we can zero the variables .prun16 STA T,X \ Zero the X-th byte from T DEX \ Decrement the loop counter BPL prun16 \ Loop back until we have zeroed all six bytes \ In part 2 above, we set (xTemp2Top xTemp2Hi) etc. to \ the vector from the anchor point (i.e. point 1) to \ point 2 LDX #2 \ Set a counter in X to work through the three axes (the \ comments below cover the iteration for the x-axis) \ For each axis in turn, we .prun17 LDA #0 \ Set R = 0 STA R LDA xTemp2Top,X \ Set A = xTemp2Top BPL prun18 \ If xTemp2Top is positive, skip the following \ instruction DEC R \ Decrement R to %11111111 \ So by this point, R contains the correct bits to \ rotate into bit 7 of A to retain the sign of A .prun18 \ Set (xTemp1Hi A T) = (xTemp2Top xTemp2Hi 0) / 2 \ \ keeping the sign intact by feeding in bits from R LSR R \ We start with the top byte ROR A STA xTemp1Hi,X LDA xTemp2Hi,X \ Then the high byte ROR A ROR T,X \ And then the low byte LDY #2 \ We now shift right by three places, so set a counter \ in Y .prun19 LSR R \ Set (xTemp1Hi A T) = (xTemp1Hi A T) / 2 ROR xTemp1Hi,X \ ROR A \ keeping the sign intact by feeding in bits from R ROR T,X DEY \ Decrement the loop counter BPL prun19 \ Loop back until we have shifted right by three places \ In all, the above does the following: \ \ (xTemp1Hi A T) = (xTemp2Top xTemp2Hi 0) / 16 \ \ while retaining the sign STA xTemp1Lo,X \ Set (xTemp1Hi xTemp1Lo T) = (xTemp1Hi A T) \ = (xTemp2Top xTemp2Hi 0) / 16 DEX \ Decrement the loop counter to move to the next axis BPL prun17 \ Loop back until we have processed all three axes \ So we have now calculated the vector from point 1 to \ point 2, divided by 16, with the result stored in the \ xTemp1 vector and the variables T, U and V used for \ the lowest bytes, like this: \ \ x-coordinate = (xTemp1Hi xTemp1Lo T) \ y-coordinate = (yTemp1Hi yTemp1Lo U) \ z-coordinate = (zTemp1Hi zTemp1Lo V) \ \ In other words, the xTemp1/T/U/V vector contains 1/16 \ of the full vector between points 5 and 21 LDX #LO(xTemp2Lo) \ Set X so the call to CopyPointToWork copies the \ coordinates to (xTemp2, yTemp2, zTemp2) LDY #5 \ Set Y so the call to CopyPointToWork copies the \ coordinates from point 5 JSR CopyPointToWork \ Copy the coordinates from point 5 to \ (xTemp2, yTemp2, zTemp2) \ So (xTemp2, yTemp2, zTemp2) now contains the \ coordinates of point 5 \ We now calculate the start and end points for the \ dashes in the middle of the runway by starting at \ point 5, and moving up the middle line in steps of \ 1/16 of the distance between points 5 and 21 .prun20 LDX #2 \ Set a counter in X to work through the three axes (the \ comments below cover the iteration for the x-axis) .prun21 CLC \ Set (xTemp2Hi xTemp2Lo W) += (xTemp1Hi xTemp1Lo T) LDA W,X \ ADC T,X \ starting with the lowest bytes STA W,X LDA xTemp2Lo,X \ Then the middle bytes ADC xTemp1Lo,X STA xTemp2Lo,X LDA xTemp2Hi,X \ And then the highest bytes ADC xTemp1Hi,X STA xTemp2Hi,X DEX \ Decrement the loop counter to move to the next axis BPL prun21 \ Loop back until we have added all three axes \ On the first iteration through this code, the full \ (xTemp2Hi xTemp2Lo W) vector contains a point 1/16 \ of the way from point 5 to point 21 LDX #LO(xTemp2Lo) \ Set X so the call to CopyWorkToPoint copies the \ coordinates from (xTemp2, yTemp2, zTemp2) INY \ Increment Y so the call to CopyPointToWork copies the \ coordinates from the next point along the dash line \ (which will be point 6 on the first iteration through \ this code, then point 7, and so on) JSR CopyWorkToPoint \ Copy the coordinates from (xTemp2, yTemp2, zTemp2) \ to point Y, the next point along the dash line CPY #19 \ Loop back to prun20 to move another 1/16 along the BNE prun20 \ dash line, until we have done point 19, meaning we \ have set the coordinates for points 6 through 19 \ (i.e. we have added 14 points, two for each of the \ seven dashes in the middle of the runway) LDX #19 \ Set the status byte for points 1 to 19 to indicate JSR SetPointVisibility \ that their coordinates and visibility have been \ calculated \ ****************************************************************************** \ \ Name: ProcessRunwayLine (Part 5 of 5) \ Type: Subroutine \ Category: Visibility \ Summary: Clip any portion of the runway outline that's behind us \ \ ****************************************************************************** \ The final step is to check whether the runway appears \ both in front of us and behind us (which will happen \ when we are sitting on the runway, for example, or \ during the final approach when trying to land) \ \ If it is, then we want to clip the part of the runway \ outline that's behind us, as close to the screen as \ possible (though we still want the runway to go behind \ us a bit, so clipping to the nearest coordinate behind \ the screen is the best approach) \ \ We start by restoring the vector that we stored in \ (xDashesVector yDashesVector zDashesVector) back to \ (xTemp1 yTemp1 zTemp1), which is the vector from one \ side of the runway to the line of dashes down the \ middle LDX #5 \ Set a counter for six bytes .prun22 LDA xDashesVectorLo,X \ Copy the X-th byte of xDashesVectorLo to the X-th byte STA xTemp1Lo,X \ of xTemp1Lo DEX \ Decrement the loop counter BPL prun22 \ Loop back until we have copied all six bytes \ So (xTemp1 yTemp1 zTemp1) is once again the vector \ from one side of the runway to the line of dashes down \ the middle \ We now work our way along the dash line, checking the \ coordinates of the points we just added to see if the \ sign of the z-coordinate changes between any of the \ points (which will indicate that the runway is both in \ front of us and behind us) LDA zPointHi+6 \ Set P to the high byte of the z-coordinate for point 6 STA P \ (which contains the sign of the coordinate) LDY #6 \ We now loop through the points we just calculated for \ the dashes, so we set a counter in Y to loop from 6 to \ 19 .prun23 LDA zPointHi,Y \ Set A = z-coordinate for point Y EOR P EOR P BMI prun24 \ If A has a set bit 7, then the z-coordinate for point \ Y has a different sign to the z-coordinate for point \ 6, so jump to prun24 as the runway is both behind us \ and in front of us INY \ Increment Y to point to the next point in the dash \ line CPY #20 \ Loop back until we have worked our way through all the BCC prun23 \ points that we just calculated BCS prun28 \ If we get here then all the points have the same sign \ z-coordinate as point 6, so jump to prun28 to return \ from the subroutine (this BCS is effectively a JMP as \ we just passed through the BCC above) .prun24 \ If we get here, then point Y has a different sign in \ its z-coordinate to point 6, so the runway is both \ behind us and in front of us \ \ We now want to find out which part is in front of us \ and which is behind \ \ We do this by working out which part of the dash line \ is in front of us and which is behind, using the \ following: \ \ 2 21 3 \ +-------+ \ | : | <- Dashes end at point 19 \ | : | \ | : | \ | : | \ | : | <- Dashes start at point 6 \ +-------+ \ 1 5 4 \ \ If the start of the dash line is behind us (i.e. the \ point 6 end), then that means we need to clip points 1 \ and 4 as close as possible to the screen, while if the \ end of the dash line is behind us (i.e. the point 19 \ end), then we need to clip points 2 and 3 as close as \ possible to the screen LDA P \ If point 6 has a positive z-coordinate, jump to prun25 BPL prun25 \ If we get here then point 6 has a negative \ z-coordinate, so point Y must have a positive \ z-coordinate, and the point before Y must have a \ negative z-coordinate \ \ In other words, the dashes start behind us, and pass \ in front of us at point Y, which is in front of us, so \ the point before Y is the last point behind us \ \ To clip the runway, we therefore need to move points 1 \ and 4 DEY \ Decrement Y to point to the last point in the sequence \ we added that has a negative z-coordinate LDA #1 \ Set Q so the second call to AddTempToPoint moves point STA Q \ 1 LDX #4 \ Set X so the first call to AddTempToPoint moves point \ 4 BNE prun26 \ Jump to prun26 to call AddTempToPoint (this BNE is \ effectively a JMP as X is never zero) .prun25 \ If we get here then point 6 has a positive \ z-coordinate, so point Y must have a negative \ z-coordinate, and the point before Y must have a \ positive z-coordinate \ \ In other words, the dashes start in front of us, and \ go behind us at point Y, which is the first point \ behind us \ \ To clip the runway, we therefore need to move points 2 \ and 3 LDA #2 \ Set Q so the second call to AddTempToPoint moves point STA Q \ 2 LDX #3 \ Set X so the first call to AddTempToPoint moves point \ 3 .prun26 \ By the time we get here, Y is the point with the \ negative z-coordinate that's nearest to the screen \ (i.e. nearest to a z-coordinate of 0) \ \ We now work out the new corner coordinates for the \ end of the runway that's behind us, by taking point Y \ and: \ \ * Adding (xTemp1 yTemp1 zTemp1) to point Y to get \ one corner (i.e. corner 3 or 4) \ \ * Subtracting (xTemp1 yTemp1 zTemp1) from point Y to \ get the other corner (i.e. corner 1 or 2) \ \ We start with the addition JSR AddTempToPoint \ Add point Y to the (xTemp1 yTemp1 zTemp1) vector and \ store the result in (xPoint, yPoint, zPoint) for \ point X \ We now negate the (xTemp1 yTemp1 zTemp1) vector so we \ can do the subtraction LDX #2 \ Set a counter in X to work through the three axes of \ the (xTemp1 yTemp1 zTemp1) vector (the comments below \ cover the iteration for the x-axis) .prun27 LDA #0 \ Negate (xTemp1Hi xTemp1Lo), starting with the low SEC \ bytes SBC xTemp1Lo,X STA xTemp1Lo,X LDA #0 \ And then the high bytes SBC xTemp1Hi,X STA xTemp1Hi,X DEX \ Decrement the loop counter to move to the next axis BPL prun27 \ Loop back until we have negated xTemp1, yTemp1 and \ zTemp1 \ The xTemp1 yTemp1 zTemp1) vector is now negated, so we \ can add it with AddTempToPoint to do the subtraction \ we want LDX Q \ Set X so the call to AddTempToPoint stores the result \ in point Q JSR AddTempToPoint \ Add point Y to the (xTemp1 yTemp1 zTemp1) vector and \ store the result in (xPoint, yPoint, zPoint) for \ point Q .prun28 RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: SetPointVisibility \ Type: Subroutine \ Category: 3D geometry \ Summary: Set the status byte for multiple points to indicate that their \ coordinates and visibility have been calculated \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ X The ID of the point to update \ \ ****************************************************************************** .SetPointVisibility LDA #%10000000 \ Set bit 7 of point X's status byte, to indicate that ORA pointStatus,X \ the point's coordinates and visibility have been STA pointStatus,X \ calculated DEX \ Decrement X to point to the previous point BNE SetPointVisibility \ Loop back until we have set the status byte for points \ X down to 1, i.e. points 1 to X RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: DrawHalfHorizon \ Type: Subroutine \ Category: Drawing lines \ Summary: Draw half of the horizon line \ \ ------------------------------------------------------------------------------ \ \ The horizon line is line ID 0, which is the line from point 31 to point 30. \ This line is only half the actual horizon, so this routine calculates the \ other half of the horizon into point 30, and draws the line from point 30 to \ point 32. \ \ Point 30 is therefore the mid-point of the two-segment horizon line, which \ goes point 31 to point 30 to point 32. \ \ The calculation looks like this: \ \ + (x_32, y_32) \ .´ | \ .´ | h \ .´ | \ (x_30, y_30) .´_______| \ .´| w \ .´ | \ .´ | h \ .´ | \ (x_31, y_31) ´--------+ \ w \ \ We already know the coordinates of points 30 and 31, and we want to calculate \ point 32. It's easy to calculate w and h in the bottom triangle: \ \ w = x_30 - x_31 \ h = y_30 - y_31 \ \ so to extend the line to point 32, we simply add w and h to the coordinates \ for point 30, like this: \ \ x_32 = x_30 + w \ = x_30 + x_30 - x_31 \ = (x_30 * 2) - x_31 \ \ y_32 = y_30 + h \ = y_30 + y_30 - y_31 \ = (y_30 * 2) - y_31 \ \ This routine draws the horizon from point 30 to point 32, while the other half \ from point 31 to 30 is drawn along with all the other lines in DrawCanopyView. \ \ ****************************************************************************** .DrawHalfHorizon LDX #30 \ Set X = 30, to use as the point ID for point 30, which \ is the start of the horizon line LDY #32 \ Set Y = 32, to use as the point ID for point 32 LDA xPointHi,X \ Set (T A) = x_30 STA T LDA xPointLo,X ASL A \ Set (T A) = (T A) * 2 ROL T \ = x_30 * 2 SEC \ Set x_32 = (T A) - x_31 SBC xPointLo+31 \ = (x_30 * 2) - x_31 STA xPointLo,Y LDA T SBC xPointHi+31 STA xPointHi,Y LDA yPointHi,X \ Set (T A) = y_30 STA T LDA yPointLo,X ASL A \ Set (T A) = (T A) * 2 ROL T \ = y_30 * 2 SEC \ Set y_32 = (T A) - y_31 SBC yPointLo+31 \ = (y_30 * 2) - y_31 STA yPointLo,Y LDA T SBC yPointHi+31 STA yPointHi,Y \ So now we have: \ \ x_32 = (x_30 * 2) - x_31 \ y_32 = (y_30 * 2) - y_31 \ \ which is what we want STX L \ Set L to point 30, so we draw the line from this start \ point STY M \ Set L to point 32, so we draw the line to this end \ point JSR DrawClippedHorizon \ Draw the line from point 30 to 32, which is the first \ half of the horizon line RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: FillUpFuelTank \ Type: Subroutine \ Category: Flight model \ Summary: Fill up the fuel tank by 1/65th of a full tank every four \ iterations of the main loop \ Deep dive: Scheduling tasks in the main loop \ \ ****************************************************************************** .FillUpFuelTank LDA mainLoopCounter \ If the mainLoopCounter is a multiple of 4, jump to AND #3 \ fuel1 to add some fuel to the tank (so we do this BEQ fuel1 \ every four iterations of the main loop) RTS \ Return from the subroutine .fuel1 TAX \ We got here because A = 0, so this sets X = 0 LDA fuelLevel \ If fuelLevel >= 65, then the tank is already full, so CMP #65 \ jump to fuel2 to skip filling it up any more BCS fuel2 INC fuelLevel \ fuelLevel < 65, so increment the fuel level by 1, to \ fill up the fuel tank by 1/65th of a full tank JMP DrawFuelPixel \ Draw an extra pixel at the top of the fuel level, so \ the fuel gauge goes up by one pixel, returning from \ the subroutine using a tail call .fuel2 \ If we get here then the fuel tank is full STX landingStatus \ Set landingStatus = 0 to disable all the landing tasks \ in the main loop, including filling up with fuel RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: UpdateFuelGauge \ Type: Subroutine \ Category: Dashboard \ Summary: Update the fuel gauge every 16 iterations of the main loop \ Deep dive: Scheduling tasks in the main loop \ \ ****************************************************************************** .UpdateFuelGauge LDA mainLoopCounter \ If the mainLoopCounter is a multiple of 16, jump to AND #15 \ upfu1 to update the fuel gauge BEQ upfu1 RTS \ Return from the subroutine .upfu1 LDX #128 \ Set X = 128, so we erase a pixel from the top of the \ fuel gauge in DrawFuelPixel below, if there is one LDA fuelLevel \ Set A = fuelLevel \ Fall through into DrawFuelPixel to erase a pixel from \ the fuel gauge at the top of the gauge if there is \ one, which will set the amount shown on the fuel gauge \ to fuelLevel \ ****************************************************************************** \ \ Name: DrawFuelPixel \ Type: Subroutine \ Category: Dashboard \ Summary: Draw or erase a pixel on the fuel gauge on the dashboard \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ A The fuel gauge pixel to draw or erase (0 to 65) \ \ X Drawing mode: \ \ * Bit 7 clear = draw (using OR logic) \ \ * Bit 7 set = erase (using EOR logic) \ \ ****************************************************************************** .DrawFuelPixel STX N \ Store the drawing mode in N CLC \ Set J = A + 184 ADC #184 \ STA J \ so the line's start y-coordinate is between: \ \ * 184, or -72, for A = 0 (empty tank) \ * 249, or -7, for A = 65 (full tank) \ \ As y-coordinates that we send to DrawVectorLine are \ relative to the top of the dashboard, this means we \ draw a pixel on the vertical line from 72 pixels below \ the top of the dashboard (i.e. the bottom of the fuel \ gauge at screen y-coordinate 231), up to 7 pixels \ below the top of the dashboard (i.e. the top of the \ fuel gauge at screen y-coordinate 166) LDA #2 \ Set I = 2, the x-coordinate of the fuel gauge line STA I LDA #1 \ Set T = 1, so line is 1 pixel wide STA T STA U \ Set U = 1, so the line is 1 pixel high LDA #0 \ Set V = 0 so the line is drawn in a positive direction STA V \ for both axes JSR DrawVectorLine \ Draw/erase a pixel at (2, A + 184) RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: ProcessVolumeKeys \ Type: Subroutine \ Category: Sound \ Summary: Adjust the volume when the volume keys are pressed \ \ ****************************************************************************** .ProcessVolumeKeys LDX #&DB \ Scan the keyboard to see if "7" is being pressed JSR ScanKeyboard BEQ volk1 \ If "7" is being pressed, jump to volk1 LDX #&EA \ Scan the keyboard to see if "8" is being pressed JSR ScanKeyboard BNE volk4 \ If "8" is not being pressed, jump to volk4 to return \ from the subroutine LDA #255 \ "8" is being pressed, which is the increase volume BNE volk2 \ key, so set A = 255 = -1 and jump to volk2 .volk1 LDA #1 \ If we get here then "7" is being pressed, which is the \ decrease volume key, so set A = 1 .volk2 \ By this point, A contains 1 if we want to decrease the \ volume, or -1 if we want to increase it, which we can \ now add to the SOUND command's amplitude parameter to \ adjust the volume, as -15 is the loudest volume and 0 \ is the quietest CLC \ Add A to byte #3 of sound #1 (low byte of amplitude) ADC soundData+10 BMI volk3 \ If the result is negative, then jump to volk3 to \ update the volume BNE volk4 \ If the result is non-zero, then we have just reduced \ the volume beyond the quietest setting of 0, so jump \ to volk4 to return from the subroutine without \ changing the volume, as it is already at the quietest \ setting .volk3 CMP #241 \ If A < 241, i.e. A < -15, then we have just increased BCC volk4 \ the volume past the loudest setting of -15, so jump \ to volk4 to return from the subroutine without \ changing the volume, as it is already at the loudest \ setting STA soundData+10 \ The new volume is valid, so update byte #3 of sound #1 \ (low byte of amplitude) with the new volume INC soundData+60 \ Increment byte #5 of sound #7 (low byte of engine \ pitch) to make the engine pitch jump up a bit, so we \ can hear the engine noise changing while adjusting the \ volume (the MakeEngineSound routine will bring it back \ down to the correct pitch) .volk4 RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: RetractFlapsIfFast \ Type: Subroutine \ Category: Flight model \ Summary: Retract the flaps if we are going faster than 150 mph \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ A The high byte of the current airspeed \ \ ****************************************************************************** .RetractFlapsIfFast CMP #14 \ First we check the high byte of the current airspeed BCC flap1 \ in A to see if it is less than 14, at which point the \ airspeed is: \ \ (14 0) * 100 / 2368 = 3584 * 100 / 2368 = 151 \ \ So if the airspeed is less than 151mph, we jump to \ flap1 to return from the subroutine without changing \ the flaps LDA flapsStatus \ If the flaps are already retracted, jump to flap1 to BEQ flap1 \ return from the subroutine without changing anything LDA #0 \ The speed is at least 151 mph and the flaps are on, STA flapsStatus \ so we retract them by setting flapsStatus to 0 JSR IndicatorF \ Update the flaps indicator .flap1 RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: xObjectPoint \ Type: Variable \ Category: 3D geometry \ Summary: Scaled x-coordinates of the points that make up objects, relative \ to the object's anchor point \ Deep dive: 3D objects \ Rotating and translating points in 3D space \ \ ------------------------------------------------------------------------------ \ \ For a point within an object, (xObjectPoint, yObjectPoint, zObjectPoint) are \ the coordinates of this point relative to the object's anchor. \ \ This is the same as saying (xObjectPoint, yObjectPoint, zObjectPoint) is the \ vector from the object's anchor to the point. \ \ This table effectively defines the shape of each object. \ \ Each vector is stored as three coordinates, with each coordinate being in the \ range 0 to 15, plus a scale factor, which is stored in bits 4 to 7 of the \ z-coordinate in zObjectPoint. The scale is given as a power of 2, so a scale \ factor of n means we scale the coordinates by 2^n (where n = 0 to 9). \ \ All vectors are positive in all three axes, so the anchor point for an object \ is therefore the closest point to the origin. \ \ ****************************************************************************** .xObjectPoint EQUB 13 \ Point ID 0 is (13, 61, 13) with scale factor 2^0 EQUB 0 \ Point ID 1 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 2 is ( 0, 0, 12) with scale factor 2^9 EQUB 0 \ Point ID 3 is ( 0, 0, 0) with scale factor 2^0 EQUB 8 \ Point ID 4 is ( 8, 0, 0) with scale factor 2^5 EQUB 0 \ Point ID 5 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 6 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 7 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 8 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 9 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 10 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 11 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 12 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 13 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 14 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 15 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 16 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 17 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 18 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 19 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 20 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 21 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 22 is ( 0, 0, 8) with scale factor 2^4 EQUB 0 \ Point ID 23 is ( 0, 0, 8) with scale factor 2^4 EQUB 10 \ Point ID 24 is (10, 0, 0) with scale factor 2^6 EQUB 0 \ Point ID 25 is ( 0, 1, 0) with scale factor 2^8 EQUB 0 \ Point ID 26 is ( 0, 1, 0) with scale factor 2^8 EQUB 0 \ Point ID 27 is ( 0, 1, 0) with scale factor 2^8 EQUB 0 \ Point ID 28 is ( 0, 1, 0) with scale factor 2^8 EQUB 4 \ Point ID 29 is ( 4, 0, 0) with scale factor 2^8 EQUB 0 \ Point ID 30 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 31 is ( 0, 0, 0) with scale factor 2^0 EQUB 4 \ Point ID 32 is ( 4, 0, 0) with scale factor 2^8 EQUB 0 \ Point ID 33 is ( 0, 0, 10) with scale factor 2^5 EQUB 0 \ Point ID 34 is ( 0, 0, 12) with scale factor 2^4 EQUB 0 \ Point ID 35 is ( 0, 0, 0) with scale factor 2^0 EQUB 4 \ Point ID 36 is ( 4, 0, 2) with scale factor 2^8 EQUB 0 \ Point ID 37 is ( 0, 0, 2) with scale factor 2^8 EQUB 12 \ Point ID 38 is (12, 0, 0) with scale factor 2^4 EQUB 5 \ Point ID 39 is ( 5, 0, 10) with scale factor 2^5 EQUB 8 \ Point ID 40 is ( 8, 0, 0) with scale factor 2^4 EQUB 0 \ Point ID 41 is ( 0, 0, 8) with scale factor 2^3 EQUB 0 \ Point ID 42 is ( 0, 10, 0) with scale factor 2^3 EQUB 0 \ Point ID 43 is ( 0, 10, 0) with scale factor 2^3 EQUB 0 \ Point ID 44 is ( 0, 0, 8) with scale factor 2^3 EQUB 0 \ Point ID 45 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 46 is ( 0, 10, 0) with scale factor 2^3 EQUB 0 \ Point ID 47 is ( 0, 10, 0) with scale factor 2^3 EQUB 12 \ Point ID 48 is (12, 0, 0) with scale factor 2^5 EQUB 0 \ Point ID 49 is ( 0, 0, 8) with scale factor 2^3 EQUB 0 \ Point ID 50 is ( 0, 0, 8) with scale factor 2^3 EQUB 12 \ Point ID 51 is (12, 0, 0) with scale factor 2^5 EQUB 11 \ Point ID 52 is (11, 0, 0) with scale factor 2^6 EQUB 0 \ Point ID 53 is ( 0, 10, 0) with scale factor 2^4 EQUB 0 \ Point ID 54 is ( 0, 10, 0) with scale factor 2^4 EQUB 0 \ Point ID 55 is ( 0, 10, 0) with scale factor 2^4 EQUB 0 \ Point ID 56 is ( 0, 10, 0) with scale factor 2^4 EQUB 2 \ Point ID 57 is ( 2, 0, 14) with scale factor 2^8 EQUB 1 \ Point ID 58 is ( 1, 0, 11) with scale factor 2^8 EQUB 3 \ Point ID 59 is ( 3, 0, 9) with scale factor 2^9 EQUB 6 \ Point ID 60 is ( 6, 0, 12) with scale factor 2^8 EQUB 10 \ Point ID 61 is (10, 0, 1) with scale factor 2^8 EQUB 3 \ Point ID 62 is ( 3, 0, 9) with scale factor 2^7 EQUB 9 \ Point ID 63 is ( 9, 0, 15) with scale factor 2^6 EQUB 6 \ Point ID 64 is ( 6, 0, 13) with scale factor 2^8 EQUB 13 \ Point ID 65 is (13, 0, 3) with scale factor 2^4 EQUB 0 \ Point ID 66 is ( 0, 0, 14) with scale factor 2^9 EQUB 1 \ Point ID 67 is ( 1, 0, 0) with scale factor 2^8 EQUB 5 \ Point ID 68 is ( 5, 0, 12) with scale factor 2^9 EQUB 1 \ Point ID 69 is ( 1, 0, 0) with scale factor 2^8 EQUB 0 \ Point ID 70 is ( 0, 0, 0) with scale factor 2^0 EQUB 9 \ Point ID 71 is ( 9, 0, 0) with scale factor 2^5 EQUB 0 \ Point ID 72 is ( 0, 0, 9) with scale factor 2^5 EQUB 8 \ Point ID 73 is ( 8, 0, 6) with scale factor 2^9 EQUB 0 \ Point ID 74 is ( 0, 0, 1) with scale factor 2^8 EQUB 14 \ Point ID 75 is (14, 0, 7) with scale factor 2^9 EQUB 0 \ Point ID 76 is ( 0, 0, 1) with scale factor 2^8 EQUB 13 \ Point ID 77 is (13, 0, 2) with scale factor 2^9 EQUB 5 \ Point ID 78 is ( 5, 0, 13) with scale factor 2^4 EQUB 12 \ Point ID 79 is (12, 0, 0) with scale factor 2^8 EQUB 15 \ Point ID 80 is (15, 0, 15) with scale factor 2^1 EQUB 0 \ Point ID 81 is ( 0, 12, 0) with scale factor 2^2 EQUB 7 \ Point ID 82 is ( 7, 12, 7) with scale factor 2^1 EQUB 6 \ Point ID 83 is ( 6, 7, 11) with scale factor 2^2 EQUB 12 \ Point ID 84 is (12, 8, 5) with scale factor 2^2 EQUB 0 \ Point ID 85 is ( 0, 10, 0) with scale factor 2^0 EQUB 0 \ Point ID 86 is ( 0, 0, 1) with scale factor 2^8 EQUB 8 \ Point ID 87 is ( 8, 0, 0) with scale factor 2^4 EQUB 0 \ Point ID 88 is ( 0, 2, 0) with scale factor 2^8 EQUB 15 \ Point ID 89 is (15, 0, 15) with scale factor 2^1 EQUB 0 \ Point ID 90 is ( 0, 10, 0) with scale factor 2^2 EQUB 4 \ Point ID 91 is ( 4, 12, 4) with scale factor 2^1 EQUB 3 \ Point ID 92 is ( 3, 5, 12) with scale factor 2^2 EQUB 10 \ Point ID 93 is (10, 7, 8) with scale factor 2^2 EQUB 0 \ Point ID 94 is ( 0, 10, 0) with scale factor 2^0 EQUB 0 \ Point ID 95 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 96 is ( 0, 0, 1) with scale factor 2^8 EQUB 10 \ Point ID 97 is (10, 0, 0) with scale factor 2^1 EQUB 0 \ Point ID 98 is ( 0, 0, 1) with scale factor 2^8 EQUB 0 \ Point ID 99 is ( 0, 2, 0) with scale factor 2^8 EQUB 0 \ Point ID 100 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 101 is ( 0, 0, 11) with scale factor 2^4 EQUB 0 \ Point ID 102 is ( 0, 0, 10) with scale factor 2^4 EQUB 11 \ Point ID 103 is (11, 0, 7) with scale factor 2^8 EQUB 11 \ Point ID 104 is (11, 0, 4) with scale factor 2^9 EQUB 13 \ Point ID 105 is (13, 0, 13) with scale factor 2^3 EQUB 14 \ Point ID 106 is (14, 0, 0) with scale factor 2^9 EQUB 4 \ Point ID 107 is ( 4, 0, 10) with scale factor 2^3 EQUB 4 \ Point ID 108 is ( 4, 0, 0) with scale factor 2^8 EQUB 13 \ Point ID 109 is (13, 0, 3) with scale factor 2^8 EQUB 12 \ Point ID 110 is (12, 0, 0) with scale factor 2^8 EQUB 13 \ Point ID 111 is (13, 0, 10) with scale factor 2^9 EQUB 4 \ Point ID 112 is ( 4, 0, 12) with scale factor 2^8 EQUB 14 \ Point ID 113 is (14, 0, 1) with scale factor 2^9 EQUB 0 \ Point ID 114 is ( 0, 0, 12) with scale factor 2^8 EQUB 3 \ Point ID 115 is ( 3, 0, 5) with scale factor 2^8 EQUB 15 \ Point ID 116 is (15, 0, 0) with scale factor 2^8 EQUB 10 \ Point ID 117 is (10, 0, 5) with scale factor 2^8 EQUB 11 \ Point ID 118 is (11, 0, 4) with scale factor 2^8 EQUB 2 \ Point ID 119 is ( 2, 0, 14) with scale factor 2^8 EQUB 14 \ Point ID 120 is (14, 0, 0) with scale factor 2^4 EQUB 0 \ Point ID 121 is ( 0, 0, 9) with scale factor 2^8 EQUB 11 \ Point ID 122 is (11, 0, 7) with scale factor 2^9 EQUB 10 \ Point ID 123 is (10, 0, 3) with scale factor 2^9 EQUB 10 \ Point ID 124 is (10, 0, 3) with scale factor 2^9 EQUB 5 \ Point ID 125 is ( 5, 0, 9) with scale factor 2^9 EQUB 14 \ Point ID 126 is (14, 0, 2) with scale factor 2^8 EQUB 0 \ Point ID 127 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 128 is ( 0, 0, 7) with scale factor 2^8 EQUB 11 \ Point ID 129 is (11, 0, 14) with scale factor 2^8 EQUB 14 \ Point ID 130 is (14, 0, 10) with scale factor 2^8 EQUB 5 \ Point ID 131 is ( 5, 0, 0) with scale factor 2^8 EQUB 4 \ Point ID 132 is ( 4, 0, 14) with scale factor 2^8 EQUB 9 \ Point ID 133 is ( 9, 0, 4) with scale factor 2^9 EQUB 10 \ Point ID 134 is (10, 0, 2) with scale factor 2^9 EQUB 0 \ Point ID 135 is ( 0, 0, 0) with scale factor 2^0 EQUB 1 \ Point ID 136 is ( 1, 0, 15) with scale factor 2^8 EQUB 5 \ Point ID 137 is ( 5, 0, 10) with scale factor 2^9 EQUB 13 \ Point ID 138 is (13, 0, 4) with scale factor 2^8 EQUB 0 \ Point ID 139 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 140 is ( 0, 0, 11) with scale factor 2^8 EQUB 13 \ Point ID 141 is (13, 0, 9) with scale factor 2^8 EQUB 15 \ Point ID 142 is (15, 0, 0) with scale factor 2^7 EQUB 2 \ Point ID 143 is ( 2, 0, 0) with scale factor 2^8 EQUB 0 \ Point ID 144 is ( 0, 0, 13) with scale factor 2^8 EQUB 9 \ Point ID 145 is ( 9, 0, 9) with scale factor 2^9 EQUB 13 \ Point ID 146 is (13, 0, 11) with scale factor 2^8 EQUB 15 \ Point ID 147 is (15, 0, 5) with scale factor 2^8 EQUB 11 \ Point ID 148 is (11, 0, 0) with scale factor 2^8 EQUB 0 \ Point ID 149 is ( 0, 0, 11) with scale factor 2^8 EQUB 11 \ Point ID 150 is (11, 0, 13) with scale factor 2^8 EQUB 13 \ Point ID 151 is (13, 0, 2) with scale factor 2^8 EQUB 2 \ Point ID 152 is ( 2, 0, 0) with scale factor 2^8 EQUB 3 \ Point ID 153 is ( 3, 0, 8) with scale factor 2^8 EQUB 10 \ Point ID 154 is (10, 0, 7) with scale factor 2^8 EQUB 8 \ Point ID 155 is ( 8, 0, 2) with scale factor 2^8 EQUB 0 \ Point ID 156 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 157 is ( 0, 0, 6) with scale factor 2^8 EQUB 4 \ Point ID 158 is ( 4, 0, 8) with scale factor 2^8 EQUB 12 \ Point ID 159 is (12, 0, 0) with scale factor 2^8 EQUB 0 \ Point ID 160 is ( 0, 0, 6) with scale factor 2^8 EQUB 6 \ Point ID 161 is ( 6, 0, 9) with scale factor 2^9 EQUB 14 \ Point ID 162 is (14, 0, 12) with scale factor 2^8 EQUB 11 \ Point ID 163 is (11, 0, 0) with scale factor 2^7 EQUB 0 \ Point ID 164 is ( 0, 0, 3) with scale factor 2^8 EQUB 5 \ Point ID 165 is ( 5, 0, 12) with scale factor 2^9 EQUB 10 \ Point ID 166 is (10, 0, 9) with scale factor 2^9 EQUB 14 \ Point ID 167 is (14, 0, 0) with scale factor 2^8 EQUB 0 \ Point ID 168 is ( 0, 0, 12) with scale factor 2^8 EQUB 5 \ Point ID 169 is ( 5, 0, 11) with scale factor 2^9 EQUB 11 \ Point ID 170 is (11, 0, 7) with scale factor 2^8 EQUB 5 \ Point ID 171 is ( 5, 0, 0) with scale factor 2^8 EQUB 0 \ Point ID 172 is ( 0, 0, 9) with scale factor 2^8 EQUB 5 \ Point ID 173 is ( 5, 0, 8) with scale factor 2^9 EQUB 13 \ Point ID 174 is (13, 0, 13) with scale factor 2^8 EQUB 2 \ Point ID 175 is ( 2, 0, 0) with scale factor 2^8 EQUB 0 \ Point ID 176 is ( 0, 0, 8) with scale factor 2^8 EQUB 2 \ Point ID 177 is ( 2, 0, 0) with scale factor 2^8 EQUB 0 \ Point ID 178 is ( 0, 0, 12) with scale factor 2^0 EQUB 12 \ Point ID 179 is (12, 0, 0) with scale factor 2^0 EQUB 14 \ Point ID 180 is (14, 0, 14) with scale factor 2^2 EQUB 5 \ Point ID 181 is ( 5, 0, 5) with scale factor 2^8 EQUB 5 \ Point ID 182 is ( 5, 0, 5) with scale factor 2^8 EQUB 0 \ Point ID 183 is ( 0, 0, 3) with scale factor 2^2 EQUB 3 \ Point ID 184 is ( 3, 0, 0) with scale factor 2^2 EQUB 14 \ Point ID 185 is (14, 0, 14) with scale factor 2^2 EQUB 3 \ Point ID 186 is ( 3, 4, 3) with scale factor 2^2 EQUB 5 \ Point ID 187 is ( 5, 0, 5) with scale factor 2^8 EQUB 0 \ Point ID 188 is ( 0, 0, 3) with scale factor 2^2 EQUB 3 \ Point ID 189 is ( 3, 0, 0) with scale factor 2^2 EQUB 14 \ Point ID 190 is (14, 0, 14) with scale factor 2^2 EQUB 3 \ Point ID 191 is ( 3, 4, 3) with scale factor 2^2 EQUB 9 \ Point ID 192 is ( 9, 0, 9) with scale factor 2^7 EQUB 4 \ Point ID 193 is ( 4, 0, 5) with scale factor 2^5 EQUB 5 \ Point ID 194 is ( 5, 0, 4) with scale factor 2^5 EQUB 9 \ Point ID 195 is ( 9, 0, 8) with scale factor 2^6 EQUB 7 \ Point ID 196 is ( 7, 4, 5) with scale factor 2^5 EQUB 4 \ Point ID 197 is ( 4, 0, 0) with scale factor 2^4 EQUB 4 \ Point ID 198 is ( 4, 3, 2) with scale factor 2^5 EQUB 0 \ Point ID 199 is ( 0, 0, 4) with scale factor 2^4 EQUB 2 \ Point ID 200 is ( 2, 3, 4) with scale factor 2^5 EQUB 0 \ Point ID 201 is ( 0, 2, 0) with scale factor 2^8 EQUB 0 \ Point ID 202 is ( 0, 2, 0) with scale factor 2^8 EQUB 0 \ Point ID 203 is ( 0, 0, 12) with scale factor 2^4 EQUB 11 \ Point ID 204 is (11, 0, 0) with scale factor 2^5 EQUB 12 \ Point ID 205 is (12, 0, 0) with scale factor 2^4 EQUB 11 \ Point ID 206 is (11, 0, 0) with scale factor 2^5 EQUB 0 \ Point ID 207 is ( 0, 8, 0) with scale factor 2^4 EQUB 0 \ Point ID 208 is ( 0, 8, 0) with scale factor 2^4 EQUB 0 \ Point ID 209 is ( 0, 8, 0) with scale factor 2^4 EQUB 0 \ Point ID 210 is ( 0, 8, 0) with scale factor 2^4 EQUB 4 \ Point ID 211 is ( 4, 1, 4) with scale factor 2^7 EQUB 1 \ Point ID 212 is ( 1, 0, 4) with scale factor 2^8 EQUB 0 \ Point ID 213 is ( 0, 0, 2) with scale factor 2^8 EQUB 0 \ Point ID 214 is ( 0, 0, 5) with scale factor 2^8 EQUB 4 \ Point ID 215 is ( 4, 2, 5) with scale factor 2^7 \ ****************************************************************************** \ \ Name: yObjectPoint \ Type: Variable \ Category: 3D geometry \ Summary: Scaled y-coordinates of the points that make up objects, relative \ to the object's anchor point \ Deep dive: 3D objects \ Rotating and translating points in 3D space \ \ ------------------------------------------------------------------------------ \ \ See xObjectPoint for an explanation of object points. \ \ ****************************************************************************** .yObjectPoint EQUB 61 \ Point ID 0 is (13, 61, 13) with scale factor 2^0 EQUB 0 \ Point ID 1 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 2 is ( 0, 0, 12) with scale factor 2^9 EQUB 0 \ Point ID 3 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 4 is ( 8, 0, 0) with scale factor 2^5 EQUB 0 \ Point ID 5 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 6 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 7 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 8 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 9 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 10 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 11 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 12 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 13 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 14 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 15 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 16 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 17 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 18 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 19 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 20 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 21 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 22 is ( 0, 0, 8) with scale factor 2^4 EQUB 0 \ Point ID 23 is ( 0, 0, 8) with scale factor 2^4 EQUB 0 \ Point ID 24 is (10, 0, 0) with scale factor 2^6 EQUB 1 \ Point ID 25 is ( 0, 1, 0) with scale factor 2^8 EQUB 1 \ Point ID 26 is ( 0, 1, 0) with scale factor 2^8 EQUB 1 \ Point ID 27 is ( 0, 1, 0) with scale factor 2^8 EQUB 1 \ Point ID 28 is ( 0, 1, 0) with scale factor 2^8 EQUB 0 \ Point ID 29 is ( 4, 0, 0) with scale factor 2^8 EQUB 0 \ Point ID 30 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 31 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 32 is ( 4, 0, 0) with scale factor 2^8 EQUB 0 \ Point ID 33 is ( 0, 0, 10) with scale factor 2^5 EQUB 0 \ Point ID 34 is ( 0, 0, 12) with scale factor 2^4 EQUB 0 \ Point ID 35 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 36 is ( 4, 0, 2) with scale factor 2^8 EQUB 0 \ Point ID 37 is ( 0, 0, 2) with scale factor 2^8 EQUB 0 \ Point ID 38 is (12, 0, 0) with scale factor 2^4 EQUB 0 \ Point ID 39 is ( 5, 0, 10) with scale factor 2^5 EQUB 0 \ Point ID 40 is ( 8, 0, 0) with scale factor 2^4 EQUB 0 \ Point ID 41 is ( 0, 0, 8) with scale factor 2^3 EQUB 10 \ Point ID 42 is ( 0, 10, 0) with scale factor 2^3 EQUB 10 \ Point ID 43 is ( 0, 10, 0) with scale factor 2^3 EQUB 0 \ Point ID 44 is ( 0, 0, 8) with scale factor 2^3 EQUB 0 \ Point ID 45 is ( 0, 0, 0) with scale factor 2^0 EQUB 10 \ Point ID 46 is ( 0, 10, 0) with scale factor 2^3 EQUB 10 \ Point ID 47 is ( 0, 10, 0) with scale factor 2^3 EQUB 0 \ Point ID 48 is (12, 0, 0) with scale factor 2^5 EQUB 0 \ Point ID 49 is ( 0, 0, 8) with scale factor 2^3 EQUB 0 \ Point ID 50 is ( 0, 0, 8) with scale factor 2^3 EQUB 0 \ Point ID 51 is (12, 0, 0) with scale factor 2^5 EQUB 0 \ Point ID 52 is (11, 0, 0) with scale factor 2^6 EQUB 10 \ Point ID 53 is ( 0, 10, 0) with scale factor 2^4 EQUB 10 \ Point ID 54 is ( 0, 10, 0) with scale factor 2^4 EQUB 10 \ Point ID 55 is ( 0, 10, 0) with scale factor 2^4 EQUB 10 \ Point ID 56 is ( 0, 10, 0) with scale factor 2^4 EQUB 0 \ Point ID 57 is ( 2, 0, 14) with scale factor 2^8 EQUB 0 \ Point ID 58 is ( 1, 0, 11) with scale factor 2^8 EQUB 0 \ Point ID 59 is ( 3, 0, 9) with scale factor 2^9 EQUB 0 \ Point ID 60 is ( 6, 0, 12) with scale factor 2^8 EQUB 0 \ Point ID 61 is (10, 0, 1) with scale factor 2^8 EQUB 0 \ Point ID 62 is ( 3, 0, 9) with scale factor 2^7 EQUB 0 \ Point ID 63 is ( 9, 0, 15) with scale factor 2^6 EQUB 0 \ Point ID 64 is ( 6, 0, 13) with scale factor 2^8 EQUB 0 \ Point ID 65 is (13, 0, 3) with scale factor 2^4 EQUB 0 \ Point ID 66 is ( 0, 0, 14) with scale factor 2^9 EQUB 0 \ Point ID 67 is ( 1, 0, 0) with scale factor 2^8 EQUB 0 \ Point ID 68 is ( 5, 0, 12) with scale factor 2^9 EQUB 0 \ Point ID 69 is ( 1, 0, 0) with scale factor 2^8 EQUB 0 \ Point ID 70 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 71 is ( 9, 0, 0) with scale factor 2^5 EQUB 0 \ Point ID 72 is ( 0, 0, 9) with scale factor 2^5 EQUB 0 \ Point ID 73 is ( 8, 0, 6) with scale factor 2^9 EQUB 0 \ Point ID 74 is ( 0, 0, 1) with scale factor 2^8 EQUB 0 \ Point ID 75 is (14, 0, 7) with scale factor 2^9 EQUB 0 \ Point ID 76 is ( 0, 0, 1) with scale factor 2^8 EQUB 0 \ Point ID 77 is (13, 0, 2) with scale factor 2^9 EQUB 0 \ Point ID 78 is ( 5, 0, 13) with scale factor 2^4 EQUB 0 \ Point ID 79 is (12, 0, 0) with scale factor 2^8 EQUB 0 \ Point ID 80 is (15, 0, 15) with scale factor 2^1 EQUB 12 \ Point ID 81 is ( 0, 12, 0) with scale factor 2^2 EQUB 12 \ Point ID 82 is ( 7, 12, 7) with scale factor 2^1 EQUB 7 \ Point ID 83 is ( 6, 7, 11) with scale factor 2^2 EQUB 8 \ Point ID 84 is (12, 8, 5) with scale factor 2^2 EQUB 10 \ Point ID 85 is ( 0, 10, 0) with scale factor 2^0 EQUB 0 \ Point ID 86 is ( 0, 0, 1) with scale factor 2^8 EQUB 0 \ Point ID 87 is ( 8, 0, 0) with scale factor 2^4 EQUB 2 \ Point ID 88 is ( 0, 2, 0) with scale factor 2^8 EQUB 0 \ Point ID 89 is (15, 0, 15) with scale factor 2^1 EQUB 10 \ Point ID 90 is ( 0, 10, 0) with scale factor 2^2 EQUB 12 \ Point ID 91 is ( 4, 12, 4) with scale factor 2^1 EQUB 5 \ Point ID 92 is ( 3, 5, 12) with scale factor 2^2 EQUB 7 \ Point ID 93 is (10, 7, 8) with scale factor 2^2 EQUB 10 \ Point ID 94 is ( 0, 10, 0) with scale factor 2^0 EQUB 0 \ Point ID 95 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 96 is ( 0, 0, 1) with scale factor 2^8 EQUB 0 \ Point ID 97 is (10, 0, 0) with scale factor 2^1 EQUB 0 \ Point ID 98 is ( 0, 0, 1) with scale factor 2^8 EQUB 2 \ Point ID 99 is ( 0, 2, 0) with scale factor 2^8 EQUB 0 \ Point ID 100 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 101 is ( 0, 0, 11) with scale factor 2^4 EQUB 0 \ Point ID 102 is ( 0, 0, 10) with scale factor 2^4 EQUB 0 \ Point ID 103 is (11, 0, 7) with scale factor 2^8 EQUB 0 \ Point ID 104 is (11, 0, 4) with scale factor 2^9 EQUB 0 \ Point ID 105 is (13, 0, 13) with scale factor 2^3 EQUB 0 \ Point ID 106 is (14, 0, 0) with scale factor 2^9 EQUB 0 \ Point ID 107 is ( 4, 0, 10) with scale factor 2^3 EQUB 0 \ Point ID 108 is ( 4, 0, 0) with scale factor 2^8 EQUB 0 \ Point ID 109 is (13, 0, 3) with scale factor 2^8 EQUB 0 \ Point ID 110 is (12, 0, 0) with scale factor 2^8 EQUB 0 \ Point ID 111 is (13, 0, 10) with scale factor 2^9 EQUB 0 \ Point ID 112 is ( 4, 0, 12) with scale factor 2^8 EQUB 0 \ Point ID 113 is (14, 0, 1) with scale factor 2^9 EQUB 0 \ Point ID 114 is ( 0, 0, 12) with scale factor 2^8 EQUB 0 \ Point ID 115 is ( 3, 0, 5) with scale factor 2^8 EQUB 0 \ Point ID 116 is (15, 0, 0) with scale factor 2^8 EQUB 0 \ Point ID 117 is (10, 0, 5) with scale factor 2^8 EQUB 0 \ Point ID 118 is (11, 0, 4) with scale factor 2^8 EQUB 0 \ Point ID 119 is ( 2, 0, 14) with scale factor 2^8 EQUB 0 \ Point ID 120 is (14, 0, 0) with scale factor 2^4 EQUB 0 \ Point ID 121 is ( 0, 0, 9) with scale factor 2^8 EQUB 0 \ Point ID 122 is (11, 0, 7) with scale factor 2^9 EQUB 0 \ Point ID 123 is (10, 0, 3) with scale factor 2^9 EQUB 0 \ Point ID 124 is (10, 0, 3) with scale factor 2^9 EQUB 0 \ Point ID 125 is ( 5, 0, 9) with scale factor 2^9 EQUB 0 \ Point ID 126 is (14, 0, 2) with scale factor 2^8 EQUB 0 \ Point ID 127 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 128 is ( 0, 0, 7) with scale factor 2^8 EQUB 0 \ Point ID 129 is (11, 0, 14) with scale factor 2^8 EQUB 0 \ Point ID 130 is (14, 0, 10) with scale factor 2^8 EQUB 0 \ Point ID 131 is ( 5, 0, 0) with scale factor 2^8 EQUB 0 \ Point ID 132 is ( 4, 0, 14) with scale factor 2^8 EQUB 0 \ Point ID 133 is ( 9, 0, 4) with scale factor 2^9 EQUB 0 \ Point ID 134 is (10, 0, 2) with scale factor 2^9 EQUB 0 \ Point ID 135 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 136 is ( 1, 0, 15) with scale factor 2^8 EQUB 0 \ Point ID 137 is ( 5, 0, 10) with scale factor 2^9 EQUB 0 \ Point ID 138 is (13, 0, 4) with scale factor 2^8 EQUB 0 \ Point ID 139 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 140 is ( 0, 0, 11) with scale factor 2^8 EQUB 0 \ Point ID 141 is (13, 0, 9) with scale factor 2^8 EQUB 0 \ Point ID 142 is (15, 0, 0) with scale factor 2^7 EQUB 0 \ Point ID 143 is ( 2, 0, 0) with scale factor 2^8 EQUB 0 \ Point ID 144 is ( 0, 0, 13) with scale factor 2^8 EQUB 0 \ Point ID 145 is ( 9, 0, 9) with scale factor 2^9 EQUB 0 \ Point ID 146 is (13, 0, 11) with scale factor 2^8 EQUB 0 \ Point ID 147 is (15, 0, 5) with scale factor 2^8 EQUB 0 \ Point ID 148 is (11, 0, 0) with scale factor 2^8 EQUB 0 \ Point ID 149 is ( 0, 0, 11) with scale factor 2^8 EQUB 0 \ Point ID 150 is (11, 0, 13) with scale factor 2^8 EQUB 0 \ Point ID 151 is (13, 0, 2) with scale factor 2^8 EQUB 0 \ Point ID 152 is ( 2, 0, 0) with scale factor 2^8 EQUB 0 \ Point ID 153 is ( 3, 0, 8) with scale factor 2^8 EQUB 0 \ Point ID 154 is (10, 0, 7) with scale factor 2^8 EQUB 0 \ Point ID 155 is ( 8, 0, 2) with scale factor 2^8 EQUB 0 \ Point ID 156 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 157 is ( 0, 0, 6) with scale factor 2^8 EQUB 0 \ Point ID 158 is ( 4, 0, 8) with scale factor 2^8 EQUB 0 \ Point ID 159 is (12, 0, 0) with scale factor 2^8 EQUB 0 \ Point ID 160 is ( 0, 0, 6) with scale factor 2^8 EQUB 0 \ Point ID 161 is ( 6, 0, 9) with scale factor 2^9 EQUB 0 \ Point ID 162 is (14, 0, 12) with scale factor 2^8 EQUB 0 \ Point ID 163 is (11, 0, 0) with scale factor 2^7 EQUB 0 \ Point ID 164 is ( 0, 0, 3) with scale factor 2^8 EQUB 0 \ Point ID 165 is ( 5, 0, 12) with scale factor 2^9 EQUB 0 \ Point ID 166 is (10, 0, 9) with scale factor 2^9 EQUB 0 \ Point ID 167 is (14, 0, 0) with scale factor 2^8 EQUB 0 \ Point ID 168 is ( 0, 0, 12) with scale factor 2^8 EQUB 0 \ Point ID 169 is ( 5, 0, 11) with scale factor 2^9 EQUB 0 \ Point ID 170 is (11, 0, 7) with scale factor 2^8 EQUB 0 \ Point ID 171 is ( 5, 0, 0) with scale factor 2^8 EQUB 0 \ Point ID 172 is ( 0, 0, 9) with scale factor 2^8 EQUB 0 \ Point ID 173 is ( 5, 0, 8) with scale factor 2^9 EQUB 0 \ Point ID 174 is (13, 0, 13) with scale factor 2^8 EQUB 0 \ Point ID 175 is ( 2, 0, 0) with scale factor 2^8 EQUB 0 \ Point ID 176 is ( 0, 0, 8) with scale factor 2^8 EQUB 0 \ Point ID 177 is ( 2, 0, 0) with scale factor 2^8 EQUB 0 \ Point ID 178 is ( 0, 0, 12) with scale factor 2^0 EQUB 0 \ Point ID 179 is (12, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 180 is (14, 0, 14) with scale factor 2^2 EQUB 0 \ Point ID 181 is ( 5, 0, 5) with scale factor 2^8 EQUB 0 \ Point ID 182 is ( 5, 0, 5) with scale factor 2^8 EQUB 0 \ Point ID 183 is ( 0, 0, 3) with scale factor 2^2 EQUB 0 \ Point ID 184 is ( 3, 0, 0) with scale factor 2^2 EQUB 0 \ Point ID 185 is (14, 0, 14) with scale factor 2^2 EQUB 4 \ Point ID 186 is ( 3, 4, 3) with scale factor 2^2 EQUB 0 \ Point ID 187 is ( 5, 0, 5) with scale factor 2^8 EQUB 0 \ Point ID 188 is ( 0, 0, 3) with scale factor 2^2 EQUB 0 \ Point ID 189 is ( 3, 0, 0) with scale factor 2^2 EQUB 0 \ Point ID 190 is (14, 0, 14) with scale factor 2^2 EQUB 4 \ Point ID 191 is ( 3, 4, 3) with scale factor 2^2 EQUB 0 \ Point ID 192 is ( 9, 0, 9) with scale factor 2^7 EQUB 0 \ Point ID 193 is ( 4, 0, 5) with scale factor 2^5 EQUB 0 \ Point ID 194 is ( 5, 0, 4) with scale factor 2^5 EQUB 0 \ Point ID 195 is ( 9, 0, 8) with scale factor 2^6 EQUB 4 \ Point ID 196 is ( 7, 4, 5) with scale factor 2^5 EQUB 0 \ Point ID 197 is ( 4, 0, 0) with scale factor 2^4 EQUB 3 \ Point ID 198 is ( 4, 3, 2) with scale factor 2^5 EQUB 0 \ Point ID 199 is ( 0, 0, 4) with scale factor 2^4 EQUB 3 \ Point ID 200 is ( 2, 3, 4) with scale factor 2^5 EQUB 2 \ Point ID 201 is ( 0, 2, 0) with scale factor 2^8 EQUB 2 \ Point ID 202 is ( 0, 2, 0) with scale factor 2^8 EQUB 0 \ Point ID 203 is ( 0, 0, 12) with scale factor 2^4 EQUB 0 \ Point ID 204 is (11, 0, 0) with scale factor 2^5 EQUB 0 \ Point ID 205 is (12, 0, 0) with scale factor 2^4 EQUB 0 \ Point ID 206 is (11, 0, 0) with scale factor 2^5 EQUB 8 \ Point ID 207 is ( 0, 8, 0) with scale factor 2^4 EQUB 8 \ Point ID 208 is ( 0, 8, 0) with scale factor 2^4 EQUB 8 \ Point ID 209 is ( 0, 8, 0) with scale factor 2^4 EQUB 8 \ Point ID 210 is ( 0, 8, 0) with scale factor 2^4 EQUB 1 \ Point ID 211 is ( 4, 1, 4) with scale factor 2^7 EQUB 0 \ Point ID 212 is ( 1, 0, 4) with scale factor 2^8 EQUB 0 \ Point ID 213 is ( 0, 0, 2) with scale factor 2^8 EQUB 0 \ Point ID 214 is ( 0, 0, 5) with scale factor 2^8 EQUB 2 \ Point ID 215 is ( 4, 2, 5) with scale factor 2^7 \ ****************************************************************************** \ \ Name: zObjectPoint \ Type: Variable \ Category: 3D geometry \ Summary: Scaled z-coordinates of the points that make up objects, relative \ to the object's anchor point \ Deep dive: 3D objects \ Rotating and translating points in 3D space \ \ ------------------------------------------------------------------------------ \ \ See xObjectPoint for an explanation of object points. \ \ ****************************************************************************** .zObjectPoint EQUB 13 \ Point ID 0 is (13, 61, 13) with scale factor 2^0 EQUB 0 \ Point ID 1 is ( 0, 0, 0) with scale factor 2^0 EQUB 156 \ Point ID 2 is ( 0, 0, 12) with scale factor 2^9 EQUB 0 \ Point ID 3 is ( 0, 0, 0) with scale factor 2^0 EQUB 80 \ Point ID 4 is ( 8, 0, 0) with scale factor 2^5 EQUB 0 \ Point ID 5 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 6 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 7 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 8 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 9 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 10 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 11 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 12 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 13 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 14 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 15 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 16 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 17 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 18 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 19 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 20 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 21 is ( 0, 0, 0) with scale factor 2^0 EQUB 72 \ Point ID 22 is ( 0, 0, 8) with scale factor 2^4 EQUB 72 \ Point ID 23 is ( 0, 0, 8) with scale factor 2^4 EQUB 96 \ Point ID 24 is (10, 0, 0) with scale factor 2^6 EQUB 128 \ Point ID 25 is ( 0, 1, 0) with scale factor 2^8 EQUB 128 \ Point ID 26 is ( 0, 1, 0) with scale factor 2^8 EQUB 128 \ Point ID 27 is ( 0, 1, 0) with scale factor 2^8 EQUB 128 \ Point ID 28 is ( 0, 1, 0) with scale factor 2^8 EQUB 128 \ Point ID 29 is ( 4, 0, 0) with scale factor 2^8 EQUB 0 \ Point ID 30 is ( 0, 0, 0) with scale factor 2^0 EQUB 0 \ Point ID 31 is ( 0, 0, 0) with scale factor 2^0 EQUB 128 \ Point ID 32 is ( 4, 0, 0) with scale factor 2^8 EQUB 90 \ Point ID 33 is ( 0, 0, 10) with scale factor 2^5 EQUB 76 \ Point ID 34 is ( 0, 0, 12) with scale factor 2^4 EQUB 0 \ Point ID 35 is ( 0, 0, 0) with scale factor 2^0 EQUB 130 \ Point ID 36 is ( 4, 0, 2) with scale factor 2^8 EQUB 130 \ Point ID 37 is ( 0, 0, 2) with scale factor 2^8 EQUB 64 \ Point ID 38 is (12, 0, 0) with scale factor 2^4 EQUB 90 \ Point ID 39 is ( 5, 0, 10) with scale factor 2^5 EQUB 64 \ Point ID 40 is ( 8, 0, 0) with scale factor 2^4 EQUB 56 \ Point ID 41 is ( 0, 0, 8) with scale factor 2^3 EQUB 48 \ Point ID 42 is ( 0, 10, 0) with scale factor 2^3 EQUB 48 \ Point ID 43 is ( 0, 10, 0) with scale factor 2^3 EQUB 56 \ Point ID 44 is ( 0, 0, 8) with scale factor 2^3 EQUB 0 \ Point ID 45 is ( 0, 0, 0) with scale factor 2^0 EQUB 48 \ Point ID 46 is ( 0, 10, 0) with scale factor 2^3 EQUB 48 \ Point ID 47 is ( 0, 10, 0) with scale factor 2^3 EQUB 80 \ Point ID 48 is (12, 0, 0) with scale factor 2^5 EQUB 56 \ Point ID 49 is ( 0, 0, 8) with scale factor 2^3 EQUB 56 \ Point ID 50 is ( 0, 0, 8) with scale factor 2^3 EQUB 80 \ Point ID 51 is (12, 0, 0) with scale factor 2^5 EQUB 96 \ Point ID 52 is (11, 0, 0) with scale factor 2^6 EQUB 64 \ Point ID 53 is ( 0, 10, 0) with scale factor 2^4 EQUB 64 \ Point ID 54 is ( 0, 10, 0) with scale factor 2^4 EQUB 64 \ Point ID 55 is ( 0, 10, 0) with scale factor 2^4 EQUB 64 \ Point ID 56 is ( 0, 10, 0) with scale factor 2^4 EQUB 142 \ Point ID 57 is ( 2, 0, 14) with scale factor 2^8 EQUB 139 \ Point ID 58 is ( 1, 0, 11) with scale factor 2^8 EQUB 153 \ Point ID 59 is ( 3, 0, 9) with scale factor 2^9 EQUB 140 \ Point ID 60 is ( 6, 0, 12) with scale factor 2^8 EQUB 129 \ Point ID 61 is (10, 0, 1) with scale factor 2^8 EQUB 121 \ Point ID 62 is ( 3, 0, 9) with scale factor 2^7 EQUB 111 \ Point ID 63 is ( 9, 0, 15) with scale factor 2^6 EQUB 141 \ Point ID 64 is ( 6, 0, 13) with scale factor 2^8 EQUB 67 \ Point ID 65 is (13, 0, 3) with scale factor 2^4 EQUB 158 \ Point ID 66 is ( 0, 0, 14) with scale factor 2^9 EQUB 128 \ Point ID 67 is ( 1, 0, 0) with scale factor 2^8 EQUB 156 \ Point ID 68 is ( 5, 0, 12) with scale factor 2^9 EQUB 128 \ Point ID 69 is ( 1, 0, 0) with scale factor 2^8 EQUB 0 \ Point ID 70 is ( 0, 0, 0) with scale factor 2^0 EQUB 80 \ Point ID 71 is ( 9, 0, 0) with scale factor 2^5 EQUB 89 \ Point ID 72 is ( 0, 0, 9) with scale factor 2^5 EQUB 150 \ Point ID 73 is ( 8, 0, 6) with scale factor 2^9 EQUB 129 \ Point ID 74 is ( 0, 0, 1) with scale factor 2^8 EQUB 151 \ Point ID 75 is (14, 0, 7) with scale factor 2^9 EQUB 129 \ Point ID 76 is ( 0, 0, 1) with scale factor 2^8 EQUB 146 \ Point ID 77 is (13, 0, 2) with scale factor 2^9 EQUB 77 \ Point ID 78 is ( 5, 0, 13) with scale factor 2^4 EQUB 128 \ Point ID 79 is (12, 0, 0) with scale factor 2^8 EQUB 31 \ Point ID 80 is (15, 0, 15) with scale factor 2^1 EQUB 32 \ Point ID 81 is ( 0, 12, 0) with scale factor 2^2 EQUB 23 \ Point ID 82 is ( 7, 12, 7) with scale factor 2^1 EQUB 43 \ Point ID 83 is ( 6, 7, 11) with scale factor 2^2 EQUB 37 \ Point ID 84 is (12, 8, 5) with scale factor 2^2 EQUB 0 \ Point ID 85 is ( 0, 10, 0) with scale factor 2^0 EQUB 129 \ Point ID 86 is ( 0, 0, 1) with scale factor 2^8 EQUB 64 \ Point ID 87 is ( 8, 0, 0) with scale factor 2^4 EQUB 128 \ Point ID 88 is ( 0, 2, 0) with scale factor 2^8 EQUB 31 \ Point ID 89 is (15, 0, 15) with scale factor 2^1 EQUB 32 \ Point ID 90 is ( 0, 10, 0) with scale factor 2^2 EQUB 20 \ Point ID 91 is ( 4, 12, 4) with scale factor 2^1 EQUB 44 \ Point ID 92 is ( 3, 5, 12) with scale factor 2^2 EQUB 40 \ Point ID 93 is (10, 7, 8) with scale factor 2^2 EQUB 0 \ Point ID 94 is ( 0, 10, 0) with scale factor 2^0 EQUB 0 \ Point ID 95 is ( 0, 0, 0) with scale factor 2^0 EQUB 129 \ Point ID 96 is ( 0, 0, 1) with scale factor 2^8 EQUB 16 \ Point ID 97 is (10, 0, 0) with scale factor 2^1 EQUB 129 \ Point ID 98 is ( 0, 0, 1) with scale factor 2^8 EQUB 128 \ Point ID 99 is ( 0, 2, 0) with scale factor 2^8 EQUB 0 \ Point ID 100 is ( 0, 0, 0) with scale factor 2^0 EQUB 75 \ Point ID 101 is ( 0, 0, 11) with scale factor 2^4 EQUB 74 \ Point ID 102 is ( 0, 0, 10) with scale factor 2^4 EQUB 135 \ Point ID 103 is (11, 0, 7) with scale factor 2^8 EQUB 148 \ Point ID 104 is (11, 0, 4) with scale factor 2^9 EQUB 61 \ Point ID 105 is (13, 0, 13) with scale factor 2^3 EQUB 144 \ Point ID 106 is (14, 0, 0) with scale factor 2^9 EQUB 58 \ Point ID 107 is ( 4, 0, 10) with scale factor 2^3 EQUB 128 \ Point ID 108 is ( 4, 0, 0) with scale factor 2^8 EQUB 131 \ Point ID 109 is (13, 0, 3) with scale factor 2^8 EQUB 128 \ Point ID 110 is (12, 0, 0) with scale factor 2^8 EQUB 154 \ Point ID 111 is (13, 0, 10) with scale factor 2^9 EQUB 140 \ Point ID 112 is ( 4, 0, 12) with scale factor 2^8 EQUB 145 \ Point ID 113 is (14, 0, 1) with scale factor 2^9 EQUB 140 \ Point ID 114 is ( 0, 0, 12) with scale factor 2^8 EQUB 133 \ Point ID 115 is ( 3, 0, 5) with scale factor 2^8 EQUB 128 \ Point ID 116 is (15, 0, 0) with scale factor 2^8 EQUB 133 \ Point ID 117 is (10, 0, 5) with scale factor 2^8 EQUB 132 \ Point ID 118 is (11, 0, 4) with scale factor 2^8 EQUB 142 \ Point ID 119 is ( 2, 0, 14) with scale factor 2^8 EQUB 64 \ Point ID 120 is (14, 0, 0) with scale factor 2^4 EQUB 137 \ Point ID 121 is ( 0, 0, 9) with scale factor 2^8 EQUB 151 \ Point ID 122 is (11, 0, 7) with scale factor 2^9 EQUB 147 \ Point ID 123 is (10, 0, 3) with scale factor 2^9 EQUB 147 \ Point ID 124 is (10, 0, 3) with scale factor 2^9 EQUB 153 \ Point ID 125 is ( 5, 0, 9) with scale factor 2^9 EQUB 130 \ Point ID 126 is (14, 0, 2) with scale factor 2^8 EQUB 0 \ Point ID 127 is ( 0, 0, 0) with scale factor 2^0 EQUB 135 \ Point ID 128 is ( 0, 0, 7) with scale factor 2^8 EQUB 142 \ Point ID 129 is (11, 0, 14) with scale factor 2^8 EQUB 138 \ Point ID 130 is (14, 0, 10) with scale factor 2^8 EQUB 128 \ Point ID 131 is ( 5, 0, 0) with scale factor 2^8 EQUB 142 \ Point ID 132 is ( 4, 0, 14) with scale factor 2^8 EQUB 148 \ Point ID 133 is ( 9, 0, 4) with scale factor 2^9 EQUB 146 \ Point ID 134 is (10, 0, 2) with scale factor 2^9 EQUB 0 \ Point ID 135 is ( 0, 0, 0) with scale factor 2^0 EQUB 143 \ Point ID 136 is ( 1, 0, 15) with scale factor 2^8 EQUB 154 \ Point ID 137 is ( 5, 0, 10) with scale factor 2^9 EQUB 132 \ Point ID 138 is (13, 0, 4) with scale factor 2^8 EQUB 0 \ Point ID 139 is ( 0, 0, 0) with scale factor 2^0 EQUB 139 \ Point ID 140 is ( 0, 0, 11) with scale factor 2^8 EQUB 137 \ Point ID 141 is (13, 0, 9) with scale factor 2^8 EQUB 112 \ Point ID 142 is (15, 0, 0) with scale factor 2^7 EQUB 128 \ Point ID 143 is ( 2, 0, 0) with scale factor 2^8 EQUB 141 \ Point ID 144 is ( 0, 0, 13) with scale factor 2^8 EQUB 153 \ Point ID 145 is ( 9, 0, 9) with scale factor 2^9 EQUB 139 \ Point ID 146 is (13, 0, 11) with scale factor 2^8 EQUB 133 \ Point ID 147 is (15, 0, 5) with scale factor 2^8 EQUB 128 \ Point ID 148 is (11, 0, 0) with scale factor 2^8 EQUB 139 \ Point ID 149 is ( 0, 0, 11) with scale factor 2^8 EQUB 141 \ Point ID 150 is (11, 0, 13) with scale factor 2^8 EQUB 130 \ Point ID 151 is (13, 0, 2) with scale factor 2^8 EQUB 128 \ Point ID 152 is ( 2, 0, 0) with scale factor 2^8 EQUB 136 \ Point ID 153 is ( 3, 0, 8) with scale factor 2^8 EQUB 135 \ Point ID 154 is (10, 0, 7) with scale factor 2^8 EQUB 130 \ Point ID 155 is ( 8, 0, 2) with scale factor 2^8 EQUB 0 \ Point ID 156 is ( 0, 0, 0) with scale factor 2^0 EQUB 134 \ Point ID 157 is ( 0, 0, 6) with scale factor 2^8 EQUB 136 \ Point ID 158 is ( 4, 0, 8) with scale factor 2^8 EQUB 128 \ Point ID 159 is (12, 0, 0) with scale factor 2^8 EQUB 134 \ Point ID 160 is ( 0, 0, 6) with scale factor 2^8 EQUB 153 \ Point ID 161 is ( 6, 0, 9) with scale factor 2^9 EQUB 140 \ Point ID 162 is (14, 0, 12) with scale factor 2^8 EQUB 112 \ Point ID 163 is (11, 0, 0) with scale factor 2^7 EQUB 131 \ Point ID 164 is ( 0, 0, 3) with scale factor 2^8 EQUB 156 \ Point ID 165 is ( 5, 0, 12) with scale factor 2^9 EQUB 153 \ Point ID 166 is (10, 0, 9) with scale factor 2^9 EQUB 128 \ Point ID 167 is (14, 0, 0) with scale factor 2^8 EQUB 140 \ Point ID 168 is ( 0, 0, 12) with scale factor 2^8 EQUB 155 \ Point ID 169 is ( 5, 0, 11) with scale factor 2^9 EQUB 135 \ Point ID 170 is (11, 0, 7) with scale factor 2^8 EQUB 128 \ Point ID 171 is ( 5, 0, 0) with scale factor 2^8 EQUB 137 \ Point ID 172 is ( 0, 0, 9) with scale factor 2^8 EQUB 152 \ Point ID 173 is ( 5, 0, 8) with scale factor 2^9 EQUB 141 \ Point ID 174 is (13, 0, 13) with scale factor 2^8 EQUB 128 \ Point ID 175 is ( 2, 0, 0) with scale factor 2^8 EQUB 136 \ Point ID 176 is ( 0, 0, 8) with scale factor 2^8 EQUB 128 \ Point ID 177 is ( 2, 0, 0) with scale factor 2^8 EQUB 12 \ Point ID 178 is ( 0, 0, 12) with scale factor 2^0 EQUB 0 \ Point ID 179 is (12, 0, 0) with scale factor 2^0 EQUB 46 \ Point ID 180 is (14, 0, 14) with scale factor 2^2 EQUB 133 \ Point ID 181 is ( 5, 0, 5) with scale factor 2^8 EQUB 133 \ Point ID 182 is ( 5, 0, 5) with scale factor 2^8 EQUB 35 \ Point ID 183 is ( 0, 0, 3) with scale factor 2^2 EQUB 32 \ Point ID 184 is ( 3, 0, 0) with scale factor 2^2 EQUB 46 \ Point ID 185 is (14, 0, 14) with scale factor 2^2 EQUB 35 \ Point ID 186 is ( 3, 4, 3) with scale factor 2^2 EQUB 133 \ Point ID 187 is ( 5, 0, 5) with scale factor 2^8 EQUB 35 \ Point ID 188 is ( 0, 0, 3) with scale factor 2^2 EQUB 32 \ Point ID 189 is ( 3, 0, 0) with scale factor 2^2 EQUB 46 \ Point ID 190 is (14, 0, 14) with scale factor 2^2 EQUB 35 \ Point ID 191 is ( 3, 4, 3) with scale factor 2^2 EQUB 121 \ Point ID 192 is ( 9, 0, 9) with scale factor 2^7 EQUB 85 \ Point ID 193 is ( 4, 0, 5) with scale factor 2^5 EQUB 84 \ Point ID 194 is ( 5, 0, 4) with scale factor 2^5 EQUB 104 \ Point ID 195 is ( 9, 0, 8) with scale factor 2^6 EQUB 85 \ Point ID 196 is ( 7, 4, 5) with scale factor 2^5 EQUB 64 \ Point ID 197 is ( 4, 0, 0) with scale factor 2^4 EQUB 82 \ Point ID 198 is ( 4, 3, 2) with scale factor 2^5 EQUB 68 \ Point ID 199 is ( 0, 0, 4) with scale factor 2^4 EQUB 84 \ Point ID 200 is ( 2, 3, 4) with scale factor 2^5 EQUB 128 \ Point ID 201 is ( 0, 2, 0) with scale factor 2^8 EQUB 128 \ Point ID 202 is ( 0, 2, 0) with scale factor 2^8 EQUB 76 \ Point ID 203 is ( 0, 0, 12) with scale factor 2^4 EQUB 80 \ Point ID 204 is (11, 0, 0) with scale factor 2^5 EQUB 64 \ Point ID 205 is (12, 0, 0) with scale factor 2^4 EQUB 80 \ Point ID 206 is (11, 0, 0) with scale factor 2^5 EQUB 64 \ Point ID 207 is ( 0, 8, 0) with scale factor 2^4 EQUB 64 \ Point ID 208 is ( 0, 8, 0) with scale factor 2^4 EQUB 64 \ Point ID 209 is ( 0, 8, 0) with scale factor 2^4 EQUB 64 \ Point ID 210 is ( 0, 8, 0) with scale factor 2^4 EQUB 116 \ Point ID 211 is ( 4, 1, 4) with scale factor 2^7 EQUB 132 \ Point ID 212 is ( 1, 0, 4) with scale factor 2^8 EQUB 130 \ Point ID 213 is ( 0, 0, 2) with scale factor 2^8 EQUB 133 \ Point ID 214 is ( 0, 0, 5) with scale factor 2^8 EQUB 117 \ Point ID 215 is ( 4, 2, 5) with scale factor 2^7 \ ****************************************************************************** \ \ Name: xRadarBuffer \ Type: Variable \ Category: Dashboard \ Summary: The x-coordinates of the runway and alien on the radar \ Deep dive: Line buffers \ \ ****************************************************************************** .xRadarBuffer EQUB &8A \ The x-coordinate of the runway on the radar, stored so \ we can erase it again EQUB &8A \ The x-coordinate of the alien on the radar, stored so \ we can erase it again \ ****************************************************************************** \ \ Name: yRadarBuffer \ Type: Variable \ Category: Dashboard \ Summary: The y-coordinates of the runway and alien on the radar \ Deep dive: Line buffers \ \ ****************************************************************************** .yRadarBuffer EQUB &D0 \ The y-coordinate of the runway on the radar, stored so \ we can erase it again EQUB &D0 \ The y-coordinate of the alien on the radar, stored so \ we can erase it again \ ****************************************************************************** \ \ Name: distanceFromHit \ Type: Variable \ Category: The Theme \ Summary: The distance from the alien we just hit, so we can work out \ whether we get hit by turbulence \ Deep dive: Explosions and turbulence \ \ ****************************************************************************** .distanceFromHit EQUB &48 \ ****************************************************************************** \ \ Name: feedingStage \ Type: Variable \ Category: The Theme \ Summary: The feeding stage of the alien we are currently processing \ \ ****************************************************************************** .feedingStage EQUB &49 \ The alien's feeding stage \ \ * 0 = large feeding alien (full up) or flying \ * 1 = medium feeding alien \ * 2 = small feeding alien \ * 3 = smallest feeding alien \ * 4 = dormant (not feeding) \ ****************************************************************************** \ \ Name: hitObjectId \ Type: Variable \ Category: The Theme \ Summary: The object ID of the alien we just hit (30 to 33) \ \ ------------------------------------------------------------------------------ \ \ This is called EPTR in the original source. \ \ ****************************************************************************** .hitObjectId EQUB &3D \ ****************************************************************************** \ \ Name: hitTimer \ Type: Variable \ Category: The Theme \ Summary: The time since we hit an alien, so we can time its explosion \ Deep dive: Explosions and turbulence \ \ ------------------------------------------------------------------------------ \ \ The hit timer starts at 27 when we make a hit. While it is non-zero, the gun \ can't be fired and any attacking aliens stop moving until the timer runs down. \ \ This is called EPLO in the original source. \ \ ****************************************************************************** .hitTimer EQUB &26 \ Zeroed in ResetVariables \ ****************************************************************************** \ \ Name: fuelUsedLo \ Type: Variable \ Category: Flight model \ Summary: The low byte of the current batch of fuel used \ \ ****************************************************************************** .fuelUsedLo EQUB &34 \ ****************************************************************************** \ \ Name: fuelUsedHi \ Type: Variable \ Category: Flight model \ Summary: The high byte of the current batch of fuel used \ \ ****************************************************************************** .fuelUsedHi EQUB &34 \ ****************************************************************************** \ \ Name: fuelLevel \ Type: Variable \ Category: Flight model \ Summary: The current fuel level \ \ ****************************************************************************** .fuelLevel EQUB &41 \ Current fuel level \ \ * 0 = empty \ \ * 65 = full \ ****************************************************************************** \ \ Name: explodeTo \ Type: Variable \ Category: The Theme \ Summary: The end point ID for exploding each of the four alien slots \ \ ****************************************************************************** .explodeTo EQUB 178 \ Alien slot 30 moves points 180 to 178 when it explodes EQUB 183 \ Alien slot 31 moves points 186 to 183 when it explodes EQUB 188 \ Alien slot 32 moves points 191 to 188 when it explodes EQUB 193 \ Alien slot 33 moves points 200 to 193 when it explodes EQUB 15 \ This byte appears to be unused \ ****************************************************************************** \ \ Name: explodeFrom \ Type: Variable \ Category: The Theme \ Summary: The starting point ID for exploding each of the four alien slots \ \ ****************************************************************************** .explodeFrom EQUB 180 \ Alien slot 30 moves points 180 to 178 when it explodes EQUB 186 \ Alien slot 31 moves points 186 to 183 when it explodes EQUB 191 \ Alien slot 32 moves points 191 to 188 when it explodes EQUB 200 \ Alien slot 33 moves points 200 to 193 when it explodes \ ****************************************************************************** \ \ Name: scoreLo \ Type: Variable \ Category: Scoring \ Summary: Score (low byte) \ \ ****************************************************************************** .scoreLo EQUB &49 \ Score (low byte of a BCD number) \ \ The score is displayed with an extra "0" added to the \ end, so this contains the score divided by 10 \ ****************************************************************************** \ \ Name: scoreHi \ Type: Variable \ Category: Scoring \ Summary: Score (high byte) \ \ ****************************************************************************** .scoreHi EQUB &3D \ Score (high byte of a BCD number) \ \ The score is displayed with an extra "0" added to the \ end, so this contains the score divided by 10 \ ****************************************************************************** \ \ Name: highScoreLo \ Type: Variable \ Category: Scoring \ Summary: High score (high byte) \ \ ****************************************************************************** .highScoreLo EQUB &26 \ High score (high byte of a BCD number) \ \ The high score is displayed with an extra "0" added to \ the end, so this contains the high score divided by 10 \ ****************************************************************************** \ \ Name: highScoreHi \ Type: Variable \ Category: Scoring \ Summary: High score (low byte) \ \ ****************************************************************************** .highScoreHi EQUB &34 \ High score (low byte of a BCD number) \ \ The high score is displayed with an extra "0" added to \ the end, so this contains the high score divided by 10 \ ****************************************************************************** \ \ Name: SetEngine \ Type: Subroutine \ Category: Flight model \ Summary: Set the engine status \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ A The new status of the engine: \ \ * 0 = engine off \ \ * 1 = engine on \ \ ****************************************************************************** .SetEngine CMP engineStatus \ If the value of engineStatus is already the same as A, BEQ seng3 \ jump to seng3 to return from the subroutine STA engineStatus \ Update the value of engineStatus to the new status in \ A TAX \ Either turn the engine sound off (if A = 0) or turn it JSR ToggleEngineSound \ on (if A is non-zero) LDA forceFactor+5 \ Set A to the force factor for zLiftDrag LDX engineStatus \ If the engine is now on, jump to seng1 BNE seng1 CLC \ Set A = A + 20 ADC #20 \ \ so having the engine off increases drag BNE seng2 \ Jump to seng2 (this BNE is effectively a JMP as A is \ never zero) .seng1 SEC \ Set A = A - 20 SBC #20 \ \ so having the engine on decreases drag .seng2 STA forceFactor+5 \ Update the force factor for zLiftDrag, so it is \ incremented by 20 when the engine is off, and reduced \ by 20 when the engine is on, i.e. having the engine \ off increases drag .seng3 RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: skillZoneLo \ Type: Variable \ Category: Scoring \ Summary: Low byte of the skill zone coordinates for testing flying skills \ Deep dive: Flying skills \ \ ****************************************************************************** .skillZoneLo EQUB &A8, &00, &56 \ Bridge skill zone 0 coordinate = (&4CA8, &0000, &8656) EQUB &A8, &60, &56 \ Bridge skill zone 3 coordinate = (&4CA8, &0060, &8656) EQUB &88, &00, &56 \ Bridge skill zone 6 coordinate = (&4C88, &0000, &8656) EQUB &30, &00, &30 \ Town skill zone 9 coordinate = (&0430, &0000, &0330) EQUB &D0, &00, &70 \ Town skill zone 12 coordinate = (&04D0, &0000, &0470) EQUB &10, &00, &70 \ Town skill zone 15 coordinate = (&0610, &0000, &0470) EQUB &E0, &00, &C0 \ Town skill zone 18 coordinate = (&04E0, &0000, &03C0) \ ****************************************************************************** \ \ Name: skillZoneHi \ Type: Variable \ Category: Scoring \ Summary: High byte of the skill zone coordinates for testing flying skills \ Deep dive: Flying skills \ \ ****************************************************************************** .skillZoneHi EQUB &4C, &00, &86 \ Bridge skill zone 0 coordinate = (&4CA8, &0000, &8656) EQUB &4C, &00, &86 \ Bridge skill zone 3 coordinate = (&4CA8, &0060, &8656) EQUB &4C, &00, &86 \ Bridge skill zone 6 coordinate = (&4C88, &0000, &8656) EQUB &04, &00, &03 \ Town skill zone 9 coordinate = (&0430, &0000, &0330) EQUB &04, &00, &04 \ Town skill zone 12 coordinate = (&04D0, &0000, &0470) EQUB &06, &00, &04 \ Town skill zone 15 coordinate = (&0610, &0000, &0470) EQUB &04, &00, &03 \ Town skill zone 18 coordinate = (&04E0, &0000, &03C0) \ ****************************************************************************** \ \ Name: skillZoneSize \ Type: Variable \ Category: Scoring \ Summary: Sizes of the skill zones for testing flying skills \ Deep dive: Flying skills \ \ ****************************************************************************** .skillZoneSize EQUB &A8, &10, &18 \ Bridge skill zone 0 size = (&A8, &10, &18) EQUB &A8, &16, &18 \ Bridge skill zone 3 size = (&A8, &16, &18) EQUB &B8, &2C, &18 \ Bridge skill zone 6 size = (&B8, &2C, &18) EQUB &A8, &44, &28 \ Town skill zone 9 size = (&A8, &44, &28) EQUB &28, &84, &48 \ Town skill zone 12 size = (&28, &84, &48) EQUB &60, &24, &38 \ Town skill zone 15 size = (&60, &24, &38) EQUB &78, &20, &60 \ Town skill zone 18 size = (&78, &20, &60) EQUB &20 \ This byte appears to be unused \ ****************************************************************************** \ \ Name: shift4Right \ Type: Variable \ Category: Maths \ Summary: Lookup table for shifting a byte four places to the right, to \ extract the high nibble \ Deep dive: Times tables and nibble arithmetic \ \ ------------------------------------------------------------------------------ \ \ In the table below, shift4Right,X contains the high byte of X * 16. So if X is \ %XXXXxxxx, shift4Right,X contains %0000XXXX. \ \ ****************************************************************************** .shift4Right FOR I%, 0, 255 EQUB HI(I% * 16) NEXT \ ****************************************************************************** \ \ Name: shift4Left \ Type: Variable \ Category: Maths \ Summary: Lookup table for shifting a byte four places to the left, to \ extract the low nibble \ Deep dive: Times tables and nibble arithmetic \ \ ------------------------------------------------------------------------------ \ \ In the table below, shift4Left,X contains the low byte of X * 16. So if X is \ %XXXXxxxx, shift4Left,X contains %xxxx0000. \ \ ****************************************************************************** .shift4Left FOR I%, 0, 255 EQUB LO(I% * 16) NEXT \ ****************************************************************************** \ \ Name: divisionHi \ Type: Variable \ Category: Maths \ Summary: Division lookup table \ \ ------------------------------------------------------------------------------ \ \ This table contains the high byte of the division lookup table, while bits 3 \ to 7 of divisionLo contain the low byte. \ \ ****************************************************************************** .divisionHi EQUB &FF, &FE, &FD, &FC, &FB, &FA, &F9, &F8 EQUB &F7, &F6, &F5, &F4, &F3, &F2, &F1, &F0 EQUB &EF, &EF, &EE, &ED, &EC, &EB, &EA, &E9 EQUB &E9, &E8, &E7, &E6, &E5, &E4, &E4, &E3 EQUB &E2, &E1, &E1, &E0, &DF, &DE, &DD, &DD EQUB &DC, &DB, &DA, &DA, &D9, &D8, &D8, &D7 EQUB &D6, &D5, &D5, &D4, &D3, &D3, &D2, &D1 EQUB &D1, &D0, &CF, &CF, &CE, &CD, &CD, &CC EQUB &CB, &CB, &CA, &C9, &C9, &C8, &C8, &C7 EQUB &C6, &C6, &C5, &C5, &C4, &C3, &C3, &C2 EQUB &C2, &C1, &C0, &C0, &BF, &BF, &BE, &BE EQUB &BD, &BC, &BC, &BB, &BB, &BA, &BA, &B9 EQUB &B9, &B8, &B8, &B7, &B7, &B6, &B6, &B5 EQUB &B5, &B4, &B4, &B3, &B3, &B2, &B2, &B1 EQUB &B1, &B0, &B0, &AF, &AF, &AE, &AE, &AD EQUB &AD, &AC, &AC, &AB, &AB, &AB, &AA, &AA EQUB &A9, &A9, &A8, &A8, &A7, &A7, &A7, &A6 EQUB &A6, &A5, &A5, &A4, &A4, &A4, &A3, &A3 EQUB &A2, &A2, &A2, &A1, &A1, &A0, &A0, &A0 EQUB &9F, &9F, &9E, &9E, &9E, &9D, &9D, &9C EQUB &9C, &9C, &9B, &9B, &9B, &9A, &9A, &99 EQUB &99, &99, &98, &98, &98, &97, &97, &97 EQUB &96, &96, &96, &95, &95, &94, &94, &94 EQUB &93, &93, &93, &92, &92, &92, &91, &91 EQUB &91, &90, &90, &90, &90, &8F, &8F, &8F EQUB &8E, &8E, &8E, &8D, &8D, &8D, &8C, &8C EQUB &8C, &8B, &8B, &8B, &8B, &8A, &8A, &8A EQUB &89, &89, &89, &88, &88, &88, &88, &87 EQUB &87, &87, &86, &86, &86, &86, &85, &85 EQUB &85, &85, &84, &84, &84, &83, &83, &83 EQUB &83, &82, &82, &82, &82, &81, &81, &81 EQUB &81, &80, &80, &80, &80, &7F, &7F, &7F \ ****************************************************************************** \ \ Name: timesTable \ Type: Variable \ Category: Maths \ Summary: Lookup table for multiplication times tables \ Deep dive: Times tables and nibble arithmetic \ \ ------------------------------------------------------------------------------ \ \ In the table below, timesTable+X*16,Y contains X * Y. \ \ To put it another way, X and Y are both in the range 0 to 15, so they are \ 4-bit values. If, in binary, they are X = %xxxx and Y = %yyyy, then: \ \ timesTable,%xxxxyyyy contains X * Y. \ \ This table is used in conjunction with the highNibble and lowNibble tables to \ look up multiplication results. \ \ ****************************************************************************** .timesTable FOR I%, 0, 15 FOR J%, 0, 15 EQUB I% * J% NEXT NEXT \ ****************************************************************************** \ \ Name: maxLineDistance \ Type: Variable \ Category: Visibility \ Summary: The furthest distance at which each line is visible \ Deep dive: Lines and points \ \ ------------------------------------------------------------------------------ \ \ Lines are only shown if they are closer than the distance in this table, so \ this table contains the maximum visible distance for each line. \ \ The table is indexed by line ID, so for line ID X, maxLineDistance,X contains \ the maximum distance at which that line is visible, in any of the individual \ axes. \ \ ****************************************************************************** .maxLineDistance EQUB 16 \ Line ID 0 EQUB 125 \ Line ID 1 EQUB 125 \ Line ID 2 EQUB 125 \ Line ID 3 EQUB 125 \ Line ID 4 EQUB 16 \ Line ID 5 EQUB 16 \ Line ID 6 EQUB 16 \ Line ID 7 EQUB 16 \ Line ID 8 EQUB 16 \ Line ID 9 EQUB 40 \ Line ID 10 EQUB 16 \ Line ID 11 EQUB 80 \ Line ID 12 EQUB 80 \ Line ID 13 EQUB 50 \ Line ID 14 EQUB 50 \ Line ID 15 EQUB 50 \ Line ID 16 EQUB 40 \ Line ID 17 EQUB 40 \ Line ID 18 EQUB 40 \ Line ID 19 EQUB 80 \ Line ID 20 EQUB 80 \ Line ID 21 EQUB 40 \ Line ID 22 EQUB 40 \ Line ID 23 EQUB 80 \ Line ID 24 EQUB 80 \ Line ID 25 EQUB 80 \ Line ID 26 EQUB 80 \ Line ID 27 EQUB 80 \ Line ID 28 EQUB 80 \ Line ID 29 EQUB 80 \ Line ID 30 EQUB 80 \ Line ID 31 EQUB 80 \ Line ID 32 EQUB 80 \ Line ID 33 EQUB 80 \ Line ID 34 EQUB 80 \ Line ID 35 EQUB 80 \ Line ID 36 EQUB 80 \ Line ID 37 EQUB 80 \ Line ID 38 EQUB 60 \ Line ID 39 EQUB 70 \ Line ID 40 EQUB 60 \ Line ID 41 EQUB 70 \ Line ID 42 EQUB 60 \ Line ID 43 EQUB 70 \ Line ID 44 EQUB 60 \ Line ID 45 EQUB 70 \ Line ID 46 EQUB 63 \ Line ID 47 EQUB 63 \ Line ID 48 EQUB 63 \ Line ID 49 EQUB 63 \ Line ID 50 EQUB 60 \ Line ID 51 EQUB 70 \ Line ID 52 EQUB 60 \ Line ID 53 EQUB 70 \ Line ID 54 EQUB 60 \ Line ID 55 EQUB 63 \ Line ID 56 EQUB 63 \ Line ID 57 EQUB 63 \ Line ID 58 EQUB 63 \ Line ID 59 EQUB 30 \ Line ID 60 EQUB 30 \ Line ID 61 EQUB 70 \ Line ID 62 EQUB 60 \ Line ID 63 EQUB 70 \ Line ID 64 EQUB 60 \ Line ID 65 EQUB 70 \ Line ID 66 EQUB 60 \ Line ID 67 EQUB 70 \ Line ID 68 EQUB 60 \ Line ID 69 EQUB 70 \ Line ID 70 EQUB 60 \ Line ID 71 EQUB 70 \ Line ID 72 EQUB 60 \ Line ID 73 EQUB 70 \ Line ID 74 EQUB 60 \ Line ID 75 EQUB 120 \ Line ID 76 EQUB 70 \ Line ID 77 EQUB 125 \ Line ID 78 EQUB 125 \ Line ID 79 EQUB 125 \ Line ID 80 EQUB 125 \ Line ID 81 EQUB 125 \ Line ID 82 EQUB 125 \ Line ID 83 EQUB 125 \ Line ID 84 EQUB 125 \ Line ID 85 EQUB 125 \ Line ID 86 EQUB 125 \ Line ID 87 EQUB 125 \ Line ID 88 EQUB 125 \ Line ID 89 EQUB 125 \ Line ID 90 EQUB 125 \ Line ID 91 EQUB 125 \ Line ID 92 EQUB 125 \ Line ID 93 EQUB 125 \ Line ID 94 EQUB 125 \ Line ID 95 EQUB 125 \ Line ID 96 EQUB 125 \ Line ID 97 EQUB 125 \ Line ID 98 EQUB 125 \ Line ID 99 EQUB 125 \ Line ID 100 EQUB 125 \ Line ID 101 EQUB 125 \ Line ID 102 EQUB 125 \ Line ID 103 EQUB 125 \ Line ID 104 EQUB 125 \ Line ID 105 EQUB 125 \ Line ID 106 EQUB 125 \ Line ID 107 EQUB 125 \ Line ID 108 EQUB 125 \ Line ID 109 EQUB 125 \ Line ID 110 EQUB 125 \ Line ID 111 EQUB 125 \ Line ID 112 EQUB 125 \ Line ID 113 EQUB 125 \ Line ID 114 EQUB 125 \ Line ID 115 EQUB 125 \ Line ID 116 EQUB 125 \ Line ID 117 EQUB 125 \ Line ID 118 EQUB 125 \ Line ID 119 EQUB 125 \ Line ID 120 EQUB 125 \ Line ID 121 EQUB 125 \ Line ID 122 EQUB 120 \ Line ID 123 EQUB 125 \ Line ID 124 EQUB 125 \ Line ID 125 EQUB 125 \ Line ID 126 EQUB 125 \ Line ID 127 EQUB 125 \ Line ID 128 EQUB 125 \ Line ID 129 EQUB 125 \ Line ID 130 EQUB 125 \ Line ID 131 EQUB 125 \ Line ID 132 EQUB 125 \ Line ID 133 EQUB 125 \ Line ID 134 EQUB 25 \ Line ID 135 EQUB 25 \ Line ID 136 EQUB 25 \ Line ID 137 EQUB 100 \ Line ID 138 EQUB 100 \ Line ID 139 EQUB 100 \ Line ID 140 EQUB 100 \ Line ID 141 EQUB 100 \ Line ID 142 EQUB 100 \ Line ID 143 EQUB 100 \ Line ID 144 EQUB 100 \ Line ID 145 EQUB 100 \ Line ID 146 EQUB 100 \ Line ID 147 EQUB 100 \ Line ID 148 EQUB 100 \ Line ID 149 EQUB 120 \ Line ID 150 EQUB 100 \ Line ID 151 EQUB 100 \ Line ID 152 EQUB 100 \ Line ID 153 EQUB 100 \ Line ID 154 EQUB 100 \ Line ID 155 EQUB 100 \ Line ID 156 EQUB 100 \ Line ID 157 EQUB 100 \ Line ID 158 EQUB 100 \ Line ID 159 EQUB 100 \ Line ID 160 EQUB 120 \ Line ID 161 EQUB 120 \ Line ID 162 EQUB 120 \ Line ID 163 EQUB 120 \ Line ID 164 EQUB 120 \ Line ID 165 EQUB 120 \ Line ID 166 EQUB 120 \ Line ID 167 EQUB 120 \ Line ID 168 EQUB 60 \ Line ID 169 EQUB 60 \ Line ID 170 EQUB 60 \ Line ID 171 EQUB 60 \ Line ID 172 EQUB 60 \ Line ID 173 EQUB 60 \ Line ID 174 EQUB 60 \ Line ID 175 EQUB 60 \ Line ID 176 EQUB 60 \ Line ID 177 EQUB 60 \ Line ID 178 EQUB 60 \ Line ID 179 EQUB 60 \ Line ID 180 EQUB 60 \ Line ID 181 EQUB 60 \ Line ID 182 EQUB 60 \ Line ID 183 EQUB 60 \ Line ID 184 EQUB 60 \ Line ID 185 EQUB 120 \ Line ID 186 EQUB 120 \ Line ID 187 EQUB 120 \ Line ID 188 EQUB 120 \ Line ID 189 EQUB 120 \ Line ID 190 EQUB 120 \ Line ID 191 EQUB 120 \ Line ID 192 EQUB &3F, &0D \ These bytes appear to be unused EQUB &03, &B1 EQUB &10, &2E EQUB &64, &6C EQUB &70, &32 EQUB &20, &53 EQUB &54, &41 EQUB &26, &37 EQUB &36, &0D EQUB &03, &B2 EQUB &19, &2E EQUB &64 \ ****************************************************************************** \ \ Name: maxObjDistance \ Type: Variable \ Category: Visibility \ Summary: The furthest distance at which each object is visible \ \ ------------------------------------------------------------------------------ \ \ Objects are only shown if they are closer than the distance in this table, so \ this table contains the maximum visible distance for each object. \ \ The table is indexed by object ID, so for object ID X, maxObjDistance,X \ contains the maximum distance at which that object is visible, in any of the \ individual axes. \ \ The initial contents of the last five bytes of this table contains workspace \ noise and is ignored. It actually contains snippets of the original source \ code. \ \ ****************************************************************************** .maxObjDistance EQUB 108 \ Object 0 EQUB 125 \ Object 1 EQUB 125 \ Object 2 EQUB 125 \ Object 3 EQUB 125 \ Object 4 EQUB 125 \ Object 5 EQUB 40 \ Object 6 EQUB 40 \ Object 7 EQUB 110 \ Object 8 EQUB 110 \ Object 9 EQUB 58 \ Object 10 EQUB 66 \ Object 11 EQUB 30 \ Object 12 EQUB 30 \ Object 13 EQUB 30 \ Object 14 EQUB 30 \ Object 15 EQUB 125 \ Object 16 EQUB 125 \ Object 17 EQUB 125 \ Object 18 EQUB 125 \ Object 19 EQUB 125 \ Object 20 EQUB 125 \ Object 21 EQUB 125 \ Object 22 EQUB 125 \ Object 23 EQUB 125 \ Object 24 EQUB 125 \ Object 25 EQUB 125 \ Object 26 EQUB 125 \ Object 27 EQUB 125 \ Object 28 EQUB 125 \ Object 29 EQUB 25 \ Object 30 EQUB 90 \ Object 31 EQUB 90 \ Object 32 EQUB 90 \ Object 33 EQUB 125 \ Object 34 EQUB 58 \ Object 35 EQUB 66 \ Object 36 EQUB 78 \ Object 37 EQUB 69 \ Object 38 EQUB 32 \ Object 39 \ ****************************************************************************** \ \ Name: lineBufferR \ Type: Variable \ Category: Drawing lines \ Summary: Line buffer storage for the start x-coordinate (R) \ Deep dive: Line buffers \ \ ------------------------------------------------------------------------------ \ \ This table stores information about lines that are drawn on-screen, so they \ can be quickly erased without having to spend precious time recalculating the \ line coordinates. The information is stored when a line is drawn by the \ DrawClippedLine routine, and is read by the EraseCanopyLines routine when the \ line is erased. \ \ The initial contents of the variable is just workspace noise and is ignored. \ It actually contains snippets of the original source code. \ \ We can buffer up to 96 lines, with 48 in each of the two line buffers, so the \ maximum number of lines on screen at any one time is 48 lines out of the 193 \ lines defined in the world. \ \ ****************************************************************************** .lineBufferR EQUB &64, &6C, &70, &32, &0D, &03, &B4, &19 EQUB &20, &20, &20, &20, &20, &20, &44, &45 EQUB &43, &26, &37, &34, &3A, &42, &4E, &45 EQUB &20, &64, &6C, &70, &31, &0D, &03, &B5 EQUB &0D, &20, &20, &20, &20, &20, &20, &72 EQUB &74, &73, &0D, &03, &B6, &05, &20, &0D EQUB &03, &BF, &25, &2E, &55, &42, &55, &4C EQUB &20, &4C, &44, &59, &23, &31, &35, &3A EQUB &53, &54, &59, &20, &4F, &42, &3A, &4C EQUB &44, &41, &23, &39, &38, &3A, &53, &54 EQUB &41, &20, &50, &50, &0D, &03, &C0, &1D EQUB &2E, &75, &62, &75, &32, &20, &54, &59 \ ****************************************************************************** \ \ Name: lineBufferW \ Type: Variable \ Category: Drawing lines \ Summary: Line buffer storage for the max/min x-coordinate (W) \ Deep dive: Line buffers \ \ ------------------------------------------------------------------------------ \ \ This table stores information about lines that are drawn on-screen, so they \ can be quickly erased without having to spend precious time recalculating the \ line coordinates. The information is stored when a line is drawn by the \ DrawClippedLine routine, and is read by the EraseCanopyLines routine when the \ line is erased. \ \ The initial contents of the variable is just workspace noise and is ignored. \ It actually contains snippets of the original source code. \ \ We can buffer up to 96 lines, with 48 in each of the two line buffers, so the \ maximum number of lines on screen at any one time is 48 lines out of the 193 \ lines defined in the world. \ \ ****************************************************************************** .lineBufferW EQUB &41, &3A, &43, &4C, &43, &3A, &41, &44 EQUB &43, &23, &26, &44, &38, &3A, &54, &41 EQUB &58, &0D, &03, &CA, &12, &20, &20, &20 EQUB &20, &20, &20, &4A, &53, &52, &20, &4D EQUB &4F, &42, &4A, &0D, &03, &D4, &12, &20 EQUB &20, &20, &20, &20, &20, &4A, &53, &52 EQUB &20, &55, &4F, &42, &4A, &0D, &03, &DE EQUB &25, &20, &20, &20, &20, &20, &20, &4C EQUB &44, &59, &20, &4F, &42, &3A, &4C, &44 EQUB &41, &20, &4F, &53, &54, &41, &54, &2C EQUB &59, &3A, &42, &4D, &49, &20, &75, &62 EQUB &75, &31, &0D, &03, &E8, &1B, &20, &20 \ ****************************************************************************** \ \ Name: lineBufferS \ Type: Variable \ Category: Drawing lines \ Summary: Line buffer storage for the start y-coordinate (S) \ Deep dive: Line buffers \ \ ------------------------------------------------------------------------------ \ \ This table stores information about lines that are drawn on-screen, so they \ can be quickly erased without having to spend precious time recalculating the \ line coordinates. The information is stored when a line is drawn by the \ DrawClippedLine routine, and is read by the EraseCanopyLines routine when the \ line is erased. \ \ The initial contents of the variable is just workspace noise and is ignored. \ It actually contains snippets of the original source code. \ \ We can buffer up to 96 lines, with 48 in each of the two line buffers, so the \ maximum number of lines on screen at any one time is 48 lines out of the 193 \ lines defined in the world. \ \ ****************************************************************************** .lineBufferS EQUB &20, &20, &20, &20, &4C, &44, &41, &23 EQUB &30, &3A, &53, &54, &41, &20, &46, &52 EQUB &46, &4C, &41, &47, &20, &0D, &03, &F2 EQUB &17, &2E, &75, &62, &75, &31, &20, &44 EQUB &45, &43, &20, &50, &50, &3A, &44, &45 EQUB &43, &20, &4F, &42, &0D, &03, &FC, &20 EQUB &20, &20, &20, &20, &20, &20, &4C, &44 EQUB &59, &20, &4F, &42, &3A, &43, &50, &59 EQUB &23, &31, &32, &3A, &42, &43, &53, &20 EQUB &75, &62, &75, &32, &0D, &04, &06, &0D EQUB &20, &20, &20, &20, &20, &20, &72, &74 EQUB &73, &0D, &04, &10, &05, &20, &0D, &04 \ ****************************************************************************** \ \ Name: lineBufferG \ Type: Variable \ Category: Drawing lines \ Summary: Line buffer storage for the max/min y-coordinate (G) \ Deep dive: Line buffers \ \ ------------------------------------------------------------------------------ \ \ This table stores information about lines that are drawn on-screen, so they \ can be quickly erased without having to spend precious time recalculating the \ line coordinates. The information is stored when a line is drawn by the \ DrawClippedLine routine, and is read by the EraseCanopyLines routine when the \ line is erased. \ \ The initial contents of the variable is just workspace noise and is ignored. \ It actually contains snippets of the original source code. \ \ We can buffer up to 96 lines, with 48 in each of the two line buffers, so the \ maximum number of lines on screen at any one time is 48 lines out of the 193 \ lines defined in the world. \ \ ****************************************************************************** .lineBufferG EQUB &1A, &24, &2E, &53, &55, &54, &52, &20 EQUB &4A, &4D, &50, &20, &54, &45, &53, &54 EQUB &3A, &42, &4D, &49, &20, &73, &75, &74 EQUB &31, &3A, &42, &45, &51, &20, &73, &75 EQUB &74, &31, &0D, &04, &24, &1B, &20, &20 EQUB &20, &20, &20, &20, &4C, &44, &41, &26 EQUB &30, &43, &43, &35, &3A, &42, &4E, &45 EQUB &20, &73, &75, &74, &31, &0D, &04, &2E EQUB &27, &20, &20, &20, &20, &20, &20, &4C EQUB &44, &41, &26, &46, &45, &36, &34, &3A EQUB &80, &23, &31, &35, &3A, &43, &4D, &50 EQUB &23, &31, &34, &3A, &42, &43, &53, &20 \ ****************************************************************************** \ \ Name: lineBufferT \ Type: Variable \ Category: Drawing lines \ Summary: Line buffer storage for the line's |x-delta| (T) \ Deep dive: Line buffers \ \ ------------------------------------------------------------------------------ \ \ This table stores information about lines that are drawn on-screen, so they \ can be quickly erased without having to spend precious time recalculating the \ line coordinates. The information is stored when a line is drawn by the \ DrawClippedLine routine, and is read by the EraseCanopyLines routine when the \ line is erased. \ \ The initial contents of the variable is just workspace noise and is ignored. \ It actually contains snippets of the original source code. \ \ We can buffer up to 96 lines, with 48 in each of the two line buffers, so the \ maximum number of lines on screen at any one time is 48 lines out of the 193 \ lines defined in the world. \ \ ****************************************************************************** .lineBufferT EQUB &73, &75, &74, &31, &0D, &04, &38, &0F EQUB &20, &20, &20, &20, &20, &20, &84, &41 EQUB &23, &31, &36, &0D, &04, &42, &19, &2E EQUB &73, &75, &74, &33, &20, &44, &45, &43 EQUB &20, &54, &48, &45, &4D, &45, &3A, &4C EQUB &44, &58, &23, &37, &0D, &04, &4C, &1C EQUB &2E, &73, &75, &74, &35, &20, &43, &50 EQUB &58, &20, &54, &48, &45, &4D, &45, &3A EQUB &42, &45, &51, &20, &73, &75, &74, &34 EQUB &0D, &04, &56, &1F, &2E, &73, &75, &74 EQUB &32, &20, &43, &4D, &50, &20, &46, &4C EQUB &44, &50, &54, &52, &2C, &58, &3A, &42 \ ****************************************************************************** \ \ Name: scoreText \ Type: Variable \ Category: Scoring \ Summary: The high score text \ \ ****************************************************************************** .scoreText EQUB 31, 1, 3 \ VDU 31, 1, 3 moves the text cursor to column 1, row 3 EQUS "HIGH SCORE: " EQUB "0" EQUB 31, 3, 10 \ VDU 31, 3, 10 moves the text cursor to column 3, row \ 10 EQUB &3A, &4A \ These bytes appear to be unused and contain assembly EQUB &4D, &50 \ code EQUB &20, &73 EQUB &75, &74 EQUB &35, &0D EQUB &04, &6A \ ****************************************************************************** \ \ Name: lineEndPointId \ Type: Variable \ Category: 3D geometry \ Summary: The point ID for a line's end point \ Deep dive: Lines and points \ \ ------------------------------------------------------------------------------ \ \ This table contains the point ID for each line's end point. \ \ The table is indexed by line ID, so for line ID X, lineEndPointId,X contains \ the point ID of the line's end point. \ \ ****************************************************************************** .lineEndPointId EQUB 30 \ Line ID 0 goes from point 31 to point 30 EQUB 2 \ Line ID 1 goes from point 1 to point 2 EQUB 3 \ Line ID 2 goes from point 2 to point 3 EQUB 4 \ Line ID 3 goes from point 3 to point 4 EQUB 1 \ Line ID 4 goes from point 4 to point 1 EQUB 7 \ Line ID 5 goes from point 6 to point 7 EQUB 9 \ Line ID 6 goes from point 8 to point 9 EQUB 11 \ Line ID 7 goes from point 10 to point 11 EQUB 13 \ Line ID 8 goes from point 12 to point 13 EQUB 15 \ Line ID 9 goes from point 14 to point 15 EQUB 17 \ Line ID 10 goes from point 16 to point 17 EQUB 19 \ Line ID 11 goes from point 18 to point 19 EQUB 59 \ Line ID 12 goes from point 114 to point 59 EQUB 114 \ Line ID 13 goes from point 115 to point 114 EQUB 42 \ Line ID 14 goes from point 41 to point 42 EQUB 43 \ Line ID 15 goes from point 42 to point 43 EQUB 44 \ Line ID 16 goes from point 43 to point 44 EQUB 46 \ Line ID 17 goes from point 45 to point 46 EQUB 47 \ Line ID 18 goes from point 46 to point 47 EQUB 48 \ Line ID 19 goes from point 47 to point 48 EQUB 53 \ Line ID 20 goes from point 49 to point 53 EQUB 54 \ Line ID 21 goes from point 50 to point 54 EQUB 55 \ Line ID 22 goes from point 51 to point 55 EQUB 56 \ Line ID 23 goes from point 52 to point 56 EQUB 58 \ Line ID 24 goes from point 59 to point 58 EQUB 57 \ Line ID 25 goes from point 58 to point 57 EQUB 51 \ Line ID 26 goes from point 57 to point 51 EQUB 60 \ Line ID 27 goes from point 52 to point 60 EQUB 61 \ Line ID 28 goes from point 60 to point 61 EQUB 62 \ Line ID 29 goes from point 49 to point 62 EQUB 63 \ Line ID 30 goes from point 50 to point 63 EQUB 64 \ Line ID 31 goes from point 62 to point 64 EQUB 65 \ Line ID 32 goes from point 63 to point 65 EQUB 66 \ Line ID 33 goes from point 64 to point 66 EQUB 116 \ Line ID 34 goes from point 115 to point 116 EQUB 113 \ Line ID 35 goes from point 116 to point 113 EQUB 113 \ Line ID 36 goes from point 112 to point 113 EQUB 112 \ Line ID 37 goes from point 111 to point 112 EQUB 111 \ Line ID 38 goes from point 61 to point 111 EQUB 67 \ Line ID 39 goes from point 65 to point 67 EQUB 119 \ Line ID 40 goes from point 66 to point 119 EQUB 120 \ Line ID 41 goes from point 67 to point 120 EQUB 68 \ Line ID 42 goes from point 119 to point 68 EQUB 69 \ Line ID 43 goes from point 120 to point 69 EQUB 70 \ Line ID 44 goes from point 68 to point 70 EQUB 71 \ Line ID 45 goes from point 69 to point 71 EQUB 117 \ Line ID 46 goes from point 70 to point 117 EQUB 81 \ Line ID 47 goes from point 80 to point 81 EQUB 85 \ Line ID 48 goes from point 82 to point 85 EQUB 85 \ Line ID 49 goes from point 83 to point 85 EQUB 85 \ Line ID 50 goes from point 84 to point 85 EQUB 118 \ Line ID 51 goes from point 71 to point 118 EQUB 72 \ Line ID 52 goes from point 117 to point 72 EQUB 73 \ Line ID 53 goes from point 118 to point 73 EQUB 74 \ Line ID 54 goes from point 72 to point 74 EQUB 75 \ Line ID 55 goes from point 73 to point 75 EQUB 90 \ Line ID 56 goes from point 89 to point 90 EQUB 94 \ Line ID 57 goes from point 91 to point 94 EQUB 94 \ Line ID 58 goes from point 92 to point 94 EQUB 94 \ Line ID 59 goes from point 93 to point 94 EQUB 96 \ Line ID 60 goes from point 95 to point 96 EQUB 98 \ Line ID 61 goes from point 97 to point 98 EQUB 76 \ Line ID 62 goes from point 74 to point 76 EQUB 77 \ Line ID 63 goes from point 75 to point 77 EQUB 78 \ Line ID 64 goes from point 76 to point 78 EQUB 79 \ Line ID 65 goes from point 77 to point 79 EQUB 101 \ Line ID 66 goes from point 78 to point 101 EQUB 100 \ Line ID 67 goes from point 79 to point 100 EQUB 102 \ Line ID 68 goes from point 101 to point 102 EQUB 103 \ Line ID 69 goes from point 100 to point 103 EQUB 105 \ Line ID 70 goes from point 102 to point 105 EQUB 104 \ Line ID 71 goes from point 103 to point 104 EQUB 107 \ Line ID 72 goes from point 105 to point 107 EQUB 106 \ Line ID 73 goes from point 104 to point 106 EQUB 109 \ Line ID 74 goes from point 107 to point 109 EQUB 109 \ Line ID 75 goes from point 106 to point 109 EQUB 215 \ Line ID 76 goes from point 213 to point 215 EQUB 110 \ Line ID 77 goes from point 109 to point 110 EQUB 122 \ Line ID 78 goes from point 121 to point 122 EQUB 121 \ Line ID 79 goes from point 177 to point 121 EQUB 123 \ Line ID 80 goes from point 122 to point 123 EQUB 123 \ Line ID 81 goes from point 177 to point 123 EQUB 145 \ Line ID 82 goes from point 144 to point 145 EQUB 145 \ Line ID 83 goes from point 146 to point 145 EQUB 147 \ Line ID 84 goes from point 146 to point 147 EQUB 147 \ Line ID 85 goes from point 148 to point 147 EQUB 148 \ Line ID 86 goes from point 144 to point 148 EQUB 158 \ Line ID 87 goes from point 157 to point 158 EQUB 159 \ Line ID 88 goes from point 158 to point 159 EQUB 160 \ Line ID 89 goes from point 159 to point 160 EQUB 160 \ Line ID 90 goes from point 157 to point 160 EQUB 161 \ Line ID 91 goes from point 164 to point 161 EQUB 162 \ Line ID 92 goes from point 163 to point 162 EQUB 161 \ Line ID 93 goes from point 162 to point 161 EQUB 163 \ Line ID 94 goes from point 164 to point 163 EQUB 168 \ Line ID 95 goes from point 165 to point 168 EQUB 166 \ Line ID 96 goes from point 165 to point 166 EQUB 167 \ Line ID 97 goes from point 166 to point 167 EQUB 168 \ Line ID 98 goes from point 167 to point 168 EQUB 172 \ Line ID 99 goes from point 169 to point 172 EQUB 171 \ Line ID 100 goes from point 169 to point 171 EQUB 124 \ Line ID 101 goes from point 142 to point 124 EQUB 171 \ Line ID 102 goes from point 172 to point 171 EQUB 175 \ Line ID 103 goes from point 176 to point 175 EQUB 174 \ Line ID 104 goes from point 175 to point 174 EQUB 173 \ Line ID 105 goes from point 176 to point 173 EQUB 174 \ Line ID 106 goes from point 173 to point 174 EQUB 151 \ Line ID 107 goes from point 150 to point 151 EQUB 151 \ Line ID 108 goes from point 152 to point 151 EQUB 149 \ Line ID 109 goes from point 152 to point 149 EQUB 150 \ Line ID 110 goes from point 149 to point 150 EQUB 138 \ Line ID 111 goes from point 139 to point 138 EQUB 138 \ Line ID 112 goes from point 137 to point 138 EQUB 137 \ Line ID 113 goes from point 136 to point 137 EQUB 139 \ Line ID 114 goes from point 136 to point 139 EQUB 133 \ Line ID 115 goes from point 132 to point 133 EQUB 135 \ Line ID 116 goes from point 132 to point 135 EQUB 134 \ Line ID 117 goes from point 135 to point 134 EQUB 134 \ Line ID 118 goes from point 133 to point 134 EQUB 129 \ Line ID 119 goes from point 128 to point 129 EQUB 130 \ Line ID 120 goes from point 129 to point 130 EQUB 131 \ Line ID 121 goes from point 130 to point 131 EQUB 131 \ Line ID 122 goes from point 128 to point 131 EQUB 209 \ Line ID 123 goes from point 203 to point 209 EQUB 126 \ Line ID 124 goes from point 125 to point 126 EQUB 126 \ Line ID 125 goes from point 127 to point 126 EQUB 127 \ Line ID 126 goes from point 125 to point 127 EQUB 141 \ Line ID 127 goes from point 140 to point 141 EQUB 124 \ Line ID 128 goes from point 141 to point 124 EQUB 143 \ Line ID 129 goes from point 140 to point 143 EQUB 142 \ Line ID 130 goes from point 143 to point 142 EQUB 154 \ Line ID 131 goes from point 153 to point 154 EQUB 155 \ Line ID 132 goes from point 154 to point 155 EQUB 156 \ Line ID 133 goes from point 155 to point 156 EQUB 153 \ Line ID 134 goes from point 156 to point 153 EQUB 179 \ Line ID 135 goes from point 178 to point 179 EQUB 180 \ Line ID 136 goes from point 178 to point 180 EQUB 180 \ Line ID 137 goes from point 179 to point 180 EQUB 184 \ Line ID 138 goes from point 183 to point 184 EQUB 185 \ Line ID 139 goes from point 183 to point 185 EQUB 185 \ Line ID 140 goes from point 184 to point 185 EQUB 186 \ Line ID 141 goes from point 183 to point 186 EQUB 184 \ Line ID 142 goes from point 186 to point 184 EQUB 185 \ Line ID 143 goes from point 186 to point 185 EQUB 189 \ Line ID 144 goes from point 188 to point 189 EQUB 190 \ Line ID 145 goes from point 188 to point 190 EQUB 190 \ Line ID 146 goes from point 189 to point 190 EQUB 191 \ Line ID 147 goes from point 188 to point 191 EQUB 189 \ Line ID 148 goes from point 191 to point 189 EQUB 190 \ Line ID 149 goes from point 191 to point 190 EQUB 215 \ Line ID 150 goes from point 108 to point 215 EQUB 194 \ Line ID 151 goes from point 193 to point 194 EQUB 195 \ Line ID 152 goes from point 193 to point 195 EQUB 195 \ Line ID 153 goes from point 194 to point 195 EQUB 196 \ Line ID 154 goes from point 193 to point 196 EQUB 194 \ Line ID 155 goes from point 196 to point 194 EQUB 195 \ Line ID 156 goes from point 196 to point 195 EQUB 200 \ Line ID 157 goes from point 193 to point 200 EQUB 198 \ Line ID 158 goes from point 194 to point 198 EQUB 198 \ Line ID 159 goes from point 197 to point 198 EQUB 200 \ Line ID 160 goes from point 199 to point 200 EQUB 88 \ Line ID 161 goes from point 86 to point 88 EQUB 87 \ Line ID 162 goes from point 99 to point 87 EQUB 201 \ Line ID 163 goes from point 39 to point 201 EQUB 40 \ Line ID 164 goes from point 202 to point 40 EQUB 25 \ Line ID 165 goes from point 22 to point 25 EQUB 28 \ Line ID 166 goes from point 21 to point 28 EQUB 23 \ Line ID 167 goes from point 26 to point 23 EQUB 24 \ Line ID 168 goes from point 27 to point 24 EQUB 88 \ Line ID 169 goes from point 201 to point 88 EQUB 99 \ Line ID 170 goes from point 88 to point 99 EQUB 99 \ Line ID 171 goes from point 202 to point 99 EQUB 202 \ Line ID 172 goes from point 201 to point 202 EQUB 28 \ Line ID 173 goes from point 25 to point 28 EQUB 26 \ Line ID 174 goes from point 25 to point 26 EQUB 27 \ Line ID 175 goes from point 26 to point 27 EQUB 28 \ Line ID 176 goes from point 27 to point 28 EQUB 29 \ Line ID 177 goes from point 22 to point 29 EQUB 40 \ Line ID 178 goes from point 33 to point 40 EQUB 34 \ Line ID 179 goes from point 205 to point 34 EQUB 37 \ Line ID 180 goes from point 40 to point 37 EQUB 38 \ Line ID 181 goes from point 205 to point 38 EQUB 209 \ Line ID 182 goes from point 207 to point 209 EQUB 210 \ Line ID 183 goes from point 209 to point 210 EQUB 208 \ Line ID 184 goes from point 210 to point 208 EQUB 208 \ Line ID 185 goes from point 207 to point 208 EQUB 204 \ Line ID 186 goes from point 210 to point 204 EQUB 206 \ Line ID 187 goes from point 208 to point 206 EQUB 207 \ Line ID 188 goes from point 205 to point 207 EQUB 211 \ Line ID 189 goes from point 35 to point 211 EQUB 211 \ Line ID 190 goes from point 212 to point 211 EQUB 211 \ Line ID 191 goes from point 36 to point 211 EQUB 215 \ Line ID 192 goes from point 214 to point 215 EQUB &D7, &74 \ These bytes appear to be unused EQUB &72, &75 EQUB &32, &3A EQUB &43 \ ****************************************************************************** \ \ Name: objectGroup \ Type: Variable \ Category: 3D geometry \ Summary: The current group number for object IDs 6, 7, 8 and 9 \ Deep dive: 3D objects \ \ ------------------------------------------------------------------------------ \ \ The current object number in each of the four object groups. The values in \ this table cycle through the following values: \ \ * 0 to 7 the current group number for object ID 6 \ * 8 to 15 the current group number for object ID 7 \ * 16 to 23 the current group number for object ID 8 \ * 24 to 31 the current group number for object ID 9 \ \ and then back round to the start. The cycling is performed by successive calls \ to the NextObjectGroup routine. \ \ ****************************************************************************** .objectGroup EQUB 0, 8, 16, 24 \ ****************************************************************************** \ \ Name: groupStart \ Type: Variable \ Category: 3D geometry \ Summary: The starting value for each object's group number \ \ ------------------------------------------------------------------------------ \ \ The starting point for the four object groups, to which we add a number in the \ range 0 to 7 to get the next number, so the group numbers cycle through the \ following values: \ \ * 0 to 7 \ * 8 to 15 \ * 16 to 23 \ * 24 to 31 \ \ ****************************************************************************** .groupStart EQUB 0, 8, 16, 24 \ ****************************************************************************** \ \ Name: xGroupObjectHi \ Type: Variable \ Category: 3D geometry \ Summary: High byte of the x-coordinate for objects in a group (6 to 9) \ Deep dive: 3D objects \ \ ****************************************************************************** .xGroupObjectHi EQUB &C8 \ Object 6: tree 0 has coordinates (&C800, &0000, &5200) EQUB &2A \ Object 6: tree 1 has coordinates (&2A00, &0000, &D200) EQUB &CF \ Object 6: tree 2 has coordinates (&CF00, &0000, &9A00) EQUB &82 \ Object 6: tree 3 has coordinates (&8200, &0000, &C900) EQUB &1D \ Object 6: tree 4 has coordinates (&1D00, &0000, &3E00) EQUB &75 \ Object 6: tree 5 has coordinates (&7500, &0000, &3300) EQUB &1A \ Object 6: tree 6 has coordinates (&1A00, &0000, &8A00) EQUB &CF \ Object 6: tree 7 has coordinates (&CF00, &0000, &EC00) EQUB &9C \ Object 7: tree 0 has coordinates (&9C00, &0000, &C600) EQUB &43 \ Object 7: tree 1 has coordinates (&4300, &0000, &E200) EQUB &E5 \ Object 7: tree 2 has coordinates (&E500, &0000, &BA00) EQUB &8A \ Object 7: tree 3 has coordinates (&8A00, &0000, &7000) EQUB &EA \ Object 7: tree 4 has coordinates (&EA00, &0000, &6E00) EQUB &22 \ Object 7: tree 5 has coordinates (&2200, &0000, &4400) EQUB &6A \ Object 7: tree 6 has coordinates (&6A00, &0000, &2000) EQUB &C5 \ Object 7: tree 7 has coordinates (&C500, &0000, &1B00) EQUB &15 \ Object 8: hill 0 has coordinates (&1500, &0000, &2F00) EQUB &C4 \ Object 8: hill 1 has coordinates (&C400, &0000, &0500) EQUB &C4 \ Object 8: hill 2 has coordinates (&C400, &0000, &0500) EQUB &CE \ Object 8: hill 3 has coordinates (&CE00, &0000, &F500) EQUB &CE \ Object 8: hill 4 has coordinates (&CE00, &0000, &F500) EQUB &CE \ Object 8: hill 5 has coordinates (&CE00, &0000, &F500) EQUB &15 \ Object 8: hill 6 has coordinates (&1500, &0000, &2F00) EQUB &11 \ Object 8: hill 7 has coordinates (&1100, &0000, &D600) EQUB &38 \ Object 9: hill 0 has coordinates (&3800, &0000, &1C00) EQUB &D5 \ Object 9: hill 1 has coordinates (&D500, &0000, &2E00) EQUB &D5 \ Object 9: hill 2 has coordinates (&D500, &0000, &2E00) EQUB &DA \ Object 9: hill 3 has coordinates (&DA00, &0000, &D300) EQUB &DA \ Object 9: hill 4 has coordinates (&DA00, &0000, &D300) EQUB &0D \ Object 9: hill 5 has coordinates (&0D00, &0000, &D900) EQUB &38 \ Object 9: hill 6 has coordinates (&3800, &0000, &1C00) EQUB &0D \ Object 9: hill 7 has coordinates (&0D00, &0000, &D900) \ ****************************************************************************** \ \ Name: zGroupObjectHi \ Type: Variable \ Category: 3D geometry \ Summary: High byte of the z-coordinate for objects in a group (6 to 9) \ Deep dive: 3D objects \ \ ****************************************************************************** .zGroupObjectHi EQUB &52 \ Object 6: tree 0 has coordinates (&C800, &0000, &5200) EQUB &D2 \ Object 6: tree 1 has coordinates (&2A00, &0000, &D200) EQUB &9A \ Object 6: tree 2 has coordinates (&CF00, &0000, &9A00) EQUB &C9 \ Object 6: tree 3 has coordinates (&8200, &0000, &C900) EQUB &3E \ Object 6: tree 4 has coordinates (&1D00, &0000, &3E00) EQUB &33 \ Object 6: tree 5 has coordinates (&7500, &0000, &3300) EQUB &8A \ Object 6: tree 6 has coordinates (&1A00, &0000, &8A00) EQUB &EC \ Object 6: tree 7 has coordinates (&CF00, &0000, &EC00) EQUB &C6 \ Object 7: tree 0 has coordinates (&9C00, &0000, &C600) EQUB &E2 \ Object 7: tree 1 has coordinates (&4300, &0000, &E200) EQUB &BA \ Object 7: tree 2 has coordinates (&E500, &0000, &BA00) EQUB &70 \ Object 7: tree 3 has coordinates (&8A00, &0000, &7000) EQUB &6E \ Object 7: tree 4 has coordinates (&EA00, &0000, &6E00) EQUB &44 \ Object 7: tree 5 has coordinates (&2200, &0000, &4400) EQUB &20 \ Object 7: tree 6 has coordinates (&6A00, &0000, &2000) EQUB &1B \ Object 7: tree 7 has coordinates (&C500, &0000, &1B00) EQUB &2F \ Object 8: hill 0 has coordinates (&1500, &0000, &2F00) EQUB &05 \ Object 8: hill 1 has coordinates (&C400, &0000, &0500) EQUB &05 \ Object 8: hill 2 has coordinates (&C400, &0000, &0500) EQUB &F5 \ Object 8: hill 3 has coordinates (&CE00, &0000, &F500) EQUB &F5 \ Object 8: hill 4 has coordinates (&CE00, &0000, &F500) EQUB &F5 \ Object 8: hill 5 has coordinates (&CE00, &0000, &F500) EQUB &2F \ Object 8: hill 6 has coordinates (&1500, &0000, &2F00) EQUB &D6 \ Object 8: hill 7 has coordinates (&1100, &0000, &D600) EQUB &1C \ Object 9: hill 0 has coordinates (&3800, &0000, &1C00) EQUB &2E \ Object 9: hill 1 has coordinates (&D500, &0000, &2E00) EQUB &2E \ Object 9: hill 2 has coordinates (&D500, &0000, &2E00) EQUB &D3 \ Object 9: hill 3 has coordinates (&DA00, &0000, &D300) EQUB &D3 \ Object 9: hill 4 has coordinates (&DA00, &0000, &D300) EQUB &D9 \ Object 9: hill 5 has coordinates (&0D00, &0000, &D900) EQUB &1C \ Object 9: hill 6 has coordinates (&3800, &0000, &1C00) EQUB &D9 \ Object 9: hill 7 has coordinates (&0D00, &0000, &D900) \ ****************************************************************************** \ \ Name: CheckTimePassed \ Type: Subroutine \ Category: Utility routines \ Summary: Flag whether 9 centiseconds have passed since the last call \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ P The current time, incrementing 100 times a second \ \ ------------------------------------------------------------------------------ \ \ Returns: \ \ C flag Returns: \ \ * Clear if fewer than 9 centiseconds have passed since \ the last call to this routine that reset the counter \ \ * Set if 9 centiseconds or more have passed since the \ last call to this routine, in which case the counter \ resets \ \ ****************************************************************************** .CheckTimePassed LDX P \ Set X = P TXA \ Set A = P - previousTime SEC \ SBC previousTime \ so A is the number of centiseconds that have passed \ since the last call to this routine (unless the time \ has wrapped, in which case it will be negative) BPL time1 \ If A is positive, skip the following three \ instructions EOR #&FF \ A is negative, so negate A using two's complement, so: CLC \ ADC #1 \ A = |P - previousTime| .time1 CMP #9 \ If |A| < 9, skip the following instruction BCC time2 STX previousTime \ Update previousTime to the current timer in X, so the \ count of 9 centiseconds since the last call can start \ again .time2 RTS \ Return from the subroutine EQUB &3D, &41 \ These bytes appear to be unused EQUB &42, &43 EQUB &77, &78 EQUB &44, &45 EQUB &46, &50 \ ****************************************************************************** \ \ Name: lineStartPointId \ Type: Variable \ Category: 3D geometry \ Summary: The point ID for a line's start point \ Deep dive: Lines and points \ \ ------------------------------------------------------------------------------ \ \ This table contains the point ID for each line's start point. \ \ The table is indexed by line ID, so for line ID X, lineStartPointId,X contains \ the point ID of the line's start point. \ \ ****************************************************************************** .lineStartPointId EQUB 31 \ Line ID 0 goes from point 31 to point 30 EQUB 1 \ Line ID 1 goes from point 1 to point 2 EQUB 2 \ Line ID 2 goes from point 2 to point 3 EQUB 3 \ Line ID 3 goes from point 3 to point 4 EQUB 4 \ Line ID 4 goes from point 4 to point 1 EQUB 6 \ Line ID 5 goes from point 6 to point 7 EQUB 8 \ Line ID 6 goes from point 8 to point 9 EQUB 10 \ Line ID 7 goes from point 10 to point 11 EQUB 12 \ Line ID 8 goes from point 12 to point 13 EQUB 14 \ Line ID 9 goes from point 14 to point 15 EQUB 16 \ Line ID 10 goes from point 16 to point 17 EQUB 18 \ Line ID 11 goes from point 18 to point 19 EQUB 114 \ Line ID 12 goes from point 114 to point 59 EQUB 115 \ Line ID 13 goes from point 115 to point 114 EQUB 41 \ Line ID 14 goes from point 41 to point 42 EQUB 42 \ Line ID 15 goes from point 42 to point 43 EQUB 43 \ Line ID 16 goes from point 43 to point 44 EQUB 45 \ Line ID 17 goes from point 45 to point 46 EQUB 46 \ Line ID 18 goes from point 46 to point 47 EQUB 47 \ Line ID 19 goes from point 47 to point 48 EQUB 49 \ Line ID 20 goes from point 49 to point 53 EQUB 50 \ Line ID 21 goes from point 50 to point 54 EQUB 51 \ Line ID 22 goes from point 51 to point 55 EQUB 52 \ Line ID 23 goes from point 52 to point 56 EQUB 59 \ Line ID 24 goes from point 59 to point 58 EQUB 58 \ Line ID 25 goes from point 58 to point 57 EQUB 57 \ Line ID 26 goes from point 57 to point 51 EQUB 52 \ Line ID 27 goes from point 52 to point 60 EQUB 60 \ Line ID 28 goes from point 60 to point 61 EQUB 49 \ Line ID 29 goes from point 49 to point 62 EQUB 50 \ Line ID 30 goes from point 50 to point 63 EQUB 62 \ Line ID 31 goes from point 62 to point 64 EQUB 63 \ Line ID 32 goes from point 63 to point 65 EQUB 64 \ Line ID 33 goes from point 64 to point 66 EQUB 115 \ Line ID 34 goes from point 115 to point 116 EQUB 116 \ Line ID 35 goes from point 116 to point 113 EQUB 112 \ Line ID 36 goes from point 112 to point 113 EQUB 111 \ Line ID 37 goes from point 111 to point 112 EQUB 61 \ Line ID 38 goes from point 61 to point 111 EQUB 65 \ Line ID 39 goes from point 65 to point 67 EQUB 66 \ Line ID 40 goes from point 66 to point 119 EQUB 67 \ Line ID 41 goes from point 67 to point 120 EQUB 119 \ Line ID 42 goes from point 119 to point 68 EQUB 120 \ Line ID 43 goes from point 120 to point 69 EQUB 68 \ Line ID 44 goes from point 68 to point 70 EQUB 69 \ Line ID 45 goes from point 69 to point 71 EQUB 70 \ Line ID 46 goes from point 70 to point 117 EQUB 80 \ Line ID 47 goes from point 80 to point 81 EQUB 82 \ Line ID 48 goes from point 82 to point 85 EQUB 83 \ Line ID 49 goes from point 83 to point 85 EQUB 84 \ Line ID 50 goes from point 84 to point 85 EQUB 71 \ Line ID 51 goes from point 71 to point 118 EQUB 117 \ Line ID 52 goes from point 117 to point 72 EQUB 118 \ Line ID 53 goes from point 118 to point 73 EQUB 72 \ Line ID 54 goes from point 72 to point 74 EQUB 73 \ Line ID 55 goes from point 73 to point 75 EQUB 89 \ Line ID 56 goes from point 89 to point 90 EQUB 91 \ Line ID 57 goes from point 91 to point 94 EQUB 92 \ Line ID 58 goes from point 92 to point 94 EQUB 93 \ Line ID 59 goes from point 93 to point 94 EQUB 95 \ Line ID 60 goes from point 95 to point 96 EQUB 97 \ Line ID 61 goes from point 97 to point 98 EQUB 74 \ Line ID 62 goes from point 74 to point 76 EQUB 75 \ Line ID 63 goes from point 75 to point 77 EQUB 76 \ Line ID 64 goes from point 76 to point 78 EQUB 77 \ Line ID 65 goes from point 77 to point 79 EQUB 78 \ Line ID 66 goes from point 78 to point 101 EQUB 79 \ Line ID 67 goes from point 79 to point 100 EQUB 101 \ Line ID 68 goes from point 101 to point 102 EQUB 100 \ Line ID 69 goes from point 100 to point 103 EQUB 102 \ Line ID 70 goes from point 102 to point 105 EQUB 103 \ Line ID 71 goes from point 103 to point 104 EQUB 105 \ Line ID 72 goes from point 105 to point 107 EQUB 104 \ Line ID 73 goes from point 104 to point 106 EQUB 107 \ Line ID 74 goes from point 107 to point 109 EQUB 106 \ Line ID 75 goes from point 106 to point 109 EQUB 213 \ Line ID 76 goes from point 213 to point 215 EQUB 109 \ Line ID 77 goes from point 109 to point 110 EQUB 121 \ Line ID 78 goes from point 121 to point 122 EQUB 177 \ Line ID 79 goes from point 177 to point 121 EQUB 122 \ Line ID 80 goes from point 122 to point 123 EQUB 177 \ Line ID 81 goes from point 177 to point 123 EQUB 144 \ Line ID 82 goes from point 144 to point 145 EQUB 146 \ Line ID 83 goes from point 146 to point 145 EQUB 146 \ Line ID 84 goes from point 146 to point 147 EQUB 148 \ Line ID 85 goes from point 148 to point 147 EQUB 144 \ Line ID 86 goes from point 144 to point 148 EQUB 157 \ Line ID 87 goes from point 157 to point 158 EQUB 158 \ Line ID 88 goes from point 158 to point 159 EQUB 159 \ Line ID 89 goes from point 159 to point 160 EQUB 157 \ Line ID 90 goes from point 157 to point 160 EQUB 164 \ Line ID 91 goes from point 164 to point 161 EQUB 163 \ Line ID 92 goes from point 163 to point 162 EQUB 162 \ Line ID 93 goes from point 162 to point 161 EQUB 164 \ Line ID 94 goes from point 164 to point 163 EQUB 165 \ Line ID 95 goes from point 165 to point 168 EQUB 165 \ Line ID 96 goes from point 165 to point 166 EQUB 166 \ Line ID 97 goes from point 166 to point 167 EQUB 167 \ Line ID 98 goes from point 167 to point 168 EQUB 169 \ Line ID 99 goes from point 169 to point 172 EQUB 169 \ Line ID 100 goes from point 169 to point 171 EQUB 142 \ Line ID 101 goes from point 142 to point 124 EQUB 172 \ Line ID 102 goes from point 172 to point 171 EQUB 176 \ Line ID 103 goes from point 176 to point 175 EQUB 175 \ Line ID 104 goes from point 175 to point 174 EQUB 176 \ Line ID 105 goes from point 176 to point 173 EQUB 173 \ Line ID 106 goes from point 173 to point 174 EQUB 150 \ Line ID 107 goes from point 150 to point 151 EQUB 152 \ Line ID 108 goes from point 152 to point 151 EQUB 152 \ Line ID 109 goes from point 152 to point 149 EQUB 149 \ Line ID 110 goes from point 149 to point 150 EQUB 139 \ Line ID 111 goes from point 139 to point 138 EQUB 137 \ Line ID 112 goes from point 137 to point 138 EQUB 136 \ Line ID 113 goes from point 136 to point 137 EQUB 136 \ Line ID 114 goes from point 136 to point 139 EQUB 132 \ Line ID 115 goes from point 132 to point 133 EQUB 132 \ Line ID 116 goes from point 132 to point 135 EQUB 135 \ Line ID 117 goes from point 135 to point 134 EQUB 133 \ Line ID 118 goes from point 133 to point 134 EQUB 128 \ Line ID 119 goes from point 128 to point 129 EQUB 129 \ Line ID 120 goes from point 129 to point 130 EQUB 130 \ Line ID 121 goes from point 130 to point 131 EQUB 128 \ Line ID 122 goes from point 128 to point 131 EQUB 203 \ Line ID 123 goes from point 203 to point 209 EQUB 125 \ Line ID 124 goes from point 125 to point 126 EQUB 127 \ Line ID 125 goes from point 127 to point 126 EQUB 125 \ Line ID 126 goes from point 125 to point 127 EQUB 140 \ Line ID 127 goes from point 140 to point 141 EQUB 141 \ Line ID 128 goes from point 141 to point 124 EQUB 140 \ Line ID 129 goes from point 140 to point 143 EQUB 143 \ Line ID 130 goes from point 143 to point 142 EQUB 153 \ Line ID 131 goes from point 153 to point 154 EQUB 154 \ Line ID 132 goes from point 154 to point 155 EQUB 155 \ Line ID 133 goes from point 155 to point 156 EQUB 156 \ Line ID 134 goes from point 156 to point 153 EQUB 178 \ Line ID 135 goes from point 178 to point 179 EQUB 178 \ Line ID 136 goes from point 178 to point 180 EQUB 179 \ Line ID 137 goes from point 179 to point 180 EQUB 183 \ Line ID 138 goes from point 183 to point 184 EQUB 183 \ Line ID 139 goes from point 183 to point 185 EQUB 184 \ Line ID 140 goes from point 184 to point 185 EQUB 183 \ Line ID 141 goes from point 183 to point 186 EQUB 186 \ Line ID 142 goes from point 186 to point 184 EQUB 186 \ Line ID 143 goes from point 186 to point 185 EQUB 188 \ Line ID 144 goes from point 188 to point 189 EQUB 188 \ Line ID 145 goes from point 188 to point 190 EQUB 189 \ Line ID 146 goes from point 189 to point 190 EQUB 188 \ Line ID 147 goes from point 188 to point 191 EQUB 191 \ Line ID 148 goes from point 191 to point 189 EQUB 191 \ Line ID 149 goes from point 191 to point 190 EQUB 108 \ Line ID 150 goes from point 108 to point 215 EQUB 193 \ Line ID 151 goes from point 193 to point 194 EQUB 193 \ Line ID 152 goes from point 193 to point 195 EQUB 194 \ Line ID 153 goes from point 194 to point 195 EQUB 193 \ Line ID 154 goes from point 193 to point 196 EQUB 196 \ Line ID 155 goes from point 196 to point 194 EQUB 196 \ Line ID 156 goes from point 196 to point 195 EQUB 193 \ Line ID 157 goes from point 193 to point 200 EQUB 194 \ Line ID 158 goes from point 194 to point 198 EQUB 197 \ Line ID 159 goes from point 197 to point 198 EQUB 199 \ Line ID 160 goes from point 199 to point 200 EQUB 86 \ Line ID 161 goes from point 86 to point 88 EQUB 99 \ Line ID 162 goes from point 99 to point 87 EQUB 39 \ Line ID 163 goes from point 39 to point 201 EQUB 202 \ Line ID 164 goes from point 202 to point 40 EQUB 22 \ Line ID 165 goes from point 22 to point 25 EQUB 21 \ Line ID 166 goes from point 21 to point 28 EQUB 26 \ Line ID 167 goes from point 26 to point 23 EQUB 27 \ Line ID 168 goes from point 27 to point 24 EQUB 201 \ Line ID 169 goes from point 201 to point 88 EQUB 88 \ Line ID 170 goes from point 88 to point 99 EQUB 202 \ Line ID 171 goes from point 202 to point 99 EQUB 201 \ Line ID 172 goes from point 201 to point 202 EQUB 25 \ Line ID 173 goes from point 25 to point 28 EQUB 25 \ Line ID 174 goes from point 25 to point 26 EQUB 26 \ Line ID 175 goes from point 26 to point 27 EQUB 27 \ Line ID 176 goes from point 27 to point 28 EQUB 22 \ Line ID 177 goes from point 22 to point 29 EQUB 33 \ Line ID 178 goes from point 33 to point 40 EQUB 205 \ Line ID 179 goes from point 205 to point 34 EQUB 40 \ Line ID 180 goes from point 40 to point 37 EQUB 205 \ Line ID 181 goes from point 205 to point 38 EQUB 207 \ Line ID 182 goes from point 207 to point 209 EQUB 209 \ Line ID 183 goes from point 209 to point 210 EQUB 210 \ Line ID 184 goes from point 210 to point 208 EQUB 207 \ Line ID 185 goes from point 207 to point 208 EQUB 210 \ Line ID 186 goes from point 210 to point 204 EQUB 208 \ Line ID 187 goes from point 208 to point 206 EQUB 205 \ Line ID 188 goes from point 205 to point 207 EQUB 35 \ Line ID 189 goes from point 35 to point 211 EQUB 212 \ Line ID 190 goes from point 212 to point 211 EQUB 36 \ Line ID 191 goes from point 36 to point 211 EQUB 214 \ Line ID 192 goes from point 214 to point 215 EQUB &6C, &03 \ These bytes appear to be unused EQUB &0A, &20 EQUB &20, &20 EQUB &20, &00 EQUB &00 \ ****************************************************************************** \ \ Name: alienScore \ Type: Variable \ Category: The Theme \ Summary: The scores for killing aliens in the various feeding stages \ \ ****************************************************************************** .alienScore EQUB &05 \ The score for a large feeding alien (50 points) EQUB &10 \ The score for a medium feeding alien (100 points) EQUB &15 \ The score for a small feeding alien (150 points) EQUB &25 \ The score for the smallest feeding alien (250 points) EQUB &00 \ This byte appears to be unused \ ****************************************************************************** \ \ Name: dormantAlienScore \ Type: Variable \ Category: The Theme \ Summary: The score for killing a dormant alien \ \ ****************************************************************************** .dormantAlienScore EQUB &40 \ The score for a dormant alien (400 points) \ ****************************************************************************** \ \ Name: sinLo \ Type: Variable \ Category: Maths \ Summary: Low byte of the sine lookup table \ Deep dive: Trigonometry \ \ ------------------------------------------------------------------------------ \ \ This table contains sine values for a quarter of a circle, i.e. for the range \ 0 to 90 degrees, or 0 to PI/2 radians. The table contains values for indexes \ 0 to 256, which cover the quarter from 0 to PI/2 radians. Entry X in the table \ is therefore (X / 256) * (PI / 2) radians of the way round the quarter circle, \ so the table at index X contains the sine of this value. \ \ The value of sine across the quarter circle ranges from 0 to 1: \ \ sin(0) = 0 \ \ sin(90) = sin(PI/2) = 1 \ \ but assembly language doesn't support fractions, so instead we store the sine \ in a 16-bit number that contains the sine multiplied by 65536, so the range of \ (sinHi sinLo) over the course of the quarter circle is 0 to 65536. It might \ help to think of sinHi as an integer ranging from 0 to 256 across the quarter \ circle, with sinLo as the fractional part ranging from 0 to 255; this is the \ approach taken in the Sine16Bit routine. \ \ In other words, entry X in this table contains sin(X) * 65536, where X ranges \ from 0 to 256 over the course of a quarter circle. \ \ ****************************************************************************** .sinLo FOR I%, 0, 256 N = SIN((I% / 256) * (PI / 2)) IF N >= 1 B% = 255 ELSE B% = LO(INT(N * 65536)) ENDIF EQUB B% NEXT \ ****************************************************************************** \ \ Name: sinHi \ Type: Variable \ Category: Maths \ Summary: High byte of the sine lookup table \ Deep dive: Trigonometry \ \ ------------------------------------------------------------------------------ \ \ See sinLo for an explanation of this table. \ \ ****************************************************************************** .sinHi FOR I%, 0, 256 N = SIN((I% / 256) * (PI / 2)) IF N >= 1 B% = 255 ELSE B% = HI(INT(N * 65536)) ENDIF EQUB B% NEXT \ ****************************************************************************** \ \ Name: alienSlot \ Type: Variable \ Category: The Theme \ Summary: Slots for up to three aliens that are ready to start moving \ towards the town \ \ ------------------------------------------------------------------------------ \ \ Each slot contains the following: \ \ * If the slot contains a negative number, then the slot is empty \ \ * Otherwise the slot contains the number of the alien in that slot (0 to 7) \ \ Each slot is associated with one of the alien objects: the first slot is for \ object ID 30, the second for object ID 31, and the third for object ID 32. \ \ We refer to these as slots 30, 31 and 32. Note that each alien in a slot has \ its own object associated with it (via the alienObjectId table) that is used \ for displaying the alien, but when an alien finishes feeding and starts to \ get ready for flying, it is also associated with the special alien objects \ 30 to 33. \ \ Slot 30 contains dormant aliens, slots 31 and 32 contain feeding aliens and \ those that are waiting for slot 33 to free up, and slot 33 (at alienToMove) \ contains the alien that's moving towards the town (only one alien can attack \ at any one time). \ \ ****************************************************************************** .alienSlot EQUB &6C \ The alien number associated with object ID 30 EQUB &6B \ The alien number associated with object ID 31 EQUB &6A \ The alien number associated with object ID 32 \ \ Zeroed in ResetVariables \ ****************************************************************************** \ \ Name: alienToMove \ Type: Variable \ Category: The Theme \ Summary: The number of the alien to move towards Acornsville in this \ iteration of the main loop \ \ ------------------------------------------------------------------------------ \ \ This slot contains the following: \ \ * If the slot contains a negative number, then the slot is empty \ \ * Otherwise the slot contains the number of the alien in that slot (0 to 7) \ \ This slot is reserved for the alien that is moving towards the town, which is \ the only alien shown on the radar. \ \ ****************************************************************************** .alienToMove EQUB &69 \ The alien number associated with object ID 33 \ \ Zeroed in ResetVariables \ ****************************************************************************** \ \ Name: mainLoopCounter \ Type: Variable \ Category: Main loop \ Summary: The main loop counter \ Deep dive: Scheduling tasks in the main loop \ \ ****************************************************************************** .mainLoopCounter EQUB &67 \ The main loop counter, which is incremented every \ iteration of the main loop \ ****************************************************************************** \ \ Name: numberOfLines \ Type: Variable \ Category: 3D geometry \ Summary: The total number of lines in Aviator's 3D world \ \ ****************************************************************************** .numberOfLines EQUB 193 \ The total number of lines in the world \ ****************************************************************************** \ \ Name: alienObjectId \ Type: Variable \ Category: The Theme \ Summary: Object IDs for each of the eight aliens \ \ ------------------------------------------------------------------------------ \ \ Contains the object ID for each of the eight aliens. A negative entry denotes \ that the alien has been destroyed, a positive entry denotes that the alien is \ still around, with the entry containing the object ID we're using for that \ alien. \ \ This is called FLDPTR in the original source code. \ \ ****************************************************************************** .alienObjectId EQUB &65, &64 \ Zeroed in ResetVariables EQUB &62, &61 EQUB &60, &5F EQUB &5E, &5C \ ****************************************************************************** \ \ Name: alienState \ Type: Variable \ Category: The Theme \ Summary: The state of each of the eight aliens \ Deep dive: Alien feeding and growth patterns \ \ ------------------------------------------------------------------------------ \ \ All eight aliens start in the dormant state (state 0). They effectively live \ in alien slot 30, which uses an object group to manage all the alien objects. \ \ Dormant aliens are promoted to state 1 in the UpdateAliens routine. One alien \ will be promoted and will start feeding once there is a vacancy in one of the \ alien slots 31 and 32. A vacancy is produced when an alien is promoted into \ alien slot 33 (preparing to take off), or when a feeding alien is destroyed. \ \ Once an alien is in state 1, the state gets incremented in the UpdateAliens \ routine on 2 of every 256 main loop iterations, until it reaches 22. \ \ An alien is only promoted from state 22 if slot 33 becomes vacant, at which \ point it is bumped up to state 23 (preparing to take off). \ \ Once an alien is in state 23, the state gets incremented in the UpdateAliens \ routine on 2 of every 256 main loop iterations, until it reaches 27, at which \ point it will take off and head for the town (see the AlienInAcornsville \ routine), before eventually descending to the town at state 28. \ \ 0 Dormant \ \ 1-7 Feeding stage 1 \ \ 8-11 Feeding stage 2 \ \ 12-15 Feeding stage 3 \ \ 16-21 Feeding stage 4 \ \ 22 The alien has finished feeding and is ready to move to the next \ stage (preparing to take off). As there can be only one alien \ flying towards the town at any one time, we have to wait for the \ attack slot (slot 33) to become vacant, at which point an alien in \ this state will be put into slot 33, and its state bumped up to 23 \ \ 23-26 Preparing to take off \ \ 27 Flying towards Acornsville \ \ 28 Descending towards Acornsville for the final attack \ \ ****************************************************************************** .alienState EQUB &5B, &5A \ Zeroed in ResetVariables EQUB &59, &58 EQUB &7D, &7C EQUB &7B, &7A \ ****************************************************************************** \ \ Name: alienStatus \ Type: Variable \ Category: The Theme \ Summary: Storage for the object status bytes for the four alien objects \ \ ****************************************************************************** .alienStatus EQUB &78, &77 EQUB &76, &75 EQUB &74, &72 \ These bytes appear to be unused EQUB &71, &70 \ ****************************************************************************** \ \ Name: matrix1Lo \ Type: Variable \ Category: Maths \ Summary: The low bytes of matrix 1 \ \ ------------------------------------------------------------------------------ \ \ If bit 0 is set in the low byte of a matrix entry, then it is negative. \ \ ****************************************************************************** .matrix1Lo EQUB &31, &39, &0D EQUB &06, &18, &10 EQUB &20, &20, &20 \ ****************************************************************************** \ \ Name: matrix2Lo \ Type: Variable \ Category: Maths \ Summary: The low bytes of matrix 2 \ \ ------------------------------------------------------------------------------ \ \ If bit 0 is set in the low byte of a matrix entry, then it is negative. \ \ ****************************************************************************** .matrix2Lo EQUB &20, &20, &20 EQUB &4C, &44, &59 EQUB &23, &33, &31 \ ****************************************************************************** \ \ Name: matrix3Lo \ Type: Variable \ Category: Maths \ Summary: The low bytes of matrix 3 \ \ ------------------------------------------------------------------------------ \ \ If bit 0 is set in the low byte of a matrix entry, then it is negative. \ \ ****************************************************************************** .matrix3Lo EQUB &0D, &06, &00 EQUB &1D, &2E, &00 EQUB &00, &00, &FE \ ****************************************************************************** \ \ Name: matrix4Lo \ Type: Variable \ Category: Maths \ Summary: The low bytes of matrix 4 \ \ ------------------------------------------------------------------------------ \ \ If bit 0 is set in the low byte of a matrix entry, then it is negative. \ \ ****************************************************************************** .matrix4Lo EQUB &20, &4C, &44 EQUB &58, &20, &50 EQUB &00, &52, &4E \ ****************************************************************************** \ \ Name: CheckFlyingSkills (Part 1 of 2) \ Type: Subroutine \ Category: Scoring \ Summary: Check whether we are performing one of the tests of flying skill \ Deep dive: Flying skills \ \ ------------------------------------------------------------------------------ \ \ This routine checks where we are, and awards the following points: \ \ * 50 points for flying under the bridge the right way up \ \ * 100 points for flying under the bridge upside down \ \ * 100 points for flying down the main street of Acornsville the right way up \ \ * 200 points for flying down the main street of Acornsville upside down \ \ There are multiple skill zones defined for each object (the bridge and the \ main street). To earn the points, we need to fly into the correct skill zones \ while avoiding others, and then back out again without hitting the ground. \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ Y The skill to check: \ \ * 2 = Flying under the suspension bridge \ \ * 34 = Flying down the main street of Acornsville \ \ ****************************************************************************** .CheckFlyingSkills LDA xPlaneHi \ Set A = xPlaneHi - the x-coordinate of either the SEC \ bridge or town, which is the high byte of the distance SBC xObjectHi,Y \ along the x-axis between the plane and the object that \ we are checking BPL skil1 \ If A is negative, flip the bits, so A contains the EOR #&FF \ absolute value of the distance along the x-axis .skil1 CMP #5 \ If the high byte of the distance is less than 5, then BCC skil3 \ we are close enough to the bridge or town to warrant \ further checks, so jump to skil3 .skil2 RTS \ Otherwise we are too far away from the bridge and town \ to score any points, so return from the subroutine .skil3 LDA zPlaneHi \ Set A = zPlaneHi - the z-coordinate of either the SEC \ bridge or town, which is the high byte of the distance SBC zObjectHi,Y \ along the z-axis between the plane and the object that \ we are checking JMP skil4 \ Jump down to part 2 of the routine EQUB &4D, &50 \ These bytes appear to be unused \ ****************************************************************************** \ \ Name: matrix1Hi \ Type: Variable \ Category: Maths \ Summary: The high bytes of matrix 1 \ \ ------------------------------------------------------------------------------ \ \ If bit 0 is set in the low byte of a matrix entry, then it is negative. \ \ ****************************************************************************** .matrix1Hi EQUB &B2, &32, &AF EQUB &0B, &F2, &51 EQUB &B6, &40, &A7 \ ****************************************************************************** \ \ Name: matrix2Hi \ Type: Variable \ Category: Maths \ Summary: The high bytes of matrix 2 \ \ ------------------------------------------------------------------------------ \ \ If bit 0 is set in the low byte of a matrix entry, then it is negative. \ \ ****************************************************************************** .matrix2Hi EQUB &B2, &0B, &B6 EQUB &32, &F2, &40 EQUB &AF, &51, &A7 \ ****************************************************************************** \ \ Name: matrix3Hi \ Type: Variable \ Category: Maths \ Summary: The high bytes of matrix 3 \ \ ------------------------------------------------------------------------------ \ \ If bit 0 is set in the low byte of a matrix entry, then it is negative. \ \ ****************************************************************************** .matrix3Hi EQUB &FA, &34, &00 EQUB &34, &FA, &00 EQUB &00, &00, &FF \ ****************************************************************************** \ \ Name: matrix4Hi \ Type: Variable \ Category: Maths \ Summary: The high bytes of matrix 4 \ \ ------------------------------------------------------------------------------ \ \ If bit 0 is set in the low byte of a matrix entry, then it is negative. \ \ ****************************************************************************** .matrix4Hi EQUB &FA, &32, &0D EQUB &34, &F2, &3F EQUB &00, &40, &F7 \ ****************************************************************************** \ \ Name: CheckFlyingSkills (Part 2 of 2) \ Type: Subroutine \ Category: Scoring \ Summary: Perform finer checks to see if we are flying under the bridge or \ along the main street of the town \ Deep dive: Flying skills \ \ ****************************************************************************** .skil4 BPL skil5 \ If A is negative, flip the bits, so A contains the EOR #&FF \ absolute value of the distance along the z-axis .skil5 CMP #5 \ If the high byte of the distance is 5 or more, then BCS skil2 \ we are too far away from the bridge or town to warrant \ further checks, so jump to skil2 to return from the \ subroutine \ If we get here then we are within a distance of &0500 \ from the place we need to check, in both the x-axis \ and z-axis, so now we need to check exactly where we \ are CPY #2 \ If Y = 2, then we are checking against the suspension BEQ skil7 \ bridge, so jump to skil7 \ If we get here then we are checking where we are \ compared to the town LDA #12 \ If we are inside skill zone 12, jump to skil8 to crash JSR CheckBridgeAndTown \ the plane BCS skil8 LDA #15 \ If we are inside skill zone 15, jump to skil8 to crash JSR CheckBridgeAndTown \ the plane BCS skil8 LDA #9 \ If we are inside skill zone 9, jump to skil8 to crash JSR CheckBridgeAndTown \ the plane BCS skil8 LDA #18 \ If we are outside skill zone 18, jump to slip9 to add JSR CheckBridgeAndTown \ any awarded points to our score BCC skil9 \ If we get here then we are: \ \ * Outside skill zone 12 \ * Outside skill zone 15 \ * Outside skill zone 9 \ * Inside skill zone 18 \ \ This means we are flying along the main street of the \ town, so we have earned ourselves some points, to be \ awarded when we exit skill zone 18 LDA #&20 \ Set A = &20 so we award 200 points for flying down \ main street while upside down LDX row25_char13_1 \ If there is a line at the bottom of the artificial BNE skil10 \ horizon indicator, then the plane is upside down, so \ jump to skil10 to bank these points in pointsToAward \ and return from the subroutine .skil6 LDA #&10 \ Set A = &10 so we award 100 points for flying down \ main street the right way up BNE skil10 \ Jump to skil10 to bank these points in pointsToAward \ and return from the subroutine .skil7 \ If we get here then we are checking where we are \ compared to the suspension bridge LDA #6 \ If we are outside skill zone 6, jump to slip9 to add JSR CheckBridgeAndTown \ any awarded points to our score BCC skil9 LDA #3 \ If we are inside skill zone 3, jump to slip9 to add JSR CheckBridgeAndTown \ any awarded points to our score BCS skil9 LDA #0 \ If we are outside skill zone 0, jump to skil8 to crash JSR CheckBridgeAndTown \ the plane BCC skil8 \ If we get here then we are: \ \ * Inside skill zone 6 \ * Outside skill zone 3 \ * Inside skill zone 0 \ \ This means we are flying under the bridge, so we have \ earned ourselves some points, to be awarded when we \ exit skill zone 6 or enter skill zone 3 LDX row25_char13_1 \ If there is a line at the bottom of the artificial BNE skil6 \ horizon indicator, then the plane is upside down, so \ jump to skil6 to award 100 points for flying under the \ bridge upside down LDA #5 \ Set A = 5 so we award 50 points for flying under the \ bridge the right way up BNE skil10 \ Jump to skil10 to bank these points in pointsToAward \ and return from the subroutine .skil8 TSX \ Add four bytes to the top of the stack, so they can be TXA \ stripped away in Crash routine, along with the two SEC \ bytes currently on the stack SBC #4 TAX TXS JMP Crash \ Jump to the Crash routine as we have just crashed .skil9 LDA pointsToAward \ We get here when we are outside the skill zone, so BEQ skil11 \ check whether we have any points to award, and if \ not, jump to skil11 to return from the subroutine \ If we get here then we have earned ourselves some \ points and have exited the skill zone, so it's time \ to award those points and give a celebratory beep LDX #0 \ Add A * 10 points to the score and make a beep by JSR ScorePoints \ calling ScorePoints with (X A) = (0 A) = A LDA #0 \ Set A = 0 so we set pointsToAward to zero below, so \ we don't award any more points until they are earned .skil10 STA pointsToAward \ Store any points we've earned in pointsToAward, so \ they get awarded when we leave the skill zone (or, if \ A = 0, this does nothing) .skil11 RTS \ Return from the subroutine EQUB &20 \ This byte appears to be unused \ ****************************************************************************** \ \ Name: scaleFactor \ Type: Variable \ Category: Flight model \ Summary: Scale factors for the flight forces (in signed powers of 2) \ Deep dive: The flight model \ \ ****************************************************************************** .scaleFactor EQUB &00 \ xMoments is scaled by 2^0 = 1 EQUB &FE \ yMoments is scaled by 2^-2 = 1/4 EQUB &FF \ zMoments is scaled by 2^-1 = 1/2 EQUB &01 \ xLiftDrag is scaled by 2^1 = 2 EQUB &04 \ yLiftDrag is scaled by 2^4 = 16 EQUB &00 \ zLiftDrag is scaled by 2^0 = 1 EQUB &FB \ zSlipMoment is scaled by 2^-5 = 1/32 EQUB &02 \ yFlapsLift is scaled by 2^2 = 4 EQUB &33 \ Unused EQUB &3A \ Unused EQUB &FF \ xControls is scaled by 2^-1 = 1/2 EQUB &FE \ yControls is scaled by 2^-2 = 1/4 EQUB &FE \ zControls is scaled by 2^-2 = 1/4 EQUB &23, &31, &38 \ These bytes appear to be unused \ ****************************************************************************** \ \ Name: lowNibble \ Type: Variable \ Category: Maths \ Summary: Lookup table for the low nibble of a value \ Deep dive: Times tables and nibble arithmetic \ \ ------------------------------------------------------------------------------ \ \ In the table below, lowNibble,X contains the low nibble of X: \ \ X AND %00001111 \ \ ****************************************************************************** .lowNibble FOR I%, 0, 255 EQUB I% AND %00001111 NEXT \ ****************************************************************************** \ \ Name: xObjectLo \ Type: Variable \ Category: 3D geometry \ Summary: Low byte of the x-coordinate for an object \ Deep dive: 3D objects \ Placing objects on the map \ \ ------------------------------------------------------------------------------ \ \ The low byte of the x-coordinate for the object with ID X is at xObjectLo,X. \ \ This is called XALO in the original source code. \ \ ****************************************************************************** .xObjectLo EQUB &23 \ Object 0 has dynamic coordinates EQUB &66 \ Object 1 has fixed coordinates (&C666, &0000, &445C) EQUB &18 \ Object 2 has fixed coordinates (&4B18, &0000, &8666) EQUB &DD \ Object 3 has fixed coordinates (&45DD, &0000, &6333) EQUB &33 \ Object 4 has fixed coordinates (&5333, &0000, &C000) EQUB &EF \ Object 5 has fixed coordinates (&8EEF, &0000, &C111) EQUB &00 \ Object 6 is a group, coords are in x/zGroupObjectHi EQUB &00 \ Object 7 is a group, coords are in x/zGroupObjectHi EQUB &00 \ Object 8 is a group, coords are in x/zGroupObjectHi EQUB &00 \ Object 9 is a group, coords are in x/zGroupObjectHi EQUB &4F \ Object 10 is unused EQUB &58 \ Object 11 is unused EQUB &00 \ Object 12 has dynamic coordinates EQUB &00 \ Object 13 has dynamic coordinates EQUB &00 \ Object 14 has dynamic coordinates EQUB &00 \ Object 15 has dynamic coordinates EQUB &EE \ Object 16 has fixed coordinates (&8EEE, &0000, &EAA6) EQUB &AA \ Object 17 has fixed coordinates (&EAAA, &0000, &D552) EQUB &88 \ Object 18 has fixed coordinates (&0888, &0000, &6555) EQUB &55 \ Object 19 has fixed coordinates (&2555, &0000, &E999) EQUB &77 \ Object 20 has fixed coordinates (&5777, &0000, &E555) EQUB &33 \ Object 21 has fixed coordinates (&1333, &0000, &ABBC) EQUB &77 \ Object 22 has fixed coordinates (&8777, &0000, &9556) EQUB &33 \ Object 23 has fixed coordinates (&E333, &0000, &9999) EQUB &66 \ Object 24 has fixed coordinates (&8666, &0000, &4DF8) EQUB &88 \ Object 25 has fixed coordinates (&D888, &0000, &0777) EQUB &DE \ Object 26 has fixed coordinates (&EDDE, &0000, &4111) EQUB &66 \ Object 27 has fixed coordinates (&4666, &0000, &2CCD) EQUB &66 \ Object 28 has fixed coordinates (&8666, &0000, &0555) EQUB &55 \ Object 29 has fixed coordinates (&B555, &0000, &7444) EQUB &00 \ Object 30 has dynamic coordinates EQUB &00 \ Object 31 has dynamic coordinates EQUB &00 \ Object 32 has dynamic coordinates EQUB &00 \ Object 33 has dynamic coordinates EQUB &40 \ Object 34 has fixed coordinates (&0440, &0000, &0340) EQUB &23 \ Object 35 is unused EQUB &33 \ Object 36 is unused EQUB &31 \ Object 37 is unused EQUB &3A \ Object 38 is unused EQUB &42 \ Object 39 is unused \ ****************************************************************************** \ \ Name: yObjectLo \ Type: Variable \ Category: 3D geometry \ Summary: Low byte of the y-coordinate for an object \ Deep dive: 3D objects \ Placing objects on the map \ \ ------------------------------------------------------------------------------ \ \ The low byte of the y-coordinate for the object with ID X is at yObjectLo,X. \ \ ****************************************************************************** .yObjectLo EQUB &43 \ Object 0 has dynamic coordinates EQUB &00 \ Object 1 has fixed coordinates (&C666, &0000, &445C) EQUB &00 \ Object 2 has fixed coordinates (&4B18, &0000, &8666) EQUB &00 \ Object 3 has fixed coordinates (&45DD, &0000, &6333) EQUB &00 \ Object 4 has fixed coordinates (&5333, &0000, &C000) EQUB &00 \ Object 5 has fixed coordinates (&8EEF, &0000, &C111) EQUB &00 \ Object 6 is a group, coords are in x/zGroupObjectHi EQUB &00 \ Object 7 is a group, coords are in x/zGroupObjectHi EQUB &00 \ Object 8 is a group, coords are in x/zGroupObjectHi EQUB &00 \ Object 9 is a group, coords are in x/zGroupObjectHi EQUB &27 \ Object 10 is unused EQUB &20 \ Object 11 is unused EQUB &00 \ Object 12 has dynamic coordinates EQUB &00 \ Object 13 has dynamic coordinates EQUB &00 \ Object 14 has dynamic coordinates EQUB &00 \ Object 15 has dynamic coordinates EQUB &00 \ Object 16 has fixed coordinates (&8EEE, &0000, &EAA6) EQUB &00 \ Object 17 has fixed coordinates (&EAAA, &0000, &D552) EQUB &00 \ Object 18 has fixed coordinates (&0888, &0000, &6555) EQUB &00 \ Object 19 has fixed coordinates (&2555, &0000, &E999) EQUB &00 \ Object 20 has fixed coordinates (&5777, &0000, &E555) EQUB &00 \ Object 21 has fixed coordinates (&1333, &0000, &ABBC) EQUB &00 \ Object 22 has fixed coordinates (&8777, &0000, &9556) EQUB &00 \ Object 23 has fixed coordinates (&E333, &0000, &9999) EQUB &00 \ Object 24 has fixed coordinates (&8666, &0000, &4DF8) EQUB &00 \ Object 25 has fixed coordinates (&D888, &0000, &0777) EQUB &00 \ Object 26 has fixed coordinates (&EDDE, &0000, &4111) EQUB &00 \ Object 27 has fixed coordinates (&4666, &0000, &2CCD) EQUB &00 \ Object 28 has fixed coordinates (&8666, &0000, &0555) EQUB &00 \ Object 29 has fixed coordinates (&B555, &0000, &7444) EQUB &00 \ Object 30 has dynamic coordinates EQUB &00 \ Object 31 has dynamic coordinates EQUB &00 \ Object 32 has dynamic coordinates EQUB &00 \ Object 33 has dynamic coordinates EQUB &00 \ Object 34 has fixed coordinates (&0440, &0000, &0340) EQUB &23 \ Object 35 is unused EQUB &34 \ Object 36 is unused EQUB &3A \ Object 37 is unused EQUB &4A \ Object 38 is unused EQUB &4D \ Object 39 is unused \ ****************************************************************************** \ \ Name: zObjectLo \ Type: Variable \ Category: 3D geometry \ Summary: Low byte of the z-coordinate for an object \ Deep dive: 3D objects \ Placing objects on the map \ \ ------------------------------------------------------------------------------ \ \ The low byte of the y-coordinate for the object with ID X is at zObjectLo,X. \ \ ****************************************************************************** .zObjectLo EQUB &50 \ Object 0 has dynamic coordinates EQUB &5C \ Object 1 has fixed coordinates (&C666, &0000, &445C) EQUB &66 \ Object 2 has fixed coordinates (&4B18, &0000, &8666) EQUB &33 \ Object 3 has fixed coordinates (&45DD, &0000, &6333) EQUB &00 \ Object 4 has fixed coordinates (&5333, &0000, &C000) EQUB &11 \ Object 5 has fixed coordinates (&8EEF, &0000, &C111) EQUB &00 \ Object 6 is a group, coords are in x/zGroupObjectHi EQUB &00 \ Object 7 is a group, coords are in x/zGroupObjectHi EQUB &00 \ Object 8 is a group, coords are in x/zGroupObjectHi EQUB &00 \ Object 9 is a group, coords are in x/zGroupObjectHi EQUB &2E \ Object 10 is unused EQUB &73 \ Object 11 is unused EQUB &00 \ Object 12 has dynamic coordinates EQUB &00 \ Object 13 has dynamic coordinates EQUB &00 \ Object 14 has dynamic coordinates EQUB &00 \ Object 15 has dynamic coordinates EQUB &A6 \ Object 16 has fixed coordinates (&8EEE, &0000, &EAA6) EQUB &52 \ Object 17 has fixed coordinates (&EAAA, &0000, &D552) EQUB &55 \ Object 18 has fixed coordinates (&0888, &0000, &6555) EQUB &99 \ Object 19 has fixed coordinates (&2555, &0000, &E999) EQUB &55 \ Object 20 has fixed coordinates (&5777, &0000, &E555) EQUB &BC \ Object 21 has fixed coordinates (&1333, &0000, &ABBC) EQUB &56 \ Object 22 has fixed coordinates (&8777, &0000, &9556) EQUB &99 \ Object 23 has fixed coordinates (&E333, &0000, &9999) EQUB &F8 \ Object 24 has fixed coordinates (&8666, &0000, &4DF8) EQUB &77 \ Object 25 has fixed coordinates (&D888, &0000, &0777) EQUB &11 \ Object 26 has fixed coordinates (&EDDE, &0000, &4111) EQUB &CD \ Object 27 has fixed coordinates (&4666, &0000, &2CCD) EQUB &55 \ Object 28 has fixed coordinates (&8666, &0000, &0555) EQUB &44 \ Object 29 has fixed coordinates (&B555, &0000, &7444) EQUB &00 \ Object 30 has dynamic coordinates EQUB &00 \ Object 31 has dynamic coordinates EQUB &00 \ Object 32 has dynamic coordinates EQUB &00 \ Object 33 has dynamic coordinates EQUB &40 \ Object 34 has fixed coordinates (&0440, &0000, &0340) EQUB &20 \ Object 35 is unused EQUB &20 \ Object 36 is unused EQUB &20 \ Object 37 is unused EQUB &20 \ Object 38 is unused EQUB &20 \ Object 39 is unused \ ****************************************************************************** \ \ Name: xObjectHi \ Type: Variable \ Category: 3D geometry \ Summary: High byte of the x-coordinate for an object \ Deep dive: 3D objects \ Placing objects on the map \ \ ------------------------------------------------------------------------------ \ \ The high byte of the x-coordinate for the object with ID X is at xObjectHi,X. \ \ This is called XAHI in the original source code. \ \ ****************************************************************************** .xObjectHi EQUB &20 \ Object 0 has dynamic coordinates EQUB &C6 \ Object 1 has fixed coordinates (&C666, &0000, &445C) EQUB &4B \ Object 2 has fixed coordinates (&4B18, &0000, &8666) EQUB &45 \ Object 3 has fixed coordinates (&45DD, &0000, &6333) EQUB &53 \ Object 4 has fixed coordinates (&5333, &0000, &C000) EQUB &8E \ Object 5 has fixed coordinates (&8EEF, &0000, &C111) EQUB &00 \ Object 6 is a group, coords are in x/zGroupObjectHi EQUB &00 \ Object 7 is a group, coords are in x/zGroupObjectHi EQUB &00 \ Object 8 is a group, coords are in x/zGroupObjectHi EQUB &00 \ Object 9 is a group, coords are in x/zGroupObjectHi EQUB &4D \ Object 10 is unused EQUB &50 \ Object 11 is unused EQUB &00 \ Object 12 has dynamic coordinates EQUB &00 \ Object 13 has dynamic coordinates EQUB &00 \ Object 14 has dynamic coordinates EQUB &00 \ Object 15 has dynamic coordinates EQUB &8E \ Object 16 has fixed coordinates (&8EEE, &0000, &EAA6) EQUB &EA \ Object 17 has fixed coordinates (&EAAA, &0000, &D552) EQUB &08 \ Object 18 has fixed coordinates (&0888, &0000, &6555) EQUB &25 \ Object 19 has fixed coordinates (&2555, &0000, &E999) EQUB &57 \ Object 20 has fixed coordinates (&5777, &0000, &E555) EQUB &13 \ Object 21 has fixed coordinates (&1333, &0000, &ABBC) EQUB &87 \ Object 22 has fixed coordinates (&8777, &0000, &9556) EQUB &E3 \ Object 23 has fixed coordinates (&E333, &0000, &9999) EQUB &86 \ Object 24 has fixed coordinates (&8666, &0000, &4DF8) EQUB &D8 \ Object 25 has fixed coordinates (&D888, &0000, &0777) EQUB &ED \ Object 26 has fixed coordinates (&EDDE, &0000, &4111) EQUB &46 \ Object 27 has fixed coordinates (&4666, &0000, &2CCD) EQUB &86 \ Object 28 has fixed coordinates (&8666, &0000, &0555) EQUB &B5 \ Object 29 has fixed coordinates (&B555, &0000, &7444) EQUB &00 \ Object 30 has dynamic coordinates EQUB &00 \ Object 31 has dynamic coordinates EQUB &00 \ Object 32 has dynamic coordinates EQUB &00 \ Object 33 has dynamic coordinates EQUB &04 \ Object 34 has fixed coordinates (&0440, &0000, &0340) EQUB &41 \ Object 35 is unused EQUB &20 \ Object 36 is unused EQUB &53 \ Object 37 is unused EQUB &49 \ Object 38 is unused EQUB &5A \ Object 39 is unused \ ****************************************************************************** \ \ Name: yObjectHi \ Type: Variable \ Category: 3D geometry \ Summary: High byte of the y-coordinate for an object \ Deep dive: 3D objects \ Placing objects on the map \ \ ------------------------------------------------------------------------------ \ \ The high byte of the y-coordinate for the object with ID X is at yObjectHi,X. \ \ ****************************************************************************** .yObjectHi EQUB &45 \ Object 0 has dynamic coordinates EQUB &00 \ Object 1 has fixed coordinates (&C666, &0000, &445C) EQUB &00 \ Object 2 has fixed coordinates (&4B18, &0000, &8666) EQUB &00 \ Object 3 has fixed coordinates (&45DD, &0000, &6333) EQUB &00 \ Object 4 has fixed coordinates (&5333, &0000, &C000) EQUB &00 \ Object 5 has fixed coordinates (&8EEF, &0000, &C111) EQUB &00 \ Object 6 is a group, coords are in x/zGroupObjectHi EQUB &00 \ Object 7 is a group, coords are in x/zGroupObjectHi EQUB &00 \ Object 8 is a group, coords are in x/zGroupObjectHi EQUB &00 \ Object 9 is a group, coords are in x/zGroupObjectHi EQUB &0D \ Object 10 is unused EQUB &07 \ Object 11 is unused EQUB &00 \ Object 12 has dynamic coordinates EQUB &00 \ Object 13 has dynamic coordinates EQUB &00 \ Object 14 has dynamic coordinates EQUB &00 \ Object 15 has dynamic coordinates EQUB &00 \ Object 16 has fixed coordinates (&8EEE, &0000, &EAA6) EQUB &00 \ Object 17 has fixed coordinates (&EAAA, &0000, &D552) EQUB &00 \ Object 18 has fixed coordinates (&0888, &0000, &6555) EQUB &00 \ Object 19 has fixed coordinates (&2555, &0000, &E999) EQUB &00 \ Object 20 has fixed coordinates (&5777, &0000, &E555) EQUB &00 \ Object 21 has fixed coordinates (&1333, &0000, &ABBC) EQUB &00 \ Object 22 has fixed coordinates (&8777, &0000, &9556) EQUB &00 \ Object 23 has fixed coordinates (&E333, &0000, &9999) EQUB &00 \ Object 24 has fixed coordinates (&8666, &0000, &4DF8) EQUB &00 \ Object 25 has fixed coordinates (&D888, &0000, &0777) EQUB &00 \ Object 26 has fixed coordinates (&EDDE, &0000, &4111) EQUB &00 \ Object 27 has fixed coordinates (&4666, &0000, &2CCD) EQUB &00 \ Object 28 has fixed coordinates (&8666, &0000, &0555) EQUB &00 \ Object 29 has fixed coordinates (&B555, &0000, &7444) EQUB &00 \ Object 30 has dynamic coordinates EQUB &00 \ Object 31 has dynamic coordinates EQUB &00 \ Object 32 has dynamic coordinates EQUB &00 \ Object 33 has dynamic coordinates EQUB &00 \ Object 34 has fixed coordinates (&0440, &0000, &0340) EQUB &38 \ Object 35 is unused EQUB &0D \ Object 36 is unused EQUB &07 \ Object 37 is unused EQUB &76 \ Object 38 is unused EQUB &1A \ Object 39 is unused \ ****************************************************************************** \ \ Name: zObjectHi \ Type: Variable \ Category: 3D geometry \ Summary: High byte of the z-coordinate for an object \ Deep dive: 3D objects \ Placing objects on the map \ \ ------------------------------------------------------------------------------ \ \ The high byte of the z-coordinate for the object with ID X is at zObjectHi,X. \ \ ****************************************************************************** .zObjectHi EQUB &2E \ Object 0 has dynamic coordinates EQUB &44 \ Object 1 has fixed coordinates (&C666, &0000, &445C) EQUB &86 \ Object 2 has fixed coordinates (&4B18, &0000, &8666) EQUB &63 \ Object 3 has fixed coordinates (&45DD, &0000, &6333) EQUB &C0 \ Object 4 has fixed coordinates (&5333, &0000, &C000) EQUB &C1 \ Object 5 has fixed coordinates (&8EEF, &0000, &C111) EQUB &00 \ Object 6 is a group, coords are in x/zGroupObjectHi EQUB &00 \ Object 7 is a group, coords are in x/zGroupObjectHi EQUB &00 \ Object 8 is a group, coords are in x/zGroupObjectHi EQUB &00 \ Object 9 is a group, coords are in x/zGroupObjectHi EQUB &5A \ Object 10 is unused EQUB &52 \ Object 11 is unused EQUB &00 \ Object 12 has dynamic coordinates EQUB &00 \ Object 13 has dynamic coordinates EQUB &00 \ Object 14 has dynamic coordinates EQUB &00 \ Object 15 has dynamic coordinates EQUB &EA \ Object 16 has fixed coordinates (&8EEE, &0000, &EAA6) EQUB &D5 \ Object 17 has fixed coordinates (&EAAA, &0000, &D552) EQUB &65 \ Object 18 has fixed coordinates (&0888, &0000, &6555) EQUB &E9 \ Object 19 has fixed coordinates (&2555, &0000, &E999) EQUB &E5 \ Object 20 has fixed coordinates (&5777, &0000, &E555) EQUB &AB \ Object 21 has fixed coordinates (&1333, &0000, &ABBC) EQUB &95 \ Object 22 has fixed coordinates (&8777, &0000, &9556) EQUB &99 \ Object 23 has fixed coordinates (&E333, &0000, &9999) EQUB &4D \ Object 24 has fixed coordinates (&8666, &0000, &4DF8) EQUB &07 \ Object 25 has fixed coordinates (&D888, &0000, &0777) EQUB &41 \ Object 26 has fixed coordinates (&EDDE, &0000, &4111) EQUB &2C \ Object 27 has fixed coordinates (&4666, &0000, &2CCD) EQUB &05 \ Object 28 has fixed coordinates (&8666, &0000, &0555) EQUB &74 \ Object 29 has fixed coordinates (&B555, &0000, &7444) EQUB &00 \ Object 30 has dynamic coordinates EQUB &00 \ Object 31 has dynamic coordinates EQUB &00 \ Object 32 has dynamic coordinates EQUB &00 \ Object 33 has dynamic coordinates EQUB &03 \ Object 34 has fixed coordinates (&0440, &0000, &0340) EQUB &41 \ Object 35 is unused EQUB &3A \ Object 36 is unused EQUB &4C \ Object 37 is unused EQUB &53 \ Object 38 is unused EQUB &52 \ Object 39 is unused \ ****************************************************************************** \ \ Name: randomNumbers \ Type: Variable \ Category: Utility routines \ Summary: A list for keeping a list of random numbers \ Deep dive: Random numbers \ \ ------------------------------------------------------------------------------ \ \ This list contains a pointer to the current position in the first byte. This \ is stored as an offset in the range 0 to 10, and is incremented every time we \ stash a new random number in the list, wrapping round to the start when it \ reaches the end. \ \ The pointer gets zeroed in the ResetVariables routine. \ \ ****************************************************************************** .randomNumbers EQUB &FB, &FD, &FF, &F9, &FB, &F8, &FB, &FA EQUB &53, &52, &FF, &FE, &01, &41, &58, &0D \ ****************************************************************************** \ \ Name: highNibble \ Type: Variable \ Category: Maths \ Summary: Lookup table for the high nibble of a value \ Deep dive: Times tables and nibble arithmetic \ \ ------------------------------------------------------------------------------ \ \ In the table below, highNibble,X contains the high nibble of X: \ \ X AND %11110000 \ \ ****************************************************************************** .highNibble FOR I%, 0, 255 EQUB I% AND %11110000 NEXT \ ****************************************************************************** \ \ Name: objectPoints \ Type: Variable \ Category: 3D geometry \ Summary: Sequences of related points that together make up objects \ Deep dive: 3D objects \ \ ****************************************************************************** .objectPoints EQUB 9 + 40 \ Object 1: 0 -> 9 EQUB 1 \ Object 1: 1 EQUB 1 \ Object 1: 2 EQUB 1 \ Object 1: 3 EQUB 1 \ Object 1: 4 EQUB 1 \ Object 1: 5 EQUB 1 \ Object 1: 6 EQUB 1 \ Object 1: 7 EQUB 1 \ Object 1: 8 EQUB 1 \ Object 1: 9 EQUB 1 \ Object 1: 10 EQUB 1 \ Object 1: 11 EQUB 1 \ Object 1: 12 EQUB 1 \ Object 1: 13 EQUB 1 \ Object 1: 14 EQUB 1 \ Object 1: 15 EQUB 1 \ Object 1: 16 EQUB 1 \ Object 1: 17 EQUB 1 \ Object 1: 18 EQUB 1 \ Object 1: 19 EQUB 1 \ Object 1: 20 EQUB 34 \ Object 34: 21 EQUB 34 \ Object 34: 22 EQUB 24 + 40 \ Object 34: 23 -> 24 EQUB 34 \ Object 34: 24 EQUB 22 + 40 \ Object 34: 25 -> 22 EQUB 23 + 40 \ Object 34: 26 -> 23 -> 24 EQUB 24 + 40 \ Object 34: 27 -> 24 EQUB 34 \ Object 34: 28 EQUB 22 + 40 \ Object 34: 29 -> 22 EQUB 0 \ Object 0: 30 EQUB 0 \ Object 0: 31 EQUB 22 + 40 \ Object 34: 32 -> 22 EQUB 34 \ Object 34: 33 EQUB 29 + 40 \ Object 34: 34 -> 29 -> 22 EQUB 8 \ Object 8: 35 EQUB 8 \ Object 8: 36 EQUB 40 + 40 \ Object 34: 37 -> 40 -> 39 EQUB 37 + 40 \ Object 34: 38 -> 37 -> 40 -> 39 EQUB 34 \ Object 34: 39 EQUB 39 + 40 \ Object 34: 40 -> 39 EQUB 2 \ Object 2: 41 EQUB 49 + 40 \ Object 2: 42 -> 49 -> 51 EQUB 50 + 40 \ Object 2: 43 -> 50 -> 52 -> 51 EQUB 48 + 40 \ Object 2: 44 -> 48 -> 52 -> 51 EQUB 2 \ Object 2: 45 EQUB 51 + 40 \ Object 2: 46 -> 51 EQUB 52 + 40 \ Object 2: 47 -> 52 -> 51 EQUB 52 + 40 \ Object 2: 48 -> 52 -> 51 EQUB 51 + 40 \ Object 2: 49 -> 51 EQUB 52 + 40 \ Object 2: 50 -> 52 -> 51 EQUB 2 \ Object 2: 51 EQUB 51 + 40 \ Object 2: 52 -> 51 EQUB 49 + 40 \ Object 2: 53 -> 49 -> 51 EQUB 50 + 40 \ Object 2: 54 -> 50 -> 52 -> 51 EQUB 51 + 40 \ Object 2: 55 -> 51 EQUB 52 + 40 \ Object 2: 56 -> 52 -> 51 EQUB 59 + 40 \ Object 3: 57 -> 59 EQUB 59 + 40 \ Object 3: 58 -> 59 EQUB 3 \ Object 3: 59 EQUB 59 + 40 \ Object 3: 60 -> 59 EQUB 59 + 40 \ Object 3: 61 -> 59 EQUB 2 \ Object 2: 62 EQUB 2 \ Object 2: 63 EQUB 2 \ Object 2: 64 EQUB 64 + 40 \ Object 2: 65 -> 64 EQUB 2 \ Object 2: 66 EQUB 66 + 40 \ Object 2: 67 -> 66 EQUB 66 + 40 \ Object 2: 68 -> 66 EQUB 68 + 40 \ Object 2: 69 -> 68 -> 66 EQUB 4 \ Object 4: 70 EQUB 4 \ Object 4: 71 EQUB 73 + 40 \ Object 4: 72 -> 73 EQUB 4 \ Object 4: 73 EQUB 75 + 40 \ Object 4: 74 -> 75 EQUB 4 \ Object 4: 75 EQUB 77 + 40 \ Object 4: 76 -> 77 -> 118 EQUB 118 + 40 \ Object 4: 77 -> 118 EQUB 79 + 40 \ Object 4: 78 -> 79 -> 77 -> 118 EQUB 77 + 40 \ Object 4: 79 -> 77 -> 118 EQUB 7 \ Object 7: 80 EQUB 80 + 40 \ Object 7: 81 -> 80 EQUB 7 \ Object 7: 82 EQUB 7 \ Object 7: 83 EQUB 7 \ Object 7: 84 EQUB 80 + 40 \ Object 7: 85 -> 80 EQUB 39 + 40 \ Object 34: 86 -> 39 EQUB 86 + 40 \ Object 34: 87 -> 86 -> 39 EQUB 86 + 40 \ Object 34: 88 -> 86 -> 39 EQUB 6 \ Object 6: 89 EQUB 89 + 40 \ Object 6: 90 -> 89 EQUB 6 \ Object 6: 91 EQUB 6 \ Object 6: 92 EQUB 6 \ Object 6: 93 EQUB 89 + 40 \ Object 6: 94 -> 89 EQUB 12 \ Object 12: 95 EQUB 13 \ Object 13: 96 EQUB 14 \ Object 14: 97 EQUB 15 \ Object 15: 98 EQUB 87 + 40 \ Object 34: 99 -> 87 -> 86 -> 39 EQUB 5 \ Object 5: 100 EQUB 5 \ Object 5: 101 EQUB 103 + 40 \ Object 5: 102 -> 103 EQUB 5 \ Object 5: 103 EQUB 5 \ Object 5: 104 EQUB 104 + 40 \ Object 5: 105 -> 104 EQUB 5 \ Object 5: 106 EQUB 106 + 40 \ Object 5: 107 -> 106 EQUB 9 \ Object 9: 108 EQUB 106 + 40 \ Object 5: 109 -> 106 EQUB 109 + 40 \ Object 5: 110 -> 109 -> 106 EQUB 3 \ Object 3: 111 EQUB 113 + 40 \ Object 3: 112 -> 113 EQUB 3 \ Object 3: 113 EQUB 3 \ Object 3: 114 EQUB 3 \ Object 3: 115 EQUB 3 \ Object 3: 116 EQUB 4 \ Object 4: 117 EQUB 4 \ Object 4: 118 EQUB 66 + 40 \ Object 2: 119 -> 66 EQUB 119 + 40 \ Object 2: 120 -> 119 -> 66 EQUB 19 \ Object 19: 121 EQUB 19 \ Object 19: 122 EQUB 19 \ Object 19: 123 EQUB 22 \ Object 22: 124 EQUB 20 \ Object 20: 125 EQUB 20 \ Object 20: 126 EQUB 20 \ Object 20: 127 EQUB 16 \ Object 16: 128 EQUB 16 \ Object 16: 129 EQUB 16 \ Object 16: 130 EQUB 16 \ Object 16: 131 EQUB 17 \ Object 17: 132 EQUB 17 \ Object 17: 133 EQUB 17 \ Object 17: 134 EQUB 17 \ Object 17: 135 EQUB 23 \ Object 23: 136 EQUB 23 \ Object 23: 137 EQUB 23 \ Object 23: 138 EQUB 23 \ Object 23: 139 EQUB 22 \ Object 22: 140 EQUB 22 \ Object 22: 141 EQUB 22 \ Object 22: 142 EQUB 22 \ Object 22: 143 EQUB 21 \ Object 21: 144 EQUB 21 \ Object 21: 145 EQUB 21 \ Object 21: 146 EQUB 21 \ Object 21: 147 EQUB 21 \ Object 21: 148 EQUB 29 \ Object 29: 149 EQUB 29 \ Object 29: 150 EQUB 29 \ Object 29: 151 EQUB 29 \ Object 29: 152 EQUB 24 \ Object 24: 153 EQUB 24 \ Object 24: 154 EQUB 24 \ Object 24: 155 EQUB 24 \ Object 24: 156 EQUB 160 + 40 \ Object 18: 157 -> 160 EQUB 159 + 40 \ Object 18: 158 -> 159 EQUB 18 \ Object 18: 159 EQUB 18 \ Object 18: 160 EQUB 27 \ Object 27: 161 EQUB 27 \ Object 27: 162 EQUB 27 \ Object 27: 163 EQUB 27 \ Object 27: 164 EQUB 28 \ Object 28: 165 EQUB 28 \ Object 28: 166 EQUB 28 \ Object 28: 167 EQUB 28 \ Object 28: 168 EQUB 25 \ Object 25: 169 EQUB 25 \ Object 25: 170 EQUB 25 \ Object 25: 171 EQUB 25 \ Object 25: 172 EQUB 26 \ Object 26: 173 EQUB 26 \ Object 26: 174 EQUB 26 \ Object 26: 175 EQUB 26 \ Object 26: 176 EQUB 19 \ Object 19: 177 EQUB 181 + 40 \ Object 30: 178 -> 181 EQUB 181 + 40 \ Object 30: 179 -> 181 EQUB 181 + 40 \ Object 30: 180 -> 181 EQUB 30 \ Object 30: 181 EQUB 31 \ Object 31: 182 EQUB 182 + 40 \ Object 31: 183 -> 182 EQUB 182 + 40 \ Object 31: 184 -> 182 EQUB 182 + 40 \ Object 31: 185 -> 182 EQUB 182 + 40 \ Object 31: 186 -> 182 EQUB 32 \ Object 32: 187 EQUB 187 + 40 \ Object 32: 188 -> 187 EQUB 187 + 40 \ Object 32: 189 -> 187 EQUB 187 + 40 \ Object 32: 190 -> 187 EQUB 187 + 40 \ Object 32: 191 -> 187 EQUB 33 \ Object 33: 192 EQUB 199 + 40 \ Object 33: 193 -> 199 -> 192 EQUB 197 + 40 \ Object 33: 194 -> 197 -> 192 EQUB 199 + 40 \ Object 33: 195 -> 199 -> 192 EQUB 199 + 40 \ Object 33: 196 -> 199 -> 192 EQUB 192 + 40 \ Object 33: 197 -> 192 EQUB 197 + 40 \ Object 33: 198 -> 197 -> 192 EQUB 192 + 40 \ Object 33: 199 -> 192 EQUB 199 + 40 \ Object 33: 200 -> 199 -> 192 EQUB 39 + 40 \ Object 34: 201 -> 39 EQUB 40 + 40 \ Object 34: 202 -> 40 -> 39 EQUB 205 + 40 \ Object 34: 203 -> 205 -> 40 -> 39 EQUB 203 + 40 \ Object 34: 204 -> 203 -> 205 -> 40 -> 39 EQUB 40 + 40 \ Object 34: 205 -> 40 -> 39 EQUB 205 + 40 \ Object 34: 206 -> 205 -> 40 -> 39 EQUB 205 + 40 \ Object 34: 207 -> 205 -> 40 -> 39 EQUB 206 + 40 \ Object 34: 208 -> 206 -> 205 -> 40 -> 39 EQUB 203 + 40 \ Object 34: 209 -> 203 -> 205 -> 40 -> 39 EQUB 204 + 40 \ Object 34: 210 -> 204 -> 203 -> 205 -> 40 -> 39 EQUB 8 \ Object 8: 211 EQUB 8 \ Object 8: 212 EQUB 9 \ Object 9: 213 EQUB 108 + 40 \ Object 9: 214 -> 108 EQUB 9 \ Object 9: 215 \ ****************************************************************************** \ \ Name: ReadJoystick \ Type: Subroutine \ Category: Keyboard \ Summary: Read the joystick axes and fire button and update the aileron, \ elevator and fire key values accordingly \ \ ****************************************************************************** .ReadJoystick LDA row29_char20_4 \ If the screen byte at row 29, block 20, pixel row 4 is BEQ rjoy1 \ zero (i.e. black) then the joystick is not enabled, so \ jump to rjoy1 to return from the subroutine LDX #1 \ Set aileronPosition to the value from ADC channel 1, JSR ReadADCChannel \ the joystick's x-position, inverted and clipped to the STA aileronPosition \ range -118 to +116 LDX #2 \ Set elevatorPosition to the value from ADC channel 2, JSR ReadADCChannel \ the joystick's y-position, inverted and clipped to the STA elevatorPosition \ range -118 to +116 LDX #0 \ Call OSBYTE with A = 128 and X = 0 to read the status LDA #128 \ of the joystick's fire button into X JSR OSBYTE TXA \ If bit 0 of X is zero, the fire button is not being AND #1 \ pressed, so jump to rjoy1 BEQ rjoy1 LDA #8 \ The fire button is being pressed, so update the key STA keyLoggerLo+5 \ logger at keyLoggerLo+5, which corresponds to the \ flaps and fire keys. We set the value to 8, the value \ from keyTable2Lo for the fire button .rjoy1 RTS \ Return from the subroutine EQUB &36 \ ****************************************************************************** \ \ Name: divisionLo \ Type: Variable \ Category: Maths \ Summary: Division lookup table \ \ ------------------------------------------------------------------------------ \ \ Bits 0 to 2 of divisionLo,X contain int(log2(X)) \ \ i.e. if divisionLo,X = n, then X fits into n + 1 binary digits \ \ Bits 3 to 7 contain the low byte of the division lookup, with bits 0 to 2 \ zeroed, and the high byte coming from divisionHi. \ \ ****************************************************************************** .divisionLo EQUB %00000000 \ Index 0 = 00000 ( 0) and 000 (0) EQUB %00000000 \ Index 1 = 00000 ( 0) and 000 (0) EQUB %00000001 \ Index 2 = 00000 ( 0) and 001 (1) EQUB %00001001 \ Index 3 = 00001 ( 1) and 001 (1) EQUB %00010010 \ Index 4 = 00010 ( 2) and 010 (2) EQUB %00011010 \ Index 5 = 00011 ( 3) and 010 (2) EQUB %00100010 \ Index 6 = 00100 ( 4) and 010 (2) EQUB %00110010 \ Index 7 = 00110 ( 6) and 010 (2) EQUB %01000011 \ Index 8 = 01000 ( 8) and 011 (3) EQUB %01010011 \ Index 9 = 01010 (10) and 011 (3) EQUB %01100011 \ Index 10 = 01100 (12) and 011 (3) EQUB %01111011 \ Index 11 = 01111 (15) and 011 (3) EQUB %10001011 \ Index 12 = 10001 (17) and 011 (3) EQUB %10100011 \ Index 13 = 10100 (20) and 011 (3) EQUB %10111011 \ Index 14 = 10111 (23) and 011 (3) EQUB %11011011 \ Index 15 = 11011 (27) and 011 (3) EQUB %11110100 \ Index 16 = 11110 (30) and 100 (4) EQUB %00010100 \ Index 17 = 00010 ( 2) and 100 (4) EQUB %00110100 \ Index 18 = 00110 ( 6) and 100 (4) EQUB %01010100 \ Index 19 = 01010 (10) and 100 (4) EQUB %01110100 \ Index 20 = 01110 (14) and 100 (4) EQUB %10011100 \ Index 21 = 10011 (19) and 100 (4) EQUB %11000100 \ Index 22 = 11000 (24) and 100 (4) EQUB %11101100 \ Index 23 = 11101 (29) and 100 (4) EQUB %00010100 \ Index 24 = 00010 ( 2) and 100 (4) EQUB %00111100 \ Index 25 = 00111 ( 7) and 100 (4) EQUB %01101100 \ Index 26 = 01101 (13) and 100 (4) EQUB %10010100 \ Index 27 = 10010 (18) and 100 (4) EQUB %11000100 \ Index 28 = 11000 (24) and 100 (4) EQUB %11110100 \ Index 29 = 11110 (30) and 100 (4) EQUB %00101100 \ Index 30 = 00101 ( 5) and 100 (4) EQUB %01011100 \ Index 31 = 01011 (11) and 100 (4) EQUB %10010101 \ Index 32 = 10010 (18) and 101 (5) EQUB %11001101 \ Index 33 = 11001 (25) and 101 (5) EQUB %00000101 \ Index 34 = 00000 ( 0) and 101 (5) EQUB %00111101 \ Index 35 = 00111 ( 7) and 101 (5) EQUB %01110101 \ Index 36 = 01110 (14) and 101 (5) EQUB %10110101 \ Index 37 = 10110 (22) and 101 (5) EQUB %11101101 \ Index 38 = 11101 (29) and 101 (5) EQUB %00101101 \ Index 39 = 00101 ( 5) and 101 (5) EQUB %01101101 \ Index 40 = 01101 (13) and 101 (5) EQUB %10101101 \ Index 41 = 10101 (21) and 101 (5) EQUB %11101101 \ Index 42 = 11101 (29) and 101 (5) EQUB %00110101 \ Index 43 = 00110 ( 6) and 101 (5) EQUB %01111101 \ Index 44 = 01111 (15) and 101 (5) EQUB %10111101 \ Index 45 = 10111 (23) and 101 (5) EQUB %00000101 \ Index 46 = 00000 ( 0) and 101 (5) EQUB %01001101 \ Index 47 = 01001 ( 9) and 101 (5) EQUB %10011101 \ Index 48 = 10011 (19) and 101 (5) EQUB %11100101 \ Index 49 = 11100 (28) and 101 (5) EQUB %00101101 \ Index 50 = 00101 ( 5) and 101 (5) EQUB %01111101 \ Index 51 = 01111 (15) and 101 (5) EQUB %11001101 \ Index 52 = 11001 (25) and 101 (5) EQUB %00011101 \ Index 53 = 00011 ( 3) and 101 (5) EQUB %01101101 \ Index 54 = 01101 (13) and 101 (5) EQUB %10111101 \ Index 55 = 10111 (23) and 101 (5) EQUB %00010101 \ Index 56 = 00010 ( 2) and 101 (5) EQUB %01100101 \ Index 57 = 01100 (12) and 101 (5) EQUB %10111101 \ Index 58 = 10111 (23) and 101 (5) EQUB %00010101 \ Index 59 = 00010 ( 2) and 101 (5) EQUB %01101101 \ Index 60 = 01101 (13) and 101 (5) EQUB %11000101 \ Index 61 = 11000 (24) and 101 (5) EQUB %00011101 \ Index 62 = 00011 ( 3) and 101 (5) EQUB %01110101 \ Index 63 = 01110 (14) and 101 (5) EQUB %11010110 \ Index 64 = 11010 (26) and 110 (6) EQUB %00101110 \ Index 65 = 00101 ( 5) and 110 (6) EQUB %10001110 \ Index 66 = 10001 (17) and 110 (6) EQUB %11101110 \ Index 67 = 11101 (29) and 110 (6) EQUB %01001110 \ Index 68 = 01001 ( 9) and 110 (6) EQUB %10101110 \ Index 69 = 10101 (21) and 110 (6) EQUB %00001110 \ Index 70 = 00001 ( 1) and 110 (6) EQUB %01101110 \ Index 71 = 01101 (13) and 110 (6) EQUB %11010110 \ Index 72 = 11010 (26) and 110 (6) EQUB %00110110 \ Index 73 = 00110 ( 6) and 110 (6) EQUB %10011110 \ Index 74 = 10011 (19) and 110 (6) EQUB %00000110 \ Index 75 = 00000 ( 0) and 110 (6) EQUB %01101110 \ Index 76 = 01101 (13) and 110 (6) EQUB %11010110 \ Index 77 = 11010 (26) and 110 (6) EQUB %00111110 \ Index 78 = 00111 ( 7) and 110 (6) EQUB %10100110 \ Index 79 = 10100 (20) and 110 (6) EQUB %00010110 \ Index 80 = 00010 ( 2) and 110 (6) EQUB %01111110 \ Index 81 = 01111 (15) and 110 (6) EQUB %11101110 \ Index 82 = 11101 (29) and 110 (6) EQUB %01010110 \ Index 83 = 01010 (10) and 110 (6) EQUB %11000110 \ Index 84 = 11000 (24) and 110 (6) EQUB %00110110 \ Index 85 = 00110 ( 6) and 110 (6) EQUB %10100110 \ Index 86 = 10100 (20) and 110 (6) EQUB %00010110 \ Index 87 = 00010 ( 2) and 110 (6) EQUB %10000110 \ Index 88 = 10000 (16) and 110 (6) EQUB %11111110 \ Index 89 = 11111 (31) and 110 (6) EQUB %01101110 \ Index 90 = 01101 (13) and 110 (6) EQUB %11100110 \ Index 91 = 11100 (28) and 110 (6) EQUB %01010110 \ Index 92 = 01010 (10) and 110 (6) EQUB %11001110 \ Index 93 = 11001 (25) and 110 (6) EQUB %01000110 \ Index 94 = 01000 ( 8) and 110 (6) EQUB %10111110 \ Index 95 = 10111 (23) and 110 (6) EQUB %00110110 \ Index 96 = 00110 ( 6) and 110 (6) EQUB %10101110 \ Index 97 = 10101 (21) and 110 (6) EQUB %00100110 \ Index 98 = 00100 ( 4) and 110 (6) EQUB %10011110 \ Index 99 = 10011 (19) and 110 (6) EQUB %00011110 \ Index 100 = 00011 ( 3) and 110 (6) EQUB %10010110 \ Index 101 = 10010 (18) and 110 (6) EQUB %00010110 \ Index 102 = 00010 ( 2) and 110 (6) EQUB %10010110 \ Index 103 = 10010 (18) and 110 (6) EQUB %00001110 \ Index 104 = 00001 ( 1) and 110 (6) EQUB %10001110 \ Index 105 = 10001 (17) and 110 (6) EQUB %00001110 \ Index 106 = 00001 ( 1) and 110 (6) EQUB %10001110 \ Index 107 = 10001 (17) and 110 (6) EQUB %00001110 \ Index 108 = 00001 ( 1) and 110 (6) EQUB %10010110 \ Index 109 = 10010 (18) and 110 (6) EQUB %00010110 \ Index 110 = 00010 ( 2) and 110 (6) EQUB %10010110 \ Index 111 = 10010 (18) and 110 (6) EQUB %00011110 \ Index 112 = 00011 ( 3) and 110 (6) EQUB %10011110 \ Index 113 = 10011 (19) and 110 (6) EQUB %00100110 \ Index 114 = 00100 ( 4) and 110 (6) EQUB %10101110 \ Index 115 = 10101 (21) and 110 (6) EQUB %00110110 \ Index 116 = 00110 ( 6) and 110 (6) EQUB %10110110 \ Index 117 = 10110 (22) and 110 (6) EQUB %00111110 \ Index 118 = 00111 ( 7) and 110 (6) EQUB %11000110 \ Index 119 = 11000 (24) and 110 (6) EQUB %01010110 \ Index 120 = 01010 (10) and 110 (6) EQUB %11011110 \ Index 121 = 11011 (27) and 110 (6) EQUB %01100110 \ Index 122 = 01100 (12) and 110 (6) EQUB %11101110 \ Index 123 = 11101 (29) and 110 (6) EQUB %01111110 \ Index 124 = 01111 (15) and 110 (6) EQUB %00000110 \ Index 125 = 00000 ( 0) and 110 (6) EQUB %10010110 \ Index 126 = 10010 (18) and 110 (6) EQUB %00100110 \ Index 127 = 00100 ( 4) and 110 (6) EQUB %10101111 \ Index 128 = 10101 (21) and 111 (7) EQUB %00111111 \ Index 129 = 00111 ( 7) and 111 (7) EQUB %11001111 \ Index 130 = 11001 (25) and 111 (7) EQUB %01011111 \ Index 131 = 01011 (11) and 111 (7) EQUB %11101111 \ Index 132 = 11101 (29) and 111 (7) EQUB %01111111 \ Index 133 = 01111 (15) and 111 (7) EQUB %00001111 \ Index 134 = 00001 ( 1) and 111 (7) EQUB %10100111 \ Index 135 = 10100 (20) and 111 (7) EQUB %00110111 \ Index 136 = 00110 ( 6) and 111 (7) EQUB %11000111 \ Index 137 = 11000 (24) and 111 (7) EQUB %01011111 \ Index 138 = 01011 (11) and 111 (7) EQUB %11101111 \ Index 139 = 11101 (29) and 111 (7) EQUB %10000111 \ Index 140 = 10000 (16) and 111 (7) EQUB %00010111 \ Index 141 = 00010 ( 2) and 111 (7) EQUB %10101111 \ Index 142 = 10101 (21) and 111 (7) EQUB %01000111 \ Index 143 = 01000 ( 8) and 111 (7) EQUB %11011111 \ Index 144 = 11011 (27) and 111 (7) EQUB %01110111 \ Index 145 = 01110 (14) and 111 (7) EQUB %00001111 \ Index 146 = 00001 ( 1) and 111 (7) EQUB %10100111 \ Index 147 = 10100 (20) and 111 (7) EQUB %00111111 \ Index 148 = 00111 ( 7) and 111 (7) EQUB %11010111 \ Index 149 = 11010 (26) and 111 (7) EQUB %01101111 \ Index 150 = 01101 (13) and 111 (7) EQUB %00001111 \ Index 151 = 00001 ( 1) and 111 (7) EQUB %10100111 \ Index 152 = 10100 (20) and 111 (7) EQUB %01000111 \ Index 153 = 01000 ( 8) and 111 (7) EQUB %11011111 \ Index 154 = 11011 (27) and 111 (7) EQUB %01111111 \ Index 155 = 01111 (15) and 111 (7) EQUB %00010111 \ Index 156 = 00010 ( 2) and 111 (7) EQUB %10110111 \ Index 157 = 10110 (22) and 111 (7) EQUB %01010111 \ Index 158 = 01010 (10) and 111 (7) EQUB %11101111 \ Index 159 = 11101 (29) and 111 (7) EQUB %10001111 \ Index 160 = 10001 (17) and 111 (7) EQUB %00101111 \ Index 161 = 00101 ( 5) and 111 (7) EQUB %11001111 \ Index 162 = 11001 (25) and 111 (7) EQUB %01101111 \ Index 163 = 01101 (13) and 111 (7) EQUB %00001111 \ Index 164 = 00001 ( 1) and 111 (7) EQUB %10101111 \ Index 165 = 10101 (21) and 111 (7) EQUB %01010111 \ Index 166 = 01010 (10) and 111 (7) EQUB %11110111 \ Index 167 = 11110 (30) and 111 (7) EQUB %10010111 \ Index 168 = 10010 (18) and 111 (7) EQUB %00110111 \ Index 169 = 00110 ( 6) and 111 (7) EQUB %11011111 \ Index 170 = 11011 (27) and 111 (7) EQUB %01111111 \ Index 171 = 01111 (15) and 111 (7) EQUB %00100111 \ Index 172 = 00100 ( 4) and 111 (7) EQUB %11000111 \ Index 173 = 11000 (24) and 111 (7) EQUB %01101111 \ Index 174 = 01101 (13) and 111 (7) EQUB %00010111 \ Index 175 = 00010 ( 2) and 111 (7) EQUB %10111111 \ Index 176 = 10111 (23) and 111 (7) EQUB %01011111 \ Index 177 = 01011 (11) and 111 (7) EQUB %00000111 \ Index 178 = 00000 ( 0) and 111 (7) EQUB %10101111 \ Index 179 = 10101 (21) and 111 (7) EQUB %01010111 \ Index 180 = 01010 (10) and 111 (7) EQUB %11111111 \ Index 181 = 11111 (31) and 111 (7) EQUB %10100111 \ Index 182 = 10100 (20) and 111 (7) EQUB %01001111 \ Index 183 = 01001 ( 9) and 111 (7) EQUB %11110111 \ Index 184 = 11110 (30) and 111 (7) EQUB %10011111 \ Index 185 = 10011 (19) and 111 (7) EQUB %01001111 \ Index 186 = 01001 ( 9) and 111 (7) EQUB %11110111 \ Index 187 = 11110 (30) and 111 (7) EQUB %10011111 \ Index 188 = 10011 (19) and 111 (7) EQUB %01001111 \ Index 189 = 01001 ( 9) and 111 (7) EQUB %11110111 \ Index 190 = 11110 (30) and 111 (7) EQUB %10100111 \ Index 191 = 10100 (20) and 111 (7) EQUB %01001111 \ Index 192 = 01001 ( 9) and 111 (7) EQUB %11111111 \ Index 193 = 11111 (31) and 111 (7) EQUB %10100111 \ Index 194 = 10100 (20) and 111 (7) EQUB %01010111 \ Index 195 = 01010 (10) and 111 (7) EQUB %00000111 \ Index 196 = 00000 ( 0) and 111 (7) EQUB %10101111 \ Index 197 = 10101 (21) and 111 (7) EQUB %01011111 \ Index 198 = 01011 (11) and 111 (7) EQUB %00001111 \ Index 199 = 00001 ( 1) and 111 (7) EQUB %10111111 \ Index 200 = 10111 (23) and 111 (7) EQUB %01101111 \ Index 201 = 01101 (13) and 111 (7) EQUB %00011111 \ Index 202 = 00011 ( 3) and 111 (7) EQUB %11001111 \ Index 203 = 11001 (25) and 111 (7) EQUB %01111111 \ Index 204 = 01111 (15) and 111 (7) EQUB %00101111 \ Index 205 = 00101 ( 5) and 111 (7) EQUB %11011111 \ Index 206 = 11011 (27) and 111 (7) EQUB %10001111 \ Index 207 = 10001 (17) and 111 (7) EQUB %01000111 \ Index 208 = 01000 ( 8) and 111 (7) EQUB %11110111 \ Index 209 = 11110 (30) and 111 (7) EQUB %10100111 \ Index 210 = 10100 (20) and 111 (7) EQUB %01011111 \ Index 211 = 01011 (11) and 111 (7) EQUB %00001111 \ Index 212 = 00001 ( 1) and 111 (7) EQUB %11000111 \ Index 213 = 11000 (24) and 111 (7) EQUB %01110111 \ Index 214 = 01110 (14) and 111 (7) EQUB %00101111 \ Index 215 = 00101 ( 5) and 111 (7) EQUB %11011111 \ Index 216 = 11011 (27) and 111 (7) EQUB %10010111 \ Index 217 = 10010 (18) and 111 (7) EQUB %01000111 \ Index 218 = 01000 ( 8) and 111 (7) EQUB %11111111 \ Index 219 = 11111 (31) and 111 (7) EQUB %10110111 \ Index 220 = 10110 (22) and 111 (7) EQUB %01101111 \ Index 221 = 01101 (13) and 111 (7) EQUB %00011111 \ Index 222 = 00011 ( 3) and 111 (7) EQUB %11010111 \ Index 223 = 11010 (26) and 111 (7) EQUB %10001111 \ Index 224 = 10001 (17) and 111 (7) EQUB %01000111 \ Index 225 = 01000 ( 8) and 111 (7) EQUB %11111111 \ Index 226 = 11111 (31) and 111 (7) EQUB %10110111 \ Index 227 = 10110 (22) and 111 (7) EQUB %01101111 \ Index 228 = 01101 (13) and 111 (7) EQUB %00100111 \ Index 229 = 00100 ( 4) and 111 (7) EQUB %11011111 \ Index 230 = 11011 (27) and 111 (7) EQUB %10010111 \ Index 231 = 10010 (18) and 111 (7) EQUB %01001111 \ Index 232 = 01001 ( 9) and 111 (7) EQUB %00001111 \ Index 233 = 00001 ( 1) and 111 (7) EQUB %11000111 \ Index 234 = 11000 (24) and 111 (7) EQUB %01111111 \ Index 235 = 01111 (15) and 111 (7) EQUB %00111111 \ Index 236 = 00111 ( 7) and 111 (7) EQUB %11110111 \ Index 237 = 11110 (30) and 111 (7) EQUB %10101111 \ Index 238 = 10101 (21) and 111 (7) EQUB %01101111 \ Index 239 = 01101 (13) and 111 (7) EQUB %00100111 \ Index 240 = 00100 ( 4) and 111 (7) EQUB %11100111 \ Index 241 = 11100 (28) and 111 (7) EQUB %10011111 \ Index 242 = 10011 (19) and 111 (7) EQUB %01011111 \ Index 243 = 01011 (11) and 111 (7) EQUB %00010111 \ Index 244 = 00010 ( 2) and 111 (7) EQUB %11010111 \ Index 245 = 11010 (26) and 111 (7) EQUB %10010111 \ Index 246 = 10010 (18) and 111 (7) EQUB %01001111 \ Index 247 = 01001 ( 9) and 111 (7) EQUB %00001111 \ Index 248 = 00001 ( 1) and 111 (7) EQUB %11001111 \ Index 249 = 11001 (25) and 111 (7) EQUB %10001111 \ Index 250 = 10001 (17) and 111 (7) EQUB %01000111 \ Index 251 = 01000 ( 8) and 111 (7) EQUB %00000111 \ Index 252 = 00000 ( 0) and 111 (7) EQUB %11000111 \ Index 253 = 11000 (24) and 111 (7) EQUB %10000111 \ Index 254 = 10000 (16) and 111 (7) EQUB %01000111 \ Index 255 = 01000 ( 8) and 111 (7) \ ****************************************************************************** \ \ Name: yLookupLo \ Type: Variable \ Category: Graphics \ Summary: Lookup table for converting pixel y-coordinate to low byte of \ screen address \ Deep dive: Converting pixel coordinates to screen addresses \ \ ****************************************************************************** .yLookupLo FOR I%, 19, 0, -1 EQUB LO(&5800 + (I% * &138)) NEXT FOR I%, 31, 20, -1 EQUB LO(&5800 + (I% * &138)) NEXT \ ****************************************************************************** \ \ Name: yLookupHi \ Type: Variable \ Category: Graphics \ Summary: Lookup table for converting pixel y-coordinate to high byte of \ screen address \ Deep dive: Converting pixel coordinates to screen addresses \ \ ****************************************************************************** .yLookupHi FOR I%, 19, 0, -1 EQUB HI(&5800 + (I% * &138)) NEXT FOR I%, 31, 20, -1 EQUB HI(&5800 + (I% * &138)) NEXT \ ****************************************************************************** \ \ Name: DrawCanopyCorners \ Type: Subroutine \ Category: Graphics \ Summary: Draw the diagonal corners at the top of the canopy \ \ ------------------------------------------------------------------------------ \ \ This routine draws the diagonal lines in the top corners of the canopy. They \ are drawn so that the area outside the canopy is set to black, while anything \ that is already on-screen inside the canopy is left alone. The diagonals are \ made up of four squares, each of them one pixel wide and two pixels high, so \ the overall size of each diagonal is one character block (four pixels wide and \ eight pixels high). \ \ ****************************************************************************** .DrawCanopyCorners LDX #7 \ Set X as a pixel row counter, starting with the last \ pixel row, so we draw the diagonals upwards from the \ bottom row of the character block to the top row of \ the character block LDA #%01110111 \ Set P to a pixel mask to clear the first pixel STA P LDA #%10001000 \ Set Q to a pixel byte with a white first pixel STA Q LDA #%11101110 \ Set R to a pixel mask to clear the last pixel STA R LDA #%00010001 \ Set S to a pixel byte with a white last pixel STA S .corn1 LDY #1 \ Each square in the diagonal is two pixels high, so we \ set a counter in Y to count the height of each square .corn2 LDA row1_char1_0,X \ We want to update the X-th pixel row in the character \ block in the top-left corner of the canopy, so fetch \ the current contents of the row AND P \ Clear the pixel pointed to by P, which on the first \ iteration round the loop will be the first pixel ORA Q \ Set the pixel pointed to by Q, which on the first \ iteration round the loop will be the first pixel STA row1_char1_0,X \ Store the updated pixel row back in screen memory, so \ on the first iteration round the loop, we just set the \ first pixel in the bottom pixel row, which is the \ bottom-left pixel of the diagonal in the top-left \ corner of the canopy LDA row1_char39_0,X \ We want to update the X-th pixel row in the character \ block in the top-right corner of the canopy, so fetch \ the current contents of the row AND R \ Clear the pixel pointed to by R, which on the first \ iteration round the loop will be the last pixel ORA S \ Set the pixel pointed to by S, which on the first \ iteration round the loop will be the last pixel STA row1_char39_0,X \ Store the updated pixel row back in screen memory, so \ on the first iteration round the loop, we just set the \ last pixel in the bottom pixel row, which is the \ bottom-right pixel of the diagonal in the top-right \ corner of the canopy DEX \ Decrement the pixel row counter so we move up to the \ pixel row above DEY \ Decrement the square counter in Y, and if it is still BPL corn2 \ positive, loop back to corn2 to draw the second row in \ this square, so we end up drawing the same pattern on \ two consecutive pixel rows, making each square two \ pixels high and one pixel wide LDA R \ We now shift each nibble in R to the left by one, so ASL A \ it moves through the following values: AND R \ STA R \ %11101110 -> %11001100 -> %10001000 -> %00000000 \ \ so with each step up the diagonal, the mask clears \ the area to the right of the diagonal to black \ \ We do this as follows (taking the case of the first \ transformation above): \ \ R = R AND (R << 1) \ = %11101110 AND (%11101110 << 1) \ = %11101110 AND %11011100 \ = %11001100 LDA P \ We now shift each nibble in P to the right by one, so LSR A \ it moves through the following values: AND P \ STA P \ %01110111 -> %00110011 -> %00010001 -> %00000000 \ \ so with each step up the diagonal, the mask clears \ the area to the left of the diagonal to black \ \ We do this as follows (taking the case of the first \ transformation above): \ \ P = P AND (P >> 1) \ = %01110111 AND (%01110111 >> 1) \ = %01110111 AND %00111011 \ = %00110011 ASL S \ We also shift S to the left, so the pixel we draw in \ the top-right diagonal moves to the left as we move up \ the diagonal, like this: \ \ %00010001 -> %00100010 -> %01000100 -> %10001000 \ \ or, putting them in the order on-screen with two rows \ per square, we get a top-right diagonal like this: \ \ %10001000 x... \ %10001000 x... \ %01000100 .x.. \ %01000100 .x.. \ %00100010 ..x. \ %00100010 ..x. \ %00010001 ...x \ %00010001 ...x LSR Q \ Finally, we shift Q to the right, so the pixel we draw \ in the top-left diagonal moves to the right as we move \ up the diagonal, like this: \ \ %10001000 -> %01000100 -> %00100010 -> %00010001 \ \ or, putting them in the order on-screen with two rows \ per square, we get a top-left diagonal like this: \ \ %00010001 ...x \ %00010001 ...x \ %00100010 ..x. \ %00100010 ..x. \ %01000100 .x.. \ %01000100 .x.. \ %10001000 x... \ %10001000 x... CPX #255 \ Loop back until we have drawn all eight rows in the BNE corn1 \ diagonal, at which point we will have decremented X \ from 0 to 255 RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: RemoveScore \ Type: Subroutine \ Category: Scoring \ Summary: Remove the score display from the screen \ \ ****************************************************************************** .RemoveScore LDY #HI(row3_char1_0) \ Set (Y X) to the screen address for row 3, character LDX #LO(row3_char1_0) \ block 1 LDA #8 \ Set R = 19, so we clear 8 character rows STA R LDA #0 \ Set X = 0 so we clear the canopy to black JSR FillCanopyRows \ Fill the 8 screen rows with black, avoiding the canopy \ edges and removing the score display from the screen RTS \ Return from the subroutine EQUB &20, &20 \ These bytes appear to be unused. They actually contain EQUB &20, &4C \ snippets of the original source code EQUB &44, &41 EQUB &26, &38 EQUB &36, &3A EQUB &43, &4C EQUB &43, &3A EQUB &41 \ ****************************************************************************** \ \ Name: lineBufferV \ Type: Variable \ Category: Drawing lines \ Summary: Line buffer storage for the line direction (V) \ Deep dive: Source code clues hidden in the game binary \ Line buffers \ \ ------------------------------------------------------------------------------ \ \ This table stores information about lines that are drawn on-screen, so they \ can be quickly erased without having to spend precious time recalculating the \ line coordinates. The information is stored when a line is drawn by the \ DrawClippedLine routine, and is read by the EraseCanopyLines routine when the \ line is erased. \ \ We can buffer up to 96 lines, with 48 in each of the two line buffers, so the \ maximum number of lines on screen at any one time is 48 lines out of the 193 \ lines defined in the world. \ \ ****************************************************************************** .lineBufferV EQUB &A2, &07, &A9, &77, &85, &70, &A9, &88 EQUB &85, &71, &A9, &EE, &85, &72, &A9, &11 EQUB &85, &73, &A0, &01, &BD, &40, &59, &25 EQUB &70, &05, &71, &9D, &40, &59, &BD, &68 EQUB &5A, &25, &72, &05, &73, &9D, &68, &5A EQUB &CA, &88, &10, &E8, &46, &72, &46, &73 EQUB &46, &70, &46, &71, &E0, &FF, &D0, &DA EQUB &60, &A0, &5B, &A2, &C0, &A9, &08, &85 EQUB &72, &A9, &00, &20, &AB, &2E, &60, &10 EQUB &0F, &2E, &53, &54, &49, &50, &20, &4C EQUB &44, &58, &23, &32, &0D, &09, &1A, &2A EQUB &2E, &73, &74, &69, &31, &20, &4C, &44 \ ****************************************************************************** \ \ Name: zPointHi \ Type: Variable \ Category: 3D geometry \ Summary: High byte of the z-coordinate for a point \ Deep dive: Lines and points \ \ ------------------------------------------------------------------------------ \ \ The high byte of the z-coordinate for the point with ID X is at zPointHi,X. \ \ The coordinate is stored as a 16-bit value (zPointHi zPointLo). \ \ The initial contents of the variable is just workspace noise and is ignored. \ It actually contains snippets of the original source code. \ \ ****************************************************************************** .zPointHi EQUB &41, &20, &58, &41, &4C, &4F, &2C, &59 EQUB &3A, &43, &4C, &43, &3A, &41, &44, &43 EQUB &20, &44, &54, &49, &50, &3A, &53, &54 EQUB &41, &26, &37, &37, &2C, &58, &0D, &09 EQUB &24, &23, &20, &20, &20, &20, &20, &20 EQUB &4C, &44, &41, &20, &58, &41, &48, &49 EQUB &2C, &59, &3A, &41, &44, &43, &23, &35 EQUB &3A, &53, &54, &41, &26, &37, &41, &2C EQUB &58, &0D, &09, &2E, &1C, &2E, &73, &74 EQUB &69, &34, &20, &54, &59, &41, &3A, &43 EQUB &4C, &43, &3A, &41, &44, &43, &23, &34 EQUB &30, &3A, &54, &41, &59, &0D, &09, &38 EQUB &1A, &2E, &73, &74, &69, &32, &20, &44 EQUB &45, &58, &3A, &42, &50, &4C, &20, &73 EQUB &74, &69, &33, &3A, &72, &74, &73, &0D EQUB &09, &42, &12, &2E, &73, &74, &69, &33 EQUB &20, &42, &45, &51, &20, &73, &74, &69 EQUB &31, &0D, &09, &4C, &1D, &20, &20, &20 EQUB &20, &20, &20, &4C, &44, &41, &20, &58 EQUB &41, &4C, &4F, &2C, &59, &3A, &53, &54 EQUB &41, &26, &37, &37, &2C, &58, &0D, &09 EQUB &56, &26, &20, &20, &20, &20, &20, &20 EQUB &4C, &44, &41, &20, &58, &41, &48, &49 EQUB &2C, &59, &3A, &53, &54, &41, &26, &37 EQUB &41, &2C, &58, &3A, &4A, &4D, &50, &20 EQUB &73, &74, &69, &34, &0D, &09, &60, &05 EQUB &20, &0D, &09, &6A, &0F, &2E, &48, &49 EQUB &54, &53, &20, &4C, &44, &58, &23, &32 EQUB &0D, &09, &74, &1C, &2E, &68, &69, &74 EQUB &32, &20, &54, &59, &41, &3A, &43, &4C EQUB &43, &3A, &41, &44, &43, &23, &34, &30 EQUB &3A, &54, &41, &59 .zLinearHi EQUB &0D \ High byte of point 252 (z-coordinate) \ \ Point 252 is used to store the sum of all the forces \ on the plane when calculating the flight model \ \ Stored as a 16-bit value (zLinearHi zLinearLo) .zGravityHi EQUB &09 \ High byte of point 253 (z-coordinate) \ \ Point 253 is used to store the gravity vector when \ calculating the flight model \ \ Stored as a 16-bit value (zGravityHi zGravityLo) .zTempPoint1Hi EQUB &7E \ High byte of point 254 (z-coordinate) \ \ Used as temporary point storage when rotating points \ in space \ \ Stored as a 16-bit value (zTempPoint1Hi zTempPoint1Lo) .zTempPoint2Hi EQUB &28 \ High byte of point 255 (z-coordinate) \ \ Used as temporary point storage when rotating points \ in space \ \ Stored as a 16-bit value (zTempPoint2Hi zTempPoint2Lo) \ ****************************************************************************** \ \ Name: xPointHi \ Type: Variable \ Category: 3D geometry \ Summary: High byte of the x-coordinate for a point \ Deep dive: Lines and points \ \ ------------------------------------------------------------------------------ \ \ The high byte of the x-coordinate for the point with ID X is at xPointHi,X. \ \ The coordinate is stored as a 16-bit value (xPointHi xPointLo). \ \ The initial contents of the variable is just workspace noise and is ignored. \ It actually contains snippets of the original source code. \ \ ****************************************************************************** .xPointHi EQUB &20, &20, &20, &20, &20, &20, &4C, &44 EQUB &41, &20, &58, &41, &4C, &4F, &2C, &59 EQUB &3A, &53, &45, &43, &3A, &53, &42, &43 EQUB &26, &37, &37, &2C, &58, &3A, &53, &54 EQUB &41, &26, &37, &34, &0D, &09, &88, &26 EQUB &20, &20, &20, &20, &20, &20, &4C, &44 EQUB &41, &20, &58, &41, &48, &49, &2C, &59 EQUB &3A, &53, &42, &43, &26, &37, &41, &2C EQUB &58, &3A, &42, &4E, &45, &20, &68, &69 EQUB &74, &31, &0D, &09, &92, &22, &20, &20 EQUB &20, &20, &20, &20, &4C, &44, &41, &26 EQUB &37, &34, &3A, &43, &4D, &50, &26, &38 EQUB &30, &2C, &58, &3A, &42, &43, &53, &20 EQUB &68, &69, &74, &31, &0D, &09, &9C, &26 EQUB &20, &20, &20, &20, &20, &20, &44, &45 EQUB &58, &3A, &42, &50, &4C, &20, &68, &69 EQUB &74, &32, &3A, &4C, &44, &41, &20, &4F EQUB &42, &3A, &53, &54, &41, &20, &45, &50 EQUB &54, &52, &0D, &09, &A6, &19, &20, &20 EQUB &20, &20, &20, &20, &54, &53, &58, &3A EQUB &49, &4E, &58, &3A, &49, &4E, &58, &3A EQUB &54, &58, &53, &0D, &09, &A7, &1D, &20 EQUB &20, &20, &20, &20, &20, &4C, &44, &41 EQUB &23, &32, &37, &3A, &53, &54, &41, &20 EQUB &45, &50, &4C, &4F, &3A, &72, &74, &73 EQUB &0D, &09, &B0, &0D, &2E, &68, &69, &74 EQUB &31, &20, &72, &74, &73, &0D, &09, &BA EQUB &05, &20, &0D, &09, &C4, &1D, &2E, &41 EQUB &44, &49, &46, &20, &4C, &44, &41, &23 EQUB &30, &3A, &53, &54, &41, &26, &37, &30 EQUB &3A, &53, &54, &41, &26, &37, &32, &0D EQUB &09, &CE, &1C, &20 \ ****************************************************************************** \ \ Name: xLinearHi \ Type: Variable \ Category: 3D geometry \ Summary: High byte of point 252 (x-coordinate) \ \ ****************************************************************************** .xLinearHi EQUB &20 \ High byte of point 252 (x-coordinate) \ \ Point 252 is used to store the sum of all the forces \ on the plane when calculating the flight model \ \ Stored as a 16-bit value (xLinearHi xLinearLo) \ ****************************************************************************** \ \ Name: xGravityHi \ Type: Variable \ Category: 3D geometry \ Summary: High byte of point 253 (x-coordinate) \ \ ****************************************************************************** .xGravityHi EQUB &20 \ High byte of point 253 (x-coordinate) \ \ Point 253 is used to store the gravity vector when \ calculating the flight model \ \ Stored as a 16-bit value (xGravityHi xGravityLo) \ ****************************************************************************** \ \ Name: xTempPoint1Hi \ Type: Variable \ Category: 3D geometry \ Summary: High byte of point 254 (x-coordinate) \ \ ****************************************************************************** .xTempPoint1Hi EQUB &20 \ High byte of point 254 (x-coordinate) \ \ Used as temporary point storage when rotating points \ in space \ \ Stored as a 16-bit value (xTempPoint1Hi xTempPoint1Lo) \ ****************************************************************************** \ \ Name: xTempPoint2Hi \ Type: Variable \ Category: 3D geometry \ Summary: High byte of point 255 (x-coordinate) \ \ ****************************************************************************** .xTempPoint2Hi EQUB &20 \ High byte of point 255 (z-coordinate) \ \ Used as temporary point storage when rotating points \ in space \ \ Stored as a 16-bit value (xTempPoint2Hi xTempPoint2Lo) \ ****************************************************************************** \ \ Name: CopyWorkToPoint \ Type: Subroutine \ Category: Utility routines \ Summary: Copy a point from the variable workspace to the point tables \ Deep dive: Multi-byte variables \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ X The low byte of the x-coordinate of the 16-bit workspace \ point to copy: \ \ * LO(xTurnHi) = (xTurn, yTurn, zTurn) \ \ * LO(xVelocityHi) = (xVelocity, yVelocity, zVelocity) \ \ * LO(xTemp2Lo) = (xTemp2, yTemp2, zTemp2) \ \ * LO(xPlaneLo) = (xPlane, yPlane, zPlane) \ \ Y The ID of the point to update in the point tables \ \ ****************************************************************************** .CopyWorkToPoint LDA xTurnHi,X \ Copy the X-th coordinate in the variable workspace to STA xPointLo,Y \ the Y-th point coordinate, starting with the low bytes LDA yTurnHi,X STA yPointLo,Y LDA zTurnHi,X STA zPointLo,Y LDA xTurnTop,X \ And then the high bytes STA xPointHi,Y LDA yTurnTop,X STA yPointHi,Y LDA zTurnTop,X STA zPointHi,Y RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: CopyPointToWork \ Type: Subroutine \ Category: Utility routines \ Summary: Copy a point from the point tables to the variable workspace \ Deep dive: Multi-byte variables \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ Y The ID of the point to copy from the point tables \ \ X The low byte of the x-coordinate of the 16-bit workspace \ point to update: \ \ * LO(xVelocityPLo) \ = (xVelocityP, yVelocityP, zVelocityP) \ \ * LO(dxVelocityLo) \ = (dxVelocity, dyVelocity, dzVelocity) \ \ * LO(dxRotationLo) \ = (dxRotation, dyRotation, dzRotation) \ \ * LO(xTemp2Lo) = (xTemp2, yTemp2, zTemp2) \ \ ****************************************************************************** .CopyPointToWork LDA xPointLo,Y \ Copy the Y-th point coordinate to the X-th coordinate STA xTurnHi,X \ in the variable workspace, starting with the low bytes LDA yPointLo,Y STA yTurnHi,X LDA zPointLo,Y STA zTurnHi,X LDA xPointHi,Y \ And then the high bytes STA xTurnTop,X LDA yPointHi,Y STA yTurnTop,X LDA zPointHi,Y STA zTurnTop,X RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: SetPointToOrigin \ Type: Subroutine \ Category: 3D geometry \ Summary: Set a point's coordinates to the origin at (0, 0, 0) \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ X The ID of the point to set to the origin \ \ ****************************************************************************** .SetPointToOrigin LDA #0 \ Set A = 0 so we set the X-th point to (0, 0, 0) below \ ****************************************************************************** \ \ Name: SetPoint \ Type: Subroutine \ Category: 3D geometry \ Summary: Set a point's coordinates to the value (a, a, a) \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ X The ID of the point to set to the origin \ \ A The value for all three coordinates \ \ ****************************************************************************** .SetPoint STA xPointLo,X \ Set the point's x-coordinate to A STA xPointHi,X STA yPointLo,X \ Set the point's y-coordinate to A STA yPointHi,X STA zPointLo,X \ Set the point's z-coordinate to A STA zPointHi,X RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: CheckLineDistance \ Type: Subroutine \ Category: Visibility \ Summary: Check whether a point on a line is within the visible distance for \ the line \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ X The line ID of the line to check \ \ Y The point ID of the point to check \ \ ------------------------------------------------------------------------------ \ \ Returns: \ \ A Contains the result as follows: \ \ * The current value of showLine if the point is close \ enough to be visible \ \ * 1 if the point is too far away to be visible \ \ ****************************************************************************** .CheckLineDistance LDA xPointHi,Y \ Set A to the high byte of the point's x-coordinate BPL dist1 \ If the x-coordinate is positive, skip the following \ instruction EOR #&FF \ Otherwise flip the x-coordinate so it's positive, so: \ \ A = |xPointHi| .dist1 CMP maxLineDistance,X \ If A >= this line's visible distance, then the point BCS dist4 \ is too far away to be seen in the x-axis, so jump to \ dist4 to return a "not visible" result LDA yPointHi,Y \ Set A to the high byte of the point's y-coordinate BPL dist2 \ If the y-coordinate is positive, skip the following \ instruction EOR #&FF \ Otherwise flip the y-coordinate so it's positive, so: \ \ A = |yPointHi| .dist2 CMP maxLineDistance,X \ If A >= this line's visible distance, then the point BCS dist4 \ is too far away to be seen in the y-axis, so jump to \ dist4 to return a "not visible" result LDA zPointHi,Y \ Set A to the high byte of the point's z-coordinate BPL dist3 \ If the z-coordinate is positive, skip the following \ instruction EOR #&FF \ Otherwise flip the z-coordinate so it's positive, so: \ \ A = |zPointHi| .dist3 CMP maxLineDistance,X \ If A < this line's visible distance, then the point BCC dist5 \ is close enough to be visible in the z-axis, so jump \ to dist5 to return an "is visible" result .dist4 \ If we get here then the point is too far away to be \ visible in at least one axis LDA #1 \ Set A = 1 as the return value for a "not visible" \ result RTS \ Return from the subroutine .dist5 LDA #0 \ The point is close enough to be visible, so return the ORA showLine \ current value of showLine (this could be achieved by a \ simple LDA showLine instruction, so perhaps this more \ convoluted approach is left over from a different \ version of the routine) RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: AddTempToPoint (Part 2 of 2) \ Type: Subroutine \ Category: 3D geometry \ Summary: Check whether the vector addition overflowed and set the point's \ visibility accordingly \ \ ****************************************************************************** .addv1 \ This routine follows on directly from the part 1 of \ AddTempToPoint PHP \ Store the flags for the z-axis addition on the stack, \ so we can check them below LDX #2 \ We now want to check whether the vector addition \ overflowed in any direction, so set as a counter for \ the three axes .addv2 PLP \ Retrieve the flags from the stack, so we work through \ the flags from the z-, y- and x-axes in turn BVC addv3 \ If the C flag is clear then this addition didn't \ overflow, so jump to addv3 to move on to the next axis LDA #%01000000 \ The addition overflowed for this axis, so set bit 6 of STA showLine \ showLine so the line containing this point is marked \ as not being visible .addv3 DEX \ Decrement the counter to move on to the next axis BPL addv2 \ Loop back until we have checked the flags from all \ three additions RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: CheckBridgeAndTown \ Type: Subroutine \ Category: Scoring \ Summary: Check whether we are safely flying under the bridge or down the \ main street in Acornsville \ Deep dive: Flying skills \ \ ------------------------------------------------------------------------------ \ \ This routine checks whether the plane is within a certain distance of a \ specific coordinate from the (skillZoneHi skillZoneLo) table. If it is within \ the correct distance, then it is within the skill zone. \ \ The plane is said to be within a coordinate's skill zone if the following \ checks are all true. They start with larger checks and whittle it down to more \ fine-grained checks, as follows: \ \ * The vector from the skill zone coordinate to the plane is in a positive \ direction along each axis, i.e. the plane is somewhere inside a cuboid \ that has the skill zone coordinate at its bottom-left corner, nearest the \ origin (the origin is at ground level in the bottom-left corner of the \ map). \ \ * The length of this vector along each axis is < 1024, so the plane is close \ enough to the skill zone coordinate for the next check to be done, i.e. \ within the rectangular cuboid described above, so the plane is within a \ cube of size 1024 in the skill zone coordinate's corner. \ \ * The length of the vector along each axis divided by 4 is within the margin \ for this skill zone (as defined in the skillZoneSize table), i.e. the \ plane is within an even smaller rectangular cuboid, again in the skill \ zone coordinate's corner, with dimensions given in the skillZoneSize \ table. \ \ In short, the plane is in the skill zone if it's inside a box whose dimensions \ are given in the skillZoneSize table, and whose corner nearest the origin is \ at the skill zone coordinate given in the skillZone table - in other words, \ the cuboid at the skill zone coordinate, with the dimensions in skillZoneSize. \ \ Or, even shorter, the plane is in a skill zone if this is true for all three \ axes: \ \ skill zone <= plane < skill zone + skill zone \ coordinate coordinate coordinate size * 4 \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ A Determines which skill zone coordinate to check: \ \ * 0, 3, 6 = under the suspension bridge \ \ * 9, 12, 15, 18 = along main street in Acornsville \ \ ------------------------------------------------------------------------------ \ \ Returns: \ \ C flag The result: \ \ * 0 = We are currently flying outside this skill zone \ \ * 1 = We are currently flying inside this skill zone \ \ ****************************************************************************** .CheckBridgeAndTown STA Q \ Set Q to the skill zone to check LDY #2 \ We now work through the three axes (z, y, x), so set \ Y = 2 to act as an axis counter, going 2, 1, 0 .town1 TYA \ Set X = Q + Y CLC \ ADC Q \ so X is an index into the skill zone coordinate tables TAX \ for skill zone Q and axis Y SEC \ Set (A P) = plane coordinate - skillZone coordinate LDA xPlaneLo,Y \ SBC skillZoneLo,X \ starting with the high bytes STA P LDA xPlaneHi,Y \ And then the low bytes, so (A P) contains the vector SBC skillZoneHi,X \ from the skill zone coordinate to the plane, along \ axis Y BMI town2 \ If (A P) is negative, jump to town2 to clear the C \ flag LSR A \ Set (A P) = (A P) / 4 ROR P \ LSR A \ If the high byte in A is non-zero, then the original A BNE town2 \ must be less than %10000000000 (1024), so jump to ROR P \ town2 to clear the C flag LDA P \ If the low byte in P >= the margin for this skill zone CMP skillZoneSize,X \ in this axis, jump to town2 to clear the C flag BCS town2 DEY \ Decrement the axis counter to point to the next axis BPL town1 \ Loop back until we have checked all three axes \ If we get here, then: \ \ * The vector from the skill zone coordinate to the \ plane is in a positive direction along each axis \ \ * The length of the vector along each axis is < 1024 \ \ * The length of the vector along each axis divided \ by 4 is within the margin for this skill zone \ \ which means we are currently flying within this safe \ zone SEC \ Set the C flag to indicate we are currently flying \ within this skill zone RTS \ Return from the subroutine .town2 CLC \ Clear the C flag to indicate we are currently flying \ outside this skill zone RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: ScorePoints \ Type: Subroutine \ Category: Scoring \ Summary: Increase the score and make a beep \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ (X A) The number of points to add to the score (in BCD), \ divided by 10, so adding &15 will add 150 to the score \ \ ****************************************************************************** .ScorePoints JSR UpdateScore \ Increase the score by the number of points in A LDA #3 \ Make sound #3, a long, medium beep JSR MakeSound RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: UpdateScore \ Type: Subroutine \ Category: Scoring \ Summary: Increase the score by a specified number of points \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ (X A) The number of points to add to the score (in BCD) \ \ ****************************************************************************** .UpdateScore SED \ The scores are stored in BCD format, so first of all \ we set the D flag to switch arithmetic to decimal CLC \ We now want to add (X A) points to the score in ADC scoreLo \ (scoreHi scoreLo), so we start by adding the low bytes STA scoreLo \ and store the result in scoreLo TXA \ And then we add the high bytes and store the result ADC scoreHi \ in scoreHi STA scoreHi BCS scor1 \ If the addition overflowed (so the high byte is \ greater than &99), jump to scor1 to return from the \ subroutine CPX #&99 \ If X is not &99, jump to scor1 to return from the BNE scor1 \ subroutine (in practice, this routine is only ever \ called with X = 0, so we should never get here) LDA #0 \ If we get here then X = &99 and the addition of the STA scoreLo \ high bytes overflowed, so reset the score to zero STA scoreHi .scor1 CLD \ Clear the D flag to switch arithmetic back to normal RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: UpdateHighScore \ Type: Subroutine \ Category: Scoring \ Summary: If this is a high score, update the high score \ \ ****************************************************************************** .UpdateHighScore LDA scoreHi \ If scoreHi < highScoreHi then we have not achieved a CMP highScoreHi \ high score, so jump to high3 to return from the BCC high3 \ subroutine BNE high1 \ If the high byte of the score and high score are not \ equal, then we know scoreHi > highScoreHi, so jump to \ high1 to update the high score LDA scoreLo \ If we get here then scoreHi = highScoreHi, so now we CMP highScoreLo \ check the low bytes. If ScoreLo < highScoreLo then we BCC high3 \ have not achieved a high score, so jump to high3 to \ return from the subroutine .high1 LDA scoreLo \ (scoreHi scoreLo) > (highScoreHi highScoreLo), so this STA highScoreLo \ is a new high score, so update the 16-bit high score LDA scoreHi \ to the new score STA highScoreHi .high3 RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: DisplayScore \ Type: Subroutine \ Category: Scoring \ Summary: Print the scores on-screen \ \ ****************************************************************************** .DisplayScore LDX #0 \ Print characters 0-15 from scoreText, which moves the LDY #16 \ text cursor to column 1, row 3 and prints JSR PrintScoreText \ "HIGH SCORE: " LDA highScoreHi \ Print the high byte of the high score JSR PrintScore LDA highScoreLo \ Print the low byte of the high score JSR PrintScore LDX #16 \ Print characters 16-19 from scoreText, which prints a LDY #20 \ "0" and moves the text cursor to column 3, row 10 JSR PrintScoreText LDX #8 \ Print characters 8-15 from scoreText, which prints LDY #16 \ "SCORE: " JSR PrintScoreText LDA scoreHi \ Print the high byte of the score JSR PrintScore LDA scoreLo \ Print the low byte of the score JSR PrintScore LDA #'0' \ Print a "0" JSR OSWRCH RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: PrintScore \ Type: Subroutine \ Category: Scoring \ Summary: Print a score \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ A The byte to print, in BCD format (so each nibble is one \ decimal digit) \ \ ****************************************************************************** .PrintScore STA T \ Store the byte to print in T LSR A \ Set A to the high nibble of the score (bits 4-7) LSR A LSR A LSR A CLC \ Print the high nibble as a digit ADC #'0' JSR OSWRCH LDA T \ Set A to the low nibble of the score (bits 0-3) AND #%00001111 CLC \ Print the low nibble as a digit ADC #'0' JSR OSWRCH RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: PrintScoreText \ Type: Subroutine \ Category: Scoring \ Summary: Print text when showing the scores on-screen \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ X Offset of the first character to print from scoreText \ \ Y Offset of the character that's just after the end of the \ string that we want to print from scoreText \ \ ****************************************************************************** .PrintScoreText STY T \ Set Y to the offset of the character that's just after \ the end of the string to print .prin1 LDA scoreText,X \ Print the X-th character from scoreText JSR OSWRCH INX \ Increment X to point to the next character CPX T \ Loop back to print the next character until we have BNE prin1 \ printed all of them RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: Crash \ Type: Subroutine \ Category: Flight model \ Summary: Make a crashing sound, flash the canopy and start a new game \ \ ****************************************************************************** .Crash LDA #0 \ Turn off the engine sound JSR ToggleEngineSound LDA #5 \ Make sound #5, the sound of a crash JSR MakeSound LDX #%11111111 \ Fill the canopy with white, leaving the canopy edges JSR FillCanopy \ alone, to give a flash effect LDA #10 \ Delay for 10^3 loop iterations JSR Delay JSR ClearCanopy \ Clear the canopy to black, leaving the canopy edges \ alone JSR DrawCanopyCorners \ Redraw the diagonal canopy corners, which were cleared \ by the flashing canopy effect LDA #90 \ Delay for 90^3 loop iterations JSR Delay JSR TerminateGame \ Terminate the game TSX \ Remove six bytes from the top of the stack, which TXA \ removes the top three return addresses that were put CLC \ there by JSR instructions. This enables us to jump ADC #6 \ straight back to NewGame without leaving any remnants TAX \ of the call stack behind TXS JMP NewGame \ Jump to NewGame to start a new game \ ****************************************************************************** \ \ Name: NextObjectGroup \ Type: Subroutine \ Category: 3D geometry \ Summary: Cycle to the next object group \ \ ------------------------------------------------------------------------------ \ \ If the object ID passed to the routine is 6, 7, 8 or 9, then this object is \ part of an object group, in which case this routine increments the value \ in objectGroup for this object, so: \ \ * Object 6 increments objectGroup+0 through 0 to 7 and round again \ * Object 7 increments objectGroup+1 through 8 to 15 and round again \ * Object 8 increments objectGroup+2 through 16 to 23 and round again \ * Object 9 increments objectGroup+3 through 24 to 31 and round again \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ Y The Object ID to move on to the next set of coordinates \ \ ------------------------------------------------------------------------------ \ \ Returns: \ \ C flag The result of the operation: \ \ * Set if this object ID is 6, 7, 8 or 9 \ \ * Clear otherwise \ \ ****************************************************************************** .NextObjectGroup CPY #6 \ If Y < 6, jump to nobj1 to return from the subroutine BCC nobj1 \ with the C flag clear CPY #10 \ If Y >= 10, jump to nobj1 to return from the BCS nobj1 \ subroutine with the C flag clear \ If we get here then Y = 6, 7, 8 or 9 LDA objectGroup-6,Y \ Increment the value in objectGroup for this object CLC \ from 0-7, adding in the value from groupStart (which ADC #1 \ contains the start value for each group, i.e. 0, 8, AND #7 \ 16 and 24) ORA groupStart-6,Y STA objectGroup-6,Y SEC \ Set the C flag RTS \ Return from the subroutine .nobj1 CLC \ Clear the C flag RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: ExplodeAlien \ Type: Subroutine \ Category: The Theme \ Summary: Explode an alien, making the alien split apart \ Deep dive: Explosions and turbulence \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ hitObjectId The object ID of the exploding alien (30 to 33) \ \ ****************************************************************************** .ExplodeAlien LDA hitTimer \ If hitTimer is zero then there is no exploding alien, BEQ expl6 \ so jump to expl8 via expl6 to return from the \ subroutine LDA #2 \ Otherwise we do have an exploding alien, so make sound JSR MakeSound \ #2, the sound of an alien being destroyed LDX hitObjectId \ Set X to the object ID of the object we hit (30 to 33) LDY explodeFrom-30,X \ Set Y to the "from" point ID for exploding this alien \ slot LDA explodeTo-30,X \ Set U to the "to" point ID for exploding this alien STA U \ slot LDX #2 \ Set X = 2 to act as a shift counter in the following \ loop CPX feedingStage \ If X >= feedingStage, i.e. feedingStage <= 2, then BCS expl1 \ skip the following instruction LDX feedingStage \ Set X to the feeding stage, so X now contains the \ feeding stage, capped to a maximum value of 2 .expl1 LDA #%11111111 \ We now take %11111111 and shift it right by X + 1 \ places .expl2 LSR A \ Shift X right by one place DEX \ Decrement the shift counter BPL expl2 \ Loop back until we have shifted right by X + 1 places STA P \ Store the result in P, so we get the following: \ \ * P = %01111111 if feedingStage = 0 \ * P = %00111111 if feedingStage = 1 \ * P = %00011111 if feedingStage >= 2 \ In the following outer loop, Y iterates through the \ pointIDs, starting at the "from" ID we fetched from \ above and ending at the "to" value \ \ For each point, it adds or subtracts a random number \ to the point coordinate, with the random number being \ scaled along with P (so fatter aliens have their \ points moved further) .expl3 LDX #LO(xTemp2Lo) \ Set X so the call to CopyPointToWork copies the \ coordinates to (xTemp2, yTemp2, zTemp2) JSR CopyPointToWork \ Copy the coordinates from point Y to \ (xTemp2, yTemp2, zTemp2), so it contains the \ coordinates of the next point to process in Y STY T \ Store the loop counter in T so we can retrieve it \ later \ We now add (or subtract) a random number to each \ of the three axes in the (xTemp2, yTemp2, zTemp2) \ coordinate, using a different random number for \ each axis, setting the sign of the random number \ according to bit 0 (which is random), shifting the \ number right and AND'ing with P to give us a random \ number in the following range: \ \ * -128 to +127 if feedingStage = 0 \ * -64 to +63 if feedingStage = 1 \ * -32 to +31 if feedingStage >= 2 LDY #2 \ Set an index in Y for the inner loop, to work through \ the three axes of (xTemp2, yTemp2, zTemp2), from \ zTemp2 to xTemp2 \ \ The comments below are for the x-coordinate .expl4 LDA #0 \ Set R = 0 to act as the top byte for (R A) STA R JSR NextRandomNumber \ Set A to point to the next item in the randomNumbers \ list TAX \ Set A to the random number from the randomNumbers list LDA randomNumbers+1,X LSR A \ Shift A to the right, moving bit 0 into the C flag AND P \ Set A = A AND P BCC expl5 \ If bit 0 of A before the shift was clear, skip the \ following two instructions, as we are going to leave \ (R A) as a positive number DEC R \ Otherwise we want (R A) to be negative, so decrement R \ to &FF to act as the top byte in a negative (R A) EOR #&FF \ Flip all the bits in A to negate it, so in all we have \ (R A) = ~(A AND P) .expl5 ADC xTemp2Lo,Y \ Set (xTemp2Hi xTemp2Lo) = (xTemp2Hi xTemp2Lo) + (R A) STA xTemp2Lo,Y \ \ starting with the low bytes LDA R \ And then the high bytes ADC xTemp2Hi,Y STA xTemp2Hi,Y DEY \ Decrement the loop counter to move on to the next axis BPL expl4 \ Loop back until we have done all three axes \ We have now added random numbers to all three axes in \ (xTemp2, yTemp2, zTemp2), scaled to the alien's size, \ so now to copy the result back to the original point \ coordinates LDY T \ Restore the loop counter that we stored in T, so Y now \ contains the point ID once again LDX #LO(xTemp2Lo) \ Set X so the call to CopyWorkToPoint copies the \ coordinates from (xTemp2, yTemp2, zTemp2) JSR CopyWorkToPoint \ Copy the coordinates from (xTemp2, yTemp2, zTemp2) \ to point T DEY \ Decrement the outer loop counter to move on to the \ next point in the "from" to "to" range CPY U \ Loop back until we have done the "to" point BCS expl3 DEC hitTimer \ Decrement the hit timer, to move the current explosion \ process along by 1 BNE expl6 \ If the timer is still non-zero, then the explosion \ hasn't finished, so skip the following instruction JSR ScoreHitPoints \ The alien has finished exploding, so award points for \ its destruction .expl6 LDA hitTimer \ If hitTimer <> 26, jump to expl8 to return from the CMP #26 \ subroutine BNE expl8 \ If we get here then hitTimer = 26, so it was 27 before \ the decrement above, which is right at the start \ of the explosion LDA zTemp2Hi \ Set A to the high byte of the alien's new z-coordinate LDX hitObjectId \ If we didn't hit the flying alien, jump to expl7 to CPX #33 \ set distanceFromHit to the z-coordinate BNE expl7 SEC \ We hit the flying alien, so set A = A - 8 to store in SBC #8 \ distanceFromHit, which increases the chance of \ turbulence (as turbulence only kicks in when \ distanceFromHit < 16) BPL expl7 \ If the subtraction reduced A to below zero, set A = 0 LDA #0 \ to store in distanceFromHit .expl7 STA distanceFromHit \ Store the z-coordinate (or the z-coordinate - 8) in \ distanceFromHit, so turbulence will be applied if we \ are too close to the alien .expl8 RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: ScoreHitPoints \ Type: Subroutine \ Category: Scoring \ Summary: Award points for destroying an alien, and remove the alien from \ its slot and the radar, if required \ \ ****************************************************************************** .ScoreHitPoints LDA #255 \ Set A to a negative number so we can clear out the \ alien slot below LDY hitObjectId \ Set Y to the ID of the object we just hit with our \ bullets CPY #30 \ If Y is not 30, then jump to hitp1 to clear out the BNE hitp1 \ slot LDX alienSlot \ Set X to the number of the alien in slot 30 JMP hitp2 \ Jump to hitp2 to skip the following .hitp1 \ If we get here then we just hit an alien in slot 31 \ to 33 LDX alienSlot-30,Y \ Set X to the number of the alien in slot Y STA alienSlot-30,Y \ Store A (which is negative) in slot Y to empty the \ slot .hitp2 STA alienObjectId,X \ Reset the object ID associated with the alien that \ we just hit (i.e. the one in the range 16 to 29) CPY #33 \ If Y is not 33, then jump to hitp3 as only alien 33 BNE hitp3 \ appears on the radar JSR ResetRadar \ Remove the alien from the radar LDA #3 \ Set A = 3 and jump down to hitp5 to award 30 points BNE hitp5 \ for destroying the flying alien (this BNE is \ effectively a JMP as A is never zero) .hitp3 LDX feedingStage \ Set X to feedingStage, the feeding stage of the alien \ we just hit (if it isn't dormant or flying) CPY #31 \ If Y >= 31, i.e. this is not alien 30, then jump to BCS hitp4 \ hitp4 to award a score based on the alien's feeding \ stage LDA dormantAlienScore \ This is alien 30, which is always dormant, so fetch JMP hitp5 \ the score for killing a dormant alien (400 points) \ and jump down to hitp5 to increase the score .hitp4 LDA alienScore,X \ Fetch the score for killing an alien in the feeding \ state in A (0 = fattest alien, 3 = slimmest alien) .hitp5 LDX #0 \ Add A * 10 points to the score by calling UpdateScore JSR UpdateScore \ with (X A) = (0 A) = A RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: SetRandomNumber \ Type: Subroutine \ Category: Utility routines \ Summary: Set the next item in the randomNumbers list to a new random number \ and update the pointer to point to it \ Deep dive: Random numbers \ \ ****************************************************************************** .SetRandomNumber LDX randomNumbers \ Fetch the pointer for the randomNumbers list 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 STA randomNumbers+1,X \ Set the next item in the list to the random number we \ just fetched \ Fall through into NextRandomNumber to move the list \ pointer to point to the new number we just added \ ****************************************************************************** \ \ Name: NextRandomNumber \ Type: Subroutine \ Category: Utility routines \ Summary: Point to the next item in the randomNumbers list \ Deep dive: Random numbers \ \ ------------------------------------------------------------------------------ \ \ The pointer for the randomNumbers list is stored in the first location. Each \ call to this routine increments the pointer through 0 to 10, after which it \ wraps back round to 0. \ \ ------------------------------------------------------------------------------ \ \ Returns: \ \ A Points to the next number in the randomNumbers list \ \ ****************************************************************************** .NextRandomNumber LDA randomNumbers \ Set A = randomNumbers + 1 CLC \ ADC #1 \ so A points to the next item in the randomNumbers list CMP #11 \ If A < 11, skip the following instruction as the BCC rand1 \ pointer hasn't yet reached the end of the list LDA #0 \ A >= 11, which is past the end of the list, so set \ A = 0 to set the pointer back at the start of the list .rand1 STA randomNumbers \ Store the updated pointer in randomNumbers RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: MakeSound \ Type: Subroutine \ Category: Sound \ Summary: Make a sound \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ A The sound number from the soundData table (0 to 7) \ \ ****************************************************************************** .MakeSound ASL A \ Set A = A * 8 ASL A \ ASL A \ so we can use it as an index into the soundData table, \ which has 8 bytes per entry CLC \ Set (Y X) = soundData + A ADC #LO(soundData) \ TAX \ starting with the low byte in X LDA #7 \ Set A = 7 for the OSWORD command to make a sound BNE MakeSoundEnvelope \ Jump to MakeSoundEnvelope to set up Y and apply the \ OSWORD command to the (Y X) block, which makes the \ relevant sound (this BNE is effectively a JMP as A is \ never zero) \ ****************************************************************************** \ \ Name: TerminateGame \ Type: Subroutine \ Category: Setup \ Summary: Terminate the current game \ \ ****************************************************************************** .TerminateGame LDA #0 \ Turn off the engine sound JSR ToggleEngineSound JSR UpdateHighScore \ If this is a high score, update the high score JSR DisplayScore \ Print the scores on-screen .term1 LDX #&B6 \ Scan the keyboard to see if RETURN is being pressed JSR ScanKeyboard BNE term1 \ Loop back to keep scanning until RETURN is pressed RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: DefineEnvelope \ Type: Subroutine \ Category: Sound \ Summary: Define a sound envelope \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ A The offset of the sound envelope data in envelopeData: \ \ * A = 0 for the first envelope definition \ \ * A = 14 for the first envelope definition \ \ ****************************************************************************** .DefineEnvelope CLC \ Set (Y X) = envelopeData + A ADC #LO(envelopeData) \ TAX \ starting with the low byte LDA #8 \ Set A = 8 for the OSWORD command to define an envelope \ Fall through into MakeSoundEnvelope to set up Y and \ apply the OSWORD command to the (Y X) block, which \ defines the relevant sound envelope \ ****************************************************************************** \ \ Name: MakeSoundEnvelope \ Type: Subroutine \ Category: Sound \ Summary: Either make a sound or set up an envelope \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ A The action: \ \ * A = 7 make a sound \ \ * A = 8 to define a sound envelope \ \ X The low byte of the address of the OSWORD block \ \ C flag The outcome of the addition of the low bytes of (Y X) \ \ ****************************************************************************** .MakeSoundEnvelope LDY #HI(envelopeData) \ Set y to the high byte of the envelopeData block \ address, so (Y X) now points to the relevant envelope \ or sound data block BCC senv1 \ If the addition we did before calling the routine \ didn't overflow, skip the next instruction INY \ The above addition overflowed, so increment the high \ byte of (Y X) to point to the next page in memory .senv1 JSR OSWORD \ Call OSWORD with action A, as follows: \ \ * A = 7 to make the sound at (Y X) \ \ * A = 8 to set up the sound envelope at (Y X) RTS \ Return from the subroutine NOP \ This instruction appears to be unused \ ****************************************************************************** \ \ Name: ToggleEngineSound \ Type: Subroutine \ Category: Sound \ Summary: Turn the engine sound on or off \ \ ------------------------------------------------------------------------------ \ \ Arguments: \ \ A Defines the action: \ \ * 0 = turn engine sound off \ \ * Non-zero = turn engine sound on with pitch A \ \ ****************************************************************************** .ToggleEngineSound BNE MakeEngineSound \ If A is non-zero then jump to MakeEngineSound to set \ up and make the engine sound LDA #0 \ Make sound #0, to turn off the engine sound JSR MakeSound \ Fall through into ResetEngineSound to reset the pitch \ of the engine sound \ ****************************************************************************** \ \ Name: ResetEngineSound \ Type: Subroutine \ Category: Sound \ Summary: Reset the pitch of the engine sound \ \ ****************************************************************************** .ResetEngineSound LDA #255 \ Set byte #5 of sound #7 (low byte of engine pitch) to STA soundData+60 \ 255, which is the default setting RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: MakeEngineSound \ Type: Subroutine \ Category: Sound \ Summary: Make the engine sound, with the choppiness and pitch affected by \ thrust and airspeed \ \ ****************************************************************************** .MakeEngineSound LDA engineStatus \ If engineStatus is zero, then the engine is not BEQ engs4 \ running, so jump to engs4 to return from the \ subroutine LDA thrustHi \ Set (K A) = (thrusthi thrustLo) STA K \ = Thrust LDA thrustLo LDY #3 \ Set Y = 3 to act as a shift counter in the following \ loop, where we right shift (K A) four times .engs1 LSR K \ Set (K A) = (R A) / 2 ROR A \ = Thrust / 2 DEY \ Decrement the shift counter BPL engs1 \ Loop back until we have shifted right by 4 places, so \ we now have: \ \ (K A) = (K A) / 8 \ = Thrust / 8 \ \ We now ignore the high byte in K, so presumably it is \ zero CLC \ Set K = A + zVelocityPHi ADC zVelocityPHi \ = (Thrust / 8) + zVelocityPHi STA K LDA #50 \ Set A = 50 - K SEC \ = 50 - ((Thrust / 8) + zVelocityPHi) SBC K BEQ engs2 \ If A = 0, jump to engs2 to set the engine choppiness \ to 1 (a very smooth engine sound) BPL engs3 \ If A is positive, jump to engs3 to set the engine \ choppiness to A .engs2 LDA #1 \ If we get here then A is either 0 or negative, so we \ set A = 1 to use as the choppiness value in the engine \ sound, giving a very smooth engine sound .engs3 \ By the time we get here, A is in the range 1 to 50 and \ represents the engine choppiness, with a lower value \ indicating a smoother engine, and a higher value \ making the sound jump up and down in pitch more \ \ In other words, the lower the forward airspeed and \ thrust, the choppier the engine sound \ \ We can apply this to the sound envelope for the engine \ by altering the amount that the pitch changes in each \ stage of the sound envelope. These values are set in \ the third, fourth and fifth values in the envelope \ definition, with larger values giving us a larger \ amount of pitch change, and hence more choppiness in \ the engine sound STA envelopeData+2 \ Set the third and fifth values of sound envelope 1 to STA envelopeData+4 \ the value in A, and set the fourth value to the EOR #&FF \ inverse of A, which is -(A + 1) as this is a signed STA envelopeData+3 \ value \ We now want to set the pitch of the engine sound so \ that higher thrust and airspeed give us a higher \ pitch, making our Spitfire scream through the air \ \ The engine pitch is controlled by byte #5 of sound #7, \ at soundData+60, which contains the low byte of the \ engine pitch, so we now need to calculate a suitable \ pitch value LDA K \ Set A = K + 80 CLC \ = (Thrust / 8) + zVelocityPHi + 50 ADC #80 \ \ so A is a higher value when we have higher thrust and \ airspeed, which is what we want CMP soundData+60 \ If the pitch in byte #5 of sound #7 is already at this BEQ engs4 \ value, jump to engs4 to return from the subroutine, as \ we don't need to change the pitch STA soundData+60 \ Otherwise set byte #5 of sound #7 to our new pitch in \ A, so the pitch of the engine sound goes up with our \ thrust and airspeed LDA #0 \ Call DefineEnvelope with A = 0 to redefine the first JSR DefineEnvelope \ sound envelope with the new choppiness values LDA #7 \ Make sound #7, the engine pitch, to change the engine JSR MakeSound \ sound's pitch and choppiness LDA #1 \ Make sound #1, the engine amplitude, to ensure that JSR MakeSound \ both engine sounds are being made, as we need to make \ both #1 and #7 for the sound to work .engs4 RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: DrawGunSights \ Type: Subroutine \ Category: Graphics \ Summary: Draw the canopy corners and the gun sights, if shown \ \ ****************************************************************************** .DrawGunSights JSR DrawCanopyCorners \ Draw the canopy corners LDX #&DA \ Scan the keyboard to see if "I" is being pressed JSR ScanKeyboard BNE guns1 \ If "I" is not being pressed, jump to guns1 BIT gunSights \ If bit 6 of gunSights is set, then "I" is still being BVS guns3 \ held down from a previous call to this routine, so \ jump to guns3 to move on from the subroutine LDA #%11000000 \ Set A = gunSights with bits 6 and 7 flipped, which we EOR gunSights \ will set as the new value of gunSights below BMI guns2 \ If the result has bit 7 set, that means that bit 7 of \ gunSights is currently clear, so jump to guns2 as the \ sights are not already being shown, so we don't need \ to remove them \ If we get here then bit 7 of gunSights is set, so the \ sights are already on-screen, and we aren't still \ holding down "I" from a previous call, so now we \ remove the sights PHA \ Store A on the stack so we can retrieve it below LDY #HI(row6_char1_0) \ Set (Y X) to the screen address for row 6, character LDX #LO(row6_char1_0) \ block 1 LDA #3 \ Set R = 3, so we clear 3 character rows for the sights STA R LDA #0 \ Set X = 0 so we clear the sights to black JSR FillCanopyRows \ Fill the 3 screen rows with black, avoiding the canopy \ edges and removing the sights from the screen PLA \ Retrieve A from the stack JMP guns2 \ Jump down to guns2 to set gunSights to the value in A .guns1 \ If we get here then "I" is not being pressed LDA #%10000000 \ Extract bit 7 from gunSights into A, which clears bit AND gunSights \ 6 to indicate that "I" is not being pressed, while \ leaving the current state of bit 7 alone .guns2 STA gunSights \ Update gunSights to the new value in A .guns3 BPL ToggleJoystick \ If bit 7 of the new value of gunSights is clear, then \ the sights are no longer being shown, so jump down to \ ToggleJoystick to move on to the next routine \ If we get here, then bit 7 of gunSights is set, so we \ need to draw the sights LDY #7 \ First we draw the vertical bar in the middle of the \ sights, which runs through block 20 on rows 6 and 7, \ so we set a pixel row counter in Y to count through \ the rows in each character block .guns4 LDA #%10001000 \ Draw a vertical bar in the first pixel of the Y-th ORA row6_char20_0,Y \ pixel row in character block 20 on row 6, keeping STA row6_char20_0,Y \ whatever is already on screen LDA #%10001000 \ Draw a vertical bar in the first pixel of the Y-th ORA row7_char20_0,Y \ pixel row in character block 20 on row 7, keeping STA row7_char20_0,Y \ whatever is already on screen DEY \ Decrement the pixel row counter BPL guns4 \ Loop back to draw the next pixel row until we have \ drawn a vertical line through both character blocks \ Now to draw the horizontal bar of the gun sights LDA #%01110111 \ First we draw the left end of the horizontal bar, ORA row8_char11_0 \ which is starts with the three rightmost pixels in STA row8_char11_0 \ character block 11 on row 8 SEC \ Set the C flag for the subtraction below LDY #136 \ The rest of the line is made up of 17 character blocks \ with four pixels in each block, so we set a counter in \ Y to work as an offset from the left end of the line, \ counting 8 bytes per character block (17 * 8 = 136), \ working from the right end of the line to the left .guns5 LDA #%11111111 \ Draw a horizontal bar of four pixels starting at the STA row8_char11_0,Y \ Y-th offset from the start of character block 11 on \ row 8 TYA \ Set Y = Y - 8, so Y now points to the four pixels to SBC #8 \ the left, as we work or way along the line TAY BNE guns5 \ Loop back to draw the next four pixels of the line \ until we have drawn the whole horizontal line \ Fall through into ToggleJoystick to check for the \ joystick key \ ****************************************************************************** \ \ Name: ToggleJoystick \ Type: Subroutine \ Category: Keyboard \ Summary: Toggle the joystick setting \ \ ------------------------------------------------------------------------------ \ \ Note that the game uses row29_char20_4 to check the joystick status, so it \ is not only an on-screen indicator, it's also the joystick status variable. \ \ ****************************************************************************** .ToggleJoystick LDX #&9F \ Scan the keyboard to see if TAB is being pressed JSR ScanKeyboard BNE tjoy1 \ If TAB is not being pressed, jump to tjoy1 LDA pressingTab \ If pressingTab is non-zero, then we are still pressing BNE tjoy3 \ TAB from a previous visit to this routine, so jump to \ tjoy3 to return from the subroutine \ This is a new press of TAB, so now we want to toggle \ the joystick setting LDA row29_char20_4 \ Toggle the joystick indicator pixel above the middle EOR #%10001000 \ of the rudder indicator STA row29_char20_4 LDA #128 \ Set A = 128 to use as the value for pressingTab below, \ to indicate that TAB is being pressed BNE tjoy2 \ Jump to tjoy2 to skip the following instruction (this \ BNE is effectively a JMP as A is never zero) .tjoy1 LDA #0 \ If we get here then TAB is not being pressed, so we \ set A = 0 to set as the value for pressingTab .tjoy2 STA pressingTab \ Set pressingTab to the value in A, which will be 0 if \ TAB is not being pressed, or 128 if it is being \ pressed .tjoy3 RTS \ Return from the subroutine EQUB &8D \ This byte appears to be unused \ ****************************************************************************** \ \ Name: envelopeData \ Type: Variable \ Category: Sound \ Summary: Data for two sound envelopes \ \ ------------------------------------------------------------------------------ \ \ There are two sound envelopes defined in Aviator. \ \ * Envelope 1 defines the engine sound. The third, fourth and fifth values \ (envelopeData+2, envelopeData+3 and envelopeData+4) control the choppiness \ of the engine sound and get altered by the MakeEngineSound routine, \ depending on the thrust and airspeed. These values control the change of \ pitch per step in the three stages of the sound envelope, so higher values \ make the sound jump up and down more in terms of pitch, which makes the \ sound more choppy and less smooth. \ \ * Envelope 2 defines the sound of gunfire, both for our Spitfire and for the \ Theme aliens. \ \ ****************************************************************************** .envelopeData EQUB 1, 1, 50, -50, 50, 1, 2, 1, 0, 0, 0, 0, 0, 0 EQUB 2, 1, -5, 0, 0, 10, 0, 0, 120, -1, -24, -10, 120, 116 \ ****************************************************************************** \ \ Name: soundData \ Type: Variable \ Category: Sound \ Summary: OSWORD blocks for making the various game sounds \ \ ------------------------------------------------------------------------------ \ \ Sound data. To make a sound, the MakeSound passes the bytes in this table to \ OSWORD 7. These bytes are the OSWORD equivalents of the parameters passed to \ the SOUND keyword in BASIC. The parameters have these meanings: \ \ channel/flush, amplitude (or envelope number if 1-4), pitch, duration \ \ where each value consists of two bytes, with the low byte first and the high \ byte second. \ \ For the channel/flush parameter, the top nibble of the low byte is the flush \ control (where a flush control of 0 queues the sound, and a flush control of \ 1 makes the sound instantly), while the bottom nibble of the low byte is the \ channel number . When written in hexadecimal, the first figure gives the flush \ control, while the second is the channel (so &13 indicates flush control = 1 \ and channel = 3). \ \ So when we call MakeSound with A = 5 to make sound #5, this is what the \ routine effectively does: \ \ SOUND &10, -13, 4, 12 \ \ which makes a sound with flush control 1 on channel 0, and with amplitude -13, \ pitch 4 and duration 12. Meanwhile, sound #2 is like this: \ \ SOUND &13, 2, 220, 2 \ \ which makes a sound with flush control 1 on channel 3, using envelope 2, \ and with pitch 220 and duration 2. The two sound envelopes (1 and 2) are set \ up by the DefineEnvelope routine. \ \ ****************************************************************************** .soundData EQUB &10, &00 \ Sound #0: Stop engine sound (SOUND &10, 0, 0, 0) EQUB &00, &00 EQUB &00, &00 EQUB &00, &00 EQUB &10, &00 \ Sound #1: Engine amplitude (SOUND &10, -5, 3, 255) EQUB &FB, &FF \ EQUB &03, &00 \ Second parameter (soundData+10) gets changed in the EQUB &FF, &00 \ ProcessVolumeKeys routine to alter the volume EQUB &13, &00 \ Sound #2: Alien destroyed (SOUND &13, 2, 220, 2) EQUB &02, &00 \ EQUB &DC, &00 \ Uses sound envelope 2 EQUB &02, &00 EQUB &13, &00 \ Sound #3: High g-forces (SOUND &13, -15, 120, 7) EQUB &F1, &FF EQUB &78, &00 \ This sound is a long, medium beep that indicates the EQUB &07, &00 \ plane's wings are coming off EQUB &13, &00 \ Sound #4: Plane is stalling (SOUND &13, -12, 0, 1) EQUB &F4, &FF \ EQUB &00, &00 \ This sound is a short, low beep EQUB &01, &00 EQUB &10, &00 \ Sound #5: Crash (SOUND &10, -13, 4, 12) EQUB &F3, &FF EQUB &04, &00 EQUB &0C, &00 EQUB &13, &00 \ Sound #6: Our gunfire (SOUND &13, 2, 60, 2) EQUB &02, &00 EQUB &3C, &00 EQUB &02, &00 EQUB &11, &00 \ Sound #7: Engine pitch (SOUND &11, 1, 255, 255) EQUB &01, &00 \ EQUB &FF, &00 \ Uses sound envelope 1 EQUB &FF, &00 \ \ The fifth byte (soundData+60) is the low byte of the \ sound's pitch and gets changed in various places \ to change the pitch of the engine sound \ ****************************************************************************** \ \ Name: PrintTooLate \ Type: Subroutine \ Category: The Theme \ Summary: Print the "TOO LATE!" message at column 6, row 7 on-screen \ \ ****************************************************************************** .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 \ Type: Variable \ Category: The Theme \ Summary: The "TOO LATE!" message shown when the aliens land in Acornsville \ \ ------------------------------------------------------------------------------ \ \ 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 \ Type: Variable \ Category: Graphics \ Summary: Lookup table for converting pixel x-coordinate to low byte of \ screen address \ \ ------------------------------------------------------------------------------ \ \ See xLookupHi for an explanation of this table. \ \ ****************************************************************************** .xLookupLo FOR I%, 0, 39 EQUB LO(I% * 8) NEXT \ ****************************************************************************** \ \ Name: xLookupHi \ Type: Variable \ Category: Graphics \ Summary: Lookup table for converting pixel x-coordinate to high byte of \ screen address \ \ ------------------------------------------------------------------------------ \ \ 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 \ Type: Variable \ Category: Keyboard \ Summary: Internal key numbers of high priority keys that are scanned first \ Deep dive: The key logger \ \ ------------------------------------------------------------------------------ \ \ 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 \ Type: Variable \ Category: Keyboard \ Summary: Internal key numbers of lower priority keys that are scanned \ second \ Deep dive: The key logger \ \ ------------------------------------------------------------------------------ \ \ 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 \ 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 \ \ ------------------------------------------------------------------------------ \ \ 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 \ Type: Variable \ Category: Keyboard \ Summary: Key logger value (low byte) for key presses in keyTable2 \ Deep dive: The key logger \ \ ------------------------------------------------------------------------------ \ \ 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 \ Type: Variable \ Category: Keyboard \ Summary: Key logger value (high byte) for key presses in keyTable2 \ Deep dive: The key logger \ \ ------------------------------------------------------------------------------ \ \ 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 \ Type: Variable \ Category: Keyboard \ Summary: Key logger value (low byte) for key presses in keyTable1 \ Deep dive: The key logger \ \ ------------------------------------------------------------------------------ \ \ 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 \ Type: Variable \ Category: Keyboard \ Summary: Key logger value (high byte) for key presses in keyTable1 \ Deep dive: The key logger \ \ ------------------------------------------------------------------------------ \ \ 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 \ Type: Variable \ Category: Sound \ Summary: The sound of us making contact with the ground while landing \ \ ------------------------------------------------------------------------------ \ \ 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 \ 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 \ \ ****************************************************************************** .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 \ Type: Variable \ Category: Dashboard \ Summary: The value range of a quadrant in each indicator \ \ ****************************************************************************** .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 \ Type: Variable \ Category: Dashboard \ Summary: The maximum x-delta for the hand line in each indicator \ \ ****************************************************************************** .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 \ Type: Variable \ Category: Dashboard \ Summary: The maximum y-delta for the hand line in each indicator \ \ ****************************************************************************** .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 \ Type: Variable \ Category: Dashboard \ Summary: Line buffer storage for the start x-coordinate for each indicator \ line (I) \ Deep dive: Line buffers \ \ ****************************************************************************** .indicatorLineI \ Storage for the x-coordinate of the starting point 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 \ Type: Variable \ Category: Dashboard \ Summary: Line buffer storage for the start y-coordinate for each indicator \ line (J) \ Deep dive: Line buffers \ \ ****************************************************************************** .indicatorLineJ \ Storage for the y-coordinate of the starting point 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 \ 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 \ Type: Variable \ Category: Dashboard \ Summary: The base value for each indicator \ \ ****************************************************************************** .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 \ 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 \ ****************************************************************************** \ \ Name: indicatorMax \ Type: Variable \ Category: Dashboard \ Summary: The maximum value shown on each indicator \ \ ****************************************************************************** .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 \ Type: Variable \ Category: Dashboard \ Summary: Line buffer storage for the indicator line's |x-delta| (T) \ Deep dive: Line buffers \ \ ****************************************************************************** .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 \ Type: Variable \ Category: Dashboard \ Summary: Line buffer storage for the indicator line's |y-delta| (U) \ Deep dive: Line buffers \ \ ****************************************************************************** .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 \ Type: Variable \ Category: Dashboard \ Summary: Line buffer storage for the indicator line's direction (V) \ Deep dive: Line buffers \ \ ****************************************************************************** .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 \ Type: Variable \ Category: Dashboard \ Summary: Temporary storage \ \ ****************************************************************************** .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 \ Type: Variable \ Category: Dashboard \ Summary: Temporary storage \ \ ****************************************************************************** .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 \ Type: Variable \ Category: Dashboard \ Summary: The first indicator counter \ \ ****************************************************************************** .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 \ Type: Variable \ Category: Dashboard \ Summary: The second indicator counter \ \ ****************************************************************************** .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 \ Type: Variable \ Category: Dashboard \ Summary: Temporary storage \ \ ****************************************************************************** .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 \ Type: Variable \ Category: Dashboard \ Summary: The value of the altimeter's large "minute" hand \ \ ****************************************************************************** .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) \ 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 \ \ ------------------------------------------------------------------------------ \ \ 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 a 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) \ Type: Subroutine \ Category: Flight model \ Summary: Convert velocity to the plane's perspective and calculate various \ aerodynamic forces \ Deep dive: The flight model \ \ ------------------------------------------------------------------------------ \ \ 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) \ Type: Subroutine \ Category: Flight model \ Summary: If we are near to an exploding alien, apply turbulence \ Deep dive: The flight model \ Explosions and turbulence \ \ ------------------------------------------------------------------------------ \ \ 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) \ Type: Subroutine \ Category: Flight model \ Summary: Calculate the dxTurn and xLinear vectors, and the slipRate \ Deep dive: The flight model \ \ ------------------------------------------------------------------------------ \ \ 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) \ Type: Subroutine \ Category: Flight model \ Summary: Calculate the forces for when we are on the ground \ Deep dive: The flight model \ On-ground calculations \ \ ------------------------------------------------------------------------------ \ \ 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 \ forwards 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 \ set landingStatus = 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) \ 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 \ \ ------------------------------------------------------------------------------ \ \ 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) \ Type: Subroutine \ Category: Flight model \ Summary: Calculate fuel usage \ Deep dive: The flight model \ \ ------------------------------------------------------------------------------ \ \ 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 empty, so turn the engine off JSR SetEngine .fmod20 RTS \ Return from the subroutine \ ****************************************************************************** \ \ Name: AdjustTurn \ Type: Subroutine \ Category: Flight model \ Summary: Adjust the plane's turn rate \ \ ------------------------------------------------------------------------------ \ \ 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 \ Type: Subroutine \ Category: Flight model \ Summary: Adjust the plane's velocity vector \ \ ------------------------------------------------------------------------------ \ \ 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 \ Type: Subroutine \ Category: Flight model \ Summary: Move the plane and adjust its rotation \ \ ------------------------------------------------------------------------------ \ \ 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 \ \ Finally, 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) \ Type: Subroutine \ Category: Flight model \ Summary: Set up various variables to use in the aerodynamics calculations \ Deep dive: The flight model \ \ ------------------------------------------------------------------------------ \ \ 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) \ 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 \ \ ------------------------------------------------------------------------------ \ \ 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) \ Type: Subroutine \ Category: Flight model \ Summary: Calculate various aerodynamic figures \ Deep dive: The flight model \ \ ------------------------------------------------------------------------------ \ \ 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 \ Type: Subroutine \ Category: Flight model \ Summary: Scale the flight forces by the relevant scale factors \ Deep dive: The flight model \ Ripping the wings off \ \ ------------------------------------------------------------------------------ \ \ 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 \ Type: Subroutine \ Category: Maths \ Summary: Multiply an 8-bit and a 16-bit number \ Deep dive: Times tables and nibble arithmetic \ \ ------------------------------------------------------------------------------ \ \ 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 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 \ Type: Subroutine \ Category: Maths \ Summary: Negate a 16-bit number \ \ ------------------------------------------------------------------------------ \ \ 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 \ Type: Subroutine \ Category: Flight model \ Summary: Calculate the pitching, rolling and yawing moments due to the \ current pitch, roll and yaw rates \ \ ------------------------------------------------------------------------------ \ \ 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 \ 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 \ \ ------------------------------------------------------------------------------ \ \ 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 control'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 control'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 control'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) \ Type: Subroutine \ Category: Flight model \ Summary: Calculate the (dxTurn, dyTurn, dzTurn) vector \ \ ------------------------------------------------------------------------------ \ \ 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) \ Type: Subroutine \ Category: Flight model \ Summary: Calculate the (xLinear, yLinear, zLinear) vector \ \ ------------------------------------------------------------------------------ \ \ 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 \ Type: Subroutine \ Category: Flight model \ Summary: Check whether the plane is over the runway \ Deep dive: Take-offs and landings \ On-ground calculations \ \ ------------------------------------------------------------------------------ \ \ 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) \ Type: Subroutine \ Category: Flight model \ Summary: If this is an emergency landing, make the landing a bumpy one \ Deep dive: Take-offs and landings \ \ ------------------------------------------------------------------------------ \ \ 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) \ 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 \ \ ------------------------------------------------------------------------------ \ \ 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) \ Type: Subroutine \ Category: Flight model \ Summary: If we are taxiing, restrict the plane's vertical movement and roll \ Deep dive: Take-offs and landings \ \ ------------------------------------------------------------------------------ \ \ 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 taxiing, 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) \ Type: Subroutine \ Category: Flight model \ Summary: If we are taxiing with the undercarriage up, restrict pitching \ Deep dive: Take-offs and landings \ \ ------------------------------------------------------------------------------ \ \ 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) \ 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 \ \ ------------------------------------------------------------------------------ \ \ 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) \ Type: Subroutine \ Category: Flight model \ Summary: If we are not taxiing, process the landing \ Deep dive: Take-offs and landings \ \ ------------------------------------------------------------------------------ \ \ 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) \ 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 \ \ ------------------------------------------------------------------------------ \ \ 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 \ Type: Subroutine \ Category: Flight model \ Summary: Apply a random amount of roll or bumpiness to the plane \ Deep dive: Take-offs and landings \ \ ------------------------------------------------------------------------------ \ \ 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 \ Type: Subroutine \ Category: Maths \ Summary: Add or subtract a scaled amount to a variable \ \ ------------------------------------------------------------------------------ \ \ 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 \ Type: Subroutine \ Category: Flight model \ Summary: Multiply the high byte of the plane's altitude by a 16-bit number \ \ ------------------------------------------------------------------------------ \ \ 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 \ 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 \ Type: Subroutine \ Category: Setup \ Summary: The main entry point for the game: move code into lower memory and \ call it \ \ ****************************************************************************** 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 \ \ ****************************************************************************** COPYBLOCK &0D00, &1100, &5800 \ Code between &0D00 and &10FF starts out at \ &5800 before being moved COPYBLOCK &0B00, &0D00, &5C00 \ Code between &0B00 and &0CFF starts out at \ &5C00 before being moved SAVE "3-assembled-output/AVIA.bin", LOAD%, P%