# Revs G source

```       Name: MultiplyCoords                                          [Show more]
Type: Subroutine
Category: Maths (Arithmetic)
Summary: Multiply a 16-bit coordinate value and a 16-bit factor, optionally
tallying or changing the sign of the result
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* RotateVector calls MultiplyCoords
* ApplySteeringForce calls via MultiplyCoords+7
* ApplySteeringSpeed calls via MultiplyCoords+7

This routine multiplies two 16-bit values and stores the result, optionally
negating the result, or adding it to the existing contents of the destination.

The first number (specified by parameter N, so let's call it variableN) is a
16-bit signed integer, while the second number (specified by parameter X, so
let's call it variableX) is a 16-bit sign-magnitude number with the sign in
bit 0 of the low byte. The result is stored in the variable specified by
parameter K, so let's call it variableK.

The values of bits 6 and 7 of A affect the result as follows:

* If A = %00000000, then we calculate the following:

variableK = variableN * variableX

* If A = %01000000, then we calculate the following:

variableK = variableK + variableN * variableX

* If A = %10000000, then we calculate the following:

variableK = - variableN * variableX

* If A = %11000000, then we calculate the following:

variableK = variableK - variableN * variableX

Arguments:

N                    Offset of the 16-bit signed number to multiply:

* 0 = xPlayerSpeed

* 1 = zPlayerSpeed

* 6 = xPlayerAccel

* 7 = zPlayerAccel

* 8 = xVelocity

* 9 = zVelocity

* 10 = xTyreForceNose

* 12 = zTyreForceNose

X                    Offset of the 16-bit sign-magnitude value to multiply:

* 0 = sinYawAngle

* 1 = cosYawAngle

* 2 = (steeringHi steeringLo)

K                    Offset of the variable to store the result in:

* 3 = xAcceleration

* 4 = zAcceleration

* 8 = xVelocity

* 9 = zVelocity

* 12 = zTyreForceNose

* 14 = xSteeringForce

A                    Details of the operation to perform:

* Bit 6 defines whether we add or store:

* 0 = store the result in the variable defined by K,
overwriting the existing contents

* 1 = add the result to the variable defined by K

* Bit 7 defines the sign to apply to the result:

* 0 = do not negate the multiplication

* 1 = negate the multiplication

Other entry points:

MultiplyCoords+7     Use the following variables instead of the above:

* Y = Offset of the 16-bit signed number to multiply

* A = Offset of the variable to store the result in

* H = Details of the operation to perform

.MultiplyCoords

LDY N                  \ Set Y to the offset of the 16-bit signed number

STA H                  \ Store the details of the operation to perform in H

\ point

\ This is where we join the subroutine when called via
\ MultiplyCoords+7

STA K                  \ Set K to the offset of the variable to store the
\ result in

.mcoo1

LDA xPlayerSpeedHi,Y   \ Set (QQ PP) to the 16-bit signed number pointed to by
STA PP                 \ Y (variableY)
LDA xPlayerSpeedTop,Y
STA QQ

LDA sinYawAngleLo,X    \ Set (SS RR) to the 16-bit sign-magnitude number
STA RR                 \ pointed to by X (variableX)
LDA sinYawAngleHi,X
STA SS

JSR Multiply16x16      \ Set (A T) = (QQ PP) * (SS RR)
\
\ And apply the sign from bit 7 of H

STA U                  \ Set (U T) = (A T)
\           = (QQ PP) * (SS RR)

LDY K                  \ Set Y to K, so we can store the result in the variable
\ pointed to by K

BVS AddCoords          \ the result to the variable pointed to by K

LDA T                  \ Store the result in the variable pointed to by K
STA xPlayerSpeedHi,Y
LDA U
STA xPlayerSpeedTop,Y

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Maths (Geometry)
Summary: Subtract from a specified coordinate variable
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* ApplyWingBalance calls SubtractCoords

This routine subtracts (U T) from the specified 16-bit variable.

Specifically, it calculates:

variableY = variableY - (U T) * abs(A)

where A is the last variable to be loaded before the subroutine call. So if
the call follows an LDA instruction, for example, the following is calculated
if A is positive:

variableY = variableY - (U T)

and the following is calculated if A is negative:

variableY = variableY + (U T)

Arguments:

N flag               Determines the action:

* If positive, subtract (U T)

* If negative, add (U T)

.SubtractCoords

\
\   variableY = variableY + (U T)

JSR Negate16Bit+2      \ Set (A T) = -(U T)

STA U                  \ Set (U T) = (A T)
\           = -(U T)

\ Fall through into AddCoords to calculate:
\
\   variableY = variableY - (U T)

Type: Subroutine
Category: Maths (Geometry)
Summary: Add to a specified coordinate variable
Context: See this subroutine on its own page
References: This subroutine is called as follows:

This routine adds (U T) to the specified 16-bit variable.

Arguments:

Y                    Offset of the variable to update:

* 3 = xAcceleration

* 4 = zAcceleration

* 6 = xPlayerAccel

* 9 = zVelocity

* 12 = zTyreForceNose

* 14 = xSteeringForce

LDA xPlayerSpeedHi,Y   \ Add (U T) to (xPlayerSpeedTop xPlayerSpeedHi)
CLC                    \
ADC T                  \ starting with the low bytes
STA xPlayerSpeedHi,Y

LDA xPlayerSpeedTop,Y  \ And then the high bytes
STA xPlayerSpeedTop,Y

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Maths (Geometry)
Summary: Rotate a vector from the 3D world coordinate system into the frame
of reference of the player's car
Deep dive: The core driving model
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* ApplyDrivingModel calls RotateCoordToCar

Calculate the following:

[ xVelocity ]   [ cosYawAngle   0   -sinYawAngle ]   [ xPlayerSpeed ]
[     -     ] = [      0        1         0      ] . [ yPlayerSpeed ]
[ zVelocity ]   [ sinYawAngle   0    cosYawAngle ]   [ zPlayerSpeed ]

This rotates the player's delta vector from the 3D world coordinate system to
the frame of reference of the player's car.

The individual calculations are as follows:

xVelocity = xPlayerSpeed * cosYawAngle - zPlayerSpeed * sinYawAngle

zVelocity = xPlayerSpeed * sinYawAngle + zPlayerSpeed * cosYawAngle

.RotateCoordToCar

LDY #0                 \ Set Y = 0, so in the call to RotateVector, variableY
\ is xPlayerSpeed and variableY+1 is zPlayerSpeed

LDA #8                 \ Set A = 8, so in the call to RotateVector, we store
\ the result in xVelocity and zVelocity

LDX #%11000000         \ Set bits 6 and 7 of X, to set the polarity in the call
\ to RotateVector

\
\   xVelocity =   xPlayerSpeed * cosYawAngle
\               - zPlayerSpeed * sinYawAngle
\
\   zVelocity =   xPlayerSpeed * sinYawAngle
\               + zPlayerSpeed * cosYawAngle
\
\ This BNE is effectively a JMP as X is never zero

Type: Subroutine
Category: Maths (Geometry)
Summary: Rotate a vector from the frame of reference of the player's car
into the 3D world coordinate system
Deep dive: The core driving model
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* ApplyDrivingModel calls RotateCarToCoord

Calculate the following:

[ xAcceleration ]   [  cosYawAngle   0   sinYawAngle ]   [ xPlayerAccel ]
[       -       ] = [       0        1        0      ] . [       -      ]
[ zAcceleration ]   [ -sinYawAngle   0   cosYawAngle ]   [ zPlayerAccel ]

This rotates the xPlayerAccel vector from the frame of reference of the
player's car into the 3D world coordinate system.

The rotation matrix is the transpose of the matrix from RotateCoordToCar,
which is the inverse, so the RotateCarToCoord routine reverses the rotation in
the RotateCoordToCar routine.

The individual calculations are as follows:

xAcceleration = xPlayerAccel * cosYawAngle + zPlayerAccel * sinYawAngle

zAcceleration = zPlayerAccel * cosYawAngle - xPlayerAccel * sinYawAngle

.RotateCarToCoord

LDY #6                 \ Set Y = 6, so in the call to RotateVector, variableY
\ is xPlayerAccel and variableY+1 is zPlayerAccel

LDA #3                 \ Set A = 3, so in the call to RotateVector, we store
\ the result in xAcceleration and zAcceleration

LDX #%01000000         \ Set bit 6 and clear bit 7 of X, to set the polarity in
\ the call to RotateVector

\ Fall through into RotateVector to calculate the
\ following:
\
\   xAcceleration =   xPlayerAccel * cosYawAngle
\                   + zPlayerAccel * sinYawAngle
\
\   zAcceleration =   zPlayerAccel * cosYawAngle
\                   - xPlayerAccel * sinYawAngle

Type: Subroutine
Category: Maths (Geometry)
Summary: Rotate a vector by a rotation matrix
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* RotateCoordToCar calls RotateVector

If bit 7 of X is clear, this routine calculates:

[  variableA  ]   [ cosYawAngle   0   -sinYawAngle ]   [  variableY  ]
[      -      ] = [      0        1         0      ] . [      -      ]
[ variableA+1 ]   [ sinYawAngle   0    cosYawAngle ]   [ variableY+1 ]

by doing these individual calculations:

variableA = variableY * cosYawAngle + variableY+1 * sinYawAngle

variableA+1 = variableY+1 * cosYawAngle - variableY * sinYawAngle

If bit 7 of X is set, this routine calculates:

[  variableA  ]   [  cosYawAngle   0   sinYawAngle ]   [  variableY  ]
[      -      ] = [       0        1        0      ] . [      -      ]
[ variableA+1 ]   [ -sinYawAngle   0   cosYawAngle ]   [ variableY+1 ]

by doing these individual calculations:

variableA = variableY * cosYawAngle - variableY+1 * sinYawAngle

variableA+1 = variableY+1 * cosYawAngle + variableY * sinYawAngle

For it to work, the routine must be called with bit 6 of X set.

Arguments:

Y                    Offset of the 16-bit signed number to multiply:

* 0 = xPlayerSpeed and zPlayerSpeed

* 6 = xPlayerAccel and zPlayerAccel

A                    Offset of the variable to store the result in:

* 3 = xAcceleration and zAcceleration

* 8 = xVelocity and zVelocity

X                    Details of the operation to perform on the second and
fourth multiplications:

* Bit 6 needs to be set

* Bit 7 defines the sign to apply to the result:

* 0 = do not negate the result

* 1 = negate the result

.RotateVector

STY N                  \ Set N to the offset of the 16-bit signed number, and
\ let's call this number variableY (as it is specified
\ by parameter Y)

STA K                  \ Set K to the offset of the variable to store the
\ result in, and let's call this number variableA (as it
\ is specified by parameter A)

STX GG                 \ Store the details of the operation to perform in GG

LDX #1                 \ Set X = 1, so in the call to MultiplyCoords we use
\ cosYawAngle as the 16-bit sign-magnitude value to
\ multiply

LDA #%00000000         \ Set A = %00000000, so in the call to MultiplyCoords we
\ overwrite the result rather than adding, and do not
\ negate the multiplication

JSR MultiplyCoords     \ Set variableA = variableY * variableX
\               = variableY * cosYawAngle

DEX                    \ Set X = 0, so in the call to MultiplyCoords we use
\ sinYawAngle as the 16-bit sign-magnitude value to
\ multiply

INC N                  \ Point to the next variable after the 16-bit signed
\ number we just used, so in the call to MultiplyCoords
\ we use variableY+1 as the 16-bit signed number

LDA GG                 \ Set A to the details of the operation to perform in
\ GG, as specified by parameter X
\
\ Bit 6 of parameter X is always set in calls to this
\ routine, so we add the result to variableA

JSR MultiplyCoords     \ Set:
\
\   variableA = variableA + variableY+1 * variableX
\             = variableA + variableY+1 * sinYawAngle
\
\ if bit 7 of parameter X is clear, or:
\
\   variableA = variableA - variableY+1 * sinYawAngle
\
\ if bit 7 of parameter X is set

INX                    \ Set X = 1, so in the call to MultiplyCoords we use
\ cosYawAngle as the 16-bit sign-magnitude value to
\ multiply

INC K                  \ Point to the next variable after the one we just
\ stored the result in, so in the call to MultiplyCoords
\ we use variableA+1 to store the result

LDA #%00000000         \ Set A = %00000000, so in the call to MultiplyCoords we
\ overwrite the result rather than adding, and do not
\ negate the multiplication

JSR MultiplyCoords     \ Set variableA+1 = variableY+1 * variableX
\                 = variableY+1 * cosYawAngle

DEX                    \ Set X = 0, so in the call to MultiplyCoords we use
\ sinYawAngle as the 16-bit sign-magnitude value to
\ multiply

DEC N                  \ Point back to the original variable for the 16-bit
\ signed number we just used, so in the call to
\ MultiplyCoords we use variableY again as the 16-bit
\ signed number

LDA GG                 \ Set A to the details of the operation to perform in
EOR #%10000000         \ GG with bit 7 flipped, which is as specified by
\ parameter X, but with a flipped sign
\
\ Bit 6 of parameter X is always set in calls to this
\ routine, so we add the result to variableA+1

JSR MultiplyCoords     \ Set:
\
\   variableA+1 = variableA+1 - variableY * variableX
\               = variableA+1 - variableY * sinYawAngle
\
\ if bit 7 of parameter X is clear, or:
\
\   variableA+1 = variableA+1 + variableY * sinYawAngle
\
\ if bit 7 of parameter X is set

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Driving model
Summary: Apply the deltas in the x-axis and z-axis to the player's
coordinates
Deep dive: The core driving model
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* ApplyDrivingModel calls ApplyDeltas

Calculate the following:

xPlayerCoord = xPlayerCoord + xPlayerSpeed * 2

zPlayerCoord = zPlayerCoord + zPlayerSpeed * 2

playerYawAngle = playerYawAngle + spinYawAngle

.ApplyDeltas

LDX #1                 \ Set X = 1, to act as an axis counter for xPlayerSpeed
\ and zPlayerSpeed
\
\ The comments below are for when X = 1

LDY #2                 \ Set Y = 2, to act as a variable counter in the
\ following loop, iterating through values 0 and 2, as
\ Y is decremented twice at the end of the loop
\
\ The comments below are for when Y = 2

.delt1

LDA #0                 \ Set V = 0, to use as the sign bit for (V A T)
STA V

LDA xPlayerSpeedHi,X   \ Set (V A T) = zPlayerSpeed
STA T
LDA xPlayerSpeedTop,X

BPL delt2              \ If A is positive, jump to delt2 to skip the following
\ instruction

DEC V                  \ Set V = &FF, so it contains the correct sign bit for
\ (V A T)

.delt2

ASL T                  \ Set (U A T) = (V A T) << 1
ROL A
ROL V
STA U

LDA xPlayerCoordLo,Y   \ Set zPlayerCoord = zPlayerCoord + (U A T)
ADC T                  \                  = zPlayerCoord + zPlayerSpeed * 2
STA xPlayerCoordLo,Y   \
\ starting with the low bytes

LDA xPlayerCoordHi,Y   \ Then the high bytes
STA xPlayerCoordHi,Y

LDA xPlayerCoordTop,Y  \ And then the top bytes
STA xPlayerCoordTop,Y

DEY                    \ Decrement Y twice so the next iteration adds to the
DEY                    \ xPlayerCoord variable

DEX                    \ Decrement X so the next iteration sets (V A T) to
\ xPlayerSpeed

BPL delt1              \ Loop back until we have updated both xPlayerCoord and
\ zPlayerCoord

LDA playerYawAngleLo   \ Set playerYawAngle = playerYawAngle + spinYawAngle
CLC                    \
ADC spinYawAngleHi     \ starting with the low bytes
STA playerYawAngleLo

LDA playerYawAngleHi   \ And then the high bytes
STA playerYawAngleHi

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Driving model
Summary: Update the player's velocity and spin yaw angle
Deep dive: The core driving model
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* ApplyDrivingModel calls UpdateVelocity

Calculate the following:

xPlayerSpeed = xPlayerSpeed + xAcceleration << 5

zPlayerSpeed = zPlayerSpeed + zAcceleration << 3

spinYawAngle = spinYawAngle + spinYawDelta << 3

.UpdateVelocity

LDX #2                 \ Set X = 2, to act as an axis counter in the following
\ loop, working through three axes from 2 to 0

.delf1

LDA #0                 \ Set V = 0, to use as the sign bit for (V A T)
STA V

LDA xAccelerationLo,X  \ Set (A T) = xAcceleration for axis X
STA T
LDA xAccelerationHi,X

BPL delf2              \ If A is positive, jump to delf2 to skip the following
\ instruction

DEC V                  \ Set V = &FF, so it contains the correct sign bit for
\ (V A T)

.delf2

LDY #3                 \ Set Y = 3, to act as a shift counter in the loop
\ below, so we left-shift by three places

CPX #2                 \ If X <> 2, jump to delf3 to skip the following
BNE delf3              \ instruction

\ If we get here then X = 2, so (A T) contains
\ spinYawDelta

LDY #5                 \ Set Y = 5, so for the third axis, we left-shift by
\ five places

.delf3

ASL T                  \ Set (V A T) = (V A T) << 1
ROL A
ROL V

DEY                    \ Decrement the shift counter in Y

BNE delf3              \ Loop back until we have left-shifted Y times

STA U                  \ Set (V U T) = (V A T)

\ In the following, we update:
\
\   * xPlayerSpeed when X = 0
\
\   * zPlayerSpeed when X = 1
\
\   * spinYawAngle when X = 2
\
\ The following comments are for when X = 0

LDA xPlayerSpeedLo,X   \ Set xPlayerSpeed = xPlayerSpeed + (V U T)
CLC                    \
ADC T                  \ starting with the low bytes
STA xPlayerSpeedLo,X

LDA xPlayerSpeedHi,X   \ Then the high bytes
STA xPlayerSpeedHi,X

LDA xPlayerSpeedTop,X  \ And then the top bytes
STA xPlayerSpeedTop,X

DEX                    \ Decrement the axis counter in X to point to the next
\ axis

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

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Driving model
Summary: Process the key press for starting the engine
Deep dive: Modelling the engine
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* ApplyEngine calls ProcessEngineStart

.ProcessEngineStart

\ The routine is called from ApplyEngine when the engine
\ is not running

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

LDY gearNumber         \ If gearNumber = 1, then we are in neutral, so jump to
DEY                    \ engs1
BEQ engs1

\ If we get here then we are still in gear

LDA playerSpeedHi      \ Set A = playerSpeedHi

BNE engs3              \ If A <> 0 then we are moving, so jump to engs3 to
\ start the engine and set the rev counter to
\ playerSpeedHi, as the engine can be restarted after
\ stalling if we are going fast enough (i.e. if
\ playerSpeedHi > 0)

.engs1

\ If we get here then we are either in neutral, or we
\ are not in neutral but are not moving

LDA #0                 \ Set A = 0 to set as the value of the rev counter

BEQ SetRevsNoTorque    \ Jump to SetRevsNoTorque to zero the rev counter and
\ engine torque (this BEQ is effectively a JMP as A is
\ always zero)

.engs2

\ If we get here then then the engine is not running and
\ "T" is being pressed

LDA VIA+&68            \ Read 6522 User VIA T1C-L timer 2 low-order counter
\ (SHEILA &68), which decrements one million times a
\ second and will therefore be pretty random

AND oddsOfEngineStart  \ Set A = A mod oddsOfEngineStart

\ Otherwise keep going to start the engine (which has a
\ chance of 1 in oddsOfEngineStart of happening) and set
\ the revs to zero (as A is zero)

.engs3

\ If we get here then the engine is not running, and we
\ are either already moving, or "T" is being pressed and
\ we passed through the above (a 1 in oddsOfEngineStart
\ chance)

LDX #7                 \ Set oddsOfEngineStart = 7, to reset it back to the
STX oddsOfEngineStart  \ default chance of starting the engine

LDX #&FF               \ Set engineStatus = &FF to turn on the engine
STX engineStatus

BMI ThrobRevsNoTorque  \ Jump to ThrobRevsNoTorque to set the rev counter to A
\ with a random throb added (this BMI is effectively a
\ JMP as X is always negative)

Type: Subroutine
Category: Driving model
Summary: Calculate the value of the rev counter according to the throttle
being applied and zero the engine torque
Deep dive: Modelling the engine
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* ApplyEngine calls CalcRevsNoTorque
* ApplyEngine calls via CalcRevsNoTorque-2

Other entry points:

CalcRevsNoTorque-2   Set clutchEngaged to A before running the routine

STA clutchEngaged      \ Set clutchEngaged = A

.CalcRevsNoTorque

LDA revCount           \ Set A to the current rev counter

LDX throttleBrakeState \ If throttleBrakeState <> 1 then the throttle is not
BNE urev1

ADC #7                 \ Set A = A + 7
\       = revCount + 7

CMP throttleBrake      \ If A >= throttleBrake, then the rev counter is higher
BCS urev1              \ than the amount of throttle being applied, so jump to
\ urev1

CMP #140               \ If A < 140, jump to SetRevsNoTorque to set the rev
BCC SetRevsNoTorque    \ counter to the new amount and zero the engine torque,
\ returning from the subroutine using a tail call

.urev1

CMP #42                \ If A < 42, jump to urev2 to set A = 40, the idling
BCC urev2              \ level of the rev counter

SEC                    \ Set A = A - 12
SBC #12

BCS ThrobRevsNoTorque  \ Jump to ThrobRevsNoTorque to set the rev counter to
\ the new amount, with a random throb added, and zero
\ the engine torque, returning from the subroutine using
\ a tail call (this BCS is effectively a JMP, as we know
\ the subtraction won't underflow as A >= 42)

.urev2

LDA #40                \ Set A = 40 to set as the rev count

\ Fall through into ThrobRevsNoTorque to set the rev
\ counter to 40, with a random throb added

Type: Subroutine
Category: Driving model
Summary: Set the rev counter after adding a random throb and zero the
engine torque
Deep dive: Modelling the engine
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* CalcRevsNoTorque calls ThrobRevsNoTorque
* ProcessEngineStart calls ThrobRevsNoTorque

Set the following:

* revCount = A + rand(0-7)

* revsOnGearChange = revCount

* engineTorque = 0

* soundRevTarget = revTarget + 25

.ThrobRevsNoTorque

STA T                  \ Store A in T

LDA VIA+&68            \ Read 6522 User VIA T1C-L timer 2 low-order counter
\ (SHEILA &68), which decrements one million times a
\ second and will therefore be pretty random

AND #7                 \ Set A = A mod 8, which is a random number in the range
\ 0 to 7

CLC                    \ Set A = A + T
ADC T                  \       = random 0-7 + T

\ Fall through into SetRevsNoTorque to set the rev
\ counter to A and zero the engine torque

Type: Subroutine
Category: Driving model
Summary: Set the rev counter and zero the engine torque
Deep dive: Modelling the engine
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* CalcRevsNoTorque calls SetRevsNoTorque
* ProcessEngineStart calls SetRevsNoTorque

Set the following:

* revCount = A

* revsOnGearChange = revCount

* engineTorque = 0

* soundRevTarget = revTarget + 25

.SetRevsNoTorque

STA revCount           \ Set revCount = A

STA revsOnGearChange   \ Set revsOnGearChange = A

\ Fall through into ZeroEngineTorque to zero
\ engineTorque and set soundRevTarget

Type: Subroutine
Category: Driving model
Summary: Zero engineTorque
Deep dive: Modelling the engine
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* ApplyEngine calls ZeroEngineTorque

Set the following:

* engineTorque = 0

* soundRevTarget = revTarget + 25

.ZeroEngineTorque

LDA #0                 \ Set A = 0 to set as the value of engineTorque

\
\   engineTorque = 0
\
\   soundRevTarget = revTarget + 25
\
\ and return from the subroutine using a tail call

Type: Subroutine
Category: Driving model
Summary: Apply the effects of the engine
Deep dive: The core driving model
Modelling the engine
Matching the code to the driving model
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* ApplyDrivingModel calls ApplyEngine

Calculate the following:

* If heightAboveTrack <> 0, jump to CalcRevsNoTorque to calculate the rev
count and zero the engine torque

* If a gear change key is being pressed, jump to CalcRevsNoTorque-2 to set
bit 7 of clutchEngaged to indicate that the clutch is not engaged,
calculate the rev count and zero the engine torque

* If we are in neutral, jump to CalcRevsNoTorque to calculate the rev count
and zero the engine torque

* Otherwise, calculate the engine torque based on gear ratio, power and
revs, setting the engineTorque, revCount and soundRevTarget variables
as follows:

* Set (A T) = trackGearRatio * playerSpeed * 8

* If clutch is not engaged:

* If any of these are true:

* Throttle is not being applied
* playerSpeedHi >= 22
* This is a race and we are not showing the blue lights
* This is a race, we are showing the blue lights and still have 10
iterations until the green
* A >= revsOnGearChange, so the revs are higher than at the last
gear change

then engage the clutch, otherwise clutch stays disengaged and we do:

A = revsOnGearChange

If A >= 108, A = A - 2 and revsOnGearChange = revsOnGearChange - 2

* Set revCount = A

* We now calculate the power being generated by the engine at this rev
count. First, we cap the rev count to a maximum of 170:

max(170, revCount)

so in the following revCount is this capped number.

* If revCount < 3, turn the engine off, zero the torque and quit

* If 3 <= revCount < 83, set A = A * 2 + 152

* If 83 <= revCount < 87, set A = 186 - (revCount - 83)

* If 87 <= revCount < 92, set A = 182 - ((revCount - 87) * 4)

* If 92 <= revCount, set A = 162 - (revCount - 92) * 2

* Set engineTorque = trackGearPower * A

* Set soundRevTarget = revCount + 25

.ApplyEngine

LDA engineStatus       \ If the engine is not on, jump to ProcessEngineStart to
BEQ ProcessEngineStart \ process the key press for starting the engine,
\ returning from the subroutine using a tail call

BNE CalcRevsNoTorque   \ calculate the revs and zero the engine torque,
\ returning from the subroutine using a tail call

LDA gearChangeKey      \ If bit 7 of gearChangeKey is set then a gear change
BMI CalcRevsNoTorque-2 \ key is being pressed, so jump to CalcRevsNoTorque-2 to
\ set bit 7 of clutchEngaged to indicate that the clutch
\ is not engaged, calculate the revs and zero the engine
\ torque, returning from the subroutine using a tail
\ call

LDY gearNumber         \ If gearNumber = 1, then we are in neutral, so jump to
DEY                    \ CalcRevsNoTorque, returning from the subroutine using
BEQ CalcRevsNoTorque   \ a tail call

\ If we get here then the engine is engaged and the car
\ is on the track, so we need to calculate the engine
\ torque and revs

LDA playerSpeedLo      \ Set (A T) = (playerSpeedHi playerSpeedLo)
STA T
LDA playerSpeedHi

ASL T                  \ Set (A T) = (A T) << 1
ROL A

PHP                    \ Store the flags on the stack, so we can retrieve it
\ below

BMI engi1              \ If bit 7 of (A T) is set, jump to engi1 to skip the
\ following

ASL T                  \ Set (A T) = (A T) << 1
ROL A                  \
\ so we only do this shift if we won't lose a bit off
\ the left end of A

.engi1

STA U                  \ Set U to the high byte of (A T), i.e. to the high byte
\ of either speed * 2 or speed * 4
\
\ As the high byte in playerSpeedHi contains the speed
\ in mph and the low byte contains the fraction, this
\ just discards the fractional part of the calculation

LDX gearNumber         \ Set A to the gear ratio for the current gear, which is
LDA trackGearRatio,X   \ defined in the track data file at trackGearRatio

JSR Multiply8x8        \ Set (A T) = A * U
\           = trackGearRatio * high byte of (A T)
\           = trackGearRatio * speed * 2
\       or    trackGearRatio * speed * 4

ASL T                  \ Set (A T) = (A T) << 1
ROL A                  \           = trackGearRatio * speed * 4
\             trackGearRatio * speed * 8

PLP                    \ Retrieve the flags we stored on the stack above, and
BPL engi2              \ if bit 7 is clear, skip the following, as we already
\ doubled the result above

ASL T                  \ Set (A T) = (A T) << 1
ROL A                  \
\ So we only do this shift if we didn't do it above,
\ which means we have the following result, calculated
\ in one of two ways to preserve accuracy:
\
\   (A T) = trackGearRatio * playerSpeed * 8

.engi2

BIT clutchEngaged      \ If bit 7 of clutchEngaged is clear then the clutch is

LDY throttleBrakeState \ If throttleBrakeState <> 1 then the throttle is not
DEY                    \ being applied, so jump to engi4 to engage the clutch
BNE engi4

LDY playerSpeedHi      \ If playerSpeedHi >= 22, jump to engi4 to engage the
CPY #22                \ clutch
BCS engi4

LDY raceStarting       \ Set Y = raceStarting

BPL engi3              \ If bit 7 of raceStarting is clear, then we are not on
\ the grid at the start of a race, so jump to engi3 to
\ check the revs to decide if the clutch has engaged

CPY #160               \ If raceStarting <> 160, then we are not showing the
BNE engi4              \ blue lights at the start of the race, so jump to engi4
\ to engage the clutch

\ If we get here then we are showing the blue lights at
\ the start of the race, which we keep showing until the
\ main loop counter is a multiple of 64 (at which point
\ we show the green lights)

PHA                    \ Store A on the stack to we can retrieve it below

LDA mainLoopCounterLo  \ If mainLoopCounterLo mod 64 < 53, clear the C flag
AND #63
CMP #53

PLA                    \ Retrieve the value of A we stored on the stack above

BCC engi4              \ If mainLoopCounterLo mod 64 < 53, then we have at
\ least 63 - 53 = 10 main loop iterations (including
\ this one) to go until the green lights appear, so jump
\ to engi4 to engage the clutch

.engi3

\ If we get here then we are either not on the starting
\ grid, or we are on the starting grid, the blue lights
\ are showing and we have fewer than 10 main loop
\ iterations to go until the lights turn green

\ Above, we set (A T) = trackGearRatio * speed * 8

CMP revsOnGearChange   \ If A < revsOnGearChange, jump to engi5 to process the
BCC engi5              \ clutch being disengaged

\ Otherwise A >= revsOnGearChange, so the revs are
\ higher than at the last gear change, so fall through
\ into engi4 to engage the clutch

.engi4

\ If we get here then we engage the clutch

LDY #0                 \ Set clutchEngaged = 0 to indicate that the clutch is
STY clutchEngaged      \ engaged

BEQ engi6              \ Jump to engi6 (this BEQ is effectively a JMP as Y is
\ always zero)

.engi5

\ If we get here then the clutch is not engaged

LDA revsOnGearChange   \ Set A = revsOnGearChange

CMP #108               \ If A < 108, i.e. revsOnGearChange < 108, jump to engi6
BCC engi6              \ to set the revs to this amount

\ If we get here then revsOnGearChange >= 108

SEC                    \ Set revsOnGearChange = revsOnGearChange - 2
SBC #2                 \
STA revsOnGearChange   \ And fall through into engi6 to set revCount to the new
\ value of revsOnGearChange

.engi6

STA revCount           \ Set revCount = A

CMP #170               \ If A < 170, jump to engi7 to skip the following
BCC engi7              \ instruction

LDA #170               \ Set A = 170, so A has a maximum value of 170:
\
\   A = max(170, revCount)

.engi7

BCS engi8

INC engineStatus       \ The rev count has fallen below 3, so increment
\ engineStatus from &FF to 0, which turns the engine off

JMP ZeroEngineTorque   \ Jump to ZeroEngineTorque to set the engine torque to
\ zero, returning from the subroutine using a tail call

.engi8

SEC                    \ Set A = A - 66
SBC #66                \       = revCount - 66

BMI engi9              \ If A is negative (i.e. revCount < 66), jump to engi9

BCS engi10

.engi9

\ If we get here then either A < 0 or A < 17, so
\ 3 <= revCount < 83

ASL A                  \ Set A = A * 2 + 152
CLC

.engi10

\ If we get here then A >= 0 and A >= 17, so
\ revCount >= 83

SEC                    \ Set A = A - 17
SBC #17                \       = revCount - 66 - 17
\       = revCount - 83

CMP #4                 \ If A >= 4 (i.e. revCount >= 87), jump to engi11
BCS engi11

\ If we get here then A is in the range 0 to 3 and
\ 83 <= revCount < 87

EOR #&FF               \ Set A = ~A, so A is in the range 255 to 252

CLC                    \ Set A = A + 187
ADC #187               \       = ~A + 187
\       = -A - 1 + 187
\       = 186 - A
\       = 186 - (revCount - 83)

BCS engi13             \ Jump to engi13 (the BCS is effectively a JMP as the
\ above addition will always overflow)

.engi11

\ If we get here then A >= 4, so revCount >= 87

SEC                    \ Set A = A - 4
SBC #4                 \       = revCount - 83 - 4
\       = revCount - 87

CMP #5                 \ If A >= 5, (i.e. revCount >= 92), jump to engi12
BCS engi12

\ If we get here then A is in the range 0 to 5

ASL A                  \ A = ~(A * 4)
ASL A
EOR #&FF

CLC                    \ Set A = A + 183
ADC #183               \       = ~(A * 4) + 183
\       = -(A * 4) - 1 + 183
\       = 182 - (A * 4)
\       = 182 - ((revCount - 87) * 4)

BCS engi13             \ Jump to engi13 (the BCS is effectively a JMP as the
\ above addition will always overflow)

.engi12

\ If we get here then A >= 5, so revCount >= 92

SEC                    \ Set A = ~((A - 5) * 2)
SBC #5
ASL A
EOR #&FF

CLC                    \ Set A = A + 163
ADC #163               \       = ~((A - 5) * 2) + 163
\       = -((A - 5) * 2) - 1 + 163
\       = 162 - (A - 5) * 2
\       = 162 - ((revCount - 87) - 5) * 2
\       = 162 - (revCount - 92) * 2

.engi13

STA U                  \ Set U to A

LDA trackGearPower,X   \ Set A to the gear power for the current gear, which is
\ defined in the track data file at trackGearPower

JSR Multiply8x8        \ Set (A T) = A * U
\           = trackGearPower * U

\ Fall through into SetEngineTorque to set the engine
\ torque to the high byte in A

Type: Subroutine
Category: Driving model
Summary: Set engineTorque and soundRevTarget
Deep dive: Modelling the engine
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* ZeroEngineTorque calls SetEngineTorque

Set the following:

* engineTorque = A

* soundRevTarget = revTarget + 25

.SetEngineTorque

STA engineTorque       \ Set engineTorque = A

LDA revCount           \ Set soundRevTarget = revCount + 25
CLC
STA soundRevTarget

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Driving model
Summary: Calculate the tyre forces on the car
Deep dive: The core driving model
Skidding
Matching the code to the driving model
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* ApplyTyresAndSkids calls ApplyTyreForces

Calculate the following:

* If xVelocity is non-zero and xVelocity and xTyreForceNose for tyre X have
the same sign:

* Set the high byte of xTyreForceNose = -xVelocity * 2^5

* Rotate a 1 into bit 7 of tyreSqueal for tyre X and finish

* xTyreForceNose = -xVelocity * 2^5

* Call GetTyreForces to set (A T), H and (NN MM)

* If the throttle is being applied and we are processing the front tyres,
set:

zTyreForceNose = 0

A = |xTyreForceNoseHi|

otherwise:

* If the throttle is being applied, then set:

zTyreForceNose or zTyreForceRear = (A T) * abs(H)

otherwise set:

zTyreForceNose or zTyreForceRear = max((A T), (NN MM)) * abs(H)

* Set the following (as appropriate for tyre X):

A =   max(|xTyreForceNoseHi|, |zTyreForceNoseHi|)
+ min(|xTyreForceNoseHi|, |zTyreForceNoseHi|) / 2

* Rotate a new bit 7 into tyreSqueal for tyre X as follows:

* Clear if A <= wingForce for tyre X

* Set if A > wingForce for tyre X

Arguments:

X                    The set of tyres to process:

* 0 = front tyres

* 1 = rear tyres

.ApplyTyreForces

LDA xVelocityLo        \ Set T = xVelocityLo
STA T

ORA xVelocityHi        \ Set A = xVelocityLo OR xVelocityHi and store the flags
PHP                    \ on the stack, which will be zero if both the high and
\ low bytes of xVelocity are zero, i.e. if xVelocity = 0

LDA xVelocityHi        \ Set (A T) = (xVelocityHi xVelocityLo)

JSR Negate16Bit        \ Set (A T) = -(A T)
\           = -xVelocity

LDY #5                 \ Set Y = 5 to act as a shift counter in the following
\ loop

.tfor1

ASL T                  \ Set (A T) = (A T) << 1
ROL A

DEY                    \ Decrement the shift counter

BNE tfor1              \ Loop back until we have shifted left by five places,
\ so we now have:
\
\   (A T) = (A T) * 2^5
\         = -xVelocity * 2^5

STA xTyreForceNoseHi,X \ Set xTyreForceNoseHi for tyre X to the high byte of
\ the result

PLP                    \ Retrieve the flags that we stored on the stack, which
\ will be zero if xVelocity = 0

BEQ tfor2              \ If xVelocity = 0, jump to tfor2 to set
\ xTyreForceNoseLo to the low byte of the result

\ If we get here then xVelocity is non-zero

EOR xVelocityHi        \ If xVelocityHi and xTyreForceNoseHi for tyre X have
\ the same sign, this will clear bit 7 of A

SEC                    \ Set the C flag

BPL tfor7              \ If bit 7 of A is clear, then xVelocityHi and
\ xTyreForceNoseHi for tyre X have the same sign, so
\ jump to tfor7 with the C flag set to rotate a 1 into
\ bit 7 of tyreSqueal for tyre X

.tfor2

\ If we get here then either xVelocity = 0, or xVelocity
\ is non-zero and xVelocity and xTyreForceNose have
\ different signs

LDA T                  \ Set xTyreForceNoseLo = T, so we now have:
STA xTyreForceNoseLo,X \
\   xTyreForceNose = (A T)
\                  = -xVelocity * 2^5

JSR GetTyreForces      \ Calculate the tyre forces due to the throttle or
\ brakes:
\
\   * (A T) = the force
\
\   * H = the sign of the force
\
\   * G = X + 2, so the call to ApplyLimitThrottle sets
\     zTyreForceNose or zTyreForceRear accordingly
\
\   * If the throttle is not being applied, (NN MM) is
\     a maximum value for the force
\
\ If the throttle is being applied and we are processing
\ the front tyres, only G is calculated and the C flag
\ is set

BCC tfor3              \ If the C flag is clear then GetTyreForces successfully
\ to keep going

LDA #0                 \ Set zTyreForceNose = 0
STA zTyreForceNoseLo
STA zTyreForceNoseHi

LDA xTyreForceNoseHi   \ Set A = xTyreForceNoseHi

JSR Absolute8Bit       \ Set A = |A|
\       = |xTyreForceNoseHi|

.tfor3

JSR ApplyLimitThrottle \ If the throttle is being applied, then set
\ zTyreForceNose or zTyreForceRear to:
\
\   (A T) * abs(H)
\
\ otherwise set it to:
\
\    max((A T), (NN MM)) * abs(H)

LDA zTyreForceNoseHi,X \ Set A = zTyreForceNoseHi for tyre X

JSR Absolute8Bit       \ Set A = |A|
\       = |zTyreForceNoseHi|

STA T                  \ Set T = A
\       = |zTyreForceNoseHi|

LDA xTyreForceNoseHi,X \ Set A = xTyreForceNoseHi for tyre X

JSR Absolute8Bit       \ Set A = |A|
\       = |xTyreForceNoseHi|

CMP T                  \ If A < T then |xTyreForceNoseHi| < |zTyreForceNoseHi|,

\ If we get here then |xTyreForceNoseHi| >=
\ |zTyreForceNoseHi|, so we halve T

LSR T                  \ Set T = T >> 1
\       = |zTyreForceNoseHi| / 2

.tfor4

LSR A                  \ Set A = A >> 1
\       = |xTyreForceNoseHi| / 2

.tfor5

CLC                    \ Set A = A + T
\ So if |zTyreForceNoseHi| > |xTyreForceNoseHi|:
\
\   A = |zTyreForceNoseHi| + |xTyreForceNoseHi| / 2
\
\ or if |xTyreForceNoseHi| >= |zTyreForceNoseHi|:
\
\   A = |xTyreForceNoseHi| + |zTyreForceNoseHi| / 2
\
\ In other words:
\
\   A =  max(|xTyreForceNoseHi|, |zTyreForceNoseHi|)
\      + min(|xTyreForceNoseHi|, |zTyreForceNoseHi|) / 2

.tfor6

CMP wingForce,X        \ If A <> wingForce for tyre X, jump to tfor7 with the C
BNE tfor7              \ flag set as follows:
\
\   * Clear if A < wingForce
\
\   * Set if A > wingForce

CLC                    \ Clear the C flag for when A = wingForce, so in all we
\ have the following for the C flag:
\
\   * Clear if A <= wingForce
\
\   * Set if A > wingForce

.tfor7

ROR tyreSqueal,X       \ Rotate the C flag into bit 7 of tyreSqueal for tyre X

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Driving model
Summary: Calculate the tyre forces when the car is skidding
Deep dive: Skidding
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* ApplyTyresAndSkids calls ApplySkidForces

Calculate the following:

* Set xTyreForceNose or xTyreForceRear
= max(wingForce95 << 8, scaled |xVelocity|) * abs(-xVelocity)

* Call GetTyreForces to set (A T), H and (NN MM)

* If the throttle is being applied and we are processing the front tyres,
return from the subroutine

* If A < wingForce95, then:

* If the throttle is being applied, then

zTyreForceNose or zTyreForceRear = (A T) * abs(H)

otherwise set:

zTyreForceNose or zTyreForceRear = max((A T), (NN MM)) * abs(H)

* If A >= wingForce95, then:

* Set (A T) = wingForce95 << 8

* If the throttle is being applied, then

zTyreForceNose or zTyreForceRear = (A T) * abs(H)

otherwise set:

zTyreForceNose or zTyreForceRear = max((A T), (NN MM)) * abs(H)

* If the throttle is being applied and we are processing the rear tyres,
set:

xTyreForceNose or xTyreForceRear = 0

Arguments:

X                    The set of tyres to process:

* 0 = front tyres

* 1 = rear tyres

.ApplySkidForces

LDA #0                 \ Set zTyreForceNose for tyre X to 0
STA zTyreForceNoseHi,X
STA zTyreForceNoseLo,X

LDY #8                 \ Set Y = 8 so the call to Scale16Bit scales xVelocity

JSR Scale16Bit         \ Scale up |xVelocity| by 2^5, capping the result at the
\ maximum possible positive value of &7Fxx, and
\ returning the result in (NN MM)

LDA xVelocityHi        \ Set H = xVelocityHi with the sign flipped, so that
EOR #%10000000         \ abs(H) = abs(-xVelocity)
STA H

LDA #0                 \ Set T = 0
STA T

LDA wingForce95,X      \ Set A to wingForce95 for tyre X

STX G                  \ Set G to the tyre number in X so the ApplyLimitAndSign
\ routine sets xTyreForceNose or xTyreForceRear
\ accordingly

JSR ApplyLimitAndSign  \ Set xTyreForceNose or xTyreForceRear
\                       = max((A T), (NN MM)) * abs(H)
\
\   =   max(wingForce95 << 8, scaled |xVelocity|)
\     * abs(-xVelocity)

JSR GetTyreForces      \ Calculate the tyre forces due to the throttle or
\ brakes:
\
\   * (A T) = the force
\
\   * H = the sign of the force
\
\   * G = X + 2, so the call to ApplyLimitThrottle sets
\     zTyreForceNose or zTyreForceRear accordingly
\
\   * If the throttle is not being applied, (NN MM) is
\     a maximum value for the force
\
\ If the throttle is being applied and we are processing
\ the front tyres, only G is calculated and the C flag
\ is set

BCS skid2              \ If the C flag is set then GetTyreForces did not return
\ the subroutine

CMP wingForce95,X      \ If A < wingForce95 for tyre X, jump to skid1
BCC skid1

LDA #0                 \ Set T = 0
STA T

LDA wingForce95,X      \ Set A to wingForce95 for tyre X, so
\ A = min(A, wingForce95)

JSR ApplyLimitThrottle \ If the throttle is being applied, then set
\ zTyreForceNose or zTyreForceRear to:
\
\   (A T) * abs(H)
\
\ otherwise set it to:
\
\    max((A T), (NN MM)) * abs(H)

LDY throttleBrakeState \ If throttleBrakeState <> 1 then the throttle is not
BNE skid2

CPX #0                 \ If X = 0, then we are processing the front tyres, so

\ If we get here then the throttle is being applied and
\ we are processing the rear tyres

LDA #0                 \ Set xTyreForceNose for tyre X to 0
STA xTyreForceNoseHi,X
STA xTyreForceNoseLo,X

BEQ skid2              \ Jump to skid2 to return from the subroutine (this BEQ
\ is effectively a JMP as A is always zero)

.skid1

JSR ApplyLimitThrottle \ If the throttle is being applied, then set
\ zTyreForceNose or zTyreForceRear to:
\
\   (A T) * abs(H)
\
\ otherwise set it to:
\
\    max((A T), (NN MM)) * abs(H)

.skid2

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Driving model
Summary: Apply a maximum limit to a 16-bit number, unless the throttle is
being applied
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* ApplySkidForces calls ApplyLimitThrottle
* ApplyTyreForces calls ApplyLimitThrottle

If the throttle is being applied, then set:

variableG = (A T) * abs(H)

otherwise set:

variableG = max((A T), (NN MM)) * abs(H)

Arguments:

G                    Offset of the variable to set:

* 0 = xTyreForceNose

* 1 = xTyreForceRear

* 2 = zTyreForceNose

* 3 = zTyreForceRear

.ApplyLimitThrottle

LDY throttleBrakeState \ If throttleBrakeState = 1, then the throttle is being
DEY                    \ applied, so jump to lims1 to skip applying the maximum
BEQ lims1              \ value to variableG, so:
\
\   variableG = (A T) * abs(H)

\ Otherwise fall through into ApplyLimitAndSign to set:
\
\   variableG = max((A T), (NN MM)) * abs(H)

Type: Subroutine
Category: Driving model
Summary: Apply a maximum limit and a sign to a 16-bit number
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* ApplySkidForces calls ApplyLimitAndSign
* ApplyLimitThrottle calls via lims1

Set the variable specified in G (let's call it variableG) as follows:

variableG = max((A T), (NN MM)) * abs(H)

Arguments:

G                    Offset of the variable to set:

* 0 = xTyreForceNose

* 1 = xTyreForceRear

* 2 = zTyreForceNose

* 3 = zTyreForceRear

Other entry points:

lims1                Skip applying the maximum value to variableG, so we set:

variableG = (A T) * abs(H)

.ApplyLimitAndSign

CMP NN                 \ If A < NN, jump to lims1 to skip the following
BCC lims1

LDA MM                 \ Set (A T) = (NN MM)
STA T
LDA NN

.lims1

BIT H                  \ Set the N flag to the sign of H, so the call to
\ Absolute16Bit sets the sign of (A T) to abs(H)

JSR Absolute16Bit      \ Set the sign of (A T) to match the sign bit in H

LDY G                  \ Set Y to the offset in G

STA xTyreForceNoseHi,Y \ Store (A T)
LDA T
STA xTyreForceNoseLo,Y

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Maths (Arithmetic)
Summary: Scale up a 16-bit value by 2^5
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* ApplySkidForces calls Scale16Bit
* GetTyreForces calls Scale16Bit

Scale up a 16-bit value by 2^5, capping the result at the maximum possible
positive value of &7Fxx, and ensuring that the result is positive.

Arguments:

Y                    The offset of the variable to scale

* 8 = xVelocity

* 9 = zVelocity

Returns:

(NN MM)              The scaled value

.Scale16Bit

LDA xPlayerSpeedHi,Y   \ Set (A MM) to the variable pointed to by Y, which we
STA MM                 \ call variableY
LDA xPlayerSpeedTop,Y

BPL scal1              \ If the top byte in A is positive, jump to scal1 to
\ skip the following

LDA #0                 \ Negate (A MM), starting with the low bytes
SEC
SBC MM
STA MM

LDA #0                 \ And then the high bytes, so we now have:
SBC xPlayerSpeedTop,Y  \
\   (A MM) = |variableY|

.scal1

LDY #5                 \ Set Y = 5, to act as a shift counter in the following
\ loop

.scal2

ASL MM                 \ Set (A MM) = (A MM) << 1
ROL A

BMI scal4              \ If bit 7 of the top byte in A is set, jump to scal4
\ to stop shifting and set the top byte to the largest
\ possible positive top byte

DEY                    \ Decrement the shift counter

BNE scal2              \ Loop back until we have left-shifted five times

.scal3

STA NN                 \ Set (NN MM) = (A MM) to return as the result

RTS                    \ Return from the subroutine

.scal4

LDA #%01111111         \ Set A = %01111111 to act as the largest possible
\ positive top byte

Type: Subroutine
Category: Driving model
Summary: Calculate the tyre forces due to the throttle or brakes
Deep dive: The core driving model
Skidding
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* ApplySkidForces calls GetTyreForces
* ApplyTyreForces calls GetTyreForces

Calculate the following:

* If the throttle is not being applied:

* (NN MM) = scaled up |zVelocity|

* H = zVelocityHi with the sign flipped

* (A T) = throttleBrake * wingForce           (rear tyres)
= throttleBrake * wingForce * 3 / 4   (front tyres)

* C flag clear

* If the throttle is being applied:

* When we are processing the front tyres:

* C flag set

* When we are processing the rear tyres:

* H = gearNumber - 1

* (A T) = (throttleBrake * engineTorque) / 2

* C flag clear

The routine also sets G to 2 or 3 (for the front or rear tyres).

Arguments:

X                    The set of tyres to process:

* 0 = front tyres

* 1 = rear tyres

Returns:

C flag               Calculation status:

* Set if we are returning a set of calculated values

* Clear if we are not returning any values (which
happens if the throttle is being applied and we are
processing the front tyres)

G                    Set to the correct offset for calling ApplyLimitThrottle
or ApplyLimitAndSign:

* 2 = zTyreForceNose

* 3 = zTyreForceRear

H                    The sign of the force

(A T)                The force

(NN MM)              If the throttle is not being applied, a maximum for the
force

.GetTyreForces

TXA                    \ Set G = X + 2
CLC                    \
ADC #2                 \ So G = 2 for the front tyres and 3 for the rear tyres
STA G

LDY throttleBrakeState \ If throttleBrakeState = 1 then the throttle is being
BEQ tyfo1

\ If we get here then the throttle is not being applied

LDY #9                 \ Set Y = 8 so the call to Scale16Bit scales zVelocity

JSR Scale16Bit         \ Scale up |zVelocity| by 2^5, capping the result at the
\ maximum possible positive value of &7Fxx, and
\ returning the result in (NN MM)

LDA zVelocityHi        \ Set H = zVelocityHi with the sign flipped
EOR #%10000000
STA H

LDA wingForce,X        \ Set A to wingForce for tyre X

CPX #1                 \ If X = 1, then we are processing the rear tyres, so
BEQ tyfo2              \ jump to tyfo2 to use this value of A in the
\ calculation

LSR A                  \ Set A = (A / 2 + wingForce) / 2
CLC                    \       = (wingForce / 2 + wingForce) / 2
ADC wingForce,X        \       = wingForce * 3 / 4
LSR A

JMP tyfo2              \ Jump to tyfo2 to use this value of A in the
\ calculation

.tyfo1

\ If we get here then the throttle is being applied

CPX #1                 \ If X <> 1, then we are processing the front tyres, so
BNE tyfo4              \ jump to tyfo4 to return from the subroutine with the C
\ flag set

LDA gearNumber         \ Set H = gearNumber - 1
SEC
SBC #1
STA H

LDA engineTorque       \ Set A = engineTorque

.tyfo2

STA U                  \ Store the value of A in U to use in the multiplication
\ below

LDA throttleBrake      \ Set A to the amount of throttle or brake being applied

JSR Multiply8x8        \ Set (A T) = A * U
\           = throttleBrake * U

LDY throttleBrakeState \ If throttleBrakeState <> 1 then the throttle is not
BNE tyfo3

LSR A                  \ The throttle is being applied, so set:
ROR T                  \
\   (A T) = (A T) / 2

.tyfo3

CLC                    \ Clear the C flag to indicate that we are returning a
\ set of calculated values

RTS                    \ Return from the subroutine

.tyfo4

SEC                    \ Set the C flag to indicate that we are not returning
\ any calculated values

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Driving model
Summary: Apply the effects of driving or braking on grass
Deep dive: The core driving model
Driving on grass
Matching the code to the driving model
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* ApplyDrivingModel calls ApplyGrassOrTrack

* If all the following are true:

* There is grass under at least one side of the car
* bumpyGrassHeight = 0
* heightAboveTrack = 0
* Bit 7 of playerDrift is set

then calculate the following to make the car jump when it hits the verge:

* yGravityDelta = playerSpeedHi / 2
* yJumpHeight = playerSpeedHi / 4
* heightAboveTrack = heightAboveTrack + 1
* spinYawAngleHi = spinYawAngleHi >> 1 with bit 7 set

and make the crash/contact sound

* If there is grass under at least one side of the car, then:

* bumpyGrassHeight = random number in the range 1 to 7 that is higher with
higher speeds

otherwise:

* bumpyGrassHeight = 0

* For each wing, calculate the following:

* If the brakes are not being applied, set brakeForce = 0, otherwise set:

* brakeForce = -zTyreForceBoth / 8    (front wing)
* brakeForce =  zTyreForceBoth / 8    (rear wing)

* If we are driving on grass on both sides of the car, then set:

wingForce = wingForceGrass + brakeForce

otherwise calculate:

wingForce = wingSetting * min(53, playerSpeedHi) * abs(zVelocity)
+ wingForceTrack + brakeForce

* Set:

wingForce95 = wingForce * 243 / 256

.ApplyGrassOrTrack

LDA #0                 \ Set A = 0

LDY throttleBrakeState \ If throttleBrakeState is non-zero, then the brakes are
BNE gras1              \ not being applied, so jump to gras1 to set H = 0 and
\ G = 0

\ If we get here then the brakes are being applied

LDA zTyreForceBoth     \ Set A = zTyreForceBoth

PHP                    \ Store the N flag on the stack, so the PLP below sets
\ the N flag according to the value of zTyreForceBoth

LSR A                  \ Set A = A >> 3
LSR A                  \       = zTyreForceBoth / 8
LSR A

BPL gras1              \ the following instruction

ORA #%11100000         \ Set bits 5-7 of A to ensure that the value of A
\ retains the correct sign after being shifted

\ By this point, A = zTyreForceBoth / 8 and has the
\ correct sign

.gras1

STA H                  \ Set H = A

EOR #&FF               \ Set G = -A
CLC
STA G

\ So if the brakes are not being applied we have:
\
\   * G = 0
\   * H = 0
\
\ and if the brakes are being applied, we have:
\
\   * G = -zTyreForceBoth / 8
\   * H = zTyreForceBoth / 8
\
\ G is used in the front wing calculation below, while
\ H is used in the rear wing calculation

LDA playerSpeedHi      \ Set U = playerSpeedHi
STA U

LDX #0                 \ Set X = 0, to store as the value of bumpyGrassHeight
\ if we are not driving on grass

\ The leftSurface and rightSurface locations are in
\ screen memory, and are just to the left or right of
\ the dashboard, so:
\
\   * If leftSurface = &FF then there is grass under the
\     left edge of the dashboard
\
\   * If rightSurface = &FF then there is grass under
\     the right edge of the dashboard

LDA leftSurface        \ Set W so it is &FF if there is grass under both edges
AND rightSurface       \ of the dashboard
STA W

LDA leftSurface        \ If leftSurface = &FF, then there is grass under the
CMP #&FF               \ left side of the car, so jump to gras2
BEQ gras2

LDA rightSurface       \ If rightSurface <> &FF, then there isn't grass under
CMP #&FF               \ the right side of the car, so jump to gras4 to skip
BNE gras4              \ the following and set bumpyGrassHeight to 0

.gras2

\ If we get here then there is grass under at least one
\ side of the car, and W = &FF if there is grass under
\ both sides

LDA VIA+&68            \ Read 6522 User VIA T1C-L timer 2 low-order counter
\ (SHEILA &68), which decrements one million times a
\ second and will therefore be pretty random

JSR Multiply8x8        \ Set (A T) = A * U
\           = random * playerSpeedHi
\
\ So A is a random number that is higher with higher
\ speeds

AND #7                 \ Set X = A mod 8
TAX

BNE gras3              \ If X = 0, set X = 1
INX

\ So X is now a random number in the range 1 to 7 that
\ is higher with higher speeds

.gras3

BNE gras4

ORA heightAboveTrack   \ LDA is not required here, so perhaps it's left over
BNE gras4              \ from development)

BIT playerDrift        \ If bit 7 of playerDrift is clear, then the player's
BPL gras4              \ car is not moving significantly sideways, so jump to
\ gras4

\ If we get here then the player's car is drifting, and
\ the car is not jumping on bumpy grass (as
\ bumpyGrassHeight = 0) and is glued to the track (as
\ heightAboveTrack = 0)

JSR ApplyVergeJump     \ Calculate the following variables to apply a jump to
\ the car when we hit the track verge:
\
\   yGravityDelta = playerSpeedHi / 2
\
\   yJumpHeight = playerSpeedHi / 4
\
\   heightAboveTrack = heightAboveTrack + 1
\
\   spinYawAngleHi = spinYawAngleHi >> 1 with bit 7 set
\
\ and make the crash/contact sound

.gras4

STX bumpyGrassHeight   \ Set bumpyGrassHeight = X, so:
\
\   * If we are not driving on grass, then
\     bumpyGrassHeight = 0
\
\   * If we are driving on grass, then bumpyGrassHeight
\     is set to a random number in the range 1 to 7 that
\     is higher with higher speeds

LDX #1                 \ We now run the following loop, once for each wing
\ setting, so set X to use as a loop counter, from 1
\ (for the front wing) to 0 (for the rear wing)

.gras5

LDA playerSpeedHi      \ Set A = playerSpeedHi

BCC gras6

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

.gras6

STA U                  \ Store A in U, so we now have the following:
\
\   U = min(53, playerSpeedHi)

LDA wingSetting,X      \ Set A to the scaled wing setting for wing X

JSR Multiply8x8        \ Set (A T) = A * U
\           = wingSetting * min(53, playerSpeedHi)

BIT zVelocityHi        \ Set the N flag according to the sign in bit 7 of
\ zVelocityHi, so the call to Absolute8Bit sets the
\ sign of A to the same sign as zVelocity

JSR Absolute8Bit       \ Set A = A * abs(zVelocity)
\       = wingSetting * min(53, playerSpeedHi)
\                     * abs(zVelocity)

CLC                    \ Set A = A + wingForceTrack for this wing
ADC wingForceTrack,X   \       = wingSetting * min(53, playerSpeedHi)
\                     * abs(zVelocity)
\         + wingForceTrack

LDY #243               \ Set U = 243 to use in the 95% calculation below
STY U

LDY W                  \ If W <> &FF, then there is not grass under both sides
CPY #&FF               \ of the car, so jump to gras7 to skip the following
BNE gras7

LDA wingForceGrass,X   \ There is grass under both sides of the car, so set A
\ to the wingForceGrass value for this wing

LDY #&FF               \ Set Y = &FF (this has no effect as Y is already &FF,
\ so this instruction is a bit of a mystery)

.gras7

CLC                    \ Set A = A + G (front wing) or H (rear wing)
\ This adds the effect of the brakes being applied

STA wingForce,X        \ Store A in wingForce for this wing

JSR Multiply8x8        \ Set (A T) = A * U
\           = A * 243
\
\ so A = A * 243 / 256
\      = A * 0.95

STA wingForce95,X      \ Store A in wingForce95 for this wing

DEX                    \ Decrement the loop counter in X to point to the next
\ wing

BPL gras5              \ Loop back until we have processed both wings

RTS                    \ Return from the subroutine

Type: Variable
Category: Driving model
Summary: The base downward force from the weight of the car when on the
track, to which the downward force from the wings is added
Deep dive: The core driving model
Context: See this variable on its own page
References: This variable is used as follows:
* ApplyGrassOrTrack uses wingForceTrack

.wingForceTrack

EQUB 53                \ Front wing

EQUB 53                \ Rear wing

Type: Variable
Category: Driving model
Summary: The base downward force from the weight of the car when on grass,
to which the downward force from the wings is added
Deep dive: The core driving model
Driving on grass
Context: See this variable on its own page
References: This variable is used as follows:
* ApplyGrassOrTrack uses wingForceGrass

.wingForceGrass

EQUB 25                \ Front wing

EQUB 26                \ Rear wing

Type: Subroutine
Category: Driving model
Summary: Apply the effect of the wing settings
Deep dive: The core driving model
Driving on grass
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* ApplyDrivingModel calls ApplyWingBalance

This routine calculates the following:

xPlayerAccel = xPlayerAccel - scaledSpeed * xPrevVelocityHi

zPlayerAccel = zPlayerAccel
- scaledSpeed * (wingBalance * playerSpeedHi + 2048)
* abs(zVelocity)

where scaledSpeed = playerSpeedHi       if bumpyGrassHeight = 0
playerSpeedHi * 2   otherwise

and wingBalance = 60 + (rearWingSetting * 3 + frontWingSetting) / 2

.ApplyWingBalance

LDA xPrevVelocityHi    \ Set A = xPrevVelocityHi

JSR Absolute8Bit       \ Set A = |A|
\       = |xPrevVelocityHi|

STA U                  \ Set U = A
\       = |xPrevVelocityHi|

BCS bala1

LDA playerSpeedHi      \ Set A = playerSpeedHi, so A has a minimum value of
\ playerSpeedHi

.bala1

LDY bumpyGrassHeight   \ If bumpyGrassHeight = 0, jump to bala2 to skip the
BEQ bala2              \ following

ASL A                  \ Set A = A * 2
\         playerSpeedHi * 2

.bala2

STA W                  \ Set W = A
\       = playerSpeedHi       if bumpyGrassHeight = 0
\         playerSpeedHi * 2   otherwise
\
\ Let's call this value scaledSpeed

JSR Multiply8x8        \ Set (A T) = A * U
\           = scaledSpeed * |xPrevVelocityHi|

STA U                  \ Set (U T) = (A T)
\           = scaledSpeed * |xPrevVelocityHi|

LDY #6                 \ Set Y = 6, so the call to SubtractCoords uses
\ xPlayerAccel

LDA xPrevVelocityHi    \ Set A = xPrevVelocityHi so the call to SubtractCoords
\ sets the sign to abs(xPrevVelocity)

JSR SubtractCoords     \ Set:
\
\   variableY = variableY - (U T) * abs(A)
\
\ so that's:
\
\   xPlayerAccel = xPlayerAccel
\                  - scaledSpeed * |xPrevVelocityHi|
\                                * abs(xPrevVelocity)
\
\       = xPlayerAccel - scaledSpeed * xPrevVelocityHi

LDA playerSpeedHi      \ Set U = playerSpeedHi
STA U

LDA wingBalance        \ Set A = wingBalance, which is calculated as:
\
\   60 + (rearWingSetting * 3 + frontWingSetting) / 2

JSR Multiply8x8        \ Set (A T) = A * U
\           = wingBalance * playerSpeedHi

CLC                    \ Set V = A + 8
STA V                  \ so (V T) = (A T) + (8 0)
\          = wingBalance * playerSpeedHi + 2048

LDA W                  \ Set U = W
STA U                  \       = scaledSpeed

JSR Multiply8x16       \ Set:
\
\   (U T) = U * (V T)
\         =   scaledSpeed
\           * (wingBalance * playerSpeedHi + 2048)

LDY #7                 \ Set Y = 6, so the call to SubtractCoords uses
\ zPlayerAccel

LDA zVelocityHi        \ Set A = zVelocityHi so the call to SubtractCoords sets
\ the sign to abs(zVelocity)

JSR SubtractCoords     \ Set:
\
\   variableY = variableY - (U T) * abs(A)
\
\ so that's:
\
\   zPlayerAccel = zPlayerAccel - scaledSpeed
\                 * (wingBalance * playerSpeedHi + 2048)
\                 * abs(zVelocity)

RTS                    \ Return from the subroutine

Type: Subroutine
Category: 3D objects
Summary: Create an object for the road sign
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* MainDrivingLoop (Part 2 of 5) calls BuildRoadSign

LDX currentPlayer      \ Set X to the driver number of the current player

LDY objTrackSection,X  \ Set Y to the track section number * 8 for the current
\ player

LDA trackSectionData,Y \ Set A to bits 4-7 of the trackSectionData for the
LSR A                  \ track section, shifted into bits 0-3, so A contains
LSR A                  \ the number of the sign for this section (0 to 15)
LSR A
LSR A

STA thisSignNumber     \ Store the sign number in thisSignNumber, so we can
\ retrieve it below

CMP previousSignNumber \ If previousSignNumber doesn't already contain this
BNE sign1              \ sign number, jump to sign1 to skip the following

\ We get here if we are calling BuildRoadSign again for
\ the same sign number (we set previousSignNumber to
\ the current sign number later in the routine)
\
\ This ensures that we display signs at the start of
\ sections that have a different value in bits 4-7 of
\ trackSectionData compared to the previous section, so
\ signs appear when we move into a section with a new
\ value in bits 4-7 of the trackSectionData (and that
\ value is the number of the sign to show)

ADC #0                 \ Increment the sign number in A (we know the C flag is
\ set, as the above comparison was equal)

AND #15                \ Restrict the result to the range 0 to 15, i.e. set A
\ to A mod 16

.sign1

TAX                    \ Set X to the sign number, which will either be the
\ sign number from trackSectionData for this section, or
\ the sign number plus 1

\ We now draw sign number X, by fetching the track sign
\ vector, adding it to the track section coordinate to
\ get the sign's 3D coordinates, and subtracting the
\ player's coordinates to get the sign's vector relative
\ to the player (i.e. relative to the camera)
\
\ We then calculate the yaw and pitch angles and store
\ them in object 23, so it can be drawn by the call to
\ DrawCarOrSign from the main driving loop

LDY #2                 \ Set Y = 2, so the call to AddScaledVector scales by
\ 2 ^ (8 - Y) = 2 ^ 6

STY W                  \ Set W = 2, which gets decremented through each call
\ to AddScaledVector as we work through each axis, so it
\ stores the results in each axis of xRoadSignCoord in
\ turn

LDA zTrackSignVector,X \ Set A to the 3D z-coordinate of the track sign vector

\              = zPlayerCoord - zTrackSignVector * 2 ^ 6

LDY #4                 \ Set Y = 4, so the call to AddScaledVector scales by
\ 2 ^ (8 - Y) = 2 ^ 4

LDA yTrackSignVector,X \ Set A to the 3D y-coordinate of the track sign vector

\              = yPlayerCoord - yTrackSignVector * 2 ^ 4

LDY #2                 \ Set Y = 2, so the call to AddScaledVector scales by
\ 2 ^ (8 - Y) = 2 ^ 6

LDA xTrackSignVector,X \ Set A to the 3D x-coordinate of the track sign vector

\              = xPlayerCoord - xTrackSignVector * 2 ^ 6

\ We now have:
\
\   [ xRoadSignCoord ]   [ xPlayerCoord ]
\   [ yRoadSignCoord ] = [ yPlayerCoord ]
\   [ zRoadSignCoord ]   [ zPlayerCoord ]
\
\                        [ xTrackSignVector * 2 ^ 6 ]
\                      - [ yTrackSignVector * 2 ^ 4 ]
\                        [ zTrackSignVector * 2 ^ 6 ]

LDA trackSignData,X    \ Set objectType to the object type for road sign X,
AND #%00000111         \ which comes from bits 0-2 of trackSignData, and
CLC                    \ gets 7 added to get the range 7 to 12
STA objectType

LDA trackSignData,X    \ Set Y to the track section number for road sign X,
AND #%11111000         \ which comes from bits 3-7 of trackSignData
TAY

LDX #&FD               \ Set X = &FD so the calls to GetSectionCoord,
\ GetObjYawAngle and GetObjPitchAngle use xCoord2,
\ yCoord2 and zCoord2

JSR GetSectionCoord    \ Copy the first trackSectionI coordinate for track
\ section Y into xCoord2, so xCoord2 contains the 3D
\ coordinates of the inside track for the start of this
\ track section, i.e.
\
\   [ xCoord2 ]   [ xTrackSectionI ]
\   [ yCoord2 ] = [ yTrackSectionI ]
\   [ zCoord2 ]   [ zTrackSectionI ]

LDY #6                 \ Set Y = 6 so the call to GetObjYawAngle uses
\ xRoadSignCoord for the second variable, so we
\ calculate the yaw and pitch angles for an object at
\ the following 3D coordinates (if we just consider the
\ x-axis, for clarity):
\
\  = xTrackSectionI - (xPlayerCoord - xTrackSignVector)
\  = xTrackSectionI - xPlayerCoord + xTrackSignVector
\  = xTrackSectionI + xTrackSignVector - xPlayerCoord
\
\ The xPlayerCoord vector contains the 3D coordinates
\ of the player's car, so the above gives us the vector
\ from the player's car to the sign
\
\ So this is the vector we use to calculate the object's
\ angles, to show the sign in the correct place by the
\ side of the track from the viewpoint of the player

JSR GetObjYawAngle     \ Calculate the object's yaw angle, returning it in
\ (JJ II)

LDA II                 \ Store the yaw angle from (JJ II) in (objYawAngleHi+23
STA objYawAngleLo+23   \ objYawAngleLo+23), so we use object 23 to store the
LDA JJ                 \ sign object
STA objYawAngleHi+23

\ Now that the sign object has been built and the angles
\ calculated, we can check for any collisions between
\ the player and the sign

SEC                    \ Set A = JJ - playerYawAngleHi
SBC playerYawAngleHi

JSR Absolute8Bit       \ Set A = |A|
\       = |JJ - playerYawAngleHi|
\
\ So A contains the screen distance between the player

CMP #64                \ If A < 64, jump to sign2 to skip the following
BCC sign2

LDY thisSignNumber     \ Set previousSignNumber to the sign number from the
STY previousSignNumber \ trackSectionData, so the next time we display a sign
\ it will be the next sign

.sign2

LDY #37                \ Set Y = 37 to use as the collision distance in the
\ call to CheckForContact below

CMP #110               \ If A < 110, jump to sign3 to skip the following
BCC sign3

LDY #80                \ Set Y = 80 to use as the collision distance in the
\ call to CheckForContact below

.sign3

LDA #23                \ Set objectNumber = 23, so if we are colliding with the
STA objectNumber       \ sign, CheckForContact sets collisionDriver to object
\ 23 (which is now the sign object)

JSR CheckForContact    \ Check to see if the object and the player's car are
\ close enough for contact, specifically if they are
\ within a distance of Y from each other

\ The final step is to calculate the object's pitch
\ angle, and then we are done building the sign object

LDY #6                 \ Set Y = 6 so the call to GetObjPitchAngle uses

JSR GetObjPitchAngle   \ Calculate the object's pitch angle, returning it
\ in A and LL
\
\ If the object is not visible on-screen, the C flag is
\ set, which will hide the object in the following call
\ to SetObjectDetails

JSR SetObjectDetails   \ Set the object's visibility, scale and type

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Maths (Geometry)
Summary: Add a scaled vector to another vector, one axis at a time
Context: See this subroutine on its own page
References: This subroutine is called as follows:

This routine does the following calculation, one axis at a time (starting with
the z-axis, then the y-axis and x-axis):

[ xRoadSignCoord ]   [ xPlayerCoord ]   [ xTrackSignVector * 2 ^ (8 - Y) ]
[ yRoadSignCoord ] = [ yPlayerCoord ] - [ yTrackSignVector * 2 ^ (8 - Y) ]
[ zRoadSignCoord ]   [ zPlayerCoord ]   [ zTrackSignVector * 2 ^ (8 - Y) ]

The value of Y can be varied between calls to change the scale factor on a
per-axis basis.

Arguments:

A                    The relevant from xTrackSignVector, yTrackSignVector,
zTrackSignVector

W                    Set to 2 for the first call

Y                    The scale factor for this axis

PHA                    \ Set (V A T) = (0 A 0)
LDA #0                 \             = A * 256
STA T
STA V
PLA

BPL addv1              \ If A is positive then V is already the correct high

DEC V                  \ Otherwise, decrement V to &FF so it's the correct
\ high byte for (V A T)

\ We now shift (V A T) right by Y places

LSR V                  \ Set (V A T) = (V A T) >> 1
ROR A
ROR T

DEY                    \ Decrement the shift counter

BNE addv1              \ Loop back until we have right-shifted Y times

STA U                  \ Set (V U T) = (V A T)
\             = A * 256 >> Y
\             = A * 2 ^ (8 - Y)
\
\ We know that V doesn't contain any data, just sign
\ bits, so this means:
\
\   (U T) = A * 2 ^ (8 - Y)
\         = xTrackSignVector * 2 ^ (8 - Y)

LDY W                  \ Fetch the axis index from W

DEC W                  \ Decrement the axis index in W, ready for the next call

LDA xPlayerCoordHi,Y   \ Set xRoadSignCoord = xPlayerCoord - (U T)
SEC                    \
SBC T                  \ starting with the low bytes

LDA xPlayerCoordTop,Y  \ And then the high bytes
SBC U

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Drivers
Summary: Initialise all 20 drivers on the starting grid
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* MainLoop (Part 1 of 6) calls InitialiseDrivers

Arguments:

X                    The routine is always called with X = 0

.InitialiseDrivers

STX setSpeedForDriver  \ Set setSpeedForDriver = 0, to use as a loop counter
\ when initialising all 20 drivers

STX raceClass          \ Set raceClass = 0 (Novice)

JSR GetSectionSteering \ Set up the optimum steering for each section for a
\ Novice race, storing the results in sectionSteering
\ and returning with X unchanged

\ The following loop works starts with X = 0, and then
\ loops down from 19 to 1, working its way through each
\ of the 20 drivers

.driv1

TXA                    \ Set A to the current driver number in X

STA driversInOrder,X   \ Set driversInOrder for driver X to the driver number

LSR A                  \ Set the grid row for driver X to driver number >> 1,
NOP                    \ so drivers 0 and 1 are on row 0, drivers 2 and 3 are
STA driverGridRow,X    \ on row 1, and so on, up to row 9 at the back of the
\ grid

JSR SetDriverSpeed     \ Set the base speed for driver X
\
\ It also decrements X to the next driver number and

LDA #0                 \ Zero (totalPointsTop totalPointsHi totalPointsLo) for
STA totalPointsLo,X    \ driver X
STA totalPointsHi,X
STA totalPointsTop,X

TXA                    \ If X <> 0, loop back to driv1 to process the next
BNE driv1              \ driver, until we have processed all 20 of them

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Text
Summary: Prints a text token on the second text line at the top of the
driving screen
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* PrintSecondLineGap calls PrintSecondLine
* ResetVariables calls PrintSecondLine

Arguments:

X                    The token number (0 to 54)

.PrintSecondLine

LDA #33                \ Set A = 33 to use as the value for yCursor below, so
\ we print the text token on the second text line at the
\ top of the driving screen

BNE PrintFirstLine+2   \ Jump to PrintFirstLine+2 to print the token in X on
\ the second text line in the driving screen (this BNE
\ is effectively a JMP as A is never zero)

Type: Subroutine
Category: Text
Summary: Prints a text token on the first text line at the top of the
driving screen
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* PrintSecondLineGap calls PrintFirstLine
* ResetVariables calls PrintFirstLine
* UpdateLapTimers calls PrintFirstLine
* PrintSecondLine calls via PrintFirstLine+2

Arguments:

X                    The token number (0 to 54)

Other entry points:

PrintFirstLine+2     Print the token on the second text line at the top of
the driving screen

.PrintFirstLine

LDA #24                \ Set A = 24 to use as the value for yCursor below, so
\ we print the text token on the first line of the two
\ text lines at the top of the driving screen

STA yCursor            \ Move the cursor to pixel row A (which will either be
\ the first or the second text line at the top of the
\ screen)

LDA #1                 \ Move the cursor to character column 1
STA xCursor

\ Fall through into PrintToken to print the token in X
\ at (xCursor, yCursor)

Type: Subroutine
Category: Text
Summary: Print a recursive token
Deep dive: Text tokens
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* GetWingSettings calls PrintToken
* MainLoop (Part 1 of 6) calls PrintToken
* MainLoop (Part 2 of 6) calls PrintToken
* MainLoop (Part 3 of 6) calls PrintToken
* MainLoop (Part 5 of 6) calls PrintToken
* PrintDriverPrompt calls PrintToken
* PrintDriverTable calls PrintToken
* PrintRaceClass calls PrintToken
* SetScreenMode7 calls PrintToken
* WaitForSpaceReturn calls PrintToken

Addresses of token strings are in the (tokenHi tokenLo) table. Tokens are
numbered from 0 to 54.

Each token's string contains bytes that are printed as follows:

* 0-159     Print character n
* 160-199   Print n - 160 spaces (0 to 39)
* 200-254   Print token n - 200 (0 to 54)
* 255       End of token

Arguments:

X                    The token number (0 to 54)

(xCursor, yCursor)   The on-screen position for the token

.PrintToken

LDY #0                 \ We are about to work our way through the token, one
\ byte at a time, so set a byte counter in Y

.toke1

LDA tokenHi,X          \ Set (S R) = the X-th entry in (tokenHi tokenLo), which
STA S                  \ points to the string of bytes in token X
LDA tokenLo,X
STA R

.toke2

LDA (R),Y              \ Set A to the Y-th byte at (S R), which contains the
\ next character in the token

CMP #255               \ If A = 255 then we have reached the end of the token,
BEQ toke8              \ so jump to toke8 to return from the subroutine

CMP #200               \ If A < 200 then this byte is not another token, so

SEC                    \ A >= 200, so this is a pointer to another token
SBC #200               \ embedded in the current token, so subtract 200 to get
\ the embedded token's number

STA T                  \ Store the embedded token's number in T

TXA                    \ Store X and Y on the stack so we can retrieve them
PHA                    \ after printing the embedded token
TYA
PHA

LDX T                  \ Set X to the number of the embedded token we need to
\ print

CPX #54                \ If X <> 54, jump to toke3 to skip the following three
BNE toke3              \ instructions and print the embedded token

LDX #0                 \ X = 54, so call PrintHeader with X = 0 to print
\ at column 4, row 3, in yellow text on a red background

JMP toke4              \ Skip the following instruction

.toke3

JSR PrintToken         \ Print token X (so if it also contains embedded tokens,
\ they will also be expanded and printed)

.toke4

PLA                    \ Retrieve X and Y from the stack
TAY
PLA
TAX

INY                    \ Increment the byte counter

JMP toke1              \ Loop back to print the next byte in the token, making
\ sure to recalculate (S R) as it will have been
\ corrupted by the call to PrintToken

.toke5

\ If we get here then A < 200, so this byte is not
\ another token

CMP #160               \ If A < 160, jump to toke6 to skip the following three
BCC toke6              \ instructions

SBC #160               \ A is in the range 160 to 199, so subtract 160 to get
\ the number of spaces to print, in the range 0 to 39

JSR PrintSpaces        \ Print the number of spaces in A

BEQ toke7              \ Skip the following instruction (this BNE is
\ effectively a JMP as PrintSpaces sets the Z flag)

.toke6

JSR PrintCharacter     \ Print the character in A (which is in the range 0 to
\ 159)

.toke7

INY                    \ Increment the byte counter

JMP toke2              \ Loop back to print the next byte in the token

.toke8

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Driving model
Summary: Apply a jump to the player's car when hitting the track verge
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* ApplyGrassOrTrack calls ApplyVergeJump

Calculate the following:

yGravityDelta = playerSpeedHi / 2

yJumpHeight = playerSpeedHi / 4

heightAboveTrack = heightAboveTrack + 1

spinYawAngleHi = spinYawAngleHi >> 1 with bit 7 set

and make the crash/contact sound.

.ApplyVergeJump

LDA playerSpeedHi      \ Set A = playerSpeedHi

\ Fall through into ApplyBounce to calculate the
\ variables and make the crash/contact sound

Type: Subroutine
Category: Driving model
Summary: Apply a bounce to the player's car when it hits the ground
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* ApplyElevation (Part 4 of 5) calls ApplyBounce

Calculate the following:

yGravityDelta = A / 2

yJumpHeight = A / 4

heightAboveTrack = heightAboveTrack + 1

spinYawAngleHi = spinYawAngleHi >> 1 with bit 7 set

and make the crash/contact sound.

.ApplyBounce

LSR A                  \ Set yGravityDelta = A / 2
STA yGravityDelta

LSR A                  \ Set yJumpHeight = A / 4
STA yJumpHeight

INC heightAboveTrack   \ Set heightAboveTrack = heightAboveTrack + 1

SEC                    \ Rotate spinYawAngleHi right, inserting a 1 into bit 7
ROR spinYawAngleHi

LDA #4                 \ Make sound #4 (crash/contact) at the current volume
JSR MakeSound-3        \ level

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Screen mode
Summary: Switch to the custom screen mode
Deep dive: Hidden secrets of the custom screen mode
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* MainDrivingLoop (Part 1 of 5) calls SetCustomScreen

Returns:

screenSection        screenSection is set to -2, so the interrupt handler at
ScreenHandler does not do anything straight away, but
leaves the palette mapped to black, so the screen is
blank

.SetCustomScreen

SEI                    \ Disable interrupts so we can update the 6845 registers

\ First we switch screen mode to the custom screen mode
\ used for the race, which is based on mode 5 but is
\ shorter at 26 character rows rather than 40
\
\ We do this by first reprogramming registers R0 to R13
\ of the 6845 CRTC chip using the values in the
\ screenRegisters table (see the screenRegisters
\ variable for details), and then programming register 0
\ of the Video ULA to the same value as standard mode 5

LDX #13                \ We are about to write values into registers R0 to R13
\ so set a register counter in X to count down from 13
\ to 0

.cust1

STX VIA+&00            \ Put register number X into SHEILA &00, so we can now
\ set the value of this 6845 register

LDA screenRegisters,X  \ Set register X to the X-th value of screenRegisters
STA VIA+&01

DEX                    \ Decrement the register counter

BPL cust1              \ Loop back until we have set registers R0 to R13 to the
\ values in the screenRegisters table

DEX                    \ Set screenSection = -2, as the above loop finishes
STX screenSection      \ with X = -1

CLI                    \ Re-enable interrupts

LDA #154               \ Call OSBYTE with A = 154 to set register 0 of the
LDX #%11000100         \ Video ULA to the value in X, which sets the following,
JSR OSBYTE             \ reading from bit 7 to bit 0:
\
\   %1  = master cursor size = large cursor
\   %10 = width of cursor in bytes = 2
\   %0  = 6845 clock rate select = low frequency clock
\   %01 = number of characters per line = 20
\   %0  = teletext output select = on-chip serialiser
\   %0  = flash colour select = first colour selected
\
\ These values are the same as in standard mode 5, and
\ this call finishes the switch to our custom screen
\ mode

CLC                    \ Clear the C flag for the additions in the following
\ loop

\ We now send the following bytes to the Video ULA
\ palette in SHEILA &21, by starting at 7 and adding &10
\ to send &07, &17, &27 ... &E7, &F7
\
\ This maps all four logical colours (the top nibble) to
\ &7 EOR 7 (the bottom nibble, EOR 7), which maps them
\ to colour 0, or black

LDA #&07               \ Set A = &07 as the first byte to send

.cust2

STA VIA+&21            \ Send A to SHEILA &21 to send the palette byte in A to
\ the Video ULA

ADC #&10               \ Set A = A + &10

BCC cust2              \ Loop back until the addition overflows after we send
\ &F7 to the ULA

SEI                    \ Disable interrupts so we can update the VIAs

LDA IRQ1V              \ Store the current address from the IRQ1V vector in
LDA IRQ1V+1            \ implementing the custom screen mode

LDA #2                 \ This instruction appears to have no effect, as we are
\ about to overwrite A and the processor flags

.cust3

BIT VIA+&4D            \ Read the 6522 System VIA interrupt flag register IFR
\ (SHEILA &4D), which has bit 1 set if vertical sync
\ has occurred on the video system

BEQ cust3              \ Loop back to cust3 to keep reading the System VIA
\ until the vertical sync occurs

LDA #%01000000         \ Set 6522 User VIA auxiliary control register ACR
STA VIA+&6B            \ (SHEILA &6B) bits 7 and 6 to disable PB7 (which is one
\ of the pins on the user port) and set continuous
\ interrupts for timer 1

ORA VIA+&4B            \ Set 6522 System VIA auxiliary control register ACR
STA VIA+&4B            \ (SHEILA &6B) bit 6 to set continuous interrupts for
\ timer 1

LDA #%11000000         \ Set 6522 User VIA interrupt enable register IER
STA VIA+&6E            \ (SHEILA &4E) bits 6 and 7 (i.e. enable the Timer1
\ interrupt from the User VIA)

STA VIA+&4E            \ Set 6522 System VIA interrupt enable register IER
\ (SHEILA &4E) bits 6 and 7 (i.e. enable the Timer1
\ interrupt from the System VIA)

LDA #&D4               \ Set 6522 User VIA T1C-L timer 1 low-order counter to
STA VIA+&64            \ (SHEILA &64) to &D4 (so this sets the low-order
\ counter but does not start counting until the
\ high-order counter is set)

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

LDA #&01               \ Set 6522 System VIA T1L-L timer 1 low-order latches
STA VIA+&46            \ to &01 (so this sets the low-order counter but does
\ not start counting until the high-order counter is
\ set)

LDA #&3D               \ Set 6522 System VIA T1C-H timer 1 high-order counter
STA VIA+&45            \ to &3D, to start the T1 counter counting down from
\ &3D01

LDA #&1E               \ Set 6522 System VIA T1L-L timer 1 low-order latches
STA VIA+&46            \ to &1E (so this sets the low-order counter but does
\ not start counting until the high-order counter is
\ set)

STA VIA+&66            \ Set 6522 User VIA T1L-L timer 1 low-order latches
\ to &1E (so this sets the low-order counter but does
\ not start counting until the high-order counter is
\ set)

LDA #&4E               \ Set 6522 System VIA T1L-H timer 1 high-order latches
STA VIA+&47            \ to &4E (so this sets the timer to &4E1E (19998) but
\ does not start counting until the current timer has
\ run down)

STA VIA+&67            \ Set 6522 User VIA T1L-H timer 1 high-order latches
\ to &4E (so this sets the timer to &4E1E (19998) but
\ does not start counting until the current timer has
\ run down)

LDA #HI(ScreenHandler) \ Set the IRQ1V vector to ScreenHandler, so the
STA IRQ1V+1            \ ScreenHandler routine is now the interrupt handler
LDA #LO(ScreenHandler)
STA IRQ1V

CLI                    \ Re-enable interrupts

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Screen mode
Summary: The IRQ handler for the custom screen mode
Deep dive: Hidden secrets of the custom screen mode
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* SetCustomScreen calls ScreenHandler

The screen handler starts a new screen with screenSection = -1, and then
increments it through 0, 1, 2, 3, 4 and 5, at which point this handler stops
doing anything.

Other entry points:

\ control to the next interrupt handler

.ScreenHandler

LDA VIA+&6D            \ Set A to the 6522 User VIA interrupt flag register IFR
\ (SHEILA &46D)

AND #%01000000         \ Extract bit 6, which is set when 6522 User VIA timer 1
\ runs down to zero

BEQ ScreenHandler-3    \ If the Timer1 interrupt has not fired, jump up to
\ ScreenHandler-3 as we do not need to do anything at
\ this point

STA VIA+&6D            \ Set bit 6 of the 6522 User VIA interrupt flag register
\ IFR (SHEILA &6D) to clear the timer 1 interrupt (the
\ timer will already have restarted as we set it to
\ continuous interrupts in SetCustomScreen)

TXA                    \ Store X on the stack so we can preserve it through the
PHA                    \ interrupt handler

CLD                    \ Clear the D flag to switch arithmetic to normal

BEQ hand1

CMP #2                 \ If screenSection < 2, i.e. screenSection = 1, jump to
BCC hand5              \ hand5

BEQ hand9

BCS hand11             \ If screenSection >= 3, i.e. screenSection = 4, jump
\ to hand11

.hand1

\ If we get here, then screenSection = 0, so we set the
\ screen mode to 4 and the palette for the top two lines
\ of text (where the race information is printed)

LDA #%10001000         \ Set the Video ULA control register (SHEILA &20) to
STA VIA+&20            \ %10001000, which is the same as switching to mode 4

LDX #15                \ We now send the 16 palette bytes at paletteSection0 to
\ the Video ULA palette in SHEILA &21, so set a loop
\ counter in X

.hand2

LDA paletteSection0,X  \ Set the X-th byte of paletteSection0 to the Video ULA
STA VIA+&21            \ palette

DEX                    \ Decrement the loop counter

BPL hand2              \ Loop back until we have sent all 16 bytes

.hand3

LDA #&C4               \ Set (X A) = &0FC4 to latch into the User VIA timer 1,
LDX #&0F               \ so on the next timer loop it counts down from &0FC4
\ (4036)

BNE hand13             \ Jump to hand13 to latch (X A) into User VIA timer 1
\ and return from the subroutine (this BNE is
\ effectively a JMP as X is never zero)

.hand4

\ If we get here, then screenSection is negative

CMP #&FF               \ If screenSection <> -1, then jump to hand14 to
BNE hand14             \ return from the interrupt handler

\ If we get here, then screenSection = -1

INC screenSection      \ Set screenSection = 0

BEQ hand3              \ Jump to hand3 to set timer 1 counting down from &0FC4
\ and return from the interrupt handler (this BNE is
\ effectively a JMP as screenSection is always zero)

.hand5

\ If we get here, then screenSection = 1, so we change
\ the palette so everything is blue, as this is the
\ portion of cloudless sky between the text at the top
\ of the screen and the car and track at the bottom

LDA #%11000100         \ Set the Video ULA control register (SHEILA &20) to
STA VIA+&20            \ %11000100, which is the same as switching to mode 5

CLC                    \ Clear the C flag for the additions in the following
\ loop

\ We now send the following bytes to the Video ULA
\ palette in SHEILA &21, by starting at 3 and adding &10
\ to send &03, &13, &23 ... &E3, &F3
\
\ This maps all four logical colours (the top nibble) to
\ &3 EOR 7 (the bottom nibble, EOR 7), which maps them
\ to colour 4, or blue

LDA #&03               \ Set A = &03 as the first byte to send

.hand6

STA VIA+&21            \ Send A to SHEILA &21 to send the palette byte in A to
\ the Video ULA

ADC #&10               \ Set A = A + &10

BCC hand6              \ Loop back until the addition overflows after we send
\ &F3 to the ULA

LDA #&3C               \ Set screenTimer2 = &153C - screenTimer1
SEC                    \
SBC screenTimer1       \ starting with the low bytes
STA screenTimer2

LDA #&15               \ And then the high bytes
SBC screenTimer1+1
STA screenTimer2+1

LDA screenTimer1       \ Set (X A) = screenTimer1 to latch into the User VIA
LDX screenTimer1+1     \ timer 1, so on the next timer loop it counts down from
\ screenTimer1

BCS hand13             \ Jump to hand13 to latch (X A) into User VIA timer 1
\ and return from the subroutine (this BCS is
\ effectively a JMP as the C flag is still set from
\ above)

.hand7

\ If we get here, then screenSection = 2

LDX #15                \ We now send the 16 palette bytes at paletteSection2 to
\ the Video ULA palette in SHEILA &21, so set a loop
\ counter in X

.hand8

LDA paletteSection2,X  \ Set the X-th byte of paletteSection2 to the Video ULA
STA VIA+&21            \ palette

DEX                    \ Decrement the loop counter

BPL hand8              \ Loop back until we have sent all 16 bytes

LDA screenTimer2       \ Set (X A) = screenTimer2 to latch into the User VIA
LDX screenTimer2+1     \ timer 1, so on the next timer loop it counts down from
\ screenTimer2

BNE hand13             \ Jump to hand13 to latch (X A) into User VIA timer 1
\ and return from the subroutine (this BNE is
\ effectively a JMP as X is never zero)

.hand9

\ If we get here, then screenSection = 3

LDX #3                 \ We now send the 3 palette bytes at paletteSection3 to
\ the Video ULA palette in SHEILA &21, so set a loop
\ counter in X

.hand10

LDA paletteSection3,X  \ Set the X-th byte of paletteSection2 to the Video ULA
STA VIA+&21            \ palette

DEX                    \ Decrement the loop counter

BPL hand10             \ Loop back until we have sent all 16 bytes

LDA #&00               \ Set (X A) = &1E00 to latch into the User VIA timer 1,
LDX #&1E               \ so on the next timer loop it counts down from &1E00
\ (7680)

BNE hand13             \ Jump to hand13 to latch (X A) into User VIA timer 1
\ and return from the subroutine (this BNE is
\ effectively a JMP as X is never zero)

.hand11

\ If we get here, then screenSection = 4

LDX #3                 \ We now send the 3 palette bytes at paletteSection4 to
\ the Video ULA palette in SHEILA &21, so set a loop
\ counter in X

.hand12

LDA paletteSection4,X  \ Set the X-th byte of paletteSection2 to the Video ULA
STA VIA+&21            \ palette

DEX                    \ Decrement the loop counter

BPL hand12             \ Loop back until we have sent all 16 bytes

STX screenSection      \ Set screenSection = -1, as the above loop finishes
\ with X = 255

JSR AnimateTyres       \ Animate the tyres on either side of the screen

LDA #&FF               \ Set 6522 User VIA T2C-H timer 2 high-order counter
STA VIA+&69            \ (SHEILA &69) to &FF to start the T2 counter
\ counting down from &FFxx at a rate of 1 MHz

LDA #&16               \ Set (X A) = &0B16 to latch into the User VIA timer 1,
LDX #&0B               \ so on the next timer loop it counts down from &0B16
\ (2838)

.hand13

STX VIA+&67            \ Set 6522 User VIA T1L-H and T1L-L to set both timer 1
STA VIA+&66            \ latches (so this sets the timer to (X A) but does not
\ start counting until the current timer has run down)

INC screenSection      \ Increment the screen section counter to move on to the
\ next section

.hand14

PLA                    \ Restore X from the stack
TAX

LDA &FC                \ Set A to the interrupt accumulator save register,
\ which restores A to the value it had on entering the
\ interrupt

RTI                    \ Return from interrupts, so this interrupt is not
\ passed on to the next interrupt handler, but instead
\ the interrupt terminates here

Type: Variable
Category: Screen mode
Summary: The 6845 registers for the custom screen mode
Deep dive: Hidden secrets of the custom screen mode
Context: See this variable on its own page
References: This variable is used as follows:
* SetCustomScreen uses screenRegisters

The custom screen mode used during the race is based on standard mode 5, but
with the following differences:

* Horizontal sync position = 45 instead of 49

* Vertical displayed       = 26 instead of 32

* Vertical sync position   = 32 instead of 34

* Screen memory start      = &5A80 instead of &5800

So essentially it is a shorter mode 5 that takes up less memory, adjusts the
vertical and horizontal sync positions accordingly, and lives in screen memory
from &5A80 to &7AFF (as there are 26 character rows of 40 characters, with 8
bytes per character, giving 26 * 40 * 8 = 8320 bytes of screen memory, and
&5A80 + 8320 = &7B00).

.screenRegisters

EQUB 63                \ Set 6845 register R0 = 63
\
\ This is the "horizontal total" register, which sets
\ the horizontal sync frequency, i.e. the number of
\ horizontal characters minus one. This value is the
\ same as in standard mode 5

EQUB 40                \ Set 6845 register R1 = 40
\
\ This is the "horizontal displayed" register, which
\ defines the number of character blocks per horizontal
\ character row. This value is the same as in standard
\ mode 5

EQUB 49                \ Set 6845 register R2 = 45
\
\ This is the "horizontal sync position" register, which
\ defines the position of the horizontal sync pulse on
\ the horizontal line in terms of character widths from
\ the left-hand side of the screen. For comparison this
\ is 49 for mode 5, but is adjusted for our custom
\ screen

EQUB &24               \ Set 6845 register R3 = &24
\
\ This is the "sync width" register, which sets the
\ horizontal sync width in characters using the low
\ nibble (i.e. 4), and the vertical sync width in the
\ high nibble (i.e. 2). These values are the same as in
\ standard mode 5

EQUB 38                \ Set 6845 register R4 = 38
\
\ This is the "vertical total" register, which contains
\ the integer part of the vertical sync frequency minus
\ one. This value is the same as in standard mode 5

EQUB 0                 \ Set 6845 register R5 = 0
\
\ This is the "vertical total adjust" register, which
\ contains the fractional part of the vertical sync
\ frequency. This value is the same as in standard mode
\ 5

EQUB 26                \ Set 6845 register R6 = 26
\
\ This is the "vertical displayed" register, which sets
\ the number of displayed character rows to 26. For
\ comparison, this value is 32 for standard modes 4 and
\ 5, but we claw back six rows for storing code above
\ the end of screen memory

EQUB 32                \ Set 6845 register R7 = 32
\
\ This is the "vertical sync position" register, which
\ determines the vertical sync position with respect to
\ the reference, programmed in character row times. For
\ comparison this is 34 for mode 5, but needs to be
\ adjusted for our custom screen's vertical sync

EQUB %00000001         \ Set 6845 register R8 = %00000001
\
\ This is the "interlace and display" register, which
\ sets the following, reading from bit 7 to bit 0:
\
\   %00 = no delay in the cursor blanking signal
\   %00 = no delay in the display blanking signal
\   %00 = not used
\   %01 = interlace sync mode
\
\ These values are the same as in standard mode 5

EQUB 7                 \ Set 6845 register R9 = 7
\
\ This is the "scan lines per character" register, and
\ contains the number of scan lines per character row,
\ including spacing, minus one. This value is the same
\ as in standard mode 5

EQUB %01100111         \ Set 6845 register R10 = %01100111
\
\ This is the "cursor start" register, which sets the
\ following, reading from bit 7 to bit 0:
\
\   %0 = not used
\   %1 = enable blink feature
\   %1 = set blink frequency to 32 times the field rate
\   %00111 = cursor end scan line
\
\ These values are the same as in standard mode 5

EQUB 8                 \ Set 6845 register R11 = 8
\
\ This is the "cursor end" register, which sets the
\ cursor end scan line. This value is the same as in
\ standard mode 5

EQUB &0B               \ Set 6845 register R12 = &0B and R13 = &50
EQUB &50               \
\ This sets 6845 registers (R12 R13) = &0B50 to point
\ to the start of screen memory in terms of character
\ rows. There are 8 pixel lines in each character row,
\ so to get the actual address of the start of screen
\ memory, we multiply by 8:
\
\   &0B50 * 8 = &5A80
\
\ So this sets the start of screen memory to &5A80

Type: Variable
Category: Screen mode
Summary: Stores the previous value of IRQ1V before we install our custom
IRQ handler
Context: See this variable on its own page
References: This variable is used as follows:

EQUW 0

Type: Variable
Category: Screen mode
Summary: The screen timer offset between the start of section 2 and the
start of section 3
Deep dive: Hidden secrets of the custom screen mode
Context: See this variable on its own page
References: This variable is used as follows:
* MoveHorizon uses screenTimer1
* ScreenHandler uses screenTimer1

.screenTimer1

EQUW &04D8

Type: Variable
Category: Screen mode
Summary: The screen timer offset between the start of section 3 and the
start of section 4
Deep dive: Hidden secrets of the custom screen mode
Context: See this variable on its own page
References: This variable is used as follows:
* ScreenHandler uses screenTimer2

.screenTimer2

EQUW &1064

Type: Subroutine
Category: Screen mode
Summary: Disable the custom screen mode and switch to mode 7
Deep dive: Hidden secrets of the custom screen mode
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* MainDrivingLoop (Part 5 of 5) calls KillCustomScreen

.KillCustomScreen

SEI                    \ Disable interrupts so we can update the interrupt
\ vector and VIA

LDA irq1Address        \ Set the IRQ1V vector to irq1Address, which removes the
STA IRQ1V              \ custom screen interrupt handler from the chain
STA IRQ1V+1

LDA #%01000000         \ Set 6522 User VIA interrupt enable register IER
STA VIA+&6E            \ (SHEILA &4E) bit 6 (i.e. disable the Timer1 interrupt
\ from the User VIA, as we no longer need it)

CLI                    \ Re-enable interrupts

JSR FlushSoundBuffers  \ Flush all four sound channel buffers

\ Fall through into SetScreenMode7 to switch to mode 7

Type: Subroutine
Category: Screen mode
Summary: Change to screen mode 7 and hide the cursor
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* SetupGame calls SetScreenMode7

.SetScreenMode7

LDA #128               \ Set printMode = 128 so the call to PrintToken prints
STA printMode          \ characters using OSWRCH (for mode 7)

LDX #46                \ Print token 46, which changes to screen mode 7 and
JSR PrintToken         \ hides the cursor

RTS                    \ Return from the subroutine

Type: Variable
Category: Screen mode
Summary: The section of the screen that is currently being drawn by the
custom screen interrupt handler (0 to 4)
Deep dive: Hidden secrets of the custom screen mode
Context: See this variable on its own page
References: This variable is used as follows:
* MainDrivingLoop (Part 3 of 5) uses screenSection
* ScreenHandler uses screenSection
* SetCustomScreen uses screenSection

.screenSection

EQUB 0

Type: Subroutine
Category: Screen mode
Summary: Move the position of the horizon palette switch up or down,
depending on the current track pitch angle
Deep dive: Hidden secrets of the custom screen mode
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* MainDrivingLoop (Part 2 of 5) calls MoveHorizon

.MoveHorizon

LDA #60                \ Set A = 60 - horizonLine
SEC                    \
SBC horizonLine        \ So A is larger when the horizon is low (i.e. when we
\ are cresting a hill), and smaller when the horizon is
\ high (i.e. when we are in a dip)

BPL hori1              \ If A >= 0, then horizonLine <= 60, so jump to hori1

CMP #&F5               \ If A >= -11, then horizonLine <= 71, so jump to hori2
BCS hori2              \ with the C flag set

LDA #&F5               \ Otherwise set A = -11 and set the C flag, so A has a
SEC                    \ minimum value of -11

BCS hori2              \ Jump to hori2 (this BCS is effectively a JMP as we
\ just set the C flag)

.hori1

\ If we get here then A >= 0, i.e. horizonLine <= 60

CMP #18                \ If A < 18, jump to hori2 to skip the following two
BCC hori2              \ instructions

LDA #18                \ Otherwise set A = 18 and clear the C flag, so A has a
CLC                    \ maximum value of 18

.hori2

PHP                    \ Store the C flag on the stack, which will be clear if
\ A >= 0, or set if A < 0 (so the C flag is effectively
\ the sign bit of A)

STA U                  \ Set (U A) = (A 0)
LDA #0                 \           = A * 256
\
\ where -11 <= A < 18 and the sign bit of A is in C

ROR U                  \ Set (U A) = (U A) >> 1, inserting the sign bit from C
ROR A                  \ into bit 7

PLP                    \ Set the C flag to the sign bit once again

ROR U                  \ Set (U A) = (U A) >> 1, inserting the sign bit from C
ROR A                  \ into bit 7
\
\ So by this point, we have:
\
\   (U A) = A * 256 / 4
\         = A * 64
\
\ with the correct sign, so (U A) is in the range -704
\ to 1152, and is larger when the horizon is low (i.e.
\ when we are cresting a hill), and smaller when the
\ horizon is high (i.e. when we are in a dip)
\
\ We now add this figure to screenTimer1, which
\ determines the height of the horizon portion of the
\ custom screen mode, i.e. where the palette switches
\ from blue sky to the green ground
\
\ So when we are cresting a hill, (U A) is large and so
\ is timer 1, and therefore so is the size of the sky
\ above the horizon in section 2 of the screen, so the
\ horizon dips down
\
\ Conversely, when we are in a dip, (U A) is small and
\ so is timer 1, so the size of the sky section above
\ the horizon is smaller, so the horizon rises up
\
\ The range of screenTimer1 values from the following
\ calculation is therefore:
\
\   Minimum: &04D8 - 704 = &0218 (we are in a dip)
\
\   Maximum: &04D8 + 1152 = &0958 (we are on a hill)

SEI                    \ Disable interrupts so we can update the custom screen
\ variables

CLC                    \ Set screenTimer1 = (U A) + &04D8
STA screenTimer1       \ starting with the low bytes

LDA #&04               \ And then the high bytes
STA screenTimer1+1

CLI                    \ Re-enable interrupts

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Drivers
Summary: Increment the lap number and lap times for a specific driver
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* MoveObjectForward calls UpdateLaps

Update the lap number and lap times for driver X, but only if the following
are true:

* Driver number is in the range 0 to 19

* Bit 7 of updateLapTimes is clear

* Bit 6 of the driver's car's object status byte is clear (so the car is
still racing)

* If this is the current player, pastHalfway must be 1 (so the player is in
the second half of the track)

If these conditions are met, then the lap is incremented.

Arguments:

X                    Driver number (only drivers 0 to 19 have lap times)

updateLapTimes       If bit 7 is set, this routine does nothing

.UpdateLaps

BIT updateLapTimes     \ If bit 7 of updateLapTimes is set, jump to ulap1 to
BMI ulap1              \ return from the subroutine without doing anything

CPX #20                \ If X >= 20 then this is not a driver number, so jump
BCS ulap1              \ to ulap1 to return from the subroutine

LDA objectStatus,X     \ If bit 6 of the driver's car object status byte is
ASL A                  \ set, then the car has finished racing, so jump to
BMI ulap1              \ ulap1 to return from the subroutine without updating
\ the lap number

CPX currentPlayer      \ If driver X is not the current player, jump to ulap3
BNE ulap3              \ to skip updating the lap number top of the screen

\ If we get here then this is the current player, so now
\ we check the value of pastHalfway

DEC pastHalfway        \ If pastHalfway = 1, then the player is in the second
BEQ ulap2              \ half of the track, so set pastHalfway to 0 as the
INC pastHalfway        \ player has just passwd the starting line into the
\ first half of the track, and jump to ulap2 to update
\ the lap details

.ulap1

RTS                    \ Return from the subroutine

.ulap2

\ If we get here then this is the current player, and
\ pastHalfway has just been changed from 1 to 0, to
\ denote a new lap

LDA #%10000000         \ Set bit 7 and clear bit 6 of updateDrivingInfo so the
STA updateDrivingInfo  \ lap number gets updated at the top of the screen

.ulap3

\ If we get here then we have passed all the checks, so
\ it's time to increment the lap number and times,
\ starting with the lap number

LDA driverLapNumber,X  \ Set A to the current lap number for driver X

BMI ulap4              \ If A is negative, skip the following instruction

INC driverLapNumber,X  \ Increment the current lap number for driver X

.ulap4

\ We now decide whether to increment the lap times

BIT raceStarted        \ If bit 7 of raceStarted is clear then this is practice

\ If we get here then this is a race

CMP numberOfLaps       \ If the current lap number for driver X < the number
BCC ulap7              \ of laps in the race, then this is not the last lap in

BEQ ulap6              \ If the current lap number for driver X = the number
\ of laps in the race, then this is the last lap in

\ If we get here then the current lap number is bigger
\ than the number of laps in the race, which means the
\ driver has already finished the race and is still
\ driving, so we don't increment the lap times

RTS                    \ Return from the subroutine

.ulap5

\ If we get here then this is practice or qualifying

CPX currentPlayer      \ If X <= the driver number of the current player, jump
BEQ ulap7              \ to ulap7
BCC ulap7

\ If we get here then driver X has a bigger number than
\ the current player, so we ignore it (is this because
\ the only players with numbers higher than the current
\ player are other players, rather than drivers)

RTS                    \ Return from the subroutine

.ulap6

\ If we get here then this is a race and this is the
\ last lap in the race, so this driver just finished the
\ race (as this routine is all about incrementing the
\ lap number)

CPX currentPlayer      \ If X <> the driver number of the current player, jump
BNE ulap7              \ to ulap7

\ If we get here then driver X is the current player, so
\ the current player just finished the race

LDA #80                \ Set leaveTrackTimer = 80, so we leave the track in 80

.ulap7

\ If we get here, then it's time to increment the lap
\ times

SED                    \ Set the D flag to switch arithmetic to Binary Coded
\ Decimal (BCD)

\ We now subtract driver X's total race time from the
\ current clock time, to see whether this is a new best
\ time

SEC                    \ Set T = clockTenths - totalRaceTenths for driver X
LDA clockTenths
SBC totalRaceTenths,X
STA T

LDA clockSeconds       \ Set A = clockSeconds - totalRaceSeconds for driver X
SBC totalRaceSeconds,X

BCS ulap8              \ If the subtraction underflowed, add 60 seconds to the
CLC

.ulap8

STA U                  \ Set U = A
\       = clockSeconds - totalRaceSeconds for driver X

LDA clockMinutes       \ Set H = clockMinutes - totalRaceMinutes for driver X
SBC totalRaceMinutes,X
STA H

\ So by this point, (H U T) contains the time difference
\ between the clock time and driver X's total race time,
\ which is the current lap time (as we last updated the
\ driver's total time at the end of the last lap)

BCC ulap9              \ If the subtraction underflowed, then somehow the clock
\ timer is showing a smaller time than driver X's total
\ race time, so this isn't a new best lap time, and we

SEC                    \ Subtract (H U T) - driver X's best lap time
LDA T
SBC bestLapTenths,X
LDA U
SBC bestLapSeconds,X
LDA H
SBC bestLapMinutes,X

BCS ulap9              \ If the subtraction didn't underflow, then (H U T) is
\ bigger than driver X's best lap time, so this isn't a

\ (H U T) is lower than driver X's best lap time, so we
\ have a new best lap time

LDA T                  \ Set driver X's best lap time to (H U T), setting the
AND #&F0               \ second digit of the tenths figure to 0
STA bestLapTenths,X
LDA U
STA bestLapSeconds,X
LDA H
STA bestLapMinutes,X

.ulap9

LDA clockTenths        \ Set the total race time for driver X to the clock time
STA totalRaceTenths,X  \ so we can use it to work out the lap time for the next
LDA clockSeconds       \ lap
STA totalRaceSeconds,X
LDA clockMinutes
STA totalRaceMinutes,X

CLD                    \ Clear the D flag to switch arithmetic to normal

RTS                    \ Return from the subroutine

NOP                    \ These instructions have no effect - presumably they
NOP                    \ are left over from changes during development

Type: Subroutine
Category: Drivers
Summary: Zero the specified timer
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* MainDrivingLoop (Part 1 of 5) calls ZeroTimer
* ResetVariables calls ZeroTimer
* UpdateLapTimers calls ZeroTimer

Arguments:

X                    The timer to set to zero:

* 0 = the clock timer
(clockMinutes clockSeconds clockTenths)

* 1 = the lap timer
(lapMinutes lapSeconds lapTenths)

Returns:

A                    A = 0 and the Z flag is set (so a BEQ will branch)

.ZeroTimer

LDA #0                 \ Zero clockTenths or lapTenths
STA clockTenths,X

STA clockSeconds,X     \ Zero clockSeconds or lapSeconds

STA clockMinutes,X     \ Zero clockMinutes or lapMinutes

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Text
Summary: Print the best lap time and the current lap time at the top of the
screen
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* ResetVariables calls PrintBestLapTime
* UpdateLapTimers calls PrintBestLapTime

.PrintBestLapTime

LDX #32                \ Move the cursor to character column 32 (to just after
STX xCursor            \ "Best time" in token 40)

INX                    \ Move the cursor to pixel row 33 (i.e. the second text
STX yCursor            \ line at the top of the screen)

LDX currentPlayer      \ Set X to the driver number of the current player, so
\ the call to PrintTimer prints the lap time for the
\ current driver

LDA #%00100110         \ Print the best lap time for driver X in the following
JSR PrintTimer         \ format:
\
\   * %00 Minutes: No leading zeroes, print both digits
\   * %10 Seconds: Leading zeroes, print both digits
\   * %0  Tenths: Print tenths of a second
\   * %11 Tenths: Leading zeroes, no second digit

\ Fall through into PrintLapTime to print the current
\ lap time at the top of the screen

Type: Subroutine
Category: Text
Summary: Print the current lap time at the top of the screen
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* UpdateLapTimers calls PrintLapTime
* UpdateLapTimers calls via PrintLapTime+2

This routine prints the current lap time in the header at the top of the
screen in the following format:

* Minutes: No leading zeroes, print both digits
* Seconds: Leading zeroes, print both digits
* Tenths: Do not print tenths of a second

Other entry points:

PrintLapTime+2       Format the lap time using the format value in A (see
PrintTimer for details)

.PrintLapTime

LDA #%00101000         \ Set A so the current lap time is printed in the
\ following format by the call to PrintTimer:
\
\   * %00 Minutes: No leading zeroes, print both digits
\   * %10 Seconds: Leading zeroes, print both digits
\   * %1  Tenths: Do not print tenths of a second

LDX #10                \ Move the cursor to character column 10 (to just after
STX xCursor            \ "Lap time" in token 40)

LDX #33                \ Move the cursor to pixel row 33 (i.e. the second text
STX yCursor            \ line at the top of the screen)

LDX #21                \ Print (lapMinutes lapSeconds lapTenths) in the format
JSR PrintTimer         \ given in A

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Keyboard
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* ProcessDrivingKeys (Part 1 of 6) calls GetADCChannel
* ProcessDrivingKeys (Part 3 of 6) calls GetADCChannel

This routine reads a joystick axis and returns a value with 0 representing the
stick being at the centre point, and -127 and +127 representing the left/right
or up/down values.

Arguments:

* 1 = joystick X

* 2 = joystick Y

Returns:

A                    The high byte of the channel value, converted to an
absolute figure in the range 0 to 127

X                    The sign of the result:

* 0 = positive, i.e. right or down

* 1 = negative, i.e. left or up

C flag               Clear if A < 10

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 A contains the high byte of the
\ channel value

\ The channel value in A will be in the range 0 to 255,
\ with 128 representing the stick being in the centre,
\ so now we need to flip this around into the range 0 to
\ 127, with the sign given in X

LDX #1                 \ Set X = 1, to denote a negative result (left or down),
\ which we will change below if this is a positive
\ result

CLC                    \ Set A = A + 128, so in terms of 8-bit numbers, this
ADC #128               \ does the following:
\
\   * 0-127 goes to 128-255
\
\   * 128-255 goes to 256-383, i.e. 0-127
\
\ So A is now in the range 128 to 255 for low readings
\ from the ADC (right or down), or 0 to 127 for high

BPL adcc1              \ If A is in the range 0 to 127, skip the following two
\ instructions as the result is already in the correct
\ range, 0 to 127, and X is set to 1 for left or up

EOR #&FF               \ Flip the value of A, so the range 128 to 255 flips to
\ the range 127 to 0

DEX                    \ Set X = 0 to denote a positive result, right or down

CMP #10                \ Clear the C flag if A < 10

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Main loop
Summary: Increment the timers and the main loop counter
Deep dive: Scheduling tasks in the main loop
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* FinishRace calls ProcessTime
* MainDrivingLoop (Part 2 of 5) calls ProcessTime

.ProcessTime

\ First, we update the timerAdjust counter, which
\ iterates from trackTimerAdjust down to zero and back
\ round again, but only if trackTimerAdjust <> 255
\
\ The timerAdjust counter is used to control the speed
\ of the timers in the AddTimeToTimer routine

BNE tick1              \ counter in timerAdjust, as it hasn't wrapped round yet

\ If we get here then timerAdjust = 0, so we need to
\ wrap round to trackTimerAdjust again

INX                    \
\ We add the 1 so we can decrement it back to
\ enabled)

BEQ tick2              \ If X = 0, then trackTimerAdjust must be 255, in which

.tick1

DEX                    \ Set timerAdjust = X - 1
\ So the clock adjustment counter decrements on each
\ iteration round the main loop

.tick2

LDA raceStarting       \ If bit 7 of raceStarting is set, then the race is in
BMI tick3              \ the process of starting but hasn't started yet, so

LDX #0                 \ Increment the clock timer

.tick3

INC mainLoopCounterLo  \ Increment the main loop counter in (mainLoopCounterHi
\ mainLoopCounterLo), starting with the low byte

BNE tick4              \ And then the high byte, if the low byte overflowed
INC mainLoopCounterHi

.tick4

LDA clockSeconds       \ If clockSeconds = 0, skip the following
BEQ tick5

LDA mainLoopCounterLo  \ If mainLoopCounterLo mod 32 <> 0, which will be true
AND #31                \ for 31 out of 32 iterations round the main loop, jump
BNE tick6              \ to tick6 to return from the subroutine

.tick5

\ We only get here when mainLoopCounterLo mod 31 = 0,
\ which is once every 32 iterations of the main driving
\ loop

JSR SetDriverSpeed     \ Set the speed for the driver number specified in
\ setSpeedForDriver, and increment setSpeedForDriver so
\ the next time we get here (in 32 iterations of the
\ main loop) we set the speed for the next driver

.tick6

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Drivers
Summary: Decrement X to the previous position number (from 19 to 0 and
round again), which gives the position ahead of X
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* ProcessOvertaking (Part 1 of 3) calls GetPositionAhead

DEX                    \ Decrement X

BPL prev1              \ If X is >= 0, jump to prev1 to skip the following
\ instruction

LDX #19                \ Set X = 19, so repeated calls to this routine will
\ decrement X down to 0, and then start again at 19

.prev1

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Drivers
Summary: Increment X to the next position number (from 0 to 19 and round
again), which gives the position behind X
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* DrawCars calls GetPositionBehind
* MoveAndDrawCars calls GetPositionBehind
* ProcessOvertaking (Part 3 of 3) calls GetPositionBehind
* SetPlayerPositions calls GetPositionBehind

.GetPositionBehind

INX                    \ Increment X

CPX #20                \ If X < 20, jump to getb1 to skip the following
BCC getb1              \ instruction

LDX #0                 \ Set X = 0, so repeated calls to this routine will
\ increment X up to 19, and then start again at 0

.getb1

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Text
Summary: Print a character on-screen
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* Print2DigitBCD calls PrintCharacter
* PrintDriverName calls PrintCharacter
* PrintSpaces calls PrintCharacter
* PrintTimer calls PrintCharacter
* PrintToken calls PrintCharacter
* PrintGearNumber calls via PrintCharacter-6

Arguments:

A                    Character number (ASCII code, 0 to 159)

printMode            Bit 7 determines how the character is printed on-screen:

* 0 = poke the character directly into screen memory
(for the custom screen mode)

* 1 = print the character with OSWRCH (for mode 7)

(xCursor, yCursor)   For the custom screen only, this is the coordinate where
we should print the character, where xCursor is the
character column and yCursor is the pixel row of the
bottom of the character

Returns:

A                    A is unchanged

Other entry points:

PrintCharacter-6     Print double-width character (this is used to print the
double-width number on the gear stick)

The routine must be called twice to print double-width
characters, which are drawn as follows:

* Bit 7 of W is set = draw the right half

* Bit 7 of W is clear = draw the left half

W must be non-zero when the routine is called on this
entry point, otherwise the routine will print characters
at the normal width

STA characterDef       \ Store the character number in characterDef

JMP char1              \ Jump to char1 to skip the printMode check and use the
\ current value of W

.PrintCharacter

BIT printMode          \ If bit 7 of printMode is set, jump to char8 to print
BMI char8              \ the character in A with OSWRCH

STA characterDef       \ Store the character number in characterDef

LDA #0                 \ Set W = 0 to indicate we should print a single-width
STA W                  \ character

.char1

TXA                    \ Store X and Y on the stack so we can retrieve them
PHA                    \ after we have printed the character
TYA
PHA

LDY #HI(characterDef)  \ Call OSWORD with A = 10 and (Y X) = characterDef,
LDX #LO(characterDef)  \ which puts the character definition for the specified
LDA #10                \ character into characterDef+1 to characterDef+8
JSR OSWORD

LDA W                  \ If W = 0, jump to char5 to skip the following
BEQ char5

\ If we get here, then W is non-zero, so we now update
\ the character definition to contain just one half of
\ the character in the left half of the character
\ definition, as follows:
\
\   * If bit 7 of W is set, put the right half of the
\     character into left half of the character
\     definition
\
\   * If bit 7 of W is clear, put the left half of the
\     character into left half of the character
\     definition

LDX #8                 \ We are now going to work our way through each pixel
\ row of the character definition, so set X as a loop
\ counter for each byte in the character definition

.char2

LDA characterDef,X     \ Set A to the bitmap for the X-th row of the character
\ definition

BIT W                  \ If bit 7 of W is set, jump to char3 to skip the next
BMI char3              \ two instructions

AND #%11110000         \ Clear the four pixels in the right half of the pixel
JMP char4              \ row

.char3

ASL A                  \ Shift A to the left so the right half of the pixel
ASL A                  \ row moves to the left half
ASL A
ASL A

.char4

STA characterDef,X     \ Store the updated pixel row byte in the X-th row of
\ the character definition

DEX                    \ Decrement the row counter

BNE char2              \ Loop back until we have processed all eight rows in
\ the character definition

.char5

LDY yCursor            \ Set (Q P) to the screen address of the character block
LDA xCursor            \ containing character column xCursor and pixel row
JSR GetScreenAddress-2 \ yCursor, and set Y to the pixel row number within that
\ block
\
\ As yCursor is the pixel row of the bottom of where we
\ should print the character, (Q P) now points to the
\ address where the bottom pixel row of the character
\ should go

LDX #8                 \ We are now going to work our way through each pixel
\ row of the character definition, poking each row to
\ screen memory, from the bottom row of the character
\ to the top, so set a counter in X for eight rows

.char6

LDA characterDef,X     \ Store the X-th row of the character definition in the
STA (P),Y              \ Y-th byte of (Q P)

DEY                    \ Decrement the pixel row number to point to the row
\ above

BPL char7              \ If Y is positive then we are still within the

LDA P                  \ Otherwise we need to move to the bottom pixel row of
SEC                    \ the character row above, so set:
SBC #&40               \
STA P                  \   (Q P) = (Q P) - &140
\
\ starting with the low bytes

LDA Q                  \ And then the high bytes, so (Q P) contains the screen
SBC #1                 \ address of the character block above (as each
STA Q                  \ character row contains &140 bytes)

LDY #7                 \ Set Y = 7 to point to the bottom pixel row in the new
\ character block

.char7

DEX                    \ Decrement the character pixel row counter

BNE char6              \ Loop back to poke the next row into screen memory
\ until we have poked all eight rows

INC xCursor            \ Move the cursor to the right by one character, as we
\ have just printed a full character

PLA                    \ Retrieve X and Y from the stack
TAY
PLA
TAX

LDA characterDef       \ Set A to the character number, so A is unchanged by
\ the routine

RTS                    \ Return from the subroutine

.char8

JSR OSWRCH             \ Print the character in A

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Drawing pixels
Summary: Return the screen address for a specified screen coordinate
Context: See this subroutine on its own page
References: This subroutine is called as follows:

Arguments:

A                    The screen x-coordinate in pixels (0 to 159)

Y                    The screen y-coordinate in pixels

Returns:

(Q P)                The address of the character block containing the screen
coordinates

Y                    The pixel row within the character block containing the
screen coordinates

Other entry points:

GetScreenAddress-2   Treat the x-coordinate as a character column number
rather than a pixel coordinate (0 to 39)

ASL A                  \ Set A = A << 2
ASL A                  \       = x-coord << 2
\
\ so in the following, (Q P) gets set to x-coord << 3,
\ or x-coord * 8, which gives us the correct byte number
\ for this coordinate on the character row, as each
\ character block contains eight bytes

STA P                  \ Set (Q P) = A << 1
LDA #0                 \           = x-coord << 1
ASL P                  \           = x-coord * 2
ROL A                  \
STA Q                  \ so (Q P) contains the correct byte number for this
\ coordinate as an offset from the start address of the
\ character row, as each character row contains 320
\ bytes, and the x-coordinate in A is in the range 0 to
\ 160 (i.e. each character block is two pixels wide)

TYA                    \ Set X = Y
LSR A                  \       = y-coord >> 3
LSR A                  \
LSR A                  \ so X is the character row number for this coordinate
TAX

\ The X-th entry in the (yLookupHi yLookupLo) table
\ contains the screen address of the start of character
\ row X in the custom screen, so we now add this to
\ (Q P) to get the screen address of the correct
\ character block on this row

LDA yLookupLo,X        \ Set (Q P) = (Q P) + X-th yLookup entry
CLC                    \
ADC P                  \ starting with the low bytes
STA P

LDA yLookupHi,X        \ And then the high bytes
STA Q

TYA                    \ Set Y = Y mod 8, to set it to the pixel row within the
AND #7                 \ character block for the coordinate
TAY

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Dashboard
Summary: Erase a line by replacing each pixel in the line with its original
contents
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* UpdateDashboard calls EraseRevCounter

.EraseRevCounter

LDX lineBufferSize     \ Set X to the size of the line buffer

BEQ erev2              \ If the line buffer is empty, jump to erev2 to return
\ from the subroutine, as there is no line to erase

DEX                    \ Decrement X so that it can work as a buffer counter
\ working through buffer entries X down to 0

.erev1

LDA lineBufferAddrLo,X \ Set (Q P) to the screen address of the X-th pixel in
STA P                  \ the line buffer
STA Q

LDA lineBufferPixel,X  \ Set A to the original screen contents of the X-th in
\ the line buffer

LDY #0                 \ Restore the pixel to its original screen content, i.e.
STA (P),Y              \ the pixel that was there before we drew a line over
\ the top of it

DEX                    \ Decrement the buffer counter

BPL erev1              \ Loop back until we have restored all the pixels in the
\ line buffer

STY lineBufferSize     \ Set lineBufferSize = 0, to reset the line buffer

.erev2

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Dashboard
Summary: Update the rev counter and steering wheel lines on the dashboard
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* MainDrivingLoop (Part 5 of 5) calls UpdateDashboard

.UpdateDashboard

JSR EraseRevCounter    \ Erase the dial hand on the rev counter and the line on
\ the steering wheel

JSR DrawRevCounter     \ Redraw the dial hand on the rev counter

\ We now draw the line on the steering wheel

LDA steeringLo         \ Set T = steeringLo
STA T

LSR A                  \ Set the C flag to bit 0 of steeringLo (the sign bit)
\ which is set if we are steering left, or clear if we
\ are steering right

PHP                    \ Store the C flag on the stack

LDA #2                 \ Set A = 2, to use as the value of V to send to the
\ DrawDashboardLine routine (shallow slope, right and
\ down)

BCS upda1              \ If bit 0 of steeringLo is set, skip the following
\ instruction

LDA #5                 \ Bit 0 of steeringLo is clear, so set A = 5 to use
\ as the value of V to send to the DrawDashboardLine
\ routine (shallow slope, left and down)

.upda1

STA V                  \ Set V = A, which we will pass to the DrawDashboardLine
\ routine

LDA steeringHi         \ Set A = steeringHi, so (A T) = (steeringHi steeringLo)

ASL T                  \ Set (A T) = (A T) << 1
ROL A                  \
\ setting the C flag to the top bit of (A T)

BCS upda2              \ If the C flag is set, skip the following four
\ instructions to set A = 60, as the wheel is turned so
\ much that the indicator would be off the bottom of the
\ screen

BCC upda4

CMP #61                \ If A < 61, i.e. 38 <= A <= 60, jump to upda3 to
BCC upda3              \ skip the following instruction

.upda2

LDA #60                \ Set A = 60, the maximum value of A, so when we fall
\ through into the next calculation, with the C flag
\ set, we set:
\
\   Y = ~A + 76 + C
\     = ~A + 76 + 1
\     = ~A + 1 + 76
\     = -A + 76
\     = -60 + 76
\     = 16

.upda3

\ If we get here then the indicator is a long way away
\ from the centre of the wheel, as A >= 38

EOR #&FF               \ Set Y = ~A + 76 + C
ADC #76                \       = ~A + 1 + 75 + C
TAY                    \       = 75 - A        when 38 <= A <= 60
\         16            when A > 60
\
\ so Y is in the range 37 to 16, with higher values of A
\ giving lower values of A
\
\ This represents the distance between this value on the
\ steering wheel and the nearest quadrant

STY T                  \ Set T = Y (in the range 37 to 16) to pass to the
\ DrawDashboardLine routine as the amount of slope error
\ for each step along the main axis

LDX wheelPixels,Y      \ Set X to the number of pixels that would be along the
\ long axis of the line if the line went all the way to
\ the centre of the wheel, given the value of Y above

STX SS                 \ Set SS = X to pass to the DrawDashboardLine routine
\ as the cumulative amount of slope error that equates
\ to a pixel in the shorter axis

.upda4

\ If we get here then the indicator is not far away from
\ the centre of the wheel, as A < 38

TAX                    \ Set X = A (in the range 0 to 37)
\
\ This represents the distance between this value on the
\ steering wheel and the nearest quadrant

STX T                  \ Set T = X (in the range 0 to 37) to pass to the
\ DrawDashboardLine routine as the amount of slope error
\ for each step along the main axis

LDA V                  \ Flip bit 0 of V, to flip it from the first half of the
EOR #1                 \ quadrant to the second half
STA V

\ By this point, V has the following value, which we
\ pass to the DrawDashboardLine routine
\
\   * 2 when sign bit of steeringLo is set and A >= 38
\     i.e. steering left a lot
\     Shallow slope, right and down
\
\   * 3 when sign bit of steeringLo is set and A < 38
\     i.e. steering left a little
\     Steep slope, right and down
\
\   * 4 when sign bit of steeringLo is clear and A < 38
\     i.e. steering right a little
\     Steep slope, left and down
\
\   * 5 when sign bit of steeringLo is clear and A >= 38
\     i.e. steering right a lot
\     Shallow slope, left and down
\
\ These are the opposite way round to the rev counter
\ hand, which is also drawn by the DrawDashboardLine
\ routine - this is because the rev counter hand is
\ drawn from the centre outwards, while the steering
\ wheel line is drawn from the outside in

LDA wheelPixels,X      \ Set A to the number of pixels that would be along the
\ long axis of the line if the line went all the way to
\ the centre of the wheel, given the value of X above

STA SS                 \ Set SS = A to pass to the DrawDashboardLine routine
\ as the cumulative amount of slope error that equates
\ to a pixel in the shorter axis

.upda5

ASL A                  \ Set A = A * 2 + 4
CLC

EOR #&FF               \ Set Y = ~A
TAY

TXA                    \ Set A = X
\
\ where X is either the original value of A (0 to 37)
\ or the Y-th value of wheelPixels (where Y is 16 to
\ 37), which is in the range 38 to 51
\
\ In effect, this is the horizontal distance of the
\ steering line from the centre point

PLP                    \ Set the C flag to bit 0 of steeringLo (the sign bit),
\ which we stored on the stack above

BCC upda6              \ If bit 0 of steeringLo is clear, we are steering to
\ the right, so skip the following instruction

EOR #&FF               \ Set A = ~A
\       = -A - 1
\
\ so the following addition becomes:
\
\   A = A + 80
\     = -A - 1 + 80
\     = 79 - A

.upda6

CLC                    \ Set A = A + 80
\ which turns A into the position on the steering wheel
\ where 80 is the centre point at the top middle of the
\ wheel

STA W                  \ Set W = A, so W contains the position on the steering
\ wheel

AND #%11111100         \ Clear bits 0 and 1 of A, to set A = A div 4

JSR GetScreenAddress   \ Set (Q P) to the screen address for pixel coordinate
\ (A, Y), setting Y to the pixel row within the
\ character block containing the pixel (both of which we
\ pass to the DrawDashboardLine routine)

LDA W                  \ Set W = W * 2 mod 8
ASL A                  \
AND #%00000111         \ This takes the x-coordinate of the line on the
STA W                  \ steering wheel, doubled so we have 160 in the centre
\ of the steering wheel (so it matches the coordinates
\ in mode 5), and then we look at bits 0 to 2 only to
\ get the starting pixel to pass to DrawDashboardLine

LDA #%100              \ Set H = %100, so the DrawDashboardLine looks up values
STA H                  \ from the second half of the pixelByte and yLookupLo+8
\ tables (so the line is drawn on black rather than
\ white)

LDA #6                 \ Set U = 6, so the line contains up to seven pixels
STA U

JSR DrawDashboardLine  \ Draw the dashboard line

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Dashboard
Summary: Draw the hand on the rev counter
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* UpdateDashboard calls DrawRevCounter

.DrawRevCounter

LDA #%000              \ Set H = %000 to pass into DrawDashboardLine below
STA H

LDA revCount           \ Set A = revCount, which is the value we want to draw
\ on the rev counter, in the range 0 to 170

CMP #30                \ If A >= 30, skip the following instruction
BCS revs1

LDA #30                \ Set A = 30, so A is always at least 30, and is now in
\ the range 30 to 170 (we do this because the hand on
\ the rev counter doesn't fall all the way back to zero)

.revs1

STA T                  \ Set T = A

LSR A                  \ Set A = A / 2 + T
CLC                    \       = A / 2 + A
ADC T                  \       = 1.5 * A
\       = 1.5 * revCount
\
\ which is in the range 45 to 255

ROR A                  \ Set A = A / 2
\       = 0.75 * revCount
\
\ which is in the range 22 to 127

\ We now convert the value in A to the corresponding
\ position on the dial in terms of which quadrant it's
\ in, and which half of that quadrant, so we can pass
\ the details to the DrawDashboardLine routine

SEC                    \ Set A = A - 76
SBC #76

BCS revs2              \ If the subtraction went past zero, add 152, to get:
\   A = A + 76

\ So for A in the range 22 to 127, this converts:
\
\   A = 22-75  into A = 98-151
\   A = 76-127 into A = 0-51
\
\ If we consider a clock with 0 at 12 o'clock, then 38
\ at 3 o'clock, 76 at 6 o'clock and 114 at 9 o'clock,
\ A is now the position of the hand on that clock, i.e.
\ the position of the hand that we want to draw on the
\ rev counter

.revs2

\ We now calculate the quadrant that contains the hand
\ on the rev counter, numbered 0 to 3 counting clockwise
\ from top-right
\
\ We do this by calculating X = A / 38, by repeatedly
\ subtracting 38 from A until we go past zero

LDX #&FF               \ We start by setting X = -1

SEC                    \ Set the C flag for the subtraction

.revs3

INX                    \ Increment X as we are doing a subtraction

SBC #38                \ Set A = A - 38

BCS revs3              \ If the subtraction didn't take us past zero, loop back
\ to subtract another 38

ADC #38                \ Otherwise add the 38 back that pushed us over the
\ limit, so X now contains the quadrant number, and A
\ contains the remainder (i.e. the fraction that the
\ hand is past the start of the quadrant)

CMP #19                \ If the remainder is < 19, skip the following, as A
BCC revs4              \ contains the distance from the start of quadrant X to
\ the position of the hand (and the C flag is clear)

SBC #19                \ Set A = ~(A - 19) + 20
EOR #&FF               \       = ~(A - 19) + 1 + 19
CLC                    \       = -(A - 19) + 19
ADC #20                \       = 19 - (A - 19)
\
\ so A now contains the distance from the hand to the

SEC                    \ Set the C flag to indicate that A is now the distance
\ from the hand to the end of the quadrant

.revs4

\ By this point:
\
\   X = quadrant number (0 to 3)
\
\   A = distance from start of quadrant to hand (C = 0)
\       distance from hand to end of quadrant   (C = 1)
\
\ where each quadrant is 38 in size, so A is <= 19
\
\ The C flag therefore represents which half of the
\ quadrant the hand is in, 0 denoting the first half and
\ 1 denoting the second half

TAY                    \ Set Y = the distance between the hand and quadrant

STY T                  \ Set T = the distance between the hand and quadrant

TXA                    \ Ensure X is in the range 0 to 3 (it should be, but
AND #3                 \ this makes absolutely sure)
TAX

TXA                    \ This sets bits 1 and 2 of V to the quadrant number,
ROL A                  \ and bit 0 to the C flag, so the possible values are:
STA V                  \
\   * 0 = %000 = Quadrant 0, first half, 12:00 to 1:30
\   * 1 = %001 = Quadrant 0, second half, 1:30 to 3:00
\   * 2 = %010 = Quadrant 1, first half, 3:00 to 4:30
\   * 3 = %011 = Quadrant 1, second half, 4:30 to 6:00
\   * 4 = %100 = Quadrant 2, first half, 6:00 to 7:30
\   * 5 = %101 = Quadrant 2, second half, 7:30 to 9:00
\   * 6 = %110 = Quadrant 3, first half, 9:00 to 10:30
\   * 7 = %111 = Quadrant 3, second half, 10:30 to 12:00
\
\ These are the quadrant values we need to pass to the
\ DrawDashboardLine routine below

AND #%11111100         \ If bit 2 of A is zero, then the hand is in the right
BEQ revs5              \ half of the dial, so jump to revs5 to set W = 0

LDA #7                 \ Otherwise the hand is in the left half of the dial,
\ so set A so we set W = 7 below

.revs5

STA W                  \ Set W = 0 if the hand is in the right half
\         7 if the hand is in the left half
\
\ so we start drawing from the leftmost pixel when
\ drawing to the right, or the rightmost pixel when
\ drawing to the left (which ensures that the hand joins
\ the centre spoke of the rev counter without a gap)

LDA handPixels,Y       \ Set A to the number of pixels that are along the long
\ axis of the hand, given the distance between the hand
\ and quadrant that we set in Y above

STA SS                 \ Set SS to the number of pixels along the long axis

STA U                  \ Set U to the number of pixels along the long axis, to
\ pass through to the DrawDashboardLine routine below

LDA startDialLo,X      \ Set the low byte of (Q P) to the low byte of the
AND #%11111000         \ screen address for the starting point of the hand for
STA P                  \ quadrant Y, which we get from the startDialLo table,
\ and clear bits 0 to 2 so the address points to the
\ top line of the relevant character block

LDA startDialLo,X      \ Set Y to the pixel row within the character block
AND #%00000111         \ for the starting point, which we get from bits 0 to 2
TAY                    \ of the starting point's screen address

LDA startDialHi,X      \ Set the high byte of (Q P) to the high byte of the
STA Q                  \ screen address for the starting point of the hand for
\ quadrant Y, so (Q P) now contains the full address of
\ the starting point's character block

\ Fall through into DrawDashboardLine to draw a line
\ from the starting point given in (Q P) and Y, in the
\ direction given in V, with U pixels along the longest
\ axis, and in the half of the dial given in W

Type: Subroutine
Category: Dashboard
Summary: Draw a hand on the rev counter or a line on the steering wheel
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* UpdateDashboard calls DrawDashboardLine

This routine is a mode 5 Bresenham line-drawing routine, which modifies itself
to cater for lines of different slopes.

Arguments:

V                    The slope of the line to draw, which is expressed as a
3 o'clock, with quadrants ordered 0 to 3 in a clockwise
order:

* 0 = %000 = Quadrant 0, first half (12:00 to 1:30)
Steep slope, right and up
Step up along y-axis (stepAxis = DEY)
Move right along x-axis (shortAxis = INX)

* 1 = %001 = Quadrant 0, second half (1:30 to 3:00)
Shallow slope, right and up
Step right along x-axis (stepAxis = INX)
Move up along y-axis (shortAxis = DEY)

* 2 = %010 = Quadrant 1, first half (3:00 to 4:30)
Shallow slope, right and down
Step right along x-axis (stepAxis = INX)
Move down along y-axis (shortAxis = INY)

* 3 = %011 = Quadrant 1, second half (4:30 to 6:00)
Steep slope, right and down
Step down along y-axis (stepAxis = INY)
Move right along x-axis (shortAxis = INX)

* 4 = %100 = Quadrant 2, first half (6:00 to 7:30)
Steep slope, left and down
Step down along y-axis (stepAxis = INY)
Move left along x-axis (shortAxis = DEX)

* 5 = %101 = Quadrant 2, second half (7:30 to 9:00)
Shallow slope, left and down
Step left along x-axis (stepAxis = DEX)
Move down along y-axis (shortAxis = INY)

* 6 = %110 = Quadrant 3, first half (9:00 to 10:30)
Shallow slope, left and up
Step left along x-axis (stepAxis = DEX)
Move up along y-axis (shortAxis = DEY)

* 7 = %111 = Quadrant 3, second half (10:30 to 12:00)
Steep slope, left and up
Step up along y-axis (stepAxis = DEY)
Move left along x-axis (shortAxis = DEX)

(Q P)                The screen address of the character block containing
the line's starting point

Y                    The pixel row within the character block containing the
line's starting point (0 to 7)

U                    The number of pixels to step along the step axis:

* The number of pixels along the longer (step) axis
when drawing the rev counter

* 6 when drawing the steering wheel line

T                    The slope error for each step along the step axis is
T/SS, and T is set to:

* The distance between the hand and quadrant when
drawing the rev counter hand

* When drawing the steering wheel line:

0-37 when the line is close to the centre (in the
top two quadrants either side of the centre)

37-16 when line is further from the centre (in the

SS                   The slope error for each step along the step axis is
T/SS, and SS is set to:

* The same as U when drawing the rev counter hand
i.e. the number of pixels along the longer (step)
axis

* A value from wheelPixels when drawing the steering
wheel line (38 to 53)

H                    The starting index to use in the pixelByte and
yLookupLo+8 lookup tables:

* %000 when drawing the rev counter hand, so the line
gets drawn in white

* %100 when drawing the steering wheel line, so the
line gets drawn in black

W                    The pixel number (0-7) of the first pixel to draw
along the x-axis

.DrawDashboardLine

LDX V                  \ Modify the instruction at dlin2 to the V-th shortAxis
LDA shortAxis,X        \ instruction
STA dlin2

LDA stepAxis,X         \ Modify the instruction at dlin8 to the V-th stepAxis
STA dlin8              \ instruction

\ The following code has the instructions for V = %010,
\ which has INY at dlin2 for the short axis, and INX at
\ dlin8 for the step axis, so that's this kind of line:
\
\   * Quadrant 1, first half (3:00 to 4:30)
\   * Shallow slope, right and down
\   * Step right along x-axis (stepAxis = INX)
\   * Move down along y-axis (shortAxis = INY)

LDX W                  \ Set X = W, so X contains the position of the current
\ pixel within the pixel row, if there were eight pixels
\ per row

LDA #0                 \ Set A = -SS
SEC                    \
SBC SS                 \ So this is the starting point for our slope error
\ calculation

CLC                    \ Clear the C flag for the following addition

.dlin1

\ We use A to keep track of the slope error, adding the
\ step along the smaller axis (in T) until it reaches 0,
\ at which point it is a multiple of SS and we need
\ to move one pixel along the smaller axis

ADC T                  \ Set A = A + T
\
\ So A is updated with the slope error

BCC dlin3              \ If the addition didn't overflow, then the result in A
\ is still negative, so skip the following instruction

\ The slope error just overflowed (in other words, the
\ cumulative slope error in A just reached a multiple of
\ SS), so we need to adjust the slope error to make
\ it negative again, and we need to step along the
\ shorter axis

SBC SS                 \ Subtract SS from the cumulative slope error to
\ bring it back to being negative, so we can detect when
\ it reaches next multiple of SS

.dlin2

INY                    \ Increment Y to move down along the y-axis (i.e. along
\ the shorter axis)
\
\ This instruction is modified at the start of this
\ routine, depending on the slope of the line in V

.dlin3

STA II                 \ Store the updated slope error in II, so we can
\ retrieve it below, ready for the next iteration of the
\ drawing loop

TXA                    \ X contains the position of the current pixel within
LSR A                  \ the pixel row, in the range 0 to 7, so set A to half
AND #%00000011         \ this value to get the mode 5 pixel number (as there
\ are only four pixels per pixel byte on mode 5)

ORA H                  \ Set bit 2 of A if this is the steering wheel, which
\ is the same as adding 4

STA V                  \ Store the result in V, so V contains the pixel number
\ (0 to 3) of the pixel to draw, plus 4 if this is the
\ steering wheel (4 to 7)

TXA                    \ X contains the position of the current pixel within
\ the pixel line, so put this in A

BPL dlin4              \ If bit 7 of A is clear, jump to dlin4

\ Otherwise we need to move (Q P) to the previous
\ character block to the left, by subtracting 8 (as
\ there are 8 bytes per character block)

LDX #7                 \ Set X = 7 to set as the new value of W below

LDA P                  \ Set (Q P) = (Q P) - 8
SEC                    \
SBC #8                 \ starting with the low bytes
STA P

BCS dlin5              \ And then the high bytes
DEC Q

BCS dlin5              \ This instruction has no effect, as we already passed
\ through the BCS above, which is presumably a bug (this
\ should perhaps be a BCC?)

.dlin4

BCC dlin5

\ Otherwise we need to move (Q P) to the next character
\ block to the right, by adding 8 (as there are 8 bytes
\ per character block)

LDX #0                 \ Set X = 0 to set as the new value of W below

LDA P                  \ Set (Q P) = (Q P) + 8
CLC                    \
ADC #8                 \ starting with the low bytes
STA P

BCC dlin5              \ And then the high bytes
INC Q

.dlin5

STX W                  \ Store X in W, so W moves along one pixel to the right

LDX lineBufferSize     \ Set X to the size of the line buffer, which gives us
\ the index of the next empty space in the buffer

TYA                    \ Y contains the number of the pixel row within the
\ current character block, so put this in A

\ Otherwise we need to move (Q P) to the next character
\ row above, by subtracting &140 (as there are &140
\ bytes per character row)

LDA P                  \ Set (Q P) = (Q P) - &140
SEC                    \
SBC #&40               \ starting with the low bytes
STA P

LDA Q                  \ And then the high bytes
SBC #&01
STA Q

LDY #7                 \ Set Y = A = 7 as the new value of Y
TYA

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

.dlin6

BCC dlin7

\ Otherwise we need to move (Q P) to the next character
\ row below, by adding &140 (as there are &140 bytes per
\ character row)

LDA P                  \ Set (Q P) = (Q P) + &140
CLC                    \
ADC #&40               \ starting with the low bytes
STA P

LDA Q                  \ And then the high bytes
STA Q

LDY #0                 \ Set Y = A = 0 as the new value of Y
TYA

.dlin7

\ We now store the details of the pixel we are about
\ to overwrite in the line buffer, which stores a screen
\
\ character block in P to the number of the pixel row
\ within the character in A, which we can do with an ORA
\ as P only occupies bits 3 to 7, while A only occupies
\ bits 0 to 2

ORA P                  \ Store the address we are about to overwrite in the
STA lineBufferAddrLo,X \ next empty space at the end of the line buffer, i.e.
\ starting with the low byte of the address

LDA Q                  \ And then the high byte of the address

LDA (P),Y              \ Store the current pixel contents into the pixel
STA lineBufferPixel,X  \ contents buffer at lineBufferPixel

INC lineBufferSize     \ Increment the size of the pixel buffers, as we just

LDX V                  \ Set X = V, so X now contains the pixel number
\ (0 to 3) of the pixel to draw, plus 4 if this is the
\ steering wheel (4 to 7)

AND yLookupLo+8,X      \ Apply the X-th pixel mask from yLookupLo+8, so this
\ clears the X-th pixel in the pixel row (the table
\ contains the same bytes in 0 to 3 as in 0 to 7)

ORA pixelByte,X        \ OR with a pixel byte with pixel X set, so this sets
\ the X-th pixel to colour 2 (white) if X is 0 to 3, or
\ colour 0 (black) if X is 4 to 7 - so the rev counter
\ hand is white, while the steering wheel line is black

STA (P),Y              \ Draw the pixel byte to the screen

\ We now set up all the variables so we can loop back
\ to dlin1 for the next pixel

LDX W                  \ Set X = W

LDA II                 \ Set A to the current slope error, which we stored in
\ II above

CLC                    \ Clear the C flag for the addition at the start of the
\ loop

.dlin8

INX                    \ Increment X to step right along the x-axis (i.e. along
\ the longer axis)
\
\ This instruction is modified at the start of this
\ routine, depending on the slope of the line in V

DEC U                  \ Decrement the pixel counter

BMI dlin9              \ If we have drawn the correct number of pixels along
\ subroutine as we have finished drawing the line

JMP dlin1              \ Otherwise loop back to draw the next pixel

.dlin9

RTS                    \ Return from the subroutine

Type: Subroutine
Category: Dashboard
Summary: Update screen memory to animate the tyres
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* ScreenHandler calls AnimateTyres

.AnimateTyres

INC irqCounter         \ Increment irqCounter, so it gets incremented every
\ time the IRQ routine reaches section 4 of the custom
\ screen

LDA playerSpeedHi      \ Set tyreTravel = tyreTravel + playerSpeedHi + 48
CLC
STA tyreTravel

\ return from the subroutine

LDA playerMoving       \ If playerMoving = 0 then the player's car is not
BEQ tyre4              \ moving so we don't need to animate the tyres, so jump
\ to tyre4 to return from the subroutine

\ We now flip the black-and-white pixels in the tyre
\ tread at the top of the left and white tyres using an
\ EOR with %10, which flips pixels between %00 (black)
\ and %10 (white)
\
\ The tyre tread spills across three character blocks,
\ with each pixel byte covering four pixels. Taking the
\ left tyre, we have the following blocks:
\
\   1. Top-left block: tyreLeft1 to tyreLeft1+2
\
\      Three rows of four pixels at the top-left of the
\      tyre
\
\   2. Top-right block: tyreLeft2+3, tyreLeft2+4
\
\      Two rows of two pixels at the top-right of the
\      tyre
\
\   3. Bottom-left block: tyreLeft3 to tyreLeft3+4
\
\      Five rows of pixels at the bottom-left of the
\      tyre, covering 4, 4, 2, 2, 1 pixels as we work
\      our way down
\
\ This looks like this, where the number is the block
\ number above (the left side of the following is along
\ the left edge of the screen):
\
\   111122
\   111122
\   1111
\   3333
\   3333
\   33
\   33
\   3
\
\ The code below works through the various blocks,
\ applying an EOR to the tyre pixels as follows:
\
\   1. tyreLeft1, tyreLeft1+2
\
\      EOR with mask %11110000 (all four pixels)
\
\   2. tyreLeft2+3, tyreLeft2+4
\
\      EOR with mask %11000000 (left two pixels)
\
\   3. tyreLeft3 to tyreLeft3+4
\
\      shape from all four pixels at the top down to
\      just one pixel at the bottom)
\
\ A similar process is applied to the right tyre, but
\ with the shape reflected, like this:
\
\                                           221111
\                                           221111
\                                             1111
\                                             3333
\                                             3333
\                                               33
\                                               33
\                                                3
\
\ The right tyre uses tyreRight and tyreTreadRight to
\ achieve the same effect, all in the same loop as the
\ left tyre animation for maximum efficiency

LDX #4                 \ Set a loop counter to go from 4 to 0

.tyre1

LDA tyreLeft3,X        \ Set tyreLeft3 = tyreLeft3 EOR tyreTreadLeft
EOR tyreTreadLeft,X    \ to flip the pixels in the bottom-left of the tyre
STA tyreLeft3,X

LDA tyreRight3,X       \ Set tyreRight3 = tyreRight3 EOR tyreTreadRight
EOR tyreTreadRight,X   \ to flip the pixels in the bottom-right of the tyre
STA tyreRight3,X

CPX #3                 \ If X >= 3, jump to tyre2 to skip the following
BCS tyre2

LDA tyreLeft1,X        \ Flip all four pixels at tyreLeft1+X, so that's
EOR #%11110000         \ tyreLeft1+2, tyreLeft1+1 and tyreLeft1
STA tyreLeft1,X

LDA tyreRight1,X       \ Flip all four pixels at tyreRight1+X, so that's
EOR #%11110000         \ tyreRight1+2, tyreRight1+1 and tyreRight1
STA tyreRight1,X

BNE tyre3              \ Jump to tyre3 to continue the loop (this BNE is
\ effectively a JMP as A is never zero, because the
\ screen byte in A is a chequered pattern)

.tyre2

LDA tyreLeft2,X        \ Flip the two left pixels at tyreLeft2+X, so that's
EOR #%11000000         \ tyreLeft2+4 and tyreLeft2+3
STA tyreLeft2,X

LDA tyreRight2,X       \ Flip the two right pixels at tyreRight2+X, so that's
EOR #%00110000         \ tyreRight2+4 and tyreRight2+3
STA tyreRight2,X

.tyre3

DEX                    \ Decrement the loop counter

BPL tyre1              \ Loop back to animate the next tyre part

.tyre4

RTS                    \ Return from the subroutine

Type: Variable
Category: Dashboard
Summary: Tyre tread pattern for the left tyre
Context: See this variable on its own page
References: This variable is used as follows:

Contains the shape of the bottom part of the tread on the left tyre, which we
animate when the car is moving.

EQUB %11110000
EQUB %11110000
EQUB %11000000
EQUB %11000000
EQUB %10000000

Type: Variable
Category: Dashboard
Summary: Tyre tread pattern for the right tyre
Context: See this variable on its own page
References: This variable is used as follows:

Contains the shape of the bottom part of the tread on the right tyre, which we
animate when the car is moving.

EQUB %11110000
EQUB %11110000
EQUB %00110000
EQUB %00110000
EQUB %00010000

Type: Workspace
Category: Track data
Summary: This is where the track data gets loaded
Deep dive: The jigsaw puzzle binary
The track data file format
Context: See this workspace on its own page
References: This workspace is used as follows:
* SwapCode uses trackData

See the track source in revs-silverstone.asm for details of the track data. It
covers trackData and dashData41 - the latter gets moved into screen memory as
part of the memory-moving process in the SwapData routine.

.trackData

.trackSectionData

SKIP 1                 \ Various data for the track section:
\
\   * Bits 0-2: Size of the track section list
\
\   * Bits 4-7: Sign number
\
\
\ This variable is used by the following:
\
\   * GetFirstSegment
\
\ This list only includes code that refers to the
\ variable by name; there may be other references to
\ this memory location that don't use this label, and
\ these will not be mentioned above

.xTrackSectionIHi

SKIP 1                 \ High byte of the x-coordinate of the starting point of
\ the inner verge of each track section
\
\
\ This variable is used by the following:
\
\   * GetSectionCoord
\
\ This list only includes code that refers to the
\ variable by name; there may be other references to
\ this memory location that don't use this label, and
\ these will not be mentioned above

.yTrackSectionIHi

SKIP 1                 \ High byte of the y-coordinate of the starting point of
\ the inner verge of each track section
\
\
\ This variable is used by the following:
\
\   * GetSectionCoord
\
\ This list only includes code that refers to the
\ variable by name; there may be other references to
\ this memory location that don't use this label, and
\ these will not be mentioned above

.zTrackSectionIHi

SKIP 1                 \ High byte of the z-coordinate of the starting point of
\ the inner verge of each track section
\
\
\ This variable is used by the following:
\
\   * GetSectionCoord
\
\ This list only includes code that refers to the
\ variable by name; there may be other references to
\ this memory location that don't use this label, and
\ these will not be mentioned above

.xTrackSectionOHi

SKIP 1                 \ High byte of the x-coordinate of the starting point of
\ the outside verge of each track section
\
\
\ This variable is used by the following:
\
\   * GetSectionCoords
\
\ This list only includes code that refers to the
\ variable by name; there may be other references to
\ this memory location that don't use this label, and
\ these will not be mentioned above

.trackSectionTurn

SKIP 1                 \ The number of the segment in the section where
\ non-player drivers should start turning in preparation
\ for the next section
\
\
\ This variable is used by the following:
\
\   * GetSegmentSteering
\   * MoveCars (Part 1 of 2)
\
\ This list only includes code that refers to the
\ variable by name; there may be other references to
\ this memory location that don't use this label, and
\ these will not be mentioned above

.zTrackSectionOHi

SKIP 1                 \ High byte of the z-coordinate of the starting point of
\ the outside verge of each track section
\
\
\ This variable is used by the following:
\
\   * GetSectionCoords
\
\ This list only includes code that refers to the
\ variable by name; there may be other references to
\ this memory location that don't use this label, and
\ these will not be mentioned above

.trackDriverSpeed

SKIP 1                 \ The maximum speed for non-player drivers on this
\ section of the track
\
\
\ This variable is used by the following:
\
\   * GetSegmentSteering
\   * MoveCars (Part 1 of 2)
\
\ This list only includes code that refers to the
\ variable by name; there may be other references to
\ this memory location that don't use this label, and
\ these will not be mentioned above

SKIP 8 * 25            \ Section data for 25 more sections

.xTrackSignVector

SKIP 16                \ The x-coordinate of the track sign vector for each
\ sign, to be scaled and added to the inner track
\ section vector for the sign
\
\
\ This variable is used by the following:
\
\
\ This list only includes code that refers to the
\ variable by name; there may be other references to
\ this memory location that don't use this label, and
\ these will not be mentioned above

.zTrackSignVector

SKIP 16                \ The z-coordinate of the track sign vector for each
\ sign, to be scaled and added to the inner track
\ section vector for the sign
\
\
\ This variable is used by the following:
\
\
\ This list only includes code that refers to the
\ variable by name; there may be other references to
\ this memory location that don't use this label, and
\ these will not be mentioned above

.yTrackSignVector

SKIP 16                \ The y-coordinate of the track sign vector for each
\ sign, to be scaled and added to the inner track
\ section vector for the sign
\
\
\ This variable is used by the following:
\
\
\ This list only includes code that refers to the
\ variable by name; there may be other references to
\ this memory location that don't use this label, and
\ these will not be mentioned above

.xTrackSegmentI

SKIP 256               \ Vector x-coordinates between two consecutive segments
\ on the inside of the track
\
\
\ This variable is used by the following:
\
\   * ApplyElevation (Part 2 of 5)
\   * BuildCarObjects (Part 1 of 3)
\   * GetSegmentVector
\
\ This list only includes code that refers to the
\ variable by name; there may be other references to
\ this memory location that don't use this label, and
\ these will not be mentioned above

.yTrackSegmentI

SKIP 256               \ Vector y-coordinates between two consecutive segments
\ on the inside of the track
\
\
\ This variable is used by the following:
\
\   * BuildCarObjects (Part 1 of 3)
\   * GetSegmentVector
\   * MultiplyHeight
\
\ This list only includes code that refers to the
\ variable by name; there may be other references to
\ this memory location that don't use this label, and
\ these will not be mentioned above

.zTrackSegmentI

SKIP 256               \ Vector z-coordinates between two consecutive segments
\ on the inside of the track
\
\
\ This variable is used by the following:
\
\   * ApplyElevation (Part 2 of 5)
\   * BuildCarObjects (Part 1 of 3)
\   * GetSegmentVector
\
\ This list only includes code that refers to the
\ variable by name; there may be other references to
\ this memory location that don't use this label, and
\ these will not be mentioned above

.xTrackSegmentO

SKIP 256               \ Vector x-coordinates from the inside to the outside of
\ the track for each segment
\
\
\ This variable is used by the following:
\
\   * BuildCarObjects (Part 2 of 3)
\   * GetTrackSegment (Part 3 of 3)
\
\ This list only includes code that refers to the
\ variable by name; there may be other references to
\ this memory location that don't use this label, and
\ these will not be mentioned above

.zTrackSegmentO

SKIP 256               \ Vector z-coordinates from the inside to the outside of
\ the track for each segment
\
\
\ This variable is used by the following:
\
\   * BuildCarObjects (Part 2 of 3)
\   * GetTrackSegment (Part 3 of 3)
\
\ This list only includes code that refers to the
\ variable by name; there may be other references to
\ this memory location that don't use this label, and
\ these will not be mentioned above

.trackSectionFlag

SKIP 1                 \ Various flags for the track section:
\
\   * Bit 0: Section shape (Sh)
\
\     * 0 = straight section (only one segment vector)
\
\     * 1 = curved section (multiple segment vectors)
\
\   * Bit 1: Colour of left verge marks (Vcol)
\
\     * 0 = black-and-white verge marks
\
\     * 1 = red-and-white verge marks
\
\   * Bit 2: Colour of right verge marks (Vcol)
\
\     * 0 = black-and-white verge marks
\
\     * 1 = red-and-white verge marks
\
\   * Bit 3: Show corner markers on right (Mlr)
\
\     * 0 = do not show corner markers to the right of
\           the track
\
\     * 1 = show corner markers to the right of the
\           track
\
\   * Bit 4: Show corner markers on left (Mlr)
\
\     * 0 = do not show corner markers to the left of
\           the track
\
\     * 1 = show corner markers to the left of the track
\
\   * Bit 5: Corner marker colours (Mcol)
\
\     * 0 = show all corner markers in white
\
\     * 1 = show corner markers in red or white, as
\           appropriate
\
\   * Bit 6: In the extra tracks only, enable hooks to
\            generate segment vectors (G)
\
\     * 0 = disable HookDataPointers and
\           HookSegmentVector
\
\     * 1 = enable HookDataPointers and
\           HookSegmentVector
\
\   * Bit 7: Section has a maximum speed (Sp)
\
\     * 0 = this section has no maximum speed
\
\     * 1 = this section has a maximum speed
\
\
\ This variable is used by the following:
\
\   * GetFirstSegment
\   * GetSegmentSteering
\   * MoveCars (Part 1 of 2)
\
\ This list only includes code that refers to the
\ variable by name; there may be other references to
\ this memory location that don't use this label, and
\ these will not be mentioned above

.xTrackSectionILo

SKIP 1                 \ Low byte of the x-coordinate of the starting point of
\ the inner verge of each track section
\
\
\ This variable is used by the following:
\
\   * GetSectionCoord
\
\ This list only includes code that refers to the
\ variable by name; there may be other references to
\ this memory location that don't use this label, and
\ these will not be mentioned above

.yTrackSectionILo

SKIP 1                 \ Low byte of the y-coordinate of the starting point of
\ the inner verge of each track section
\
\
\ This variable is used by the following:
\
\   * GetSectionCoord
\
\ This list only includes code that refers to the
\ variable by name; there may be other references to
\ this memory location that don't use this label, and
\ these will not be mentioned above

.zTrackSectionILo

SKIP 1                 \ Low byte of the z-coordinate of the starting point of
\ the inner verge of each track section
\
\
\ This variable is used by the following:
\
\   * GetSectionCoord
\
\ This list only includes code that refers to the
\ variable by name; there may be other references to
\ this memory location that don't use this label, and
\ these will not be mentioned above

.xTrackSectionOLo

SKIP 1                 \ Low byte of the x-coordinate of the starting point of
\ the outside verge of each track section
\
\
\ This variable is used by the following:
\
\   * GetSectionCoords
\
\ This list only includes code that refers to the
\ variable by name; there may be other references to
\ this memory location that don't use this label, and
\ these will not be mentioned above

.trackSectionFrom

SKIP 1                 \ The number of the first segment vector in each
\ section, which enables us to fetch the segment vectors
\ for a given track section
\
\
\ This variable is used by the following:
\
\   * GetSectionCoords
\
\ This list only includes code that refers to the
\ variable by name; there may be other references to
\ this memory location that don't use this label, and
\ these will not be mentioned above

.zTrackSectionOLo

SKIP 1                 \ Low byte of the z-coordinate of the starting point of
\ the outside verge of each track section
\
\
\ This variable is used by the following:
\
\   * GetSectionCoords
\
\ This list only includes code that refers to the
\ variable by name; there may be other references to
\ this memory location that don't use this label, and
\ these will not be mentioned above

.trackSectionSize

SKIP 1                 \ The length of each track section in terms of segments
\
\
\ This variable is used by the following:
\
\   * GetTrackSegment (Part 2 of 3)
\   * MoveObjectBack
\   * MoveObjectForward
\
\ This list only includes code that refers to the
\ variable by name; there may be other references to
\ this memory location that don't use this label, and
\ these will not be mentioned above

SKIP 8 * 25            \ Section data for 25 more sections

.trackSteering

SKIP 24                \ The optimum steering for non-player drivers to apply
\ on each track section
\
\
\ This variable is used by the following:
\
\   * GetSectionSteering
\
\ This list only includes code that refers to the
\ variable by name; there may be other references to
\ this memory location that don't use this label, and
\ these will not be mentioned above

SKIP 2

.trackSignData

SKIP 16                \ Base coordinates and object types for 16 road signs
\
\
\ This variable is used by the following:
\
\
\ This list only includes code that refers to the
\ variable by name; there may be other references to
\ this memory location that don't use this label, and
\ these will not be mentioned above

.trackSectionCount

SKIP 1                 \ The total number of track sections * 8
\
\
\ This variable is used by the following:
\
\   * GetSectionAngles (Part 2 of 3)
\   * GetSectionSteering
\   * GetTrackSegment (Part 1 of 3)
\   * MoveObjectBack
\   * MoveObjectForward
\
\ This list only includes code that refers to the
\ variable by name; there may be other references to
\ this memory location that don't use this label, and
\ these will not be mentioned above

.trackVectorCount

SKIP 1                 \ The total number of segment vectors in the segment
\ vector tables
\
\
\ This variable is used by the following:
\
\   * UpdateVectorNumber
\
\ This list only includes code that refers to the
\ variable by name; there may be other references to
\ this memory location that don't use this label, and
\ these will not be mentioned above

.trackLength

SKIP 2                 \ The length of the full track in terms of segments
\
\
\ This variable is used by the following:
\
\   * CompareSegments
\   * MoveObjectBack
\   * MoveObjectForward
\
\ This list only includes code that refers to the
\ variable by name; there may be other references to
\ this memory location that don't use this label, and
\ these will not be mentioned above

.trackStartLine

SKIP 2                 \ The segment number of the starting line
\
\
\ This variable is used by the following:
\
\   * ResetVariables
\
\ This list only includes code that refers to the
\ variable by name; there may be other references to
\ this memory location that don't use this label, and
\ these will not be mentioned above

.trackLapTimeSec

SKIP 3                 \ Lap times for adjusting the race class (seconds)
\
\
\ This variable is used by the following:
\
\   * MainLoop (Part 4 of 6)
\
\ This list only includes code that refers to the
\ variable by name; there may be other references to
\ this memory location that don't use this label, and
\ these will not be mentioned above

.trackLapTimeMin

SKIP 3                 \ Lap times for adjusting the race class (minutes)
\
\
\ This variable is used by the following:
\
\   * MainLoop (Part 4 of 6)
\
\ This list only includes code that refers to the
\ variable by name; there may be other references to
\ this memory location that don't use this label, and
\ these will not be mentioned above

.trackGearRatio

SKIP 7                 \ The gear ratio for each gear
\
\
\ This variable is used by the following:
\
\   * ApplyEngine
\
\ This list only includes code that refers to the
\ variable by name; there may be other references to
\ this memory location that don't use this label, and
\ these will not be mentioned above

.trackGearPower

SKIP 7                 \ The power for each gear
\
\
\ This variable is used by the following:
\
\   * ApplyEngine
\
\ This list only includes code that refers to the
\ variable by name; there may be other references to
\ this memory location that don't use this label, and
\ these will not be mentioned above

.trackBaseSpeed

SKIP 3                 \ The base speed for each race class, used when
\ generating the best racing lines and non-player driver
\ speeds
\
\
\ This variable is used by the following:
\
\   * GetSectionSteering
\
\ This list only includes code that refers to the
\ variable by name; there may be other references to
\ this memory location that don't use this label, and
\ these will not be mentioned above

.trackStartPosition

SKIP 1                 \ The starting race position of the player during a
\ practice or qualifying lap
\
\
\ This variable is used by the following:
\
\   * ResetVariables
\
\ This list only includes code that refers to the
\ variable by name; there may be other references to
\ this memory location that don't use this label, and
\ these will not be mentioned above

.trackCarSpacing

SKIP 1                 \ The spacing between the cars at the start of a
\ qualifying lap, in segments
\
\
\ This variable is used by the following:
\
\   * ResetVariables
\
\ This list only includes code that refers to the
\ variable by name; there may be other references to
\ this memory location that don't use this label, and
\ these will not be mentioned above

SKIP 1                 \ Adjustment factor for the speed of the timers to allow
\ for fine-tuning of time on a per-track basis
\
\
\ This variable is used by the following:
\
\   * ProcessTime
\
\ This list only includes code that refers to the
\ variable by name; there may be other references to
\ this memory location that don't use this label, and
\ these will not be mentioned above

.trackRaceSlowdown

SKIP 1                 \ Slowdown factor for non-player drivers in the race
\
\
\ This variable is used by the following:
\
\   * MoveCars (Part 1 of 2)
\
\ This list only includes code that refers to the
\ variable by name; there may be other references to
\ this memory location that don't use this label, and
\ these will not be mentioned above

SKIP 7

Type: Variable
Category: Screen buffer
Summary: Contains part of the dashboard image that gets moved into screen
memory
Deep dive: The jigsaw puzzle binary
Context: See this variable on its own page
References: No direct references to this variable in this source file

CLEAR &594A, &5A22     \ The track data is loaded in a separate file that is
ORG &594A              \ moved to trackData after the game binary has loaded
\
\ It overwrites part of the dashboard image at that's
\ loaded as part of the main game binary at dashdata41,
\ which is moved into screen memory before the track
\ data is moved
\
\ These lines rewind BeebAsm's assembly back to
\ dashData41 (which is at address &594A), and clear
\ the block from that point to CallTrackHook, so we can
\ set the correct address for dashData41 while also
\ retaining the addresses we just set up for the track
\ data
.dashData41

SKIP 67

SKIP 149

Type: Subroutine
Category: Setup
Summary: The track file's hook code
Deep dive: The track data file format
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* SetupGame calls CallTrackHook

.CallTrackHook

BRK                    \ The SwapCode routine replaces these three bytes with
BRK                    \ the three bytes from just before the trackChecksum in
BRK                    \ the track file, which contain the three bytes of hook
\ code for the track
\
\ In the default Silverstone track that comes with the
\ original version of Revs, the three bytes of hook code
\ contain the following:
\
\   RTS
\   NOP
\   NOP
\
\ so calling this routine does nothing (see the track
\ source in the revs-silverstone.asm file for details)
\
\ Other track files, such as those in the Revs 4 Tracks
\ expansion pack, contain JMP instructions in their hook
\ code, which allows the track authors to hook in entire
\ routines that get called when those tracks are loaded

Type: Subroutine
Category: Drivers
Summary: Award points following a race
Context: See this subroutine on its own page
References: This subroutine is called as follows:
* MainLoop (Part 6 of 6) calls AwardRacePoints

This routine awards points to a driver for finishing in the top six in a race,
or for getting the fastest lap time. The points awarded are based on the
driver's race position, as per the pointsForPlace table:

* 9 points for first place
* 6 points for second place
* 4 points for third place
* 3 points for fourth place
* 2 points for fifth place
* 1 point for sixth place
* 1 point for the fastest lap

In single-player races, the points are awarded as above.

In multi-player races, an algorithm is used to share out the points in a way
that takes the relative skills into consideration. Specifically, the routine
awards this many points:

(U T) * the points from the above list

This is how (U T) is calculated:

* Single-player race:

(U T) = numberOfPlayers = 1, so we award the amount of points shown above

* Multi-player race:

If we are awarding points to the current player:

(U T) = (numberOfPlayers - 1) * numberOfPlayers

If we are awarding points to a human player but not the current player:

(U T) = numberOfPlayers

If we are awarding points to a computer driver:

(U T) = (numberOfPlayers - 1) * 2

I have no idea why the algorithm works like this. It needs more analysis!

Arguments:

X                    The race position to award points to:

* 0 to 5 for the first six places

* 6 for the fastest lap

.AwardRacePoints

LDA #0                 \ Zero the points in (racePointsHi racePointsLo) for
STA racePointsLo,X     \ race position X
STA racePointsHi,X

STA U                  \ Set U = 0, to act as the high byte of (U T)

LDY driversInOrder,X   \ Set Y to the number of the driver in race position X

CPX #6                 \ If we called the routine with X = 0 to 5, then jump to
BNE poin1              \ poin1 to skip the following instruction

LDY driversInOrder     \ We called the routine with X = 6, so set Y to the
\ winning driver's number, i.e. the driver with the
\ fastest lap

.poin1

\ By this point, Y contains the number of the driver we
\ want to give the points to, so now we calculate the
\ number of points to award

LDA numberOfPlayers    \ Set A to the number of players - 1
SEC
SBC #1

BEQ poin3              \ If A = 0 then there is only one player, so jump to
\ poin3 to skip the following

CPY currentPlayer      \ If Y is the number of the current player, jump to
BEQ poin2              \ poin2

CPY lowestPlayerNumber \ If Y >= lowestPlayerNumber then this is a human
BCS poin3              \ player but not the current player, so jump to poin3

\ If we get here then we are awarding points to a
\ computer-controlled driver

ASL A                  \ Double the value of A, to use as the value of T, so
\ we will get:
\
\   (U T) = (0 T)
\         = T
\         = A * 2
\         = (numberOfPlayers - 1) * 2

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

.poin2

\ If we get here then we are awarding points to the
\ current player

STA U                  \ Set U = A = numberOfPlayers - 1

LDA numberOfPlayers    \ Set A = numberOfPlayers

JSR Multiply8x8        \ Set (A T) = A * U
\           = (numberOfPlayers - 1) * numberOfPlayers

STA U                  \ Set (U T) = (A T)
\           = (numberOfPlayers - 1) * numberOfPlayers

.poin3

\ If we get here then either there is only one player,
\ or we are awarding points to a human player but not
\ the current player

LDA numberOfPlayers    \ Set A to the number of players, to use as the value of
\ T, so we will get:
\
\   (U T) = (0 T)
\         = (0 numberOfPlayers)
\         = numberOfPlayers

BNE poin4              \ This instruction has no effect as poin4 is the next
\ instruction anyway

.poin4

STA T                  \ Store A in T, so this sets (U T) = (U A)

.poin5

SED                    \ Set the D flag to switch arithmetic to Binary Coded
\ Decimal (BCD)

\ We now do the following addition 256 * U + T times, so
\ the total number of points added is:
\
\   (256 * U + T) * (9, 6, 4, 3, 2 or 1)
\
\ or putting it another way:
\
\   (U T) * (9, 6, 4, 3, 2 or 1)

.poin6

LDA pointsForPlace,X   \ Add the X-th entry in pointsForPlace to the X-th entry
CLC                    \ in (racePointsHi racePointsLo),  starting with the low
STA racePointsLo,X

LDA racePointsHi,X     \ And then the high bytes
STA racePointsHi,X

DEC T                  \ Decrement the counter in T

BNE poin6              \ Loop back to poin6 so we do the addition a total of T
\ times

DEC U                  \ Decrement the counter in U

BPL poin6              \ Loop back to poin6 so we do an additional U loops,
\ with the inner loop repeating 256 times as T is now 0,
\ so this does a total of 256 * U additional additions

JSR AddRacePoints      \ Add the race points from above to the accumulated
\ points for driver Y

RTS                    \ Return from the subroutine

```