Skip to navigation

Revs on the BBC Micro

Revs C source

Name: GetColour (Part 1 of 3) [Show more] Type: Subroutine Category: Screen buffer Summary: Calculate the colour of a specific pixel byte in the screen buffer
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawObjectEdge (Part 4 of 5) calls GetColour * FillAfterObject calls GetColour

Arguments: Y The track line number of the pixel byte to check blockNumber The dash data block number of the pixel byte to check (Q P) The address of the dash data block containing the pixel byte we want to check Returns: A The colour of this pixel byte in the screen buffer
IF _ACORNSOFT OR _4TRACKS .GetColour CPY horizonLine \ If Y <= horizonLine then the byte we want to check is BCC gcol1 \ below the horizon, so jump to gcol1 to work out the BEQ gcol1 \ byte's colour LDA horizonLine \ Set A to the track line number of the horizon JSR SetMarker+3 \ Call SetMarker+3 to insert a marker value at the \ horizon line LDA colourPalette+1 \ Otherwise the byte is in the sky, so set A to logical \ colour 1 (blue) from the colour palette RTS \ Return from the subroutine .gcol1 LDA #0 \ Set T = 0, to use for storing the results of the STA T \ following comparisons LDA blockNumber \ Set A to the block number containing the pixel byte \ that we want to check CMP leftVergeStart,Y \ If A >= leftVergeStart for this track line, rotate a ROL T \ 1 into T, otherwise rotate a 0 into T CMP leftTrackStart,Y \ If A >= leftTrackStart for this track line, rotate a ROL T \ 1 into T, otherwise rotate a 0 into T CMP rightVergeStart,Y \ If A >= rightVergeStart for this track line, rotate a ROL T \ 1 into T, otherwise rotate a 0 into T CMP rightGrassStart,Y \ If A >= rightGrassStart for this track line, rotate a LDA T \ 1 into the result, otherwise rotate a 0, and copy the ROL A \ results from all four comparisons into A, so we have: \ \ * Bit 0: 1 if blockNumber >= rightGrassStart \ 0 if blockNumber < rightGrassStart \ \ * Bit 1: 1 if blockNumber >= rightVergeStart \ 0 if blockNumber < rightVergeStart \ \ * Bit 2: 1 if blockNumber >= leftTrackStart \ 0 if blockNumber < leftTrackStart \ \ * Bit 3: 1 if blockNumber >= leftVergeStart \ 0 if blockNumber < leftVergeStart BNE gcol7 \ If A is non-zero, then the block number containing the \ pixel we want to check is greater than at least one \ of the verge edges, so jump to gcol7 \ If we get here then blockNumber is less than all the \ track verge block numbers LDA backgroundColour,Y \ Set A to the background colour for the track line \ containing the pixel we want to check AND #%11101100 \ Extract the following bits: \ \ * Bits 5-7 and 2: records which routine set this \ colour \ \ * Bit 3: contains the verge type that was being \ drawn when this colour was set \ \ * 0 = leftVergeStart, rightVergeStart \ \ * 1 = leftTrackStart, rightGrassStart CMP #%01000000 \ If this colour matches these bits: BEQ gcol2 \ \ * Bits 5-7 and 2 = %010 0 \ \ * Bit 3 = 0 \ \ then the colour was set by UpdateBackground to the \ value in backgroundRight when drawing leftVergeStart \ or rightVergeStart, so jump to gcol2 CMP #%10001000 \ If this colour matches these bits: BEQ gcol2 \ \ * Bits 5-7 and 2 = %100 0 \ \ * Bit 3 = 1 \ \ then the colour was set by UpdateBackground to the \ value in backgroundLeft when drawing leftTrackStart \ or rightGrassStart, so jump to gcol2 CMP #%00000100 \ If this colour matches these bits: BEQ gcol2 \ \ * Bits 5-7 and 2 = %000 1 \ \ * Bit 3 = 0 \ \ then the colour was set by SetVergeBackground when \ drawing leftVergeStartor rightVergeStart, so jump to \ gcol2 LDA rightGrassStart,Y \ Set A to the block number containing the right edge of \ the right verge BPL gcol3 \ If A is positive then jump to gcol3 BMI gcol4 \ Jump to gcol4 to return the background colour for this \ track line as the pixel's colour (this BMI is \ effectively a JMP, as we just passed through a BPL) .gcol2 LDA backgroundColour,Y \ Set A to the background colour for the track line \ containing the pixel we want to check AND #%00010000 \ If bit 4 of the background colour is set, then the BNE gcol3 \ verge being drawn when the colour was set was \ rightVergeStart or rightGrassStart, so jump to gcol3 JSR gcol8 \ Call gcol8 to process the left verge JMP gcol4 \ Jump to gcol4 to return the background colour for this \ track line as the pixel's colour .gcol3 JSR gcol12 \ Call gcol12 to process the right verge .gcol4 LDA backgroundColour,Y \ Set A to the background colour for the track line \ containing the pixel we want to check AND #%00000011 \ Extract the colour number from bits 0-1 of A into X TAX LDA colourPalette,X \ Set A to logical colour X from the colour palette RTS \ Return from the subroutine .gcol5 LDA colourPalette \ Set A to logical colour 0 (black) from the colour \ palette RTS \ Return from the subroutine .gcol6 LDA colourPalette+3 \ Set A to logical colour 3 (green) from the colour \ palette RTS \ Return from the subroutine .gcol7 \ If we get here then the block number containing the \ pixel we want to check is greater than at least one \ of the verge edges, and we have the following: \ \ * Bit 0: 1 if blockNumber >= rightGrassStart \ 0 if blockNumber < rightGrassStart \ \ * Bit 1: 1 if blockNumber >= rightVergeStart \ 0 if blockNumber < rightVergeStart \ \ * Bit 2: 1 if blockNumber >= leftTrackStart \ 0 if blockNumber < leftTrackStart \ \ * Bit 3: 1 if blockNumber >= leftVergeStart \ 0 if blockNumber < leftVergeStart LSR A \ If blockNumber >= rightGrassStart, jump to gcol6 to BCS gcol6 \ return from the subroutine with the colour green LSR A \ If blockNumber >= rightVergeStart, jump to gcol12 BCS gcol12 LSR A \ If blockNumber >= leftTrackStart, jump to gcol5 to BCS gcol5 \ return from the subroutine with the colour black \ If we get here then blockNumber >= leftVergeStart, as \ we only jump to gcol7 if at least one of the four bits \ is set, so by a process of elimination, it must be \ bit 3
Name: GetColour (Part 2 of 3) [Show more] Type: Subroutine Category: Screen buffer Summary: Process the left verge
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.gcol8 CPY vergeTopLeft \ If Y >= vergeTopLeft, jump to gcol6 to return from the BCS gcol6 \ subroutine with the colour green LDX leftSegment,Y \ Set X to the index within the track segment list of \ the segment for the left verge on this track line BMI gcol14 \ If bit 7 of X is set, then this entry in the \ rightSegment table was filled in by MapSegmentsToLines \ for a segment that doesn't have an entry in the track \ segment list, in which case the index of the last \ valid entry is captured in bits 0-6, so jump to gcol14 \ to clear bit 7 of X and return the colour of the verge \ mark for the segment beyond segment X JSR SetMarker \ Call SetMarker to insert a &AA marker into the screen \ buffer at the left verge .gcol9 LDA (P),Y \ If the current byte in the screen buffer is non-zero, BNE gcol11 \ then it is not empty, so jump to gcol11 LDA leftTrackStart,Y \ Set A to the block number containing the right edge of \ the left verge BMI gcol10 \ If bit 7 of A is set then the block number is still in \ its initialised form, so jump to gcol10 CMP blockNumber \ Set the C flag if A >= blockNumber, which contains the \ dash data block number for the current edge DEY \ Decrease the track line in Y BCS gcol9 \ If A >= blockNumber, loop back to gcol9 INY \ Increment the track line in Y .gcol10 JSR SetMarker+6 \ Call SetMarker+6 to insert a marker byte into the Y-th \ byte of the dash data block, but only if the Y-th \ entry is zero and blockOffset <= Y < V .gcol11 LDY V \ Set Y = V JMP gcol13 \ Jump to gcol13 to return the colour of the verge mark \ for the segment beyond segment X
Name: GetColour (Part 3 of 3) [Show more] Type: Subroutine Category: Screen buffer Summary: Process the right verge
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.gcol12 CPY vergeTopRight \ If Y >= vergeTopRight, jump to gcol6 to return from BCS gcol6 \ the subroutine with the colour green LDX rightSegment,Y \ Set X to the index within the track segment list of \ the segment for the right verge on this track line BMI gcol14 \ If bit 7 of X is set, then this entry in the \ rightSegment table was filled in by MapSegmentsToLines \ for a segment that doesn't have an entry in the track \ segment list, in which case the index of the last \ valid entry is captured in bits 0-6, so jump to gcol14 \ to clear bit 7 of X and return the colour of the verge \ mark for the segment beyond segment X JSR SetMarker \ Call SetMarker to insert a &AA marker into the screen \ buffer at the right verge .gcol13 LDA vergeDataRight-1,X \ Set A to entry X - 1 from vergeDataRight, which \ contains the colour of the verge for the segment \ beyond segment X AND #%00000011 \ Extract the colour number from bits 0-1 of A into X TAX LDA colourPalette,X \ Set A to logical colour X from the colour palette RTS \ Return from the subroutine .gcol14 TXA \ Clear bit 7 of X AND #%01111111 TAX BPL gcol13 \ Jump to gcol13 (this BPL is effectively a JMP as we \ just cleared bit 7 of A) ENDIF
Name: SetMarker [Show more] Type: Subroutine Category: Screen buffer Summary: Insert a marker value into a dash data block
Context: See this subroutine on its own page References: This subroutine is called as follows: * GetColour (Part 2 of 3) calls SetMarker * GetColour (Part 3 of 3) calls SetMarker * GetColour (Part 1 of 3) calls entry point SetMarker+3 * GetColour (Part 2 of 3) calls entry point SetMarker+6

This routine inserts a marker value (&AA) into the current dash data block at a specified track line, but only if the current value in the dash data block is zero, and only if the track line is in the specified range. If the routine is called via SetMarker, then it works like this: * A is set to the pitch angle (track line) of the X-th entry in the verge buffer * The A-th byte in the dash data block at (Q P) is set to the marker &AA if blockOffset <= A < Y and it is currently zero If the routine is called via SetMarker+3, then it works like this: * The A-th byte in the dash data block at (Q P) is set to the marker &AA if blockOffset <= A < Y and it is currently zero If the routine is called via SetMarker+6, then it works like this: * The Y-th byte in the dash data block at (Q P) is set to the marker &AA if blockOffset <= Y < V and it is currently zero Arguments: Y A track line number X Index of an entry in the verge buffer (SetMarker only) Other entry points: SetMarker+3 Use the value of A passed to the routine SetMarker+6 Use Y and V in place of A and Y
IF _ACORNSOFT OR _4TRACKS .SetMarker LDA yVergeRight,X \ Set A to the pitch angle of the X-th entry in the \ verge buffer (i.e. the track line number of the \ X-th entry) \ We join the subroutine here if we call SetMarker+3 STY V \ Set V to the track line number in Y TAY \ Set Y to the track line number in A \ We join the subroutine here if we call SetMarker+6 CPY V \ If Y >= V, jump to setm1 to return from the BCS setm1 \ subroutine with Y = V CPY blockOffset \ If Y < blockOffset, jump to setm1 to return from the BCC setm1 \ subroutine with Y = V LDA (P),Y \ If the Y-th byte in the dash data block is non-zero, BNE setm1 \ then it is not empty, so jump to setm1 to return from \ the subroutine with Y = V LDA #&AA \ Set the Y-th byte in the dash data block to &AA, to STA (P),Y \ act as a marker that gets picked up in the drawing \ routine .setm1 LDY V \ Restore Y to the track line number of the pixel byte \ to check, so it's unchanged by the call for calls to \ SetMarker and SetMarker+3 RTS \ Return from the subroutine ENDIF
Name: GetColourSup [Show more] Type: Subroutine Category: Screen buffer Summary: Calculate the colour of a specific pixel byte in the screen buffer
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawObjectEdge (Part 4 of 5) calls GetColourSup * FillAfterObjectSup calls GetColourSup

Arguments: Y The track line number of the pixel byte to check blockNumber The dash data block number of the pixel byte to check Returns: A The colour of this pixel byte in the screen buffer
IF _SUPERIOR OR _REVSPLUS .GetColourSup CPY horizonLine \ If Y <= horizonLine then the byte we want to check is BCC scol1 \ below the horizon, so jump to scol1 to work out the BEQ scol1 \ byte's colour LDA colourPalette+1 \ Otherwise the byte is in the sky, so set A to logical \ colour 1 (blue) from the colour palette RTS \ Return from the subroutine .scol1 LDA blockNumber \ Set A to the block number containing the pixel byte \ that we want to check CMP rightGrassStart,Y \ If A >= rightGrassStart for this track line, then the BCS scol3 \ pixel byte is in the grass to the right of the track, \ so jump to scol3 to return colour 3 (green) CMP rightVergeStart,Y \ If A >= rightVergeStart for this track line, then the BCS scol5 \ pixel byte is on the right track verge, so jump to \ scol5 to work out its colour CMP leftTrackStart,Y \ If A >= leftTrackStart for this track line, then the BCS scol2 \ pixel byte is on the track, so jump to scol2 to return \ colour 0 (black) CMP leftVergeStart,Y \ If A >= leftVergeStart for this track line, then the BCS scol4 \ pixel byte is on the left track verge, so jump to \ scol4 to work out its colour LDA backgroundColour,Y \ If we get here then the byte is to the left of the \ left track verge, so set A to the background colour of \ this track line so we can extract the colour from bits \ 0-1 below BCC scol7 \ Jump to scol7 to return the pixel byte for the colour \ in A (this BCC is effectively a JMP as we just passed \ throuhg a BCS) .scol2 LDA colourPalette \ Set A to logical colour 0 (black) from the colour \ palette RTS \ Return from the subroutine .scol3 LDA colourPalette+3 \ Set A to logical colour 3 (green) from the colour \ palette RTS \ Return from the subroutine .scol4 \ If we get here then the pixel byte is on the left \ track verge CPY vergeTopLeft \ If the track line in Y >= vergeTopLeft, jump to scol3 BCS scol3 \ to return colour 3 (green) LDA leftSegment,Y \ Set A to the index within the track segment list of \ the segment for the left verge on this track line JMP scol6 \ Jump to scol6 .scol5 \ If we get here then the pixel byte is on the right \ track verge CPY vergeTopRight \ If the track line in Y >= vergeTopRight, jump to scol3 BCS scol3 \ to return colour 3 (green) LDA rightSegment,Y \ Set A to the index within the track segment list of \ the segment for the right verge on this track line .scol6 AND #%01111111 \ Clear bit 7 of A TAX \ Set X to A LDA vergeDataRight-1,X \ Set A to entry X - 1 from vergeDataRight, which \ contains the colour of the verge mark for the segment \ beyond segment X .scol7 AND #%00000011 \ Extract the colour number from bits 0-1 of A into X TAX LDA colourPalette,X \ Set A to logical colour X from the colour palette RTS \ Return from the subroutine ENDIF
Name: AssistSteering [Show more] Type: Subroutine Category: Tactics Summary: Apply computer assisted steering (CAS) when configured Deep dive: Tactics of the non-player drivers Computer assisted steering (CAS)
Context: See this subroutine on its own page References: This subroutine is called as follows: * ProcessDrivingKeys (Part 1 of 6) calls AssistSteering * ProcessDrivingKeys (Part 2 of 6) calls entry point AssistSteeringKeys

This routine applies computer assisted steering (CAS) to the joystick and keyboard, but only if it is enabled and we are already steering (if we are not steering, then there is no steering to assist). Jumps back to: * keys11 (joystick, CAS not enabled) * keys7 (joystick, CAS enabled, joystick is hardly steering) * keys10 (keyboard, or joystick with CAS applied) Arguments: (U T) The amount of steering currently being applied by the steering wheel: * For joystick, contains the scaled joystick x-coordinate as a sign-magnitude number with the sign in bit 0 (1 = left, 0 = right) * For keyboard, contains a signed 16-bit number, negative if bit 0 of steeringLo is set (left), positive if bit 0 of steeringLo is clear (right) (A T) Same as (U T) V For keyboard only: * V = 1 if ";" is being pressed (steer right) * V = 2 if "L" is being pressed (steer left) * V = 0 if neither is being pressed Returns: A A is set to steeringLo (U T) The new amount of steering to apply, adjusted to add computer assisted steering, as a sign-magnitude number Other entry points: AssistSteeringKeys For keyboard-controlled steering
IF _SUPERIOR OR _REVSPLUS .AssistSteering JSR GetSteeringAssist \ Set X = configAssist, set the C flag to bit 7 of \ directionFacing, and update the computer assisted \ steering (CAS) indicator on the dashboard BNE asst2 \ If CAS is enabled, jump to asst2 to skip the following \ instruction and apply CAS, otherwise we jump to keys11 \ to return to the ProcessDrivingKeys routine .asst1 JMP keys11 \ Return to the ProcessDrivingKeys routine at keys11 .asst2 BCS asst1 \ If bit 7 of directionFacing is set, then our car is \ facing backwards, so jump to asst1 to jump to keys11 \ in the ProcessDrivingKeys routine, as CAS only works \ when driving forwards CMP #5 \ If A >= 5, then the joystick is currently applying BCS asst4 \ some steering, so jump to asst4 to continue applying \ CAS \ Otherwise the joystick is not being used for steering \ at the moment, so there is no steering to assist and \ we don't apply CAS JMP keys7 \ Return to the ProcessDrivingKeys routine at keys7 to \ apply no joystick steering .AssistSteeringKeys JSR GetSteeringAssist \ Set X = configAssist, set the C flag to bit 7 of \ directionFacing, and update the CAS indicator on the \ dashboard BEQ asst3 \ If CAS is not enabled, jump to asst3 to set A and jump \ to keys10 in the ProcessDrivingKeys routine BCS asst3 \ If bit 7 of directionFacing is set, then our car is \ facing backwards, so jump to asst3 to jump to keys10 \ in the ProcessDrivingKeys routine, as CAS only works \ when driving forwards LDA V \ Set A = V BNE asst5 \ If A is non-zero, then one of the steering keys is \ being held down, so jump to asst5 to continue applying \ CAS \ Otherwise the keyboard is not being used for steering \ at the moment, so there is no steering to assist and \ we don't apply CAS .asst3 JMP asst13 \ Jump to asst13 to set A to steeringLo and return to \ the ProcessDrivingKeys routine at keys210 .asst4 \ If we get here then the joystick is being used for \ steering, and (A T) contains the scaled joystick \ x-coordinate as a sign-magnitude number with the \ sign in bit 0 (1 = left, 0 = right) LDA T \ Set the C flag to the inverse of the joystick EOR #1 \ x-coordinate's sign bit from bit 0 (i.e. 0 = left, LSR A \ 1 = right) LDA #3 \ Set A to 3 (if the C flag is set, i.e. right) or 2 (if SBC #0 \ the C flag is clear, i.e. left) .asst5 \ If we get here, then either the joystick or keyboard \ is being used for steering, and we have the following: \ \ * A = 2 if we are steering left \ \ We now set X as a flag for the steering diection, so \ we can use A for other purposes LDX #50 \ Set X = 50 to use as the value for steering left CMP #2 \ If A = 2 then we are steering left, so jump to asst6 BEQ asst6 \ to skip the following instruction LDX #10 \ Set X = 10 to use as the value for steering right \ So we now have the following that we can use to check \ which direction we are steering: \ \ * X = 10 if we are steering right \ \ * X = 50 if we are steering left .asst6 \ We now spend the rest of the routine calculating the \ amount of computer assisted steering (CAS) to apply, \ returning the result in the sign-magnitude number \ (U T) \ \ First, we set the following if we are steering right: \ \ (W V) = (steeringHi steeringLo) + 256 \ \ or the following if we are steering left: \ \ (W V) = (steeringHi steeringLo) - 256 \ \ by first converting (steeringHi steeringLo) from a \ sign-magnitude number to a signed 16-bit number and \ then doing the addition or subtraction LDA steeringLo \ Set V = steeringLo STA V LSR A \ Set the C flag to the sign of steeringLo LDA steeringHi \ Set A = steeringHi \ \ So (A V) = (steeringHi steeringLo) BCC asst7 \ If the C flag is clear then (steeringHi steeringLo) is \ positive, so jump to asst7 as (A V) already has the \ correct sign \ Otherwise (steeringHi steeringLo) is negative, so we \ need to negate (A V) LDA #0 \ Set (A V) = 0 - (A V) SEC \ SBC V \ starting with the low bytes STA V LDA #0 \ And then the high bytes SBC steeringHi .asst7 CLC \ Set (A V) = (A V) + 256 ADC #1 \ = (steeringHi steeringLo) + 256 CPX #50 \ If X <> 50, then we are steering right, so jump to BNE asst8 \ asst8 SBC #2 \ X = 50, so we are steering left, so set: \ \ (A V) = (A V) - 2 * 256 \ = (steeringHi steeringLo) + 256 - 2 * 256 \ = (steeringHi steeringLo) - 256 .asst8 STA W \ Set (W V) = (A V) \ \ So if we are steering right, we have: \ \ (W V) = (steeringHi steeringLo) + 256 \ \ and if we are steering left we have: \ \ (W V) = (steeringHi steeringLo) - 256 LDA xVergeRightLo,X \ Set (A T) = X-th (xVergeRightHi xVergeRightLo) - (W V) SEC \ SBC V \ starting with the low bytes STA T LDA xVergeRightHi,X \ And then the high bytes SBC W PHP \ Store the sign flag for X-th xVergeRight - (W V) on \ the stack, so we can retrieve it below JSR Absolute16Bit \ Set (A T) = |A T| \ = |X-th xVergeRight - (W V)| STA V \ Set (V T) = (A T) \ = |X-th xVergeRight - (W V)| LDY playerSegmentIndex \ Set Y to the index of the player's segment in the \ track segment buffer LDA #60 \ Set A = 60 - playerSpeedHi SEC SBC playerSpeedHi BPL asst9 \ If the result is positive, jump to asst9 to skip the \ following instruction LDA #0 \ Set A = 0, so A is always positive, and is zero if we \ are currently doing more than 60, so: \ \ A = max(0, 60 - playerSpeedHi) .asst9 ASL A \ Set U = 32 * A * 2 ADC #32 \ = 32 + max(0, 60 - playerSpeedHi) * 2 STA U \ \ So U is 32 if we are doing more than 60, and higher \ with lower speeds LDA segmentSteering,Y \ Fetch the carSteering value to steer round the corner \ for the player's track segment AND #%01111111 \ Zero the driving direction in bit 7 CMP #64 \ If A < 64, jump to asst10 to skip the following BCC asst10 \ instruction LDA #2 \ A >= 64, i.e. bit 6 is set, so set A = 2 .asst10 CMP #8 \ If A < 8, jump to asst11 to skip the following BCC asst11 \ instruction LDA #7 \ A >= 8, so set A = 7, i.e. A = min(A, 7) .asst11 \ By now A is between 0 and 7, and is set to 2 if bit 6 \ of segmentSteering was set ASL A \ Set A = A * 16 ASL A \ ASL A \ So A is in the range 0 to 112 ASL A CMP U \ If A < U, jump to asst12 to skip the following BCC asst12 \ instruction STA U \ A >= U, so set U = A, i.e. set U = max(U, A) .asst12 JSR Multiply8x16 \ Set (U T) = U * (V T) / 256 \ = U * |X-th xVergeRight - (W V)| / 256 LDA U \ Set (A T) = (U T) \ = U * |X-th xVergeRight - (W V)| / 256 PLP \ Retrieve the sign of the X-th xVergeRight - (W V) \ calculation that we stored above JSR Absolute16Bit \ Set the sign of (A T) to that of X-th xVergeRight - \ (W V), so we now have: \ \ (A T) = U * (X-th xVergeRight - (W V)) / 256 STA U \ Set (U T) = (A T) \ = U * (X-th xVergeRight - (W V)) / 256 LDA T \ Clear bit 0 of (U T) AND #%11111110 STA T LDA steeringLo \ Set the C flag to the sign in bit 0 of steeringLo LSR A BCS asst13 \ If the C flag is set, jump to asst13 to skip the \ following instruction JSR Negate16Bit+2 \ Set (A T) = -(U T) STA U \ Set (U T) = (A T) \ = -(U T) .asst13 LDA steeringLo \ Set A = steeringLo to return from the subroutine JMP keys10 \ Return to the ProcessDrivingKeys routine at keys10 ENDIF
Name: SetSteeringLimit [Show more] Type: Subroutine Category: Keyboard Summary: Apply a maximum limit to the amount of steering
Context: See this subroutine on its own page References: This subroutine is called as follows: * ProcessDrivingKeys (Part 2 of 6) calls SetSteeringLimit

Arguments: C flag The result of CMP steeringHi Returns: (A T) A is set to |steeringHi steeringLo|
IF _SUPERIOR OR _REVSPLUS .SetSteeringLimit BCC slim1 \ Before calling this routine, we did a CMP steeringHi, \ so if A < steeringHi, jump to slim1 to return from \ the subroutine LDA steeringLo \ Set T = steeringLo with bit 0 cleared AND #%11111110 STA T LDA steeringHi \ Set A = steeringHi, so (A T) = (steeringHi steeringLo) \ with the sign bit in bit 0 cleared .slim1 RTS \ Return from the subroutine ENDIF
Name: SetPlayerDriftSup [Show more] Type: Subroutine Category: Car geometry Summary: Record player drift, but only if the player is not in the first three segments of a track section
Context: See this subroutine on its own page References: This subroutine is called as follows: * MovePlayerOnTrack calls SetPlayerDriftSup

This routine is only present in the Superior Software release. In the Acornsoft release, this routine consists of a single ROR playerDrift instruction, so the Superior Software version differs as follows: * Acornsoft sets the playerDrift flag if A >= 22 * Superior sets the playerDrift flag if A >= 22 and objSectionSegmt for the player is >= 3 So the Superior version does not record drift in the first three segments of a new track section.
IF _SUPERIOR OR _REVSPLUS .SetPlayerDriftSup BCC drif1 \ If the C flag is clear, jump to drif1 to skip the \ following LDA objSectionSegmt,X \ Set A = objSectionSegmt, which keeps track of the \ player's segment number in the current track section CMP #3 \ If A < 3, clear the C flag, if A >= 3, set the C \ flag .drif1 ROR playerDrift \ Store the C flag in bit 7 of playerDrift, so this will \ be set if the original A >= 22 and if the second \ A >= 3 RTS \ Return from the subroutine NOP \ These instruction is unused, and is included to \ pad out the code ENDIF
Name: DrawObject [Show more] Type: Subroutine Category: Drawing objects Summary: Draw an object of a specific type
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawCarOrSign calls DrawObject * DrawCornerMarkers calls DrawObject

This routine is used to draw objects such as road signs, corner markers and cars. Arguments: objectType The type of object to draw (0 to 12) scaleUp The scale factor for this object (i.e. its size) colourPalette The colour palette to use for drawing the object X Driver number: * 0-19 = map logical colour 1 according to the driver number in X: * Drivers 0, 4, 8, 12, 16 map to colour 0 (black) * Drivers 1, 5, 9, 13, 17 map to colour 1 (red) * Drivers 2, 6, 10, 14, 18 map to colour 2 (white) * Drivers 2, 7, 11, 15, 19 map to colour 3 (green) * 20-22 = this is the four-object car, which is the closest car in front of us, so map logical colour 1 according to the number of the driver in front of us (using the same logic as above) * 23 = stick with the palette in colourPalette Returns: colourPalette Gets reset back to the default palette
.DrawObject STX T \ Store the driver number in T \ We start by copying the four bytes from the standard \ colour palette in colourPalette to the object colour \ palette in objectPalette, as we use the latter to draw \ the object LDX #3 \ Set up a counter in X for copying the four palette \ bytes .dobj1 LDA colourPalette,X \ Copy the X-th byte of colourPalette to the X-th byte STA objectPalette,X \ of objectPalette DEX \ Decrement the loop counter BPL dobj1 \ Loop back until we have copied all four bytes LDA #%11110000 \ Map logical colour 2 in the colour palette to physical STA colourPalette+2 \ colour 1 (white in the track view), which sets it back \ to the default value \ \ This only has an effect when we call DrawObject from \ DrawCornerMarkers, which changes the value of colour 2 \ in colourPalette (all other calls to DrawObject leave \ the colour palette alone) \ We now set the palette differently, depending on the \ driver number in A: \ \ * 0-19 = map logical colour 1 to physical colour \ A mod 4 \ \ * 20-22 = map logical colour 1 to physical colour \ (number of the driver in front) mod 4 \ \ * 23 = don't change the palette, i.e use the palette \ from colourPalette LDA T \ Set A = T, so A contains the driver number CMP #23 \ If A = 23, jump to dobj3 to skip the following palette BEQ dobj3 \ changes CMP #20 \ If A < 20, jump to dobj2 to map logical colour 1 to BCC dobj2 \ physical colour A mod 4, in other words: \ \ * Drivers 0, 4, 8, 12, 16 map to colour 0 (black) \ * Drivers 1, 5, 9, 13, 17 map to colour 1 (red) \ * Drivers 2, 6, 10, 14, 18 map to colour 2 (white) \ * Drivers 2, 7, 11, 15, 19 map to colour 3 (green) \ If we get here then A is 20, 21 or 22, which is the \ four-object car, so we map logical colour 1 to the \ number of the driver in front of us, mod 4 LDX positionAhead \ Set X to the position of the driver in front of us LDA driversInOrder,X \ Set A the number of the driver in front of us, so we \ map logical colour 1 to physical colour A mod 4 .dobj2 AND #3 \ Set X = A mod 4 TAX LDA colourPalette,X \ Map logical colour 1 in the object palette to logical STA objectPalette+1 \ colour X from the colour palette .dobj3 LDX #0 \ Set scaleDown = 0, so the object's scaffold is not STX scaleDown \ scaled down (as 2^scaleDown = 2^0 = 1) LDA scaleUp \ Set A = scaleUp, so A contains the object size, which \ we can also interpret as the object distance CMP horizonTrackWidth \ If A >= horizonTrackWidth, then the object is closer BCS dobj4 \ than the horizon line, so jump to dobj4 to skip the \ following instruction and set lowestTrackLine to 0 (so \ the whole object is drawn) LDX horizonLine \ Otherwise the object is further away then the horizon \ line, so set X to the track line number of the \ horizon, so the parts of the object below this line do \ not get drawn (as they are below the horizon line, so \ presumably hidden by a hill) .dobj4 STX lowestTrackLine \ Set lowestTrackLine = X, so the object gets cut off at \ the horizon line when scaleUp < horizonTrackWidth CMP #64 \ If A >= 64, i.e. scaleUp >= 64, jump to dobj5 to skip BCS dobj5 \ the following \ Otherwise we can alter the values of scaleUp and \ scaleDown to be more accurate but without fear of \ overflow, by multiplying both scale factors by 4 \ (as we know 4 * scaleUp is < 256) ASL A \ Set scaleUp = A * 4 ASL A \ = scaleUp * 4 STA scaleUp LDA #2 \ Set scaleDown = 2, so the object's scaffold is scaled STA scaleDown \ down by 2^scaleDown = 2^2 = 4 \ \ So the overall scaling of the scaffold is the same, \ but we retain more accuracy .dobj5 LDX objectType \ Set X to the type of object we're going to draw \ If the object type is 10, 11 or 12, then it's one of \ the turn signs (chicane, left or right turn), so we \ draw this as two objects, starting with a blank white \ sign (object type 9) and then the sign contents \ (object 10, 11 or 12) \ \ We only draw the sign contents if our car is facing \ forwards, so the back of the sign is blank CPX #10 \ If X < 10, jump to dobj6 to skip the following BCC dobj6 \ instruction LDX #9 \ Set X = 9, so we first draw an object of type 9 for \ the blank white sign, before drawing another object of \ type objectType .dobj6 STX thisObjectType \ Store X in thisObjectType, so we can check it again \ below in case we need to draw two objects LDA scaffoldIndex,X \ Set QQ to the index of the first scaffold entry in STA QQ \ objectScaffold for object type X LDA scaffoldIndex+1,X \ Set II to the index of the first scaffold entry in STA II \ objectScaffold for object type X + 1 (so the last \ entry for object type X will be index II - 1) LDA objectIndex,X \ Set QQ to the index of the first entry in the object STA MM \ data tables for object type X (so MM will point to the \ first entry for this object in the objectTop, \ objectBottom, objectLeft, objectRight and objectColour \ tables) JSR ScaleObject \ Scale the object's scaffold by the scaleUp and \ scaleDown factors, storing the results in the \ scaledScaffold table BCS dobj7 \ If the call to ScaleObject set the C flag then the \ scaling process overflowed, in which case we do not \ draw the object, so jump to dobj7 to return from the \ subroutine JSR DrawObjectEdges \ Draw the scaled object in the screen buffer by drawing \ all the object's edges LDX objectType \ Set X to the type of object we are drawing, in case we \ need to draw a second object LDA thisObjectType \ If the object we just drew is not an object of type 9, CMP #9 \ then this is not a two-part road sign object, so jump BNE dobj7 \ to dobj7 to return from the subroutine \ Otherwise we just drew an object of type 9, for the \ blank white sign, so now we draw a second object for \ the sign's contents, but only if our car is facing \ forwards (if we are facing backwards, then we see the \ back of the sign, which is blank) LDA directionFacing \ If bit 7 of directionFacing is clear, then our car is BPL dobj6 \ facing fowards, so loop back to draw the contents of \ the sign in object type objectType .dobj7 RTS \ Return from the subroutine
Name: ScaleObject [Show more] Type: Subroutine Category: Drawing objects Summary: Scale an object's scaffold by the scale factors in scaleUp and scaleDown Deep dive: Scaling objects with scaffolds
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawObject calls ScaleObject

This routine is used when drawing objects such as road signs, corner markers and cars. It takes the values from the objectScaffold table, which contain an object's scaffold (i.e. all the essential measurements that we need to build the object), and scales them according to the values of scaleUp and scaleDown. As only scaffold measurements are used when drawing an object, this routine scales the whole object, according to the two scale factors. The value in scaleUp is the numerator of the scale factor, which scales the scaffold up, so bigger values of scaleUp give bigger objects. The value in scaleDown is the denominator of the scale factor, which scales the scaffold down, so bigger values of scaleDown give smaller objects. Arguments: QQ Index of the first objectScaffold entry for this object II Index of the last objectScaffold entry for this object (where the last entry is index II - 1) scaleUp Numerator scale factor scaleDown Denominator scale factor Returns: C flag Denotes whether the scaling was successful: * Clear if we manage to scale the scaffold * Set if the scaling of any individual scaffold measurements overflows, in which case we do not draw the object scaledScaffold The scaled scaffold scaledScaffold+8 The scaled scaffold, with each measurement negated
.ScaleObject LDA scaleUp \ Set scaleRange = scaleUp STA scaleRange LSR A \ Set scaleRange+1 = scaleUp >> 1 STA scaleRange+1 \ = scaleUp / 2 LSR A \ Set scaleRange+2 = scaleUp >> 2 STA scaleRange+2 \ = scaleUp / 4 LSR A \ Set scaleRange+3 = scaleUp >> 3 STA scaleRange+3 \ = scaleUp / 8 LSR A \ Set scaleRange+4 = scaleUp >> 4 STA scaleRange+4 \ = scaleUp / 16 LSR A \ Set scaleRange+5 = scaleUp >> 5 STA scaleRange+5 \ = scaleUp / 32 \ So scaleRange + n contains scaleUp / 2^n LDY QQ \ We now loop through the objectScaffold table from \ entry QQ to entry II - 1, so set a loop counter in Y \ to act as an index LDX #0 \ Set W = 0, to be used as an index as we populate the STX W \ scaledScaffold table, incrementing by one byte for \ each loop .prep1 LDA objectScaffold,Y \ Set A to the Y-th scaffold measurement BPL prep2 \ If bit 7 of A is clear, jump to prep2 to do the \ calculation that only uses bits 0-2 of A \ If we get here, bit 7 of A is set, so now we do the \ following calculation, where the value of A from the \ objectScaffold table is %1abbbccc: \ \ A = a * scaleUp/2 + scaleUp/2^b-2 + scaleUp/2^c-2 \ --------------------------------------------- \ 2^scaleDown \ \ = scaleUp * (a/2 + 1/2^b-2 + 1/2^c-2) \ ----------------------------------- \ 2^scaleDown \ \ scaleUp \ = ----------- * (a/2 + 1/2^b-2 + 1/2^c-2) \ 2^scaleDown \ \ scaleUp \ = ----------- * scaffold \ 2^scaleDown \ \ We then store this as the next entry in scaledScaffold \ \ Note that b and c are always in the range 3 to 7, so \ they look up the values we stored in scaleRange above AND #%00000111 \ Set X = bits 0-2 of A TAX \ = %ccc \ = c LDA scaleRange-2,X \ Set T = entry X-2 in scaleRange STA T \ = scaleUp / 2^X-2 \ = scaleUp / 2^c-2 LDA objectScaffold,Y \ Set A to the Y-th scaffold measurement STA U LSR A \ Set X = bits 3-5 of A LSR A \ = %bbb LSR A \ = b AND #%00000111 TAX LDA scaleRange-2,X \ Set A = entry X-2 in scaleRange + T CLC \ = scaleUp / 2^X-2 + scaleUp / 2^c-2 ADC T \ = scaleUp / 2^b-2 + scaleUp / 2^c-2 BIT U \ If bit 6 of U is clear, jump to prep3 BVC prep3 CLC \ If bit 6 of U is set: ADC scaleRange+1 \ \ A = A + scaleRange+1 \ = A + scaleUp / 2 JMP prep3 \ Jump to prep3 .prep2 \ If we get here, bit 7 of the Y-th objectScaffold is \ clear, so we do the following calculation, where \ A is %00000ccc: \ \ A = scaleUp / 2^c-2 \ --------------- \ 2^scaleDown \ \ = scaleUp * 1/2^c-2 \ ----------------- \ 2^scaleDown \ \ = scaleUp \ ----------- * 1/2^c-2 \ 2^scaleDown \ \ scaleUp \ = ----------- * scaffold \ 2^scaleDown \ \ We then store this as the next entry in scaledScaffold TAX \ Set A = entry c-2 in scaleRange LDA scaleRange-2,X \ = scaleUp / 2^c-2 .prep3 LDX scaleDown \ If scaleDown = 0 then the scale factor is 2^scaleDown BEQ prep5 \ = 2^0 = 1, so jump to prep5 to skip the division \ We now shift A right by X places, which is the same as \ dividing by 2^X = 2^scaleDown .prep4 LSR A \ Set A = A >> 1 DEX \ Decrement the shift counter BNE prep4 \ Loop back until we have shifted A right by X places, \ and the C flag contains the last bit shifted out from \ bit 0 of A ADC #0 \ Set A = A + C to round the result of the division to \ the nearest integer .prep5 LDX W \ Set X to W, the index into the tables we are building STA scaledScaffold,X \ Store A in the X-th byte of scaledScaffold EOR #&FF \ Set A = ~A BPL prep6 \ If bit 7 of A is clear, i.e. it was set before the \ EOR, then the result of the scaling was >= 128, which \ is an overflow of the scaling \ \ If the scaling overflows, then the object is too big \ to be drawn, so we jump to prep6 to return from the \ subroutine with the C flag set, so we do not draw this \ object and ignore all the values calculated here CLC \ Store -A in the X-th byte of scaledScaffold+8 ADC #1 STA scaledScaffold+8,X INC W \ Increment the index counter INY \ Increment the loop counter CPY II \ Loop back until Y has looped through QQ to II - 1 BNE prep1 CLC \ Clear the C flag to indicate a successful scaling RTS \ Return from the subroutine .prep6 SEC \ Set the C flag to indicate that scaling overflowed and \ the object should not be drawn RTS \ Return from the subroutine
Name: DrawObjectEdges [Show more] Type: Subroutine Category: Drawing objects Summary: Draw all the parts of an object by drawing edges into the screen buffer Deep dive: Creating objects from edges
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawObject calls DrawObjectEdges

This routine is used to draw road signs, corner markers and cars. They are drawn as edges - specifically the left and right edges - into the screen buffer in the dash data blocks. Arguments: MM The index of the first entry in the object data tables for this this object (i.e. the index of the data for the object's first part) xPixelCoord The pixel x-coordinate of the centre of the object yPixelCoord The object's y-coordinate (for the centre of the object) in terms of track lines, so 80 is the top of the track view and 0 is the bottom of the track view lowestTrackLine Hide any part of the object that's below the specified track line (typically used to stop an object from being drawn below the horizon) * 0 = draw the whole object * Non-zero = only draw the part of the object that's above this track line
.DrawObjectEdges LDY MM \ Set Y to the index of this object data in the object \ data tables \ We now work our way through the data for this object, \ drawing one part at a time, using thisObjectIndex and \ Y as the loop counter as we loop through each part \ \ Note that most object parts are defined by one set of \ object data, so they correspond to two edges (left and \ right), but object types 2 and 4 contain four-edge \ object parts, which are defined by two sets of data, \ and therefore two loop iterations .drob1 LDA colourPalette \ Set rightOfEdge to logical colour 0 in the standard STA rightOfEdge \ colour palette, so the fill colour to the left of the \ first edge is set to a default of black when we call \ DrawObjectEdge below LDA #0 \ Set prevEdgeInByte = 0 to indicate that the first edge STA prevEdgeInByte \ is not sharing a pixel byte with the previous edge (as \ there isn't a previous edge) STA edgePixelMask \ Set edgePixelMask = 0 to pass to DrawObjectEdge below \ as there is no previous edge, so there should be no \ mask for the previous edge in the same pixel byte LDX objectTop,Y \ Set A to the scaled scaffold for the top of this part LDA scaledScaffold,X \ of the object CLC \ Set A = A + yPixelCoord ADC yPixelCoord \ \ so A is now the track line of the top of the object BMI drob9 \ If A > 128, then the top of this object part is well \ above the track view, so jump to drob9 to move on to \ the next object part as this one doesn't fit on-screen CMP #80 \ If A >= 80, set A = 79, as the maximum track line at BCC drob2 \ the very top of the track view is 79 LDA #79 .drob2 STA topTrackLine \ Store A in N as the number of the top track line, to \ send to DrawObjectEdge below LDX objectBottom,Y \ Set A to the scaled scaffold for the bottom of this LDA scaledScaffold,X \ part of the object CLC \ Set A = A + yPixelCoord ADC yPixelCoord \ \ so A is now the track line of the bottom of the object BMI drob3 \ If A < 0, then the bottom of this object part is lower \ than the bottom of the track view, so jump to drob3 to \ set A = lowestTrackLine, so we only draw the object \ down to the lowest line allowed CMP lowestTrackLine \ If A >= lowestTrackLine, jump to drob4 to skip the BCS drob4 \ following .drob3 \ If we get here then either the bottom track line in A \ is negative or A < lowestTrackLine, both of which are \ below the lowest level that we want to draw, so we \ cut off the bottom of the object to fit LDA lowestTrackLine \ Set A = lowestTrackLine, so the minimum track line \ number is set to lowestTrackLine and we only draw the \ objectdown to the lowest line allowed NOP \ These instructions have no effect - presumably they NOP \ are left over from changes during development .drob4 CMP topTrackLine \ If A >= N, then the bottom track line for this object BCS drob9 \ in A is higher than the top track line in N, so jump \ to drob9 to move on to the next object part as there \ is nothing to draw for this part \ We now set up the parameters to pass to DrawObjectEdge \ below, to draw the left and right edges STA bottomTrackLine \ Set bottomTrackLine = A as the bottom track line LDX objectLeft,Y \ Set thisEdge to the scaled scaffold for the left edge LDA scaledScaffold,X \ of this part of the object, to pass to DrawObjectEdge STA thisEdge LDX objectRight,Y \ Set nextEdge to the scaled scaffold for the right LDA scaledScaffold,X \ edge of this part of the object, to pass to STA nextEdge \ DrawObjectEdge LDA objectColour,Y \ Set A to the colour data for this object part STA colourData \ Set colourData to the colour data for this object part STY thisObjectIndex \ Store the current index into the object data in \ thisObjectIndex LDY #1 \ Draw the left edge of this object part JSR DrawObjectEdge .drob5 BIT colourData \ If bit 7 is set in the colour data for this object BMI drob10 \ part, then this is a four-edge object part, so \ jump to drob10 to draw the extra two edges before \ returning here (with bit 7 of colourData clear) to \ draw the fourth edge LDA #0 \ Set A = 0 to send to DrawObjectEdge as the fill colour \ to the right, as there is no fill to the right of the \ object LDY #2 \ Draw the right edge of this object part JSR DrawObjectEdge BIT colourData \ If bit 6 is set in the colour data for this object BVS drob7 \ part, then this indicates that this is the last part \ of this object, so jump to drob7 to return from the \ subroutine as we have now drawn the whole object LDY thisObjectIndex \ Otherwise we need to move on to the next part, so set \ Y to the loop counter .drob6 INY \ Increment the loop counter to point to the data for \ the next object part JMP drob1 \ Loop back to drob1 to process the next object part .drob7 RTS \ Return from the subroutine .drob8 \ We get here when we come across data that forms the \ second and third stages of a four-edge object part, \ so we now need to skip that data as we have already \ processed it AND #%01000000 \ If bit 6 of A is set, i.e. 64 + x, jump to drob7 to BNE drob7 \ return from the subroutine, as we have just drawn the \ last part of the object we wanted to draw INY \ Increment the loop counter to point to the data for \ the next object part .drob9 LDA objectColour,Y \ Set A to the colour data for this object part BMI drob8 \ If bit 7 of A is set, i.e. 128 + x, jump to drob8 to \ skip this bit of data and move on to the next, as this \ contains the data for the second and third edges of a \ four-edge object part, and this will already have \ been processed in drob10 AND #%01000000 \ If bit 6 of A is set, i.e. 64 + x, jump to drob7 to BNE drob7 \ return from the subroutine, as we have just drawn the \ last part of the object we wanted to draw BEQ drob6 \ Jump to drob6 to move on to the next object part (this \ BEQ is effectively a JMP as we just passed through a \ BNE) .drob10 \ If we get here then the colour data for this object \ part has bit 7 set, so this is a four-edge object \ part and we need to draw the second and third edges \ \ The second and third edges are defined in the next bit \ of object data, as follows: \ \ * Second edge: nextEdge = objectLeft \ colourData = objectRight \ \ * Third edge: nextEdge = objectTop \ colourData = objectColour LDY thisObjectIndex \ Set Y to the loop counter INY \ Increment the loop counter to point to the next bit of STY thisObjectIndex \ object data (which contains the data for the second \ and third edges) LDX objectLeft,Y \ Set nextEdge to the scaled data from objectLeft for LDA scaledScaffold,X \ this object part, to pass to DrawObjectEdge STA nextEdge LDA objectRight,Y \ Set colourData to the data from objectRight for this STA colourData \ object part, to pass to DrawObjectEdge LDY #0 \ Draw the second edge of the four-edge object part JSR DrawObjectEdge LDY thisObjectIndex \ Set Y to the index into the object data LDX objectTop,Y \ Set nextEdge to the scaled data from objectTop for LDA scaledScaffold,X \ this object part, to pass to DrawObjectEdge STA nextEdge LDA objectColour,Y \ Set colourData to the data from objectColour for this STA colourData \ object part, to pass to DrawObjectEdge LDY #0 \ Draw the third edge of the four-edge object part JSR DrawObjectEdge JMP drob5 \ Loop back to drob5 to draw the fourth edge, with \ colourData set to the colour data from the third edge, \ which does not have bit 7 set
Name: GetObjYawAngle (Part 1 of 4) [Show more] Type: Subroutine Category: 3D objects Summary: Calculate an object's yaw angle Deep dive: Pitch and yaw angles
Context: See this subroutine on its own page References: This subroutine is called as follows: * BuildRoadSign calls GetObjYawAngle * GetObjectAngles calls entry point GetObjYawAngle-2 * GetSectionAngles (Part 3 of 3) calls entry point GetObjYawAngle-2 * GetSegmentYawAngle calls entry point GetObjYawAngle-2

Arguments: X The offset of the variable to use for the object's 3D coordinates * &F4 = xHelmetCoord * &FA = xCoord1 * &FD = xCoord2 Y The offset of the second variable to use: * 0 = xPlayerCoord * 6 = xRoadSignCoord Returns: (JJ II) The yaw angle of the object (J I) max(|x-delta|, |z-delta|) (H G) min(|x-delta|, |z-delta|) M The smaller yaw angle of the object, where 0 to 255 represents 0 to 45 degrees X X is preserved Other entry points: GetObjYawAngle-2 Use xPlayerCoord (Y = 0)
LDY #0 \ Use xPlayerCoord for the second variable when calling \ the routine via GetObjYawAngle-2 .GetObjYawAngle \ The vectors used in this routine are configured by the \ values of X and Y, but for the purposes of simplicity, \ the comments will assume the following: \ \ * X = &FD, xCoord2 \ \ * Y = 0, xPlayerCoord LDA xSegmentCoordILo,X \ Set (VV PP) = xCoord2 - xPlayerCoord SEC \ SBC xPlayerCoordHi,Y \ starting with the low bytes STA PP LDA xSegmentCoordIHi,X \ And then the high bytes SBC xPlayerCoordTop,Y STA VV \ Let's call this difference in x-coordinates x-delta, \ so: \ \ (VV PP) = x-delta BPL rotn1 \ If (VV PP) is positive, jump to rotn1 to skip the \ following LDA #0 \ Set (VV PP) = 0 - (VV PP) SEC \ SBC PP \ starting with the low bytes STA PP LDA #0 \ And then the high bytes SBC VV \ So (VV PP) is now positive, in other words: \ \ (VV PP) = |x-delta| .rotn1 STA SS \ Set (SS PP) = (VV PP) \ = |x-delta| LDA zSegmentCoordILo,X \ Set (GG RR) = zCoord2 - zPlayerCoord SEC \ SBC zPlayerCoordHi,Y \ starting with the low bytes STA RR LDA zSegmentCoordIHi,X \ And then the high bytes SBC zPlayerCoordTop,Y STA GG \ Let's call this difference in z-coordinates z-delta, \ so: \ \ (GG RR) = z-delta BPL rotn2 \ If (GG RR) is positive, jump to rotn2 to skip the \ following LDA #0 \ Set (GG RR) = 0 - (GG RR) SEC \ SBC RR \ starting with the low bytes STA RR LDA #0 \ And then the high bytes SBC GG \ So (GG RR) is now positive, in other words: \ \ (GG RR) = |z-delta| .rotn2 STA UU \ Set (UU RR) = (GG RR) \ = |z-delta| \ At this point we have the following: \ \ (SS PP) = |x-delta| \ \ (UU RR) = |z-delta| \ \ We now compare these two 16-bit values, starting with \ the high bytes, and then the low bytes (if the high \ bytes are the same) CMP SS \ If UU < SS, then (UU RR) < (SS PP), so jump to rotn3 BCC rotn3 BNE rotn4 \ If UU <> SS, i.e. UU > SS, then (UU RR) > (SS PP), so \ jump to rotn4 with the C flag clear \ The high bytes are equal, so now we compare the low \ bytes LDA RR \ If RR >= PP, then (UU RR) >= (SS PP), so jump to rotn4 CMP PP \ with the C flag set BCS rotn4 \ Otherwise (UU RR) < (SS PP), so fall through into \ rotn3 .rotn3 \ If we get here then (UU RR) < (SS PP), so: \ \ |z-delta| < |x-delta| LDA UU \ Set (H G) = (UU RR) STA H \ = |z-delta| LDA RR \ STA G \ and (H G) contains the smaller value LDA PP \ Set (J I) = (SS PP) STA I \ = |x-delta| LDA SS \ STA J \ and (J I) contains the larger value JMP rotn6 \ Jump to rotn6 .rotn4 \ If we get here then (UU RR) >= (SS PP), so: \ \ |z-delta| >= |x-delta| PHP \ Store the status flags on the stack, and in particular \ the Z flag, which which will be set if the two match, \ i.e. if |z-delta| = |x-delta| \ \ In other words, a BEQ would branch with these flags LDA SS \ Set (H G) = (SS PP) STA H \ = |x-delta| LDA PP \ STA G \ and (H G) contains the smaller value LDA RR \ Set (J I) = (UU RR) STA I \ = |z-delta| LDA UU \ STA J \ and (J I) contains the larger value PLP \ Retrieve the status flags we stored above BEQ rotn9 \ If (UU RR) = (SS PP), jump to rotn9 JMP rotn14 \ Jump to rotn14
Name: GetObjYawAngle (Part 2 of 4) [Show more] Type: Subroutine Category: 3D objects Summary: Calculate yaw angle for when |x-delta| > |z-delta| Deep dive: Pitch and yaw angles
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.rotn5 \ This part is called from below, if we want to scale \ the division ASL RR \ Set (UU RR) = (UU RR) << 1 ROL UU .rotn6 \ If we get here, then: \ \ * (J I) = (A PP) = |x-delta| \ \ * VV is the high byte of x-delta \ \ * (H G) = (UU RR) = |z-delta| \ \ * GG is the high byte of z-delta \ \ * |x-delta| > |z-delta| \ \ We now do the following division so we can use \ trigonometry to calculate the yaw angle: \ \ |z-delta| / |x-delta| \ \ To get started, we shift both 16-bit values to the \ left as far as possible, which we can do without \ affecting the result as we are going to divide the two \ values, so any mutual shifts will cancel each other \ out in the division \ \ Once that's done, we can drop the low bytes and just \ divide the high bytes, which retains as much accuracy \ as possible while avoiding the need for full 16-bit \ division \ \ So we keep shifting left until we get a 1 in bit 7 of \ (A PP), as that's the larger of the two values ASL PP \ Set (A PP) = (A PP) << 1 ROL A BCC rotn5 \ If we just shifted a 0 out of the high byte of (A PP), \ then we can keep shifting, so loop back to rotn6 to \ keep shifting both values ROR A \ We just shifted a 1 out of bit 7 of A, so reverse the \ shift so A contains the correct high byte (we don't \ care about the low byte any more) \ So by this point, (A PP) and (UU RR) have both been \ scaled by the same number of shifts STA V \ Set V = A, the high byte of the scaled |x-delta| LDA RR \ Set T = RR, the low byte of the scaled |z-delta|, to STA T \ use for rounding the result in Divide8x8 LDA UU \ Set A = UU, the high byte of the scaled |z-delta| CMP V \ If A = V then the high bytes of the scaled values BEQ rotn9 \ match, so jump to rotn9, which deals with the case \ when the xVector and zVector values are equal \ We have scaled both values, so now for the division of \ the high bytes JSR Divide8x8 \ Set T = 256 * A / V \ = 256 * |z-delta| / |x-delta| \ \ using the lower byte of the |z-delta| numerator for \ rounding LDA #0 \ Set II = 0 to use as the low byte for the final yaw STA II \ angle LDY T \ Set A = arctanY(T) LDA arctanY,Y \ = arctanY(|z-delta| / |x-delta|) \ \ So this is the yaw angle of the object STA M \ Store the yaw angle in M, to return from the \ subroutine LSR A \ Set (JJ II) = (A 0) >> 3 ROR II \ = A * 256 / 8 LSR A \ = A * 32 ROR II \ = arctanY(|z-delta| / |x-delta|) * 32 LSR A ROR II STA JJ LDA VV \ If VV and GG have different signs, then so do x-delta EOR GG \ and z-delta, so jump to rotn7 BMI rotn7 LDA #0 \ Negate (JJ II) SEC \ SBC II \ starting with the low bytes STA II LDA #0 \ And then the high bytes SBC JJ STA JJ .rotn7 LDA #64 \ Set A = 64, to add to the high byte below BIT VV \ If x-delta is positive, jump to rotn8 to skip the BPL rotn8 \ following instruction \ If we get here then x-delta is negative LDA #&C0 \ Set A = -64, to add to the high byte below .rotn8 CLC \ Set (JJ II) = (JJ II) + (A 0) ADC JJ \ STA JJ \ which is one of the following: \ \ (JJ II) = (JJ II) + 64 * 256 \ \ (JJ II) = (JJ II) - 64 * 256 \ \ depending on the sign of x-delta RTS \ Return from the subroutine
Name: GetObjYawAngle (Part 3 of 4) [Show more] Type: Subroutine Category: 3D objects Summary: Calculate yaw angle for when |x-delta| = |z-delta| Deep dive: Pitch and yaw angles
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.rotn9 \ If we get here, then: \ \ * VV is the high byte of x-delta \ \ * GG is the high byte of z-delta \ \ * |x-delta| = |z-delta| LDA #255 \ Set M = 255, to represent a yaw angle of 45 degrees STA M LDA #0 \ Set II = 0 to use as the low byte for the final yaw STA II \ angle BIT VV \ If x-delta is positive, jump to rotn11 BPL rotn11 \ If we get here then x-delta is negative BIT GG \ If z-delta is positive, jump to rotn10 BPL rotn10 \ If we get here then both x-delta and z-delta are \ negative LDA #&A0 \ Set (JJ II) = -96 * 256 STA JJ RTS \ Return from the subroutine .rotn10 \ If we get here then x-delta is negative and y-delta \ is positive LDA #&E0 \ Set (JJ II) = -32 * 256 STA JJ RTS \ Return from the subroutine .rotn11 \ If we get here then x-delta is positive BIT GG \ If z-delta is positive, jump to rotn12 BPL rotn12 \ If we get here then x-delta is positive and y-delta \ is negative LDA #&60 \ Set (JJ II) = 96 * 256 STA JJ RTS \ Return from the subroutine .rotn12 \ If we get here then both x-delta and z-delta are \ positive LDA #&20 \ Set (JJ II) = 32 * 256 STA JJ RTS \ Return from the subroutine
Name: GetObjYawAngle (Part 4 of 4) [Show more] Type: Subroutine Category: 3D objects Summary: Calculate yaw angle for when |x-delta| < |z-delta| Deep dive: Pitch and yaw angles
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.rotn13 \ This part is called from below, if we want to scale \ the division ASL PP \ Set (SS PP) = (SS PP) << 1 ROL SS .rotn14 \ If we get here, then: \ \ * (H G) = (SS PP) = |x-delta| \ \ * VV is the high byte of x-delta \ \ * (J I) = (A RR) = |z-delta| \ \ * GG is the high byte of z-delta \ \ * |x-delta| < |z-delta| \ \ We now do the following division so we can use \ trigonometry to calculate the yaw angle: \ \ |x-delta| / |z-delta| \ \ To get started, we shift both 16-bit values to the \ left as far as possible, which we can do without \ affecting the result as we are going to divide the two \ values, so any mutual shifts will cancel each other \ out in the division \ \ Once that's done, we can drop the low bytes and just \ divide the high bytes, which retains as much accuracy \ as possible while avoiding the need for full 16-bit \ division \ \ So we keep shifting left until we get a 1 in bit 7 of \ (A RR), as that's the larger of the two values ASL RR \ Set (A RR) = (A RR) << 1 ROL A BCC rotn13 \ If we just shifted a 0 out of the high byte of (A RR), \ then we can keep shifting, so loop back to rotn13 to \ keep shifting both values ROR A \ We just shifted a 1 out of bit 7 of A, so reverse the \ shift so A contains the correct high byte (we don't \ care about the low byte any more) \ So by this point, (A RR) and (SS PP) have both been \ scaled by the same number of shifts STA V \ Set V = A, the high byte of the scaled |z-delta| LDA PP \ Set T = PP, the low byte of the scaled |x-delta|, to STA T \ use for rounding the result in Divide8x8 LDA SS \ Set A = SS, the high byte of the scaled |x-delta| CMP V \ If A = V then the high bytes of the scaled values BEQ rotn9 \ match, so jump to rotn9, which deals with the case \ when the xVector and zVector values are equal \ We have scaled both values, so now for the division of \ the high bytes JSR Divide8x8 \ Set T = 256 * A / V \ = 256 * |x-delta| / |z-delta| \ \ using the lower byte of the |x-delta| numerator for \ rounding LDA #0 \ Set II = 0 to use as the low byte for the final yaw STA II \ angle LDY T \ Set A = arctanY(T) LDA arctanY,Y \ = arctanY(|x-delta| / |z-delta|) \ \ So this is the yaw angle of the object STA M \ Store the yaw angle in M, to return from the \ subroutine LSR A \ Set (JJ II) = (A 0) >> 3 ROR II \ = A * 256 / 8 LSR A \ = A * 32 ROR II \ = arctanY(|x-delta| / |z-delta|) * 32 LSR A ROR II STA JJ LDA VV \ If VV and GG have different signs, then so do x-delta EOR GG \ and z-delta, so jump to rotn15 BPL rotn15 LDA #0 \ Negate (JJ II) SEC \ SBC II \ starting with the low bytes STA II LDA #0 \ And then the high bytes SBC JJ STA JJ .rotn15 LDA #0 \ Set A = 0, to add to the high byte below BIT GG \ If z-delta is positive, jump to rotn16 to skip the BPL rotn16 \ following instruction \ If we get here then z-delta is negative LDA #&80 \ Set A = -128, to add to the high byte below .rotn16 CLC \ Set (JJ II) = (JJ II) + (A 0) ADC JJ \ STA JJ \ which is one of the following: \ \ (JJ II) = (JJ II) \ \ (JJ II) = (JJ II) - 128 * 256 \ \ depending on the sign of z-delta RTS \ Return from the subroutine
Name: GetObjPitchAngle [Show more] Type: Subroutine Category: 3D objects Summary: Calculate an object's pitch angle Deep dive: Pitch and yaw angles
Context: See this subroutine on its own page References: This subroutine is called as follows: * BuildRoadSign calls GetObjPitchAngle * GetObjectAngles calls entry point GetObjPitchAngle-2 * GetSectionAngles (Part 3 of 3) calls entry point GetObjPitchAngle-2 * GetSegmentAngles (Part 1 of 3) calls entry point GetObjPitchAngle-2 * GetSegmentAngles (Part 2 of 3) calls entry point GetObjPitchAngle-2

Arguments: X The offset of the variable to use for the object's 3D coordinates * &F4 = yHelmetCoord * &FA = yCoord1 * &FD = yCoord2 Y The offset of the second variable to use: * 0 = yPlayerCoord * 6 = yRoadSignCoord (L K) The result from GetObjectDistance, which is called between GetObjYawAngle and GetObjPitchAngle Returns: LL The pitch angle of the object A The pitch angle of the object (same as LL) scaleUp The scale up factor for the object scaleDown The scale down factor for the object C flag Is the object visible on-screen: * Clear if the object is on-screen * Set if it isn't on-screen N flag Set according to the y-coordinate, so a BPL following the call will branch if the y-coordinate is positive Other entry points: GetObjPitchAngle-2 Use yPlayerCoord (Y = 0)
LDY #0 \ Use xPlayerCoord for the second variable when calling \ the routine via GetObjPitchAngle-2 .GetObjPitchAngle \ The vectors used in this routine are configured by the \ values of X and Y, but for the purposes of simplicity, \ the comments will assume the following: \ \ * X = &FD, yCoord2 \ \ * Y = 0, yPlayerCoord LDA ySegmentCoordILo,X \ Set (WW QQ) = yCoord2 - yPlayerCoord SEC \ SBC yPlayerCoordHi,Y \ starting with the low bytes STA QQ LDA ySegmentCoordIHi,X \ And then the high bytes SBC yPlayerCoordTop,Y STA WW \ Let's call this difference in y-coordinates y-delta, \ so: \ \ (WW QQ) = (A QQ) = y-delta BPL pang1 \ If (A QQ) is positive, jump to pang1 to skip the \ following LDA #0 \ Set (A QQ) = 0 - (WW QQ) SEC \ SBC QQ \ starting with the low bytes STA QQ LDA #0 \ And then the high bytes SBC WW \ So (A QQ) is now positive, in other words: \ \ (A QQ) = |y-delta| .pang1 LSR A \ Set (A QQ) = (A QQ) >> 3 ROR QQ \ = |y-delta| / 8 LSR A ROR QQ LSR A ROR QQ STA TT \ Set (TT QQ) = (A QQ) \ = |y-delta| / 8 \ We now compare the two 16-bit values in (A QQ) and \ (L K) CMP L \ If A < L, then (A QQ) < (L K), so jump to pang3 BCC pang3 BNE pang2 \ If A <> L, i.e. A > L, then (A QQ) > (L K), so jump \ to pang2 to return from the subroutine with the C flag \ set \ The high bytes are equal, so now we compare the low \ bytes LDA QQ \ If QQ < K, then (A QQ) < (L K), so jump to pang3 CMP K BCC pang3 .pang2 \ If we get here then (A QQ) >= (L K), so: \ \ |y-delta| / 8 >= (L K) SEC \ Set the C flag RTS \ Return from the subroutine .pang3 LDY #0 \ Set Y = 0, which we use to count the number of shifts \ in the following calculation LDA L \ Set (A K) = (L K) JMP pang5 \ Jump to pang5 .pang4 \ This part is called from below, if we want to scale \ the division ASL QQ \ Set (TT QQ) = (TT QQ) << 1 ROL TT INY \ Increment Y .pang5 \ If we get here, then: \ \ * (TT QQ) = |y-delta| / 8 \ \ * WW is the high byte of y-delta \ \ * (A K) = |x-delta| \ \ * |x-delta| > |y-delta| / 8 \ \ * Y = 0 \ \ We now do the following division so we can use \ trigonometry to calculate the pitch angle: \ \ (|y-delta| / 8) / |x-delta| \ \ To get started, we shift both 16-bit values to the \ left as far as possible, which we can do without \ affecting the result as we are going to divide the two \ values, so any mutual shifts will cancel each other \ out in the division \ \ We count the number of shifts we do in Y \ \ Once that's done, we can drop the low bytes and just \ divide the high bytes, which retains as much accuracy \ as possible while avoiding the need for full 16-bit \ division \ \ So we keep shifting left until we get a 1 in bit 7 of \ (A K), as that's the larger of the two values ASL K \ Set (A K) = (A K) << 1 ROL A BCC pang4 \ If we just shifted a 0 out of the high byte of (A K), \ then we can keep shifting, so loop back to rotn6 to \ keep shifting both values ROR A \ We just shifted a 1 out of bit 7 of A, so reverse the \ shift so A contains the correct high byte (we don't \ care about the low byte any more) \ So by this point, (A K) and (TT QQ) have both been \ scaled by the same number of shifts STA V \ Set V = A, the high byte of the scaled |x-delta|, \ which we know is at least 128 (as bit 7 is set) STY scaleDown \ Set scaleDown to the number of shifts in Y TAY \ Set scaleUp = arctanP(A) LDA arctanP-128,Y \ = arctanP(|x-delta|) STA scaleUp LDA QQ \ Set T = QQ, the low byte of the scaled |y-delta|, to STA T \ use for rounding the result in Divide8x8 LDA TT \ Set A = TT, the high byte of the scaled |y-delta| JSR Divide8x8 \ Set T = 256 * A / V \ = 256 * (|y-delta| / 8) / |x-delta| \ \ using the lower byte of the |y-delta| numerator for \ rounding LDA T \ If T >= 128, jump to pang8 to return from the CMP #128 \ subroutine with the C flag set BCS pang8 BIT WW \ If y-delta is positive, jump to pang6 to skip the BPL pang6 \ following and add 60 to T LDA #60 \ Set A = 60 - T SEC SBC T JMP pang7 \ Jump to pang7 .pang6 CLC \ Set A = T + 60 ADC #60 .pang7 SEC \ Set LL = A - playerPitchAngle SBC playerPitchAngle STA LL CLC \ Clear the C flag to indicate success .pang8 RTS \ Return from the subroutine
Name: GetSectionAngles (Part 1 of 3) [Show more] Type: Subroutine Category: Track geometry Summary: Get the yaw and pitch angles for the inner and outer track sections Deep dive: Data structures for the track calculations The track verges
Context: See this subroutine on its own page References: This subroutine is called as follows: * GetTrackAndMarkers calls GetSectionAngles * GetSectionAngles (Part 1 of 3) calls entry point GetSectionAngles-1

This routine does the following: * Part 1: If we have fetched a new track section since the last call, shuffle the track section list along by one so we can insert the new section * Part 1: Go through the track section list and apply spin to each valid entry (for both the right and left track section), skipping the entry pointed to by the sectionListPointer * Update the entry at sectionListPointer as follows: * Part 2: Calculate the track section number for this entry, relative to the front segment in the track segment buffer * Part 3: Store the yaw and pitch angles for this section in the xVergeRight/Left and yVergeRight/Left tables Returns: xVergeRight Updated yaw angles for the entries in the track section list (i.e. indexes 0 to 5) for the right verge xVergeLeft Updated yaw angles for the entries in the track section list (i.e. indexes 0 to 5) for the left verge yVergeRight Updated pitch angles for the entries in the track section list (i.e. indexes 0 to 5) for the right verge yVergeLeft Updated pitch angles for the entries in the track section list (i.e. indexes 0 to 5) for the left verge horizonLine Updated to cater for the pitch angles of the updated track sections horizonListIndex Updated to the index of the track section that contains the horizon (i.e. the index within the track section list) Other entry points: GetSectionAngles-1 Contains an RTS
.GetSectionAngles LDA newSectionFetched \ If newSectionFetched = 0, then we have not fetched a BEQ gsec1 \ new track section since the last call, so jump to \ gsec1 to skip the following call to ShuffleSectionList JSR ShuffleSectionList \ Shuffle the track section list along by one so we can \ insert the new section, updating sectionListValid and \ sectionListPointer accordingly LDA #0 \ Reset newSectionFetched to 0 so we don't call the STA newSectionFetched \ ShuffleSectionList routine again until the next new \ section has been fetched .gsec1 LDY sectionListStart \ If sectionListStart = 6, then the track section list CPY #6 \ is zero-length, so return from the subroutine (as BEQ GetSectionAngles-1 \ GetSectionAngles-1 contains an RTS) \ \ This never happens with the Silverstone track, as for \ this track, sectionListStart is in the range 2 to 5 \ (as sectionListSize is in the range 1 to 4) LDY sectionListValid \ If sectionListValid = 6 then there are no valid CPY #6 \ entries in the track section list, so jump to gsec4 to BEQ gsec4 \ skip the spinning process (as we only apply spin to \ valid sections in the list) \ Otherwise we now loop from Y = sectionListValid up to \ 5 to work through the valid entries in the list, \ applying the yaw angle spin to each one, and skipping \ entry number sectionListPointer as we are going to \ update that entry below .gsec2 CPY sectionListPointer \ If Y = sectionListPointer, jump to gsec3 to move on to BEQ gsec3 \ the next entry in the list, as we are going to update \ this entry below STY T \ Store Y in T so we can retrieve it below when applying \ spin to the left verge TYA \ Set Y = Y + 40 CLC \ ADC #40 \ So Y now points to the section for the right verge TAY JSR SpinTrackSection \ Apply the car's current spin to the right verge track \ section in Y: \ \ * Reset vergeDataRight to zero \ \ * Subtract spinYawAngle from the yaw angles in \ xVergeRightLo, xVergeRightHi \ \ * Subtract spinPitchAngle from the pitch angle in \ yVergeRight \ \ * Update horizonListIndex and horizonLine LDY T \ Retrieve the original value of Y that we stored above JSR SpinTrackSection \ Apply the car's current spin to the left verge track \ section in T: \ \ * Reset vergeDataLeft to zero \ \ * Subtract spinYawAngle from the yaw angles in \ xVergeLeftLo, xVergeLeftHi \ \ * Subtract spinPitchAngle from the pitch angle in \ yVergeLeft \ \ * Update horizonListIndex and horizonLine .gsec3 INY \ Increment the loop counter in Y CPY #6 \ Loop back until we have updated all the valid entries BCC gsec2 \ in the track section list
Name: GetSectionAngles (Part 2 of 3) [Show more] Type: Subroutine Category: Track geometry Summary: Calculate the track section number for this track section entry Deep dive: Data structures for the track calculations The track verges
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

This part of the routine calculates the number of the track section that we want to update, i.e. the section at entry sectionListPointer in the list.
.gsec4 LDA #6 \ Set A = (6 - sectionListPointer) * 8 SEC \ SBC sectionListPointer \ This calculates the following: ASL A \ ASL A \ * A = 1 * 8 for entry #5 ASL A \ * A = 2 * 8 for entry #4 \ * A = 3 * 8 for entry #3 \ * A = 4 * 8 for entry #2 \ * A = 5 * 8 for entry #1 \ * A = 6 * 8 for entry #0 BIT directionFacing \ If bit 7 of directionFacing is clear, then we are BPL gsec5 \ facing forwards, so jump to gsec5 \ If we get here then we are facing backwards STA T \ Set T = A LDA objTrackSection+23 \ Set Y to the number * 8 of the track section for the \ front segment of the track segment buffer CLC \ Set A = A + 8 - T ADC #8 \ = frontSection * 8 + 8 - T SEC \ = (frontSection + 1 - (T / 8)) * 8 SBC T \ \ So A contains: \ \ * (frontSection - 0) * 8 for entry #5 \ * (frontSection - 1) * 8 for entry #4 \ * (frontSection - 2) * 8 for entry #3 \ * (frontSection - 3) * 8 for entry #2 \ * (frontSection - 4) * 8 for entry #1 \ * (frontSection - 5) * 8 for entry #0 \ \ So A now contains the correct section number for \ entry number number sectionListPointer BCS gsec6 \ If the subtraction didn't underflow, jump to gsec6 ADC trackSectionCount \ The subtraction underflowed, so add the total number \ of track sections * 8 given in trackSectionCount to \ wrap round to the correct section number (we know the \ C flag is clear as we just passed through a BCS) JMP gsec6 \ Jump to gsec6 .gsec5 \ If we get here then we are facing forwards CLC \ Set A = A + number * 8 of track section for the ADC objTrackSection+23 \ front segment \ = A + frontSection * 8 \ \ So A contains: \ \ * (1 + frontSection) * 8 for entry #5 \ * (2 + frontSection) * 8 for entry #4 \ * (3 + frontSection) * 8 for entry #3 \ * (4 + frontSection) * 8 for entry #2 \ * (5 + frontSection) * 8 for entry #1 \ * (6 + frontSection) * 8 for entry #0 \ \ So A now contains the correct section number for \ entry number number sectionListPointer CMP trackSectionCount \ If A < trackSectionCount then A is a valid section BCC gsec6 \ number, so jump to gsec6 SBC trackSectionCount \ The addition took us past the highest track section \ number, so subtract the total number of track sections \ * 8 given in trackSectionCount to bring it down to the \ correct section number (we know the C flag is set as \ we just passed through a BCC)
Name: GetSectionAngles (Part 3 of 3) [Show more] Type: Subroutine Category: Track geometry Summary: Calculate the yaw and pitch angles for the track section entry that we want to update Deep dive: Data structures for the track calculations The track verges
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

This part of the routine sets the yaw and pitch angles for this track section in the xVergeRight/Left and yVergeRight/Left tables.
.gsec6 TAY \ Set Y = the section number * 8 that we calculated in \ part 3 STY thisSectionNumber \ Store the section number * 8 in thisSectionNumber, so \ we can retrieve it below when looping back LDX sectionListPointer \ Set X = sectionListPointer, to use as a counter in the \ two loops below \ We run the following section twice, once for the inner \ track section coordinates with X = sectionListPointer, \ and a second time for the outer track section \ coordinates with X = sectionListPointer + 40 .gsec7 STX sectionCounter \ Store the loop counter in sectionCounter LDX #&FD \ Copy the first trackSectionI coordinate for track JSR GetSectionCoord \ section Y into xCoord2, so xCoord2 is the 3D \ coordinate of the inner track at the start of the \ section (or, if this is the second loop where Y has \ been incremented by 3, xCoord2 is the 3D coordinate \ of the outer track) JSR GetObjYawAngle-2 \ Calculate xCoord2's yaw angle, from the point of view \ of the player, returning it in (JJ II) LDY sectionCounter \ Set Y to the loop counter BIT directionFacing \ If bit 7 of directionFacing is clear, then we are BPL gsec8 \ facing forwards, so jump to gsec8 TYA \ We are facing backwards, so flip Y between EOR #40 \ sectionListPointer and sectionListPointer + 40 to do TAY \ the inner and outer track sections in reverse order \ (so we always do the right track verge first, then the \ left track verge, where right and left are relative to \ the direction we are facing) .gsec8 JSR GetSectionYawAngle \ Set the following for the Y-th section, to calculate \ the difference in yaw angle between the track section \ and the player: \ \ xVergeRight = (JJ II) - playerYawAngle \ \ Also set (L K) to the distance between the track \ section and the player's car LDX sectionCounter \ If the loop counter in X >= 40, then we are dealing CPX #40 \ with the outer track section, so jump to gsec10 as we BCS gsec10 \ don't need to repeat the pitch angle calculation (the \ track is level from left to right, so the outer track \ is the same pitch angle as the inner track) LDX #&FD \ Set X = &FD so the call to GetObjPitchAngle uses \ xCoord2, which we set above to the 3D coordinate of \ the inner track at the start of the section JSR GetObjPitchAngle-2 \ Calculate xCoord2's pitch angle, from the point \ of view of the player, returning it in A and LL LDX sectionCounter \ Set X to the loop counter, which we know is less than \ 40 at this point (and which is therefore equal to \ sectionListPointer) LDA LL \ Set A to the pitch angle that we just calculated \ for the track section STA yVergeRight,X \ Store the pitch angle in the X-th yVergeRight \ entry, for this point on the right track section STA yVergeLeft,X \ Store the same pitch angle in the X-th yVergeLeft, \ for this point on the left track section, which will \ at the same pitch angle as the track is level from \ left to right CMP horizonLine \ If A < horizonLine, then this track section is lower BCC gsec10 \ than the current horizon, so jump to gsec10 to move on \ to the outer track section, as this section will not \ be obscuring the horizon BNE gsec9 \ If A <> horizonLine, i.e. A > horizonLine, then this \ means the track section is higher than the current \ horizon line, so jump to gsec9 to set the horizon \ line to the pitch angle of this track section, as the \ section is obscuring the horizon \ If we get here, then A = horizonLine, so this section \ is at the same pitch angle as the current horizon line CPX horizonListIndex \ If X < horizonListIndex, then this section has a lower BCC gsec10 \ index than the current horizon section, so jump to \ gsec10 as horizonListIndex already contains the higher \ index, and a higher index is closer to the player, so \ we don't need to change the horizon line details .gsec9 \ If we get here then we want to update the horizon to \ the pitch angle of the track section we are updating, \ as it obscures the horizon STA horizonLine \ Set horizonLine to the pitch angle in A, so the \ horizon is set to the pitch angle of this track \ section STX horizonListIndex \ Store the index of this section in the track section \ list in horizonListIndex .gsec10 TXA \ Set A = X + 40 CLC \ = sectionListPointer + 40 ADC #40 \ \ So A now points to the outer track section coordinates \ and is ready to be put into X (and, when we look back, \ into sectionCounter) for the loop back to gcsec7 below CMP #60 \ If A >= 60, we have done both inner and outer track BCS gsec11 \ sections, so jump to gsec11 TAX \ Set X = A \ = sectionListPointer + 40 LDA thisSectionNumber \ Set Y = thisSectionNumber + 3 CLC \ ADC #3 \ So when we loop back, the offset in Y points to the TAY \ trackSectionO coordinates for the outer track section \ instead of the inner coordinates in trackSectionI (as \ the outer coordinates are 3 bytes after the inner ones \ in the track data) JMP gsec7 \ Loop back to gsec7 to process the outer track section .gsec11 \ If we get here then we have updated this entry in the \ track section list with both left and right angles, so \ we now update the list pointers LDX sectionListPointer \ Set X = sectionListPointer - 1 DEX JSR SetSectionPointers \ Update the section list pointers to move down through \ the track section list LDA #7 \ If prevHorizonIndex <= 7, then the previous call to CMP prevHorizonIndex \ GetTrackAndMarkers (on the last iteration of the main BCS gsec12 \ driving loop) had the horizon on one of the sections \ in the track section list, or the first entry in the \ track segment list (as the list starts at index 6), so \ jump to gsec12 to skip the following STA horizonLine \ If we get here then the previous iteration around the \ main loop had the horizon line on one of the track \ segments in the track segment list (but not the first \ entry in the list), so set horizonLine to 7 .gsec12 RTS \ Return from the subroutine
Name: GetSegmentYawAngle [Show more] Type: Subroutine Category: Track geometry Summary: Calculate the difference in yaw angle between a track segment and the player
Context: See this subroutine on its own page References: This subroutine is called as follows: * GetSegmentAngles (Part 1 of 3) calls GetSegmentYawAngle * GetSegmentAngles (Part 2 of 3) calls GetSegmentYawAngle

Arguments: X The offset from xSegmentCoordILo of the segment's 3D coordinates, i.e. the segment number * 3, with: * X for inner track segment coordinates * X + 120 for outer track segment coordinates segmentListPointer The index of the segment in the track segment list to use for calculations Returns: (L K) The distance between the object and the player's car A Contains the high byte of (L K)
.GetSegmentYawAngle JSR GetObjYawAngle-2 \ Calculate the segment's yaw angle, from the point of \ view of the player, returning it in (JJ II) LDY segmentListPointer \ Set Y = segmentListPointer, so the result gets stored \ in the correct position in the track segment list \ Fall through into GetSectionYawAngle to set the \ specified xVergeRight or xVergeLeft to the difference \ in the yaw angle between the player and the segment
Name: GetSectionYawAngle [Show more] Type: Subroutine Category: Track geometry Summary: Calculate the difference in yaw angle between an object and the player
Context: See this subroutine on its own page References: This subroutine is called as follows: * GetSectionAngles (Part 3 of 3) calls GetSectionYawAngle

This routine is typically used to calculate the difference in yaw angle between a track section and the player. Arguments: Y Index from xVergeRight to store the difference in yaw angle between the object and the player (JJ II) The yaw angle of the object Returns: xVergeRight The difference in the yaw angle between the object and the player (if Y points to the right verge) xVergeLeft The difference in the yaw angle between the object and the player (if Y points to the left verge) (L K) The distance between the object and the player's car A Contains the high byte of (L K) M The smaller yaw angle of the object, where 0 to 255 represents 0 to 45 degrees
.GetSectionYawAngle LDA II \ Set the following for the Y-th section: SEC \ SBC playerYawAngleLo \ xVergeRight = (JJ II) - playerYawAngle STA xVergeRightLo,Y \ \ starting with the low bytes LDA JJ \ And then the high bytes SBC playerYawAngleHi STA xVergeRightHi,Y JMP GetObjectDistance \ Set (L K) to the distance between the object and the \ player's car, with A set to L, returning from the \ subroutine using a tail call
Name: GetSegmentAngles (Part 1 of 3) [Show more] Type: Subroutine Category: Track geometry Summary: Get the yaw and pitch angles for the inner or outer track segments Deep dive: Data structures for the track calculations The track verges
Context: See this subroutine on its own page References: This subroutine is called as follows: * GetTrackAndMarkers calls GetSegmentAngles

This routine works through track segments, starting from distant segments and working backwards towards the player, calculating the angles and verge data for each segment as we go, up to a maximum of 16 segments (which is the capacity of the track segment list). Arguments: A The index of the first segment to update in the track segment list, starting at 6 for the first entry in list of right segments, and 46 for the first entry in the list of left segments X The offset from xSegmentCoordILo of the segment's 3D coordinates, i.e. the segment number * 3, with: * X for inner track segment coordinates * X + 120 for outer track segment coordinates segmentOffset The offset to use for this segment: * 0 when our car is facing in the same direction * 120 when our car is facing the opposite direction segmentDirection The relative direction of our car: * 0 when our car is facing in the same direction * 1 when our car is facing the opposite direction Returns: xVergeRight Updated yaw angles for the entries in the track segment list (i.e. indexes 6 to 21) for the right verge yVergeLeft Updated pitch angles for the entries in the track segment list (i.e. indexes 6 to 21) for the left verge edgeDistance The distance between the player's car and the nearest track edge edgeSegmentNumber The number of the segment within the track segment list that is closest to the player's car edgeSegmentPointer The index of the segment within track verge buffer that is closest to the player's car edgeYawAngle The yaw angle of the segment that is closest to the player's car xVergeRight Entries in the second part of the track segment list for the coordinates of the outside of the right track verge (i.e. indexes 22 to 37, which correspond to the yaw angles in the track segment list in indexes 6 to 21) xVergeLeft Entries in the second part of the track segment list for the coordinates of the outside of the left track verge (i.e. indexes 22 to 37, which correspond to the yaw angles in the track segment list in indexes 6 to 21) yVergeRight Pitch angles for the entries in the track segment list (i.e. indexes 6 to 21) for the right verge yVergeLeft Pitch angles for the entries in the track segment list (i.e. indexes 6 to 21) for the left verge xMarker Distance in the x-axis between the track edge and the corner marker for this segment (if there is one) vergeDataRight Data (such as colour) for this segment's right verge vergeDataLeft Data (such as colour) for this segment's left verge
.GetSegmentAngles STA segmentListPointer \ Set segmentListPointer to the index passed in A LDA #0 \ Set segmentCounter = 0, to use to count visible STA segmentCounter \ segments over the course of the following routine \ We now run the rest of the routine for each segment \ in turn, looping back to here while segments are \ visible .gseg1 JSR GetSegmentYawAngle \ Calculate the yaw angle and distance between the \ player's car and the track segment specified in X, and \ store the results in the track segment list at the \ segment list pointer \ \ Also set (A K) = (L K) = the distance between the car \ and the track segment \ We now check to see if this is the closest track \ segment we've come across in this iteration of the \ main loop, and if it is, we set a bunch of variables \ with the details of the track edge CMP edgeDistanceHi \ If A < edgeDistanceHi, then we know that (A K) and BCC gseg2 \ therefore (L K) < (edgeDistanceHi edgeDistanceLo), \ so jump to gseg2 to set (L K) as the new minimum \ distance to the verge BNE gseg3 \ If A <> edgeDistanceHi, i.e. A > edgeDistanceHi, \ then (L K) > (edgeDistanceHi edgeDistanceLo), so \ jump to gseg3 as (L K) is not a new minimum verge \ distance \ We now compare the high bytes LDA edgeDistanceLo \ If edgeDistanceLo < K, then we know that CMP K \ (L K) > (edgeDistanceHi edgeDistanceLo), so jump to BCC gseg3 \ gseg3 as (L K) is not a new minimum verge distance .gseg2 \ If we get here then we know that \ (L K) <= (edgeDistanceHi edgeDistanceLo), so we now \ set (L K) as the new minimum distance to the verge, \ and set a number of variables so we can refer to this \ nearest track edge in places like the crash routine LDA L \ Set (edgeDistanceHi edgeDistanceLo) = (L K) STA edgeDistanceHi LDA K STA edgeDistanceLo LDA segmentCounter \ Set edgeSegmentNumber = segmentCounter STA edgeSegmentNumber \ \ So edgeSegmentNumber contains the number of the \ segment within the track segment list that is closest \ to the player's car LDY segmentListPointer \ Set edgeSegmentPointer = segmentListPointer STY edgeSegmentPointer \ \ So edgeSegmentPointer contains the index of the \ segment within the track verge buffer (i.e. from \ xVergeRight) that is closest to the player's car LDA xVergeRightHi,Y \ Set edgeYawAngle = the segment's entry in STA edgeYawAngle \ xVergeRightHi \ \ So edgeYawAngle contains the yaw angle of the segment \ that is closest to the player's car, from the point of \ view of the car .gseg3 JSR GetObjPitchAngle-2 \ Calculate the segment's pitch angle, from the \ point of view of the player, returning it in A and LL \ \ If the segment is not visible on-screen, the C flag is \ set, otherwise it will be clear BCS gseg4 \ If the segment is not visible on-screen, jump to gseg4 BPL gseg10 \ If the pitch angle is positive, jump to gseg10
Name: GetSegmentAngles (Part 2 of 3) [Show more] Type: Subroutine Category: Track geometry Summary: Process a segment that is not visible by trying to process a segment that's one-quarter of the size Deep dive: Data structures for the track calculations The track verges
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.gseg4 \ If we get here then the segment is not visible or the \ segment's pitch angle is negative, so we need to \ stop processing segments \ \ However, before we stop, we try to eke out as much \ accuracy out of the last (not visible) segment by \ trying to process a segment that's one-quarter of the \ size, just in case this smaller segment is visible, in \ which case we finish with something to show for the \ last visible segment (and if not, at least we tried) LDA segmentCounter \ If segmentCounter is non-zero then we have already BNE gseg5 \ found at least one visible segment, so jump to gseg5 RTS \ Otherwise this is the first segment and it's not \ visible, so return from the subroutine as none of the \ others will be either (as we are working backwards \ through the segments towards the player, so if the \ first segment is not visible, so will all the ones \ behind it) .gseg5 LDA #0 \ Set U = 0, to use as an axis counter below STA U LDY prevSegmentOffset \ Set Y to the offset from xSegmentCoordILo of the \ previous segment's 3D coordinates STX W \ Store the segment offset that's in X in W, so we can \ retrieve it below \ We now loop through all three axes, calculating the \ difference in 3D coordinates between this segment and \ the previous segment for xSegmentCoord, ySegmentCoord \ and zSegmentCoord .gseg6 LDA xSegmentCoordILo,X \ Set (A T) to the difference between the coordinate SEC \ of the previous segment and this one, starting with SBC xSegmentCoordILo,Y \ the low bytes STA T LDA xSegmentCoordIHi,X \ And then the high bytes SBC xSegmentCoordIHi,Y CLC \ Clear the C flag BPL gseg7 \ If the result in (A T) is positive, then jump to gseg7 SEC \ Set the C flag, to indicate that the result is \ negative .gseg7 PHP \ Store the C flag on the stack, which contains the sign \ bit of the result (0 for positive, 1 for negative), so \ we can use it to rotate the correct sign but into \ (A T) in the following ROR A \ Set (A T) = (A T) >> 1 ROR T \ \ making sure to retain the correct sign in bit 7 PLP \ Fetch the C flag from the stack, so it once again \ contains the correct sign for (A T) ROR A \ Set (A T) = (A T) >> 1 ROR T \ \ making sure to retain the correct sign in bit 7 STA V \ Set (V T) = (A T) \ \ So (V T) contains the difference in 3D coordinates, \ divided by 4, and with the correct sign retained LDX U \ Set X to the axis counter in U \ \ For clarity, the following comments will assume we are \ working with the x-axis LDA xSegmentCoordILo,Y \ Set xCoord1 = xSegmentCoord for previous segment CLC \ + (V T) ADC T \ STA xCoord1Lo,X \ starting with the low bytes LDA xSegmentCoordIHi,Y \ And then the high bytes ADC V STA xCoord1Hi,X INX \ Increment the axis counter in X CPX #3 \ If X = 3, we have done all three axes, so jump to BEQ gseg8 \ gseg8 STX U \ Stote the incremented value in U, so this is the same \ as incrementing U LDX W \ Set X = W, which we set above to the segment offset \ for the current segment INY \ Increment Y to point to the next axis for the previous \ segment INX \ Increment X to point to the next axis for the current \ segment STX W \ Store the updated segment offset for the current \ segment in W JMP gseg6 \ Loop back to gseg6 to process the next axis .gseg8 \ By the time we get here, xCoord1 contains the 3D \ coordinates of the previous segment, plus a quarter \ of the vector from the previous segment to the current \ segment LDX #&FA \ Set X = &FA so the call to GetSegmentYawAngle uses \ xCoord1 JSR GetSegmentYawAngle \ Calculate the yaw angle and distance between the \ player's car and xCoord1, and store the results in \ the track segment list at the segment list pointer \ \ Also set (A K) = (L K) = the distance between the car \ and xCoord1 JSR GetObjPitchAngle-2 \ Calculate xCoord1's pitch angle, from the point \ of view of the player, returning it in A and LL \ \ If xCoord1 is not visible on-screen, the C flag is \ set, otherwise it will be clear BCS gseg9 \ If xCoord1 is not visible on-screen, jump to gseg9 \ to return from the subroutine \ If we get here then xCoord1 is visible, so we can \ store the results as our final entry in the track \ segment list LDX prevSegmentOffset \ Set X to the offset from xSegmentCoordILo of the \ previous segment's 3D coordinates, so the call to \ GetVergeAndMarkers uses the previous segment's verge \ data for our quarter segment's calculation LDA markersToDraw \ Store markersToDraw in markerNumber so we can restore STA markerNumber \ it after the call to GetVergeAndMarkers (so the call \ doesn't change the value of markersToDraw, as we \ don't want to try drawing markers with this \ quarter-size segment) JSR GetVergeAndMarkers \ Get the details for the previous segment's corner \ markers and verge marks and store them for this \ segment LDA markerNumber \ Retrieve the value of markersToDraw that we stored STA markersToDraw \ in markerNumber, so any marker calculations in the \ above call get ignored INC segmentListPointer \ Increment the segment list pointer as we just added a \ new entry to the track segment list .gseg9 RTS \ Return from the subroutine
Name: GetSegmentAngles (Part 3 of 3) [Show more] Type: Subroutine Category: Track geometry Summary: Process a visible segment Deep dive: Data structures for the track calculations The track verges
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.gseg10 \ If we get here then the segment's pitch angle is \ positive and the segment is visible on-screen JSR GetVergeAndMarkers \ Get the details for this segment's corner markers and \ verge marks LDA segmentCounter \ If segmentCounter <= edgeSegmentNumber, jump to gseg13 CMP edgeSegmentNumber \ to keep checking segments, as we haven't yet gone past BEQ gseg13 \ the closest segment to the player BCC gseg13 \ If we get here then we have gone past the closest \ segment to the player, so we need to check whether the \ segment is within the 20-degree field of view, and \ stop when the segments become hidden from view LDY segmentListPointer \ Set Y to the segment list pointer LDA xVergeRightHi,Y \ Set A to the high byte of the yaw angle of the \ segment's right verge BPL gseg11 \ If the angle is negative, negate it, so we now have EOR #&FF \ A = |yaw angle| .gseg11 CMP #20 \ If A < 20, then the segment is within the 20-degree BCC gseg13 \ field of view, so jump to gseg13 to keep checking \ segments LDA xVergeRightHi-1,Y \ Set A to the high byte of the yaw angle of the \ previous segment's right verge BPL gseg12 \ If the angle is negative, negate it, so we now have EOR #&FF \ A = |yaw angle| .gseg12 CMP #20 \ If A >= 20, then the previous segment was also outside BCS gseg16 \ the 20-degree field of view, so jump to gseg16 to \ return from the subroutine JMP gseg4 \ If we get here then the current segment is outside the \ 20-degree field of view, but the previous one wasn't, \ so we jump to gseg4 to try processing a segment that's \ one-quarter of the size, in case that fits .gseg13 \ If we get here then we have successfully processed a \ visible segment STX prevSegmentOffset \ Store the offset from xSegmentCoordILo of this \ segment's 3D coordinates in prevSegmentOffset, to use \ in the next iteration if the next segment is not \ visible INC segmentListPointer \ Increment the segment list pointer to point to the \ next entry in the list INC segmentCounter \ Increment the segment counter to indicate that we have \ populated a visible segment (so this will become 1 if \ this is the first visible segment we have processed, \ 2 for the second visible segment, and so on) LDY segmentCounter \ Set Y to the number of visible segments we have \ populated so far CPY #18 \ If Y >= 18, i.e. Y > 17, then Y was > 16 before we BCS gseg16 \ incremented it, which means we have filled the track \ segment list with 16 visible segments, so we jump to \ gseg16 to return from the subroutine to stop \ processing segments LDA segmentStep,Y \ Set T to the segment step for segment number Y, so to STA T \ get the next segment, we step back T steps in the \ track segment buffer \ \ This makes us step a long way backwards for the first \ few segments, and then make shorter steps as we get \ closer to the player TXA \ Set A to the segment offset that's in X SEC \ Set A = A - segmentOffset SBC segmentOffset \ \ so A contains the number of the segment * 3 (as X \ contains the offset from xSegmentCoordILo, which will \ be 120 + X for the X-th outer segment coordinate, so \ subtracting segmentOffset brings the offset down to \ the number * 3, whether this is an inner or outer \ coordinate) CMP T \ If A >= T, jump to gseg14 to jump back T segments BCS gseg14 \ towards the player, for the next iteration \ If we get here then A < T, so we can't jump back T \ segments or we will jump past the beginning of the \ track segment buffer, so we need to add 120 to wrap \ around to the end of the buffer before we can jump \ back T segments TXA \ Set A to the segment offset that's in X CLC \ Set A = A + 120 to wrap around to the end of the track ADC #120 \ segment buffer JMP gseg15 \ Jump to gseg15 .gseg14 TXA \ Set A to the segment offset that's in X .gseg15 SEC \ Set X = A - T SBC T \ TAX \ So the next segment to be tested is T steps backwards \ in the track segment buffer, towards the player (so \ if T = 3, we step back one segment, or if T = 13 * 3, \ we step back 13 segments) JMP gseg1 \ Loop back to gseg1 to move on to the next segment .gseg16 RTS \ Return from the subroutine
Name: MovePlayerSegment [Show more] Type: Subroutine Category: Car geometry Summary: Move the player's car in the correct direction Deep dive: Placing cars on the track
Context: See this subroutine on its own page References: This subroutine is called as follows: * MainDrivingLoop (Part 2 of 5) calls MovePlayerSegment

This routine checks whether the player has turned enough to be in a different direction (i.e. pointing forwards to pointing backwards or vice versa), and if so, it turns the player around by updating the track segment buffer for the new direction, resetting the track section list, and updating all the direction-related variables. Otherwise it works out whether the player has moved into a new segment, and if so, it updates the car's segment and section numbers accordingly.
.MovePlayerSegment LDA playerHeading \ Set A = playerHeading - spinYawAngleTop SEC \ SBC spinYawAngleTop \ So A contains the new heading of the player's car, \ once the current spin is added (i.e. it's the new \ heading of the car) \ A is an angle that represents the new direction in \ which our car will be facing, after applying spin, \ with respect to the track, like this: \ \ 0 \ -32 | +32 Overhead view of car \ \ | / \ \ | / 0 = looking straight ahead \ \|/ +64 = looking sharp right \ -64 -----+----- +64 -64 = looking sharp left \ /|\ \ / | \ \ / | \ \ -96 | +96 \ 128 \ \ An angle of 0 means our car is facing forwards along \ the track, while an angle of +32 means we are facing \ 45 degrees to the right of straight on, and an angle \ of 128 means we are facing backwards along the track BPL mpla1 \ If A is positive, jump to mpla1 to skip the following EOR #&FF \ Invert A, so this effectively reflects the angle into \ the right half of the above diagram: \ \ 0 \ | 32 \ | / \ | / \ |/ \ +----- 64 \ |\ \ | \ \ | \ \ | 96 \ 127 .mpla1 ASL A \ Set A = A << 1, so we now have: \ \ 0 \ | 64 \ | / \ | / \ |/ \ +----- 128 \ |\ \ | \ \ | \ \ | 192 \ 254 CMP #128 \ Clear the C flag if A < 128 (i.e. top-right quadrant) \ is set the C flag if A >= 128 (i.e. bottom-right \ quadrant) \ Note that bit 7 is similar, so we have: \ \ 0 \ | 64 \ | / <-- C flag and bit 7 clear \ | / \ |/ \ +----- 128 \ |\ \ | \ \ | \ <-- C flag and bit 7 set \ | 192 \ 254 EOR directionFacing \ If we are facing forwards, leave A alone, but if we \ are currently facing backwards, flip bit 7 of A BPL mpla3 \ If we are facing forwards and we are in the top-right \ quadrant, or we are facing backwards and we are in the \ bottom-right quadrant, then the direction we are \ facing is still correct, so jump to mpla3 to get on \ with moving the car \ \ Otherwise we may now be facing in a different \ direction to before, and bit 7 of A is set BCC mpla2 \ If bit 7 of A was clear before the above EOR, then we \ are in the top-right quadrant but are currently facing \ backwards, so jump to mpla2 to skip the following \ instruction EOR #%01111111 \ Bit 7 of A was set before the above EOR, so we are in \ the bottom-right quadrant, but are currently facing \ forwards, so flip bits 0-6 of A, changing the range of \ the bottom-right quadrant from 128 to 254 to \ 255 to 129 .mpla2 \ By this point, we are pointing in the opposite \ direction to the setting of directionFacing, and the \ angles are as follows: \ \ 0 \ | 64 \ | / <-- C flag and bit 7 clear \ | / \ |/_.- 127 \ +----- 255 \ |\-._ 252 \ | \ \ | \ <-- C flag and bit 7 set \ | 192 \ 129 \ \ So 0 to 127 is in the top-right quadrant, while 255 to \ 129 is the bottom-right quadrant CMP #252 \ If A >= 252, then the new angle we are facing is in BCS mpla3 \ the top sliver of the bottom-right quadrant, so jump \ to mpla3 to get on with moving the car \ If we get here then A < 252, which means we are either \ now in the top-right quadrant, or we are in the bottom \ part of the bottom-right quadrant, and we are facing \ in a different direction to directionFacing \ \ So we have now officially turned in the opposite \ direction, and need to update all the various buffers \ and variables JSR ChangeDirection \ Turn the player around by updating the track segment \ buffer for the new direction, resetting the track \ section list, and updating all the direction-related \ variables RTS \ Return from the subroutine .mpla3 \ The GetSegmentAngles routine, which has already been \ called by this point, sets up the track segment list \ and sets edgeSegmentNumber to the entry number within \ the track segment list that is closest to the player's \ car \ \ Entry 13 in the track segment list corresponds to the \ segment that's 32 behind the front segment of the \ track segment buffer, which is the position of the \ player's car, so if edgeSegmentNumber does not equal \ 13, then it means that the car has moved into a new \ segment \ \ Specifically, the values of edgeSegmentNumber mean \ the following: \ \ * 11 = player has moved forward two segments \ * 12 = player has moved forward one segment \ * 13 = player is still in the same segment \ * 14 = player has moved back one segment \ * 15 = player has moved back two segments \ \ The player can't travel more than two segments in one \ iteration of the main driving loop LDA edgeSegmentNumber \ If edgeSegmentNumber = 12, jump to mpla4 to move the CMP #12 \ player forward by one segment BEQ mpla4 BCS mpla5 \ If edgeSegmentNumber > 12, then the player is either \ in the same segment, or has moved backwards, so jump \ to mpla5 \ If we get here then edgeSegmentNumber < 12, so \ edgeSegmentNumber must be 11, so we move the player \ forwards by two segments JSR MovePlayerForward \ Move the player forwards by one segment .mpla4 BIT playerPastSegment \ If bit 0 of playerPastSegment is clear, then the BPL mpla7 \ player has not yet gone past the closest segment, so \ jump to mpla7 to return from the subroutine without \ moving forward by this segment JSR MovePlayerForward \ Move the player forwards by one segment RTS \ Return from the subroutine .mpla5 \ If we get here then edgeSegmentNumber > 12 CMP #14 \ If edgeSegmentNumber < 14, i.e. edgeSegmentNumber is BCC mpla7 \ 13, then the player has not changed segment, so jump \ to mpla7 to return from the subroutine BEQ mpla6 \ If edgeSegmentNumber = 14, jump to mpla6 to move the \ player backwards by one segment \ If we get here then edgeSegmentNumber > 14, so \ edgeSegmentNumber must be 15, so we move the player \ backward by two segments JSR MovePlayerBack \ Move the player backwards by one segment .mpla6 JSR MovePlayerBack \ Move the player backwards by one segment .mpla7 RTS \ Return from the subroutine
Name: GetTrackAndMarkers [Show more] Type: Subroutine Category: Track geometry Summary: Calculate the 3D coordinates of the track and corner markers Deep dive: The track verges Corner markers
Context: See this subroutine on its own page References: This subroutine is called as follows: * MainDrivingLoop (Part 2 of 5) calls GetTrackAndMarkers
.GetTrackAndMarkers LDA #0 \ Set horizonLine = 0, so we can calculate a new pitch STA horizonLine \ angle for the horizon in the following process JSR GetSectionAngles \ Get the yaw and pitch angles for the inner and outer \ track sections in the track section list and store the \ results in xVergeRight/Left and yVergeRight/Left LDA #255 \ Set edgeDistanceHi = 255, so GetSegmentAngles can set STA edgeDistanceHi \ it to the distance of the nearest verge LDA #13 \ Set edgeSegmentNumber = 13, as the default value for STA edgeSegmentNumber \ the number of the segment within the track segment \ list that is closest to the player's car LDA #0 \ Fetch the index details of the right track segments JSR GetSegmentDetails LDA #6 \ Get the yaw and pitch angles for the segments (and the JSR GetSegmentAngles \ verge marks and corner markers) along the right side \ of the track and store the results in xVergeRight, \ yVergeRight, xMarker and vergeDataRight LDA segmentListPointer \ Set segmentListRight = segmentListPointer STA segmentListRight \ \ So it contains the index of the last entry in the \ track segment list for the right side of the track LDA #%10000000 \ Fetch the index details of the left track segments JSR GetSegmentDetails LDA #46 \ Get the yaw and pitch angles for the segments (and the JSR GetSegmentAngles \ verge marks and corner markers) along the left side \ of the track and store the results in xVergeLeft, \ yVergeLeft, xMarker and vergeDataLeft LDA horizonListIndex \ If horizonListIndex < 40, then this is a valid index CMP #40 \ into the track verge buffer so jump to gtrm1 to skip BCC gtrm1 \ the following three instructions SEC \ Set horizonListIndex = horizonListIndex - 40 SBC #40 \ STA horizonListIndex \ so if we set horizonListIndex to the index for the \ outer track coordinates, this corrects the value to \ the index for the inner coordinates .gtrm1 TAY \ Set Y to the corrected value of horizonListIndex STY prevHorizonIndex \ Store the horizon section index in prevHorizonIndex, \ so we can refer to it in the next call to \ GetTrackAndMarkers LDA horizonLine \ If horizonLine < 79, then the horizon line is a valid CMP #79 \ number, so jump to gtrm2 to skip the following two BCC gtrm2 \ instructions LDA #78 \ Set horizonLine = 78, so the maximum value for the STA horizonLine \ horizon line is 78 .gtrm2 STA yVergeRight,Y \ Set the pitch angle for the right side of the horizon \ line in the track verge buffer to the updated value of \ horizonLine STA yVergeLeft,Y \ Set the pitch angle for the left side of the horizon \ line in the track verge buffer to the updated value of \ horizonLine LDA xVergeRightHi,Y \ Set A = xVergeRightHi - xVergeLeftHi for the horizon SEC \ section SBC xVergeLeftHi,Y JSR Absolute8Bit \ Set A = |A|, so A contains the arc of the track at \ the horizon (i.e. the track width on the section or \ segment at the horizon) in terms of the high bytes LSR A \ Set horizonTrackWidth = |A| / 2 STA horizonTrackWidth \ \ So horizonTrackWidth contains half the width of the \ track on the horizon, in terms of the high bytes RTS \ Return from the subroutine
Name: GetSegmentDetails [Show more] Type: Subroutine Category: Track geometry Summary: Get the details for the segment in front or behind Deep dive: Data structures for the track calculations The track verges
Context: See this subroutine on its own page References: This subroutine is called as follows: * GetTrackAndMarkers calls GetSegmentDetails

Arguments: A The direction in which to fetch a segment: * Bit 7 clear = forwards (right) * Bit 7 set = backwards (left) In other words, fetch the track segments from the right or left verges, according to the way we are facing Returns: segmentOffset The offset to use for this segment: * 0 when our car is facing in direction A * 120 when our car is facing opposite direction A segmentDirection The relative direction of our car: * 0 when our car is facing in direction A * 1 when our car is facing opposite direction A X Returns: * frontSegmentIndex when our car is facing in direction A * frontSegmentIndex + 120 when our car is facing the opposite direction to A (so we use the outer xSegmentCoordOLo rather than the inner xSegmentCoordILo)
.GetSegmentDetails LDX frontSegmentIndex \ Set X to the index * 3 of the front track segment in \ the track segment buffer EOR directionFacing \ If bit 7 of A and bit 7 of directionFacing are the BPL segd1 \ same, jump to segd1 TXA \ Set X = X + 120 CLC ADC #120 TAX LDA #120 \ Set A = 120, so segmentOffset gets set to 120 SEC \ Set the C flag, so segmentDirection gets set to 1 BNE segd2 \ Jump to segd2 (thie BNE is effectively a JMP as A is \ never zero .segd1 LDA #0 \ Set A = 0, so segmentOffset gets set to 0 CLC \ Clear the C flag, so segmentDirection gets set to 0 .segd2 STA segmentOffset \ Set segmentOffset = A LDA #0 \ Set segmentDirection to the C flag ROL A STA segmentDirection RTS \ Return from the subroutine
Name: GetVergeAndMarkers (Part 1 of 4) [Show more] Type: Subroutine Category: Track geometry Summary: Get the details for a segment's corner markers and verge marks Deep dive: The track verges Corner markers
Context: See this subroutine on its own page References: This subroutine is called as follows: * GetSegmentAngles (Part 2 of 3) calls GetVergeAndMarkers * GetSegmentAngles (Part 3 of 3) calls GetVergeAndMarkers

The track verge, which is shown in black-and-white or red-and-white verge marks according to the track data, extends outwards from the track edge, where the track edge is the line defined by the segment vectors. This routine calculates the verge colours and the coordinates of the outside of the verge, and it also calculates the coordinates and colours of the corner markers for this track segment. Arguments: X The offset from xSegmentCoordILo of the segment's 3D coordinates, i.e. the segment number * 3, with: * X for inner track segment coordinates * X + 120 for outer track segment coordinates LL The segment's pitch angle, from the point of view of the player scaleUp The scale up factor for the segment scaleDown The scale down factor for the segment Results: xVergeRight Entries in the second part of the track segment list for the coordinates of the outside of the right track verge (i.e. indexes 22 to 37, which correspond to the yaw angles in the track segment list in indexes 6 to 21) xVergeLeft Entries in the second part of the track segment list for the coordinates of the outside of the left track verge (i.e. indexes 22 to 37, which correspond to the yaw angles in the track segment list in indexes 6 to 21) yVergeRight Pitch angles for the entries in the track segment list (i.e. indexes 6 to 21) for the right verge yVergeLeft Pitch angles for the entries in the track segment list (i.e. indexes 6 to 21) for the left verge xMarker Distance in the x-axis between the track edge and the corner marker for this segment (if there is one) vergeDataRight Data (such as colour) for this segment's right verge vergeDataLeft Data (such as colour) for this segment's left verge
.GetVergeAndMarkers LDY segmentDirection \ Set Y to segmentDirection, which will be 0 when our \ car is facing in the same direction as the segment we \ are checking, or 1 if it's the opposite direction \ \ This determines whether we are creating the left or \ right verge, with 0 for the left verge and 1 for the \ right verge CPX #120 \ If X >= 120, jump to gmar1 to subtract 120 from the BCS gmar1 \ offset LDA segmentFlags,X \ Set A to the flags for this track segment from the \ track segment buffer BCC gmar2 \ Jump to gmar2 (this BCC is effectively a JMP as we \ just passed through a BCS) .gmar1 LDA segmentFlags-120,X \ Set A to the flags for this track segment from the \ track segment buffer .gmar2 AND segmentFlagMask,Y \ Extract the relevant bits of the segment's flags: STA W \ \ W = A AND %00101101 if Y = 0 (right verge) \ %00110011 if Y = 1 (left verge) \ \ So when we are processing the right verge, we extract \ these flags into W while zeroing the rest: \ \ * Bit 0 (section shape) \ * Bit 2 (colour of right verge marks) \ * Bit 3 (show right corner markers) \ * Bit 5 (corner marker colours) \ \ and when we are processing the left verge, we extract \ these flags into W while zeroing the rest: \ \ * Bit 0 (section shape) \ * Bit 1 (colour of left verge marks) \ * Bit 4 (show left corner markers) \ * Bit 5 (corner marker colours) AND #%00000111 \ Set Y = bits 0-2 of W, so Y is in the range 0 to 7, TAY \ where the possible values are as follows: \ \ * Y = 0 = %000 = black right, black left, straight \ * Y = 1 = %001 = black right, black left, curve \ * Y = 2 = %010 = black right, red left, straight \ * Y = 3 = %011 = black right, red left, curve \ * Y = 4 = %100 = red right, black left, straight \ * Y = 5 = %101 = red right, black left, curve \ * Y = 6 = %110 = red right, red left, straight \ * Y = 7 = %111 = red right, red left, curve LDA vergeColour,Y \ When we are processing the right verge, we know bit 1 STA V \ is clear, so the possible values of Y are as follows: \ \ * Y = 0 = %000 = black right, black left, straight \ * Y = 1 = %001 = black right, black left, curve \ * Y = 4 = %100 = red right, black left, straight \ * Y = 5 = %101 = red right, black left, curve \ \ When we are processing the left verge, we know bit 2 \ is clear, so the possible values of Y are as follows: \ \ * Y = 0 = %000 = black right, black left, straight \ * Y = 1 = %001 = black right, black left, curve \ * Y = 2 = %010 = black right, red left, straight \ * Y = 3 = %011 = black right, red left, curve \ \ So if Y = 0 or 1, then we know that the verge we are \ processing is black-and-white, otherwise it is \ red-and-white \ \ These instructions set V to the Y-th entry in the \ vergeColour table, which contains the following: \ \ * 0 when Y = 0 to 1 (black-and-white verge) \ * 1 when Y = 2 to 7 (red-and-white verge) \ \ So V = 0 if this is a black-and-white verge \ 1 if this is a red-and-white verge LDA segmentCounter \ If segmentCounter >= 3 then jump to gmar3 to process CMP #3 \ the segment's corner markers in part 2 BCS gmar3 JMP gmar9 \ Otherwise segmentCounter is 0 to 2, so jump to gmar9 \ to skip the corner markers and move on to the verge \ marks in part 4
Name: GetVergeAndMarkers (Part 2 of 4) [Show more] Type: Subroutine Category: Track geometry Summary: Calculate the segment's verge width and outside verge coordinates
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.gmar3 \ We calculate the verge width as follows: \ \ (U A) = scaleUp * 2 ^ (scaleDown - vergeScale) \ \ to determine the width of the verge marks on the side \ of the track \ \ The higher the value of (U A), the wider the verge for \ this segment \ \ The vergeScale factor is between 3 and 5, and scales \ the verge width differently for different track \ configurations, with larger values of vergeScale \ giving smaller verges \ \ This gives the following: \ \ * If both verges are black-and-white, then the \ verges are thin (vergeScale = 5), on both curved \ and straight sections \ \ * If this is a curve and at least one of the verges \ is red-and-white, or we're on a straight and both \ verges are red-and-white, then the verges are \ medium thickness (vergeScale = 4) \ \ * If this is a straight and only one of the verges \ is red-and-white, then the verges are thick \ (vergeScale = 3) LDA scaleDown \ Set Y = scaleDown - vergeScale SEC SBC vergeScale,Y TAY LDA #0 \ Set U = 0, to use as the high byte in (U A) STA U LDA scaleUp \ Set A = scaleUp \ \ So (U A) = scaleUp DEY \ Set Y = Y - 1 \ = scaleDown - vergeScale - 1 \ We now scale (U A) by 2 ^ Y, so if Y is 0 we don't \ do any scaling, if it's negative we scale down, and \ if it's positive we scale up \ \ Note that the -1 in the scale factor calculation is \ reversed by the right-shift that we apply below when \ setting bit 7 of the shifted result, so the result is \ as above, despite the extra -1 BEQ gmar6 \ If Y = 0, then there is no scaling to be done, so jump \ to gmar6 BPL gmar5 \ If Y > 0, then we need to scale up, so jump to gmar5 \ If we get here then Y < 0, so we need to scale down, \ specifically by right-shifting (U A) by |Y| places .gmar4 LSR U \ Set (U A) = (U A) >> 1 ROR A INY \ Increment the shift counter in Y BNE gmar4 \ Loop back to gmar4 to keep shifting right until we \ have shifted by |Y| places BEQ gmar6 \ Jump to gmar6 (this BEQ is effectively a JMP, as we \ just passed through a BNE) .gmar5 \ If we get here then Y > 0, so we need to scale up, \ specifically by left-shifting (U A) by Y places ASL A \ Set (U A) = (U A) << 1 ROL U DEY \ Decrement the shift counter in Y BNE gmar5 \ Loop back to gmar5 to keep shifting left until we \ have shifted by Y places .gmar6 STA T \ Set (U T) = (U A) \ \ So (U T) contains our scaled value LDA segmentDirection \ Set the C flag to bit 0 of segmentDirection, which LSR A \ will be 0 when our car is facing in the same direction \ as the segment we are checking, or 1 if it's the \ opposite direction ROR A \ Set A = A >> 1 and set bit 7 to the C flag EOR directionFacing \ If the C flag matches directionFacing, jump to gmar7 BPL gmar7 \ If we get here then this is the left verge, so we need \ to negate (U T) so the outside of the verge is to the \ left of the track, i.e. in a negative direction along \ the x-axis LDA #0 \ Negate (U T), starting with the low bytes SEC SBC T STA T LDA #0 \ And then the high bytes SBC U STA U \ So we now have our verge width result: \ \ (U T) = scaleUp * 2 ^ (scaleDown - vergeScale) \ \ where the sign of (U T) is positive for the right \ verge and negative for the left verge .gmar7 LDY segmentListPointer \ Set Y to the index of the current entry in the track \ segment list \ We now calculate the coordinates for the outside edge \ of the track verge by adding the verge width in (U T) \ to the track segment's verge coordinates, storing the \ result in the track segment list, 16 bytes after the \ corresponding track segment entry (so indexes 6 to 21 \ contain the track segment list, while indexes 22 to 37 \ contain the corresponding entries for the outside of \ the verge) LDA xVergeRightLo,Y \ Set (xVergeRightHi+16 xVergeRightLo+16) CLC \ = (xVergeRightHi xVergeRightLo) + (U T) ADC T \ STA xVergeRightLo+16,Y \ starting with the low bytes LDA xVergeRightHi,Y \ And then the high bytes ADC U STA xVergeRightHi+16,Y
Name: GetVergeAndMarkers (Part 3 of 4) [Show more] Type: Subroutine Category: Track geometry Summary: Process the segment's corner markers
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
LDA W \ If bits 3 and 4 of W are clear, which are these bits AND #%00011000 \ in the segment flags: BEQ gmar9 \ \ * Bit 3 (show right corner markers) \ * Bit 4 (show left corner markers) \ \ then we do not show any corner markers for this \ segment, so jump to gmar9 to move on to the verge \ marks in part 4 \ If we get here then we have a marker to draw for this \ segment LDY markersToDraw \ Set Y to the number of markers we have to draw CPY #3 \ If Y >= 3, then we already have three markers ready BCS gmar9 \ to show, which is the maximum at any one time, so \ jump to gmar9 to skip the following LDA segmentListPointer \ Set markerListIndex for marker Y to segmentListPointer STA markerListIndex,Y LDA W \ Set markerData for marker Y to the segment flags for STA markerData,Y \ this marker in W AND #1 \ If bit 0 of W is clear, then this is a straight track BEQ gmar8 \ section, so jump to gmar8 to skip the following \ instruction \ This is a curved section, so move the markers closer \ to the track edge by halving the distance that we \ store in xMarker LSR U \ Set (U T) = (U T) >> 1 ROR T .gmar8 LDA T \ Set (xMarkerHi xMarkerLo) for marker Y to (U T), so STA xMarkerLo,Y \ xMarker contains the width of the verge (halved if LDA U \ this is a corner), which we can use as the x-axis STA xMarkerHi,Y \ distance from the track verge to the marker INC markersToDraw \ Increment markersToDraw, as we have just added a new \ marker to the list
Name: GetVergeAndMarkers (Part 4 of 4) [Show more] Type: Subroutine Category: Track geometry Summary: Store details of the segment's verge marks
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.gmar9 \ The verge marks are either black-white-black-white \ or red-white-red-white, so we now work out which of \ these colours applies to this segment TXA \ If bit 0 of X is clear, then this is a non-white verge AND #%00000001 \ mark, so jump to gmar10 to set A = V to use as the BEQ gmar10 \ vergeDataRight for this segment LDA #2 \ Otherwise this is a white verge mark, so set A = 2 \ to use as the vergeDataRight for this segment BNE gmar11 \ Jump to gmar11 (this BNE is effectively a JMP as A is \ never zero) .gmar10 LDA V \ Set A = V, which is 0 (black verge mark) or 1 (red \ verge mark) .gmar11 LDY segmentListPointer \ Set Y to the index of the current entry in the track \ segment list STA vergeDataRight,Y \ Store A in the segment's corresponding vergeDataRight, \ so that's 2 for a white verge mark, 1 for a red verge \ mark, and 0 for a black verge mark LDA LL \ Set A to the segment's pitch angle, from the point \ of view of the player STA yVergeRight,Y \ Store the result in the segment's entry in yVergeRight \ to set the segment's pitch angle CMP #80 \ If the pitch angle is 80 or more, jump to gmar12 BCS gmar12 \ to return from the subroutine CMP horizonLine \ If the pitch angle is less than horizonLine, jump BCC gmar12 \ to gmar12 to return from the subroutine \ If we get here then the pitch angle in A is less \ than 80 and is greater or equal to horizonLine STA horizonLine \ This track segment is higher than the current horizon \ pitch angle, so the track obscures the horizon and we \ need to update horizonLine to this new pitch angle STY horizonListIndex \ Set horizonListIndex to the track segment number in Y .gmar12 RTS \ Return from the subroutine
Name: HideAllCars [Show more] Type: Subroutine Category: Car geometry Summary: Set all the cars to hidden
Context: See this subroutine on its own page References: This subroutine is called as follows: * FinishRace calls HideAllCars * MoveAndDrawCars calls HideAllCars
.HideAllCars LDX #22 \ We are about to process the car status bytes for \ drivers 0 to 19, plus the three extra car objects in \ 20 to 22 that make up the four-object car, so set a \ loop counter in X .hide1 LDA objectStatus,X \ Set bit 7 in the X-th byte of objectStatus to set the ORA #%10000000 \ car for driver X to be hidden STA objectStatus,X DEX \ Decrement the loop counter BPL hide1 \ Loop back until we have hidden all 23 cars RTS \ Return from the subroutine
Name: Delay [Show more] Type: Subroutine Category: Main loop Summary: Delay for a specified number of loops
Context: See this subroutine on its own page References: This subroutine is called as follows: * MoveAndDrawCars calls Delay

This routine performs T + (5 * 256) loop iterations, to create a delay. The value of T doesn't have much effect on the amount of delay, so it looks like this variable was chosen simply because it doesn't contain anything useful at this point.
.Delay LDX #6 \ Set X as the counter for the outer loop .dely1 DEC T \ Loop around for T iterations in the inner loop BNE dely1 DEX \ Loop around for X iterations in the outer loop BNE dely1 RTS \ Return from the subroutine
Name: MoveAndDrawCars [Show more] Type: Subroutine Category: Car geometry Summary: Move the cars around the track and draw any that are visible, up to a maximum of five
Context: See this subroutine on its own page References: This subroutine is called as follows: * MainDrivingLoop (Part 2 of 5) calls MoveAndDrawCars
.MoveAndDrawCars LDA qualifyingTime \ If bit 7 of qualifyingTime is set then this is a BMI Delay \ practice lap (i.e. qualifyingTime = 255), so there are \ no other cars to draw \ \ To maintain the same game speed as for races, we jump \ to Delay to pause for a while before returning from \ the subroutine using a tail call LDX positionBehind \ Set X to the position of the driver behind us LDY driversInOrder,X \ Set Y to the number of the driver in behind us LDA objectStatus,Y \ Clear bit 7 of the car object's status byte, to flag AND #%01111111 \ the car behind us as being visible STA objectStatus,Y JSR MoveCars \ Move the cars around the track JSR ProcessOvertaking \ Process overtaking manoeuvres for the non-player \ drivers JSR HideAllCars \ Set all the cars to be hidden JSR SetPlayerPositions \ Set the current player's position, plus the position \ ahead and the position behind LDX currentPosition \ Set X to the current player's position LDY #5 \ We now work our way through the five nearest cars in \ front of us, so set a loop counter in Y .dcar1 BIT directionFacing \ If bit 7 of directionFacing is clear, then we are BPL dcar2 \ facing forwards, so jump to dcar2 JSR GetPositionBehind \ We are facing backwards, so set X to the number of \ the position behind position X, to get the number of \ the car that we are looking at JMP dcar3 \ Jump to dcar3 to skip the following .dcar2 JSR GetPositionAhead \ We are facing forwards, so set X to the number of the \ position ahead of position X, to get the number of \ the car that we are looking at .dcar3 STY thisDriverNumber \ Store the loop counter in thisDriverNumber so we can \ retrieve it after the following call STX thisPosition \ Store the position of the car we are considering in \ thisPosition JSR BuildVisibleCar \ Build the car object if it is visible, so we can draw \ it below LDX thisPosition \ Retrieve the position of the car that we stored in \ thisPosition above LDY thisDriverNumber \ Retrieve the value of the loop counter that we stored \ in thisDriverNumber above DEY \ Decrement the loop counter BPL dcar1 \ Loop back until we have processed five cars in front JSR DrawCars \ Draw all the cars, with the closest car in front of us \ split into four objects LDX positionBehind \ Set X to the position of the driver behind us JSR BuildVisibleCar \ Build the car object if it is visible, so it can be \ shown in the mirror if close enough RTS \ Return from the subroutine
Name: SwapDriverPosition [Show more] Type: Subroutine Category: Drivers Summary: Swap the position for two drivers (i.e. overtake)
Context: See this subroutine on its own page References: This subroutine is called as follows: * ProcessOvertaking (Part 1 of 3) calls SwapDriverPosition * ResetVariables calls SwapDriverPosition

Arguments: X The first position Y The second position Returns: X The number of the driver now at position X Y The number of the driver now at position Y
.SwapDriverPosition LDA driversInOrder,X \ Set T to the number of the driver at position X STA T LDA driversInOrder,Y \ Set A to the number of the driver at position Y STA driversInOrder,X \ Set the driver at position X to the driver from \ position Y TAX \ Set X to the number of the driver now at position X LDA T \ Set the driver at position y to the driver from STA driversInOrder,Y \ position X TAY \ Set Y to the number of the driver now at position Y RTS \ Return from the subroutine
Name: ProcessOvertaking (Part 1 of 3) [Show more] Type: Subroutine Category: Tactics Summary: Process all cars for overtaking manoeuvres, checking first to see if the car has just finished overtaking the car in front Deep dive: Tactics of the non-player drivers
Context: See this subroutine on its own page References: This subroutine is called as follows: * FinishRace calls ProcessOvertaking * MoveAndDrawCars calls ProcessOvertaking

Returns: H Gets a 1 rotated left into bit 0 each time we process one car overtaking another
.ProcessOvertaking LDX currentPosition \ Set X to the current player's position, to use as a \ loop counter in the following as we work backwards \ through the field from this position .tact1 STX W \ Store the position number in W, so we can retrieve it \ during the loop LDA driversInOrder,X \ Set T to the number of the driver in position X STA T JSR GetPositionAhead \ Set X to the number of the position ahead of position \ X LDA driversInOrder,X \ Set G to the number of the driver in position X, i.e. \ the number of the driver ahead of driver T STX G \ Store the position number of the driver ahead in G, so \ we can retrieve it during the loop TAY \ Set Y to the number of the driver ahead of driver T LDX T \ Set X to the number of the driver we are currently \ processing \ So in the following, we are applying driving tactics \ to driver X (aka driver T) \ \ We start by comparing driver X with the driver ahead, \ driver Y \ \ Drivers X and Y are in positions W and G respectively LDA #0 \ Set N = 0, which we will use to build the car status STA N \ flags for driver X STA carSteering,X \ Set this driver's carSteering to 0, so by default \ driver X will drive straight (though we may change \ this below) JSR CompareCarSegments \ Set A to the number of segments between drivers X and \ Y (i.e. the race distance between the cars) BCS tact4 \ If the C flag is set then the cars are far apart, so \ jump to tact18 via tact4 to update the car status byte \ for driver X to N = 0, and then move on to the next \ driver BPL tact5 \ If the distance in A is positive then driver Y is \ still ahead of driver X, so jump to tact5 to apply \ tactics to driver X in part 2 \ If we get here then driver Y is not actually in front \ of this driver, despite being in a higher position, so \ we now need to check how far ahead driver X is CMP #&F6 \ If A < -10, driver X is not very far ahead of driver BCC tact4 \ Y, so we don't yet consider this a passing move, so \ jump to tact18 via tact4 to update the car status byte \ for driver X to N = 0, and then move on to the next \ driver \ If we get here then driver X has overtaken driver Y, \ and the distance between the cars is >= 10, so we need \ to swap their positions, as driver X has pulled far \ enough away for this to be considered a passing move LDX W \ Swap the drivers between positions W and G, i.e. swap LDY G \ the positions of driver X and Y, so driver X moves JSR SwapDriverPosition \ into a higher position, and set: \ \ * X = the number of the driver now at position W, \ i.e. the driver behind, previously referred to \ as driver Y \ \ * Y = the number of the driver now at position G, \ i.e. the driver ahead, previously referred to \ as driver X \ \ So now X and Y have swapped, so driver Y just passed \ driver X SEC \ Set bit 7 of updateDriverInfo so the driver names get ROR updateDriverInfo \ updated at the top of the screen, to reflect the new \ race positions CPY currentPlayer \ If driver Y (the one that just did the overtaking) BNE tact2 \ is not the current player, jump to tact2 to skip the \ following two instructions \ If we get here then the C flag is set, as the above \ comparison is equal \ Driver Y (the one that just did the overtaking) is the \ current player, so we now need to reduce the current \ player's position by 1 to move them into a higher \ position LDA #&99 \ Set A = -1 in BCD, which we can use to decrement the \ BCD number in positionChangeBCD below, so the current \ player's position will go down by 1 BNE tact3 \ Jump to tact3 (this BNE is effectively a JMP as A is \ never zero) .tact2 CPX currentPlayer \ If driver X (the one that just got overtaken) is not BNE tact4 \ the current player, jump to tact18 via tact4 to update \ the car status byte for this driver to N = 0, and then \ move on to the next driver \ If we get here then the C flag is set, as the above \ comparison is equal \ Driver Y (the one that just did the overtaking) is the \ current player, so we now need to reduce the current \ player's position by 1 to move them into a higher \ position LDA #&01 \ Set A = 1 in BCD, which we can use to increment the \ BCD number in positionChangeBCD below, so the current \ player's position will go up by 1 .tact3 STA T \ Set T = A, so A contains the position change in BCD LDA driverLapNumber,Y \ Set A to the lap number for the driver ahead ROL H \ Rotate the C flag into bit 0 of H, which we know is \ set from the comparisons above, so this rotates a 1 \ into bit 0 of H (though this doesn't appear to be used \ anywhere, so this instruction is a bit of a mystery) SBC driverLapNumber,X \ Subtract the lap number for this driver BNE tact4 \ If the drivers are on different laps, jump to tact18 \ via tact4 to update the car status byte for this \ driver to N = 0, and then move on to the next driver SED \ Set the D flag to switch arithmetic to Binary Coded \ Decimal (BCD) CLC \ Set positionChangeBCD = positionChangeBCD + T LDA T \ ADC positionChangeBCD \ so this applies the position change we calculated STA positionChangeBCD \ above to positionChangeBCD CLD \ Clear the D flag to switch arithmetic to normal .tact4 JMP tact18 \ Jump to tact18 to update the car status byte for \ driver X to N = 0, and then move on to the next driver
Name: ProcessOvertaking (Part 2 of 3) [Show more] Type: Subroutine Category: Tactics Summary: The car we are processing has not overtaken the car in front of it, so if applicable, we can keep maneouvring into position
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.tact5 \ We jump here with the distance between driver X and \ driver Y in A, which is positive as driver Y is ahead \ of driver X CMP #5 \ If A >= 5, then the cars are not very close, so jump BCS tact4 \ to tact18 via tact4 to update the car status byte for \ this driver to N = 0, and then move on to the next \ driver LDA carSpeedLo,X \ Set A to the high byte of the following subtraction: CLC \ SBC carSpeedLo,Y \ driver X speed - driver Y speed - 1 LDA carSpeedHi,X SBC carSpeedHi,Y ROR V \ Rotate the C flag into bit 7 of V, so bit 7 is set if \ driver X is going faster than driver Y (so driver X is \ catching up), and it's clear if driver Y is running \ away with it BPL tact4 \ If bit 7 of the ROR result is clear, i.e. the C flag \ is clear, i.e. if driver Y is driving as fast as or \ faster than driver X, jump to tact18 via tact4 to \ update the car status byte for this driver to N = 0, \ and then move on to the next driver \ If we get here then driver X is behind driver Y but is \ driving faster than driver Y, so we need to think \ about steering driver X \ \ We also know that A is positive, as the above \ subtraction didn't underflow LSR A \ Set A = A / 2 CMP #30 \ If A < 30, jump to tact6 to skip the following BCC tact6 \ instruction LDA #30 \ Set A = 30, so A is a maximum of 30 .tact6 CMP #4 \ If A >= 4, jump to tact7 to skip the following BCS tact7 \ instruction LDA #4 \ Set A = 4, so A is a minimum of 4 .tact7 STA SS \ Store A in SS, which we will use below as the amount \ of steering to apply, in the range 4 to 30, with more \ steering being applied when the cars have a bigger \ speed gap LDA T \ Set A to the number of the driver we are currently \ processing, which we stored in T in part 1 (though the \ same number is still in X, so this could be done more \ efficiently with a TXA instruction) CMP #4 \ Set the C flag if A >= 4, clear the C flag if A < 4 LDA carStatus,Y \ Set A to just bit 6 of driver Y's car status byte, AND #%01000000 \ which is the acceleration flag BEQ tact9 \ If bit 6 of driver Y's car status byte is clear, then \ driver Y is not accelerating, so jump to tact9 \ If we get here then driver Y is accelerating, so we \ get driver X to follow driver Y's racing line BCS tact8 \ If the C flag is set, then the driver number of the \ driver we are currently processing is 4 or greater, so \ jump to tact8 to skip the following instruction ORA #%10000000 \ We are currently processing one of drivers 0 to 3, who \ are the four best drivers, so set bit 7 of A to apply \ the brakes, overriding the acceleration flag in bit 6 .tact8 STA N \ Store the updated flags in N, so bit 6 of driver X \ matches driver Y's bit 6 (so their acceleration status \ matches), and bit 7 (braking) set if X = 0 to 3 LDA carRacingLine,X \ If the racing line for driver X >= the racing line for CMP carRacingLine,Y \ driver Y, set the C flag, otherwise clear it \ \ In other words, the C flag is set if driver X is to \ the left of driver Y ROR T \ Rotate the C flag into bit 7 of T, so we can use this \ bit to determine the direction that driver X should \ steer \ \ This steers driver X to the right (bit 7 set) when \ driver X is to the left of driver Y - in other words, \ it steers driver X towards driver Y's racing line, \ into driver Y's slipstream JMP tact15 \ Jump to tact15 to apply the steering direction in T to \ the amount of steering in SS .tact9 \ If we get here then driver Y is not accelerating, so \ we get driver X to move to overtake driver Y BCS tact10 \ If the C flag is set, then the driver number of the \ driver we are currently processing is 4 or greater, so \ jump to tact10 LDA #%01000000 \ Set N so it only has bit 6 set (so driver X will be STA N \ set to accelerate) LDA carRacingLine,Y \ If the racing line for driver Y >= the racing line for CMP carRacingLine,X \ driver X, set the C flag, otherwise clear it \ \ In other words, the C flag is set if driver X is to \ the right of driver Y ROR T \ Rotate the C flag into bit 7 of T, so we can use this \ bit to determine the direction that driver X should \ steer \ \ This steers driver X to the right (bit 7 set) when \ driver X is to the right of driver Y - in other words, \ it steers driver X away from driver Y's racing line, \ into an overtaking position AND #&FF \ This instruction doesn't change any values, but it \ does set the N flag according to the current value of \ A, so the call to Absolute8Bit will return |A| rather \ than being affected by the result of the ROR \ instruction JSR Absolute8Bit \ Set A = |A| \ \ As A is the racing line, this gives the distance of \ driver Y from the edge of the track, with 0 being on \ the verge, and 127 being in the centre CMP #60 \ If A < 60, then driver Y is close to the verge, so BCC tact11 \ jump to tact11 to steer driver X into the other half \ of the track to driver Y BCS tact12 \ Otherwise we steer driver X away from driver Y's \ racing line, by jumping to tact12 (this BCS is \ effectively a JMP as we just passed through a BCC) .tact10 LSR V \ Clear bit 7 of V, so we never end up applying the \ brakes in tact12 or tact14 below .tact11 LDA carRacingLine,Y \ Set T to the racing line for driver Y, specifically STA T \ so we can extract the top bit to determine which side \ of the track driver Y is on (0 = right, 1 = left), \ and to steer in the opposite direction .tact12 LDA objectStatus,X \ If bit 7 of driver X's object status byte is clear, BPL tact13 \ then the car is visible, so jump to tact13 \ If we get here then driver X is not visible 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 #31 \ Reduce the random number to the range 0 to 31 BNE tact18 \ If A is non-zero, jump to tact18 to update the car \ status byte for this driver to N, and then move on \ to the next driver \ If we get here then A is zero, which has a 3.125% \ chance of happening LDA V \ Set A to N, but with bit 7 set to bit 7 of V, so bit 7 AND #%10000000 \ of N gets set if driver X is going faster than driver ORA N \ Y, which means driver X slams on the brakes JMP tact17 \ Jump to tact17 to update the car status byte for this \ driver to the value of A, and then move on to the next \ driver .tact13 \ If we get here then driver X is visible LDA carRacingLine,Y \ Set A to the difference between the racing lines for SEC \ driver X and driver Y SBC carRacingLine,X BCS tact14 \ If the subtraction didn't underflow, jump to tact14 to \ skip the following instruction EOR #&FF \ The subtraction underflowed, so flip all the bits in \ the result to change it from negative to positive, so \ A contains the difference between the two drivers' \ racing lines, made positive .tact14 CMP #100 \ If A >= 100, then the cars are far apart in terms of BCS tact18 \ left-right spacing, so jump to tact18 to skip applying \ any steering, and instead just update the car status \ byte for this driver to N, before moving on to the \ next driver CMP #80 \ If A >= 80, then the cars are slightly closer, so jump BCS tact16 \ to tact16 to set bit 4 of driver X's car status byte \ (so driver X applies the steering we've been \ calculating) CMP #60 \ If A >= 60, then the cars are even closer, so jump to BCS tact15 \ tact15 to steer driver X in the direction in T, which \ we set above to the side of the track driver Y is on \ (0 = right, 1 = left) \ \ This therefore steers driver X away from driver Y, as \ in terms of steering 0 = steer left, 1 = steer right \ If we get here then the cars are really close, so we \ get driver X to slam on the brakes, as well as \ steering away from driver Y LDA V \ Store the bit 7 of V in bit 7 of the car status flag AND #%10000000 \ byte we are building in N, so driver X applies the ORA N \ brakes when bit 7 of V is set STA N .tact15 LDA T \ Set the steering for this driver to SS, with the top AND #%10000000 \ bit (i.e. the direction of the steering) set to the ORA SS \ sign bit of T (0 = steer left, 1 = steer right) STA carSteering,X .tact16 LDA N \ Set bit 4 of the car status flag byte we are building ORA #%00010000 \ in N, so the car does not automatically follow the \ segment's steering line in segmentSteering, and \ instead applies the steering we've been calculating .tact17 STA N \ Store A in N to use as the car status flags for this \ driver
Name: ProcessOvertaking (Part 3 of 3) [Show more] Type: Subroutine Category: Tactics Summary: Update the car status (if configured) and loop back for the next car
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.tact18 LDA carStatus,X \ Set the C flag to bit 0 of the car status flags for LSR A \ this driver LDA N \ Set A = N, to use as the new car status flags for this \ driver, but only when bit 0 of the current flag byte \ is set BCS tact19 \ If the C flag is set, i.e. bit 0 of the car status \ flags for this driver is set, jump to tact19 to skip \ the following instruction STA carStatus,X \ Set the car status flags for this driver to A (i.e. to \ the flags in N) .tact19 LDX W \ Set X to the position that we just checked, which we \ stored in W at the start of the loop JSR GetPositionBehind \ Set X to the number of the position behind position X, \ so we work backwards through the field CPX currentPosition \ If X = the current player's position, jump to tact20 BEQ tact20 \ to return from the subroutine JMP tact1 \ Otherwise jump back to tact1 to process the next \ driver in X .tact20 RTS \ Return from the subroutine
Name: CompareCarSegments [Show more] Type: Subroutine Category: Car geometry Summary: Calculate the distance between two cars, in terms of segments and progress with the current segment Deep dive: Placing cars on the track
Context: See this subroutine on its own page References: This subroutine is called as follows: * ProcessOvertaking (Part 1 of 3) calls CompareCarSegments

Extends the distance calculation in CompareSegments by rounding the segment numbers used in the calculations, depending on the cars' progress within their current segments. Arguments: X The number of driver X Y The number of driver Y
.CompareCarSegments LDA carProgress,Y \ Set the C flag according to the subtraction: SEC \ SBC carProgress,X \ carProgress for driver ahead \ - carProgress for this driver \ \ The progress figures act like a fractional byte \ in the subtraction at the start of the following \ routine, though because only the C flag is kept, they \ only serve to round the result to the nearest integer, \ rather than giving a full 24-bit result \ Fall through into CompareSegments to calculate the \ distance between the two cars, rounding the result \ according to the difference in the cars' progress \ values
Name: CompareSegments [Show more] Type: Subroutine Category: 3D objects Summary: Calculate the distance between two objects, in terms of the difference in their segment numbers Deep dive: Placing cars on the track
Context: See this subroutine on its own page References: This subroutine is called as follows: * BuildVisibleCar calls CompareSegments * PlaceCarsOnTrack calls CompareSegments

Arguments: X The number of object X Y The number of object Y C flag Determines the accuracy of the arithmetic: * Clear for a 16-bit calculation using objectSegment * For a 24-bit calculation, contains the carry from CompareCarSegments above Returns: A The distance between the two objects, negative if object X is ahead, positive if object Y is ahead T The same as A C flag How far apart the objects are: * Set if objects are far apart (distance >= 128) * Clear if they are close (distance < 128) N flag The object order (if the objects are close): * Set if object X is ahead by more than 256 * Clear otherwise H Relationship to the starting line: * Bit 7 is clear if the object are quite close (when distance < 256) but are on opposite sides of the starting line * Bit 7 set otherwise
.CompareSegments LDA objectSegmentLo,Y \ Set (A T) = objectSegment for object Y SBC objectSegmentLo,X \ - objectSegment for object X STA T \ \ starting with the low bytes LDA objectSegmentHi,Y \ And then the high bytes SBC objectSegmentHi,X \ \ So (A T) now contains the difference between the two \ objects in terms of their segment numbers, i.e. the \ distance between the two in terms of segment numbers \ \ Let's call this dSegments PHP \ Store the status register on the stack, so the N flag \ on the stack is the sign of the above subtraction, so \ it's set if objectSegmentHi for object Y < \ objectSegmentHi for object X, i.e. if object X is \ ahead by 256 segments or more BPL dist1 \ If the result of the subtraction was positive, jump \ to dist1 to skip the following instruction JSR Absolute16Bit \ The result of the subtraction was negative, so set \ (A T) = |A T| .dist1 STA U \ Set (U T) = (A T) \ = |dSegments| SEC \ Set the C flag to shift into bit 7 of H below BEQ dist2 \ If the high byte of the segment difference is zero, \ jump to dist2 to check the low byte \ If we get here then the high byte of the difference \ is non-zero, so now we need to check whether this is \ down to the objects being close but either side of the \ starting line \ \ This is because the segment number resets to zero at \ the starting line, so objects that are on either side \ of the line will have a big difference in their \ segment numbers even though they are actually quite \ close together PLA \ Flip the N flag in the status register on the stack EOR #%10000000 \ (as the N flag is bit 7 of the status register), so PHP \ the N flag on the stack is the opposite sign to the \ objectSegment subtraction we did above, in other words \ it's now clear if object X is ahead by 256 or more and \ set otherwise \ \ This caters for the situation where the cars are close \ but on either side of the starting line, in which case \ the subtraction we did above will give the wrong \ result, so this flips the order of the two objects to \ be correct \ In the following, the 16-bit number at trackLength \ contains the length of the full track in terms of \ segments LDA trackLength \ Set (A T) = trackLength - (U T) SEC \ = trackLength - |dSegments| SBC T \ STA T \ starting with the low bytes LDA trackLength+1 \ And then the high bytes SBC U BNE dist3 \ If the high byte is non-zero, then that means the \ objects are not just either side of the starting line, \ so they must be far away from each other, so jump to \ dist3 to return from the subroutine with the C flag \ set \ If we get here then the high byte is zero, which means \ the objects are quite close but are either side of the \ starting line CLC \ Clear the C flag .dist2 \ If we get here then the objects are close together \ (the high byte of the difference in segment numbers is \ zero) ROR H \ Set bit 7 of H to the C flag, so it's clear if the \ objects are quite close but on opposite sides of the \ starting line, otherwise it is set LDA T \ If T >= 128, then the difference between the objects CMP #128 \ is >= 128 segments, so jump to dist3 to return from BCS dist3 \ the subroutine with the C flag set \ If we get here then the difference between the objects \ is 127 or less, so the objects are determined to be \ close PLP \ Retrieve the status flags from the stack JSR Absolute8Bit \ Multiply the sign of A by the N flag STA T \ Set T = A LDA T \ Set the N flag according to the value of A CLC \ Clear the C flag RTS \ Return from the subroutine .dist3 \ We get here if the objects are far away from each \ other, i.e. the segment difference between them is \ >= 128 PLP \ Retrieve the status flags from the stack, to return \ from the subroutine SEC \ Set the C flag RTS \ Return from the subroutine
Name: MoveCars (Part 1 of 2) [Show more] Type: Subroutine Category: Car geometry Summary: Move the cars around the track Deep dive: Placing cars on the track Tactics of the non-player drivers
Context: See this subroutine on its own page References: This subroutine is called as follows: * FinishRace calls MoveCars * MoveAndDrawCars calls MoveCars * MoveCars (Part 1 of 2) calls entry point MoveCars-1

This part changes each car's speed. It calculates the speed change in (U A), and then applies it to the car's speed. It then moves the car round the track by the speed we just calculated by updating carProgress. Other entry points: MoveCars-1 Contains an RTS
.MoveCars LDA raceStarting \ If bit 7 of raceStarting is set, then the race is in BMI MoveCars-1 \ the process of starting and we are on the starting \ grid, so the cars are not allowed to move, so return \ from the subroutine (as MoveCars-1 contains an RTS) LDX #20 \ Set X = 20 to use as a loop counter as we work through \ all 20 cars JMP mcar20 \ Jump into the loop at mcar20 to decrement X and start \ looping through the drivers, looping back to mcar1 for \ all drivers except the current player .mcar1 \ This first part of the loop changes the car's speed as \ required LDA carStatus,X \ If bit 7 of driver X's carStatus is set, jump to BMI mcar8 \ mcar8 to apply the brakes LDY objTrackSection,X \ Set Y to the track section number * 8 for driver X LDA trackSectionFlag,Y \ Set A to the flag byte for the driver's track section BPL mcar2 \ If bit 7 of this section's flag byte is clear, jump to \ mcar2 \ If we get here then bit 7 of this section's flag byte \ is set LDA carSpeedHi,X \ If the high byte of the car's speed >= the car's CMP carSectionSpeed,X \ carSectionSpeed then jump to mcar11 to skip changing BCS mcar11 \ the car's speed, as it is already going fast enough \ for the section BCC mcar3 \ Otherwise jump to mcar3 to continue with the speed \ calculation (this BCC is effectively a JMP as we just \ passed through a BCS) .mcar2 \ If we get here then bit 7 of this section's flag byte \ is clear, and A contains this section's flag byte LSR A \ Set the C flag to bit 0 of A, i.e. to bit 0 of this \ section's flag byte BCS mcar3 \ If bit 0 of this section's flag byte is set, then this \ is a curved section, so jump to mcar3 to continue with \ the speed calculation \ If we get here then bits 0 and 7 of this section's \ flag byte are both clear, so this is a straight \ section and there is no maximum speed set LDA trackDriverSpeed,Y \ Set carSectionSpeed for this driver to the value of STA carSectionSpeed,X \ trackDriverSpeed for this track section CLC \ Set A = trackDriverSpeed - carSpeedHi - 1 SBC carSpeedHi,X BCS mcar3 \ If the subtraction didn't underflow, then \ trackDriverSpeed > carSpeedHi, so jump to mcar3 to \ continue with the speed calculation LSR A \ Set T = A >> 2 with bits 6 and 7 set LSR A \ ORA #%11000000 \ As A is negative, this divides A by 4 while keeping STA T \ the sign, so: \ \ T = A / 4 \ = (trackDriverSpeed - carSpeedHi - 1) / 4 LDA objSectionSegmt,X \ Set A = objSectionSegmt - trackSectionTurn SEC \ SBC trackSectionTurn,Y \ so this takes the driver's current segment number in \ the track section and subtracts the trackSectionTurn \ for this track section BCS mcar11 \ If the subtraction didn't underflow, then \ objSectionSegmt >= trackSectionTurn, so jump to mcar11 \ to skip changing the car's speed CMP T \ If A >= T, jump to mcar8 to apply the brakes BCS mcar8 .mcar3 \ We now set A and T to use in the calculation to work \ out the level of acceleration we need to apply to the \ car's speed LDA carSpeedHi,X \ Set A to the high byte of the car's current speed CMP #60 \ If the high byte of the car's speed in A >= 60, jump BCS mcar4 \ to mcar4 to skip the following instruction LDA #22 \ Set A = 22 .mcar4 \ By this point, A is either 22 or >= 60 STA T \ Set T = A LDA carStatus,X \ If bit 6 of driver X's carStatus is clear, then we do AND #%01000000 \ not accelerate the car, so jump to mcar5 with A = 0 BEQ mcar5 LDA #5 \ Set A = 5 .mcar5 \ By this point \ \ * T is carSpeedHi, reduced to 22 if < 60 \ \ * A is either 0 or 5, depending on bit 6 of driver \ X's carStatus, i.e. whether the car is set to be \ accelerating CLC \ Set A = A + the driver's average speed, as calculated ADC driverSpeed,X \ in the SetDriverSpeed routine \ So by this point: \ \ * T is carSpeedHi, reduced to 22 if < 60 \ \ * A is driver X's average speed, + 5 if driver X is \ accelerating \ We now apply any trackRaceSlowdown factor from the \ track data, which allows us to slow down races for \ debugging purposes (trackRaceSlowdown is set to 0 in \ in the Silverstone track, so this has no effect) BIT raceStarted \ If bit 7 of raceStarted is clear then this is practice BPL mcar6 \ or qualifying, so jump to mcar6 to skip the following \ instruction SBC trackRaceSlowdown \ This is a race, so set A = A - trackRaceSlowdown \ \ The value of trackRaceSlowdown for the Silverstone \ track is zero, so this has no effect, but if it were \ non-zero then it would reduce the speed of all cars \ in a race by that amount .mcar6 \ We now calculate (U A) = A - T to get the speed change \ to apply to the driver, given the following values: \ \ * T is carSpeedHi, reduced to 22 if < 60 \ \ * A is driver X's average speed, + 5 if driver X is \ accelerating, reduced by trackRaceSlowdown if this \ is a race \ \ In other words, T is the current speed, while A is the \ speed we should be aiming for, so \ \ (U A) = A - T \ \ will give us the delta that we need to apply to the \ car's current speed to get to the new speed, with the \ acceleration much higher when the car's current speed \ is < 60 (when T is reduced to 22) \ \ So cars can't accelerate fast once they pass a certain \ speed (carSpeedHi >= 60) LDY #0 \ Set Y = 0, so (Y A) = A SEC \ Set (Y A) = (Y A) - T SBC T \ = A - T \ \ starting with the low bytes BCS mcar7 \ And then the high bytes DEY .mcar7 STY U \ Set (U A) = (Y A) \ = A - T JMP mcar9 \ Jump to mcar9 .mcar8 \ If we get here then we are applying the brakes, so \ set (U A) to -256 so we subtract 1024 from the speed \ in the following LDA #&FF \ Set (U A) = -256 (&FF00) STA U \ LDA #0 \ so the following adds -1024 to the car speed .mcar9 \ By this point we have calculated the speed change in \ (U A), so now we apply it ASL A \ Set (U A) = (U A) * 4 ROL U ASL A ROL U CLC \ Set (A carSpeedLo) = (carSpeedHi carSpeedLo) + (U A) ADC carSpeedLo,X \ STA carSpeedLo,X \ starting with the low bytes LDA U \ And then the high bytes ADC carSpeedHi,X CMP #190 \ If the high byte in A < 190, jump to mcar10 to store BCC mcar10 \ the result in carSpeedHi LDA #0 \ Otherwise the car's speed is now a negative value, so STA carSpeedLo,X \ we zero the car's speed, starting with the low byte, \ and setting A = 0 so we also zero the high byte in the \ next instruction .mcar10 STA carSpeedHi,X \ Update the high byte of the car's speed to A .mcar11 LDA #1 \ Set V = 1, so we do the following loop twice, which STA V \ updates the car's progress by 2 x carSpeedHi .mcar12 LDA carSpeedHi,X \ Add carSpeedHi to carProgress to move the car along CLC \ the track by its speed ADC carProgress,X STA carProgress,X BCC mcar13 \ If the addition didn't overflow, jump to mcar13 to do \ the next loop JSR MoveObjectForward \ The addition overflowed, so carProgress has filled up \ and we need to move the car forwards by one segment .mcar13 DEC V \ Decrement the loop counter in V BPL mcar12 \ Loop back until we have added carSpeedHi twice
Name: MoveCars (Part 2 of 2) [Show more] Type: Subroutine Category: Car geometry Summary: Move the cars forward around the track, and apply steering Deep dive: Placing cars on the track Tactics of the non-player drivers
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

This part moves the car round the track by the speed we just calculated, and applies steering if required. The steering algorithm works as follows: * Only apply steering if the car is visible. * If any of the following are true, apply the amount of steering given in carSteering: * Bit 6 of the car's objectStatus is set * Bit 6 of the car's carSteering is clear * Bit 7 of the car's racing line = bit 7 of carSteering, so: * Car is in the right half and is steering left (when bit 7 is clear) * Car is in the left half and is steering right (when bit 7 is set) * The car is not within 30 of either verge, 30 <= carRacingLine < 226 * Otherwise, if the car's racing line is within 20 of either verge, steer away from the verge by one.
LDA objectStatus,X \ If bit 7 of the driver's car object status byte is ASL A \ set, then the car is not visible, so jump to mar20 to BCS mcar20 \ move on to the next car BMI mcar17 \ If bit 6 of the driver's car object status byte is \ set, then the car has finished racing, so jump to \ mcar17 to steer the car LDA carSteering,X \ If bit 6 of the car's carSteering is clear, jump to AND #%01000000 \ mcar17 to steer the car BEQ mcar17 LDA carRacingLine,X \ If bit 7 of the car's racing line and carSteering are EOR carSteering,X \ the same (so the car is steering into the opposite BPL mcar17 \ half of the track), jump to mcar17 to steer the car LDA carRacingLine,X \ If the car's racing line < 128, then the car is in the BPL mcar15 \ right half of the track, so jump to mcar15 to check \ how close it is to the right verge CMP #236 \ If the car's racing line < 236, jump to mcar14 BCC mcar14 \ If we get here then the car's racing line >= 236, \ which is very close to the left verge, so we steer a \ little to the right DEC carRacingLine,X \ Steer the car a little to the right BCS mcar20 \ Jump to mcar20 (this BCS is effectively a JMP as we \ just passed through a BCC) .mcar14 CMP #226 \ If the car's racing line < 226, jump to mcar17 to BCC mcar17 \ steer the car \ If we get here, then the car's racing line >= 226, \ which is quite close to the left verge BCS mcar20 \ Jump to mcar20 (this BCS is effectively a JMP as we \ just passed through a BCC) .mcar15 CMP #20 \ If the car's racing line >= 20, jump to mcar16 BCS mcar16 \ If we get here then the car's racing line < 20, which \ is very close to the right verge, so we steer a little \ to the left INC carRacingLine,X \ Steer the car a little to the left BCC mcar20 \ Jump to mcar20 (this BCC is effectively a JMP as we \ just passed through a BCS) .mcar16 CMP #30 \ If the car's racing line < 30, jump to mcar20 to move BCC mcar20 \ on to the next driver .mcar17 \ If we get here, then we need to apply the steering \ given in carSteering \ \ carSteering is a sign-magnitude number, where bit 7 is \ the sign and bits 0-5 contain the magnitude, so before \ we can apply the steering, we need to convert it into \ a signed number LDA carSteering,X \ Set A to the car's carSteering AND #%10111111 \ Clear bit 6 CLC \ Clear the C flag in preparation for the addition below BPL mcar18 \ If bit 7 of A is clear, then the amount of steering is \ positive and the number is already correct, so jump to \ mcar18 to add it to the racing line \ If we get here then the sign-magnitude number in A is \ negative, so we need to convert this into a signed \ number \ \ We do this by first setting bit 6 (which we just \ cleared above) so bits 6 and 7 are both set, and we \ then need to flip bits 0-5, to convert the positive \ magnitude into its negative equivalent \ \ We can do this in one EOR instruction, as follows EOR #%01111111 \ Set bit 6 of A and flip bits 0-5, so A is now a \ negative number that we can add to the racing line in \ order to subtract the correct amount of steering ADC carRacingLine,X \ Steer the car right by the amount in A BCS mcar19 \ If the subtraction didn't underflow then we are still \ on the track, so jump to mcar19 to update the racing \ line with the new figure BCC mcar20 \ Otherwise the subtraction just underflowed, which \ means we just steered the car off the track, so jump \ to mcar20 so we don't update the car's racing line \ (this BCC is effectively a JMP as we just passed \ through a BCS) .mcar18 ADC carRacingLine,X \ Steer the car left by the amount in A BCS mcar20 \ If the addition just overflowed then this would steer \ the car off the track, so jump to mcar20 so we don't \ update the car's racing line (so we don't actually do \ the steer) .mcar19 STA carRacingLine,X \ Update the car's racing line with the updated figure, \ to steer the car across the track .mcar20 DEX \ Decrement the loop counter to point to the next driver BMI mcar21 \ If we have worked our way through all 20 drivers, jump \ to mcar21 to return from the subroutine CPX currentPlayer \ If driver X is the current player, jump up to mcar20 BEQ mcar20 \ to move on to the next driver JMP mcar1 \ Jump up to mcar1 to process the next driver .mcar21 RTS \ Return from the subroutine
Name: BuildVisibleCar [Show more] Type: Subroutine Category: 3D objects Summary: Check the distance to the specified car and build the car object if it is close enough Deep dive: Tactics of the non-player drivers
Context: See this subroutine on its own page References: This subroutine is called as follows: * MoveAndDrawCars calls BuildVisibleCar

Arguments: X The position of the driver whose distance we want to check
.BuildVisibleCar LDA driversInOrder,X \ Set A to the number of the driver in position X STA thisDriver \ Store the driver number in thisDriver so we can \ retrieve it later STA objectNumber \ Store the driver number in objectNumber, in case we \ need to hide this driver's car below TAX \ Set X to the driver number LDY #23 \ Set Y to 23, the object number we use to store the \ front segment of the track segment buffer SEC \ Set the C flag for a 16-bit calculation in the call \ to CompareSegments JSR CompareSegments \ Set A and T to the distance between driver X and \ the front segment in the track segment buffer in \ object Y BCS bvis1 \ If the C flag is set then the two cars are far apart, \ i.e. |T| < 128, so jump to bvis1 to hide the car EOR directionFacing \ This tests whether bit 7 of directionFacing and bit 7 BMI bvis1 \ of the distance in A are different, which will happen \ if either of the following is true: \ \ * We are facing forwards (0) and driver X is ahead \ of the front segment in the track segment buffer \ (1) \ \ * We are facing backwards (1) and driver X is not \ ahead of the front segment in the track segment \ buffer (0) \ \ In both cases driver X is too far away from us to be \ seen, and bit 7 of the result of the EOR will be set, \ so jump to bvis1 to hide the car LDA T \ Set T = |T| JSR Absolute8Bit \ STA T \ so A and T contain the absolute value of the distance \ between the car and the front segment in the track \ segment buffer CMP #40 \ If |A| < 40, jump to bvis2 to skip the following BCC bvis2 \ instruction and continue creating the car object \ If we get here then the car and the front segment in \ the track segment buffer are far apart, so we hide the \ car .bvis1 JMP HideObject \ Hide the object in objectNumber, which this hides the \ car object for driver X, and return from the \ subroutine using a tail call .bvis2 \ If we get here, A and T contain the absolute value of \ the distance between the car and the front segment in \ the track segment buffer ASL A \ Set A = A * 2 \ = distance * 2 CLC \ Set A = ~(A + T) ADC T \ = ~(distance * 2 + distance) EOR #&FF \ = ~(distance * 3) SEC \ Set A = A + 1 + frontSegmentIndex ADC frontSegmentIndex \ = ~(distance * 3) + 1 + frontSegmentIndex \ = -(distance * 3) + frontSegmentIndex \ = frontSegmentIndex - distance * 3 \ \ frontSegmentIndex contains the index * 3 of the front \ segment in the track segment buffer, so this is the \ same as: \ \ (front segment index - distance) * 3 BPL bvis3 \ If the result was positive, i.e. track segment index \ > distance, jump to bvis3 to skip the following CLC \ Otherwise set A = A + 120 ADC #120 .bvis3 TAY \ Copy the result from A into Y, so Y now contains the \ track segment index * 3 of the track segment for the \ car object LDA carStatus,X \ If bit 4 of driver X's carStatus is set, then tactics AND #%00010000 \ are not enabled for this car, so jump to BNE BuildCarObjects \ BuildCarObjects to skip setting the car's steering LDA carSpeedHi,X \ If the high byte of driver X's speed is less than 50, CMP #50 \ jump to BuildCarObjects BCC BuildCarObjects LDA segmentSteering,Y \ Set the driver's carSteering to the segmentSteering STA carSteering,X \ value for this track segment, so the car follows the \ curve of the track
Name: BuildCarObjects (Part 1 of 3) [Show more] Type: Subroutine Category: 3D objects Summary: Calculate the 3D coordinate of the specified car Deep dive: Drawing a 3D car from 2D parts
Context: See this subroutine on its own page References: This subroutine is called as follows: * BuildPlayerCar calls BuildCarObjects * BuildVisibleCar calls BuildCarObjects

This routine calculates the 3D coordinate of the specified car, given its progress through the current segment and the racing line, as follows: [ xCoord2 ] [ xSegmentCoordI ] [ xTrackSegmentI ] [ yCoord2 ] = [ ySegmentCoordI mod 32 ] + [ yTrackSegmentI ] * carProgress [ zCoord2 ] [ zSegmentCoordI ] [ zTrackSegmentI ] [ xTrackSegmentO ] + [ 0 ] * carRacingLine * 4 [ zTrackSegmentO ] [ 0 ] + [ 144 ] [ 0 ] In the above: * xTrackSegmentI is the inner segment vector for the car's position * xTrackSegmentO is the outer segment vector for the car's position * xSegmentCoordI is the coordinate for the start of the car's track segment The routine then calculates the yaw and pitch angles for the car object (or objects). This part calculates the 3D coordinate of the car along the inside edge of the track, i.e. the first part of the above: [ xCoord2 ] [ xSegmentCoordI ] [ xTrackSegmentI ] [ yCoord2 ] = [ ySegmentCoordI mod 32 ] + [ yTrackSegmentI ] * carProgress [ zCoord2 ] [ zSegmentCoordI ] [ zTrackSegmentI ] The mod 32 part caps the y-coordinate to a maximum of 8192. Arguments: X The driver number of the car object to build thisDriver Same as X Y The index * 3 of the track segment to use for the calculation Returns: X X is set to the driver number in thisDriver xCoord2 Contains the object's 3D coordinates (for the one-object car) or the coordinates of the rear tyres (for the four-object car objYawAngle The object's yaw angle
.BuildCarObjects LDA segmentVector,Y \ Fetch the segment vector number for track segment Y, \ which gives us the segment vector number of the car \ object we want to build STA vectorNumber \ Store the segment vector number in vectorNumber so we \ can retrieve it in parts 2 and 3 STY T \ Store the index * 3 of the track segment in T TAY \ Set Y to the segment vector number of the car object LDA carProgress,X \ Set TT to the lowest byte of the car's progress STA TT \ through the current segment LDA carRacingLine,X \ Set UU to the car's current racing line STA UU LDA xTrackSegmentI,Y \ Set VV to the 3D x-coordinate of the inner segment STA VV \ vector for the car object LDA yTrackSegmentI,Y \ Set VV+1 to the 3D y-coordinate of the inner segment STA VV+1 \ vector for the car object LDA zTrackSegmentI,Y \ Set VV+2 to the 3D z-coordinate of the inner segment STA VV+2 \ vector for the car object \ We now calculate the following: \ \ xCoord2 = xSegmentCoordI \ + xTrackSegmentI * carProgress LDX #0 \ We are about to work our way through the three axes, \ so set X = 0 to use as an axis counter, working \ through the three axes x, y, z using X = 0, 1, 2 \ \ The comments below are for the x-axis LDA TT \ Set U = TT STA U \ = the lowest byte of the car's progress through \ the current segment LDY T \ Set Y to the index * 3 of the track segment that we \ stored above .bcar1 LDA #0 \ Set (V A) = 0 STA V LDA VV,X \ Set A to the x-coordinate of the inner segment vector \ We now calculate (V A T) = A * U, making sure we get \ the signs right BPL bcar2 \ If A is positive, jump to bcar2 to multiply A and U as \ they are \ If we get here then A is negative, so we need to apply \ the correct sign to the multiplication EOR #&FF \ Negate A (so it is now positive) CLC ADC #1 JSR Multiply8x8 \ Set (A T) = A * U EOR #&FF \ Negate A again (so it is now the correct sign for the CLC \ multiplication) ADC #1 BCS bcar3 \ If the addition just overflowed, then the result is \ now positive, which means V is already the correct \ high byte for (V A T), so jump to bcar3 DEC V \ Otherwise, decrement V to &FF so it's the correct \ high byte for (V A T) BCC bcar3 \ Jump to bcar3 (this BCC is effectively a JMP as we \ just passed through a BCS) .bcar2 JSR Multiply8x8 \ Set (A T) = A * U .bcar3 \ By this point, we have the following, signed result: \ \ (V A T) = A * U \ = xTrackSegmentI * carProgress \ \ We now add (V A) to the Y-th entry in xSegmentCoordI, \ which is the xSegmentCoordI entry for the track \ segment passed to the routine (Y contains the \ index * 3 of the track segment), and store the result \ in xCoord2 \ \ For the y-axis of the coordinate, i.e. for the \ multiplication: \ \ yCoord2 = ySegmentCoordI + (V A) \ \ then we add ySegmentCoordIHi mod 32 instead of \ ySegmentCoordIHi CLC \ Set (xCoord2Hi xCoord2Lo) ADC xSegmentCoordILo,Y \ = (xSegmentCoordIHi xSegmentCoordILo) + (V A) STA xCoord2Lo,X \ \ starting with the low bytes LDA xSegmentCoordIHi,Y \ And then the high bytes (though with a short interlude \ for when X = 1, when we add ySegmentCoordIHi mod 32 \ instead) PHP \ Store the C flag on the stack so we can retrieve it \ when adding the high bytes below CPX #1 \ If X = 1, set A = A mod 32 BNE bcar4 \ = ySegmentCoordIHi mod 32 AND #31 \ \ This caps the y-coordinate to a maximum of 8192 .bcar4 PLP \ Now we can finally add the high bytes ADC V STA xCoord2Hi,X INY \ Increment the axis pointer for xSegmentCoordI INX \ Increment the axis pointer for xCoord2 CPX #3 \ Loop back until X has looped through all three axes BNE bcar1
Name: BuildCarObjects (Part 2 of 3) [Show more] Type: Subroutine Category: 3D objects Summary: Add the racing line to the 3D coordinate of the specified car Deep dive: Drawing a 3D car from 2D parts
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

This part adds in the vector from the inside edge of the track to the car, i.e. the second part of the above: [ xCoord2 ] [ xCoord2 ] [ xTrackSegmentO ] [ yCoord2 ] = [ yCoord2 ] + [ 0 ] * carRacingLine * 4 [ zCoord2 ] [ zCoord2 ] [ zTrackSegmentO ] [ 0 ] + [ 144 ] [ 0 ]
\ We start by calculating the following: \ \ xCoord2 = xSegmentCoordI \ + xTrackSegmentO * carRacingLine * 4 \ \ for the x-axis and z-axis only LDY vectorNumber \ Set Y to the segment vector number that we stored \ above LDA xTrackSegmentO,Y \ Set VV to the 3D x-coordinate of the outer segment STA VV \ vector for the car object LDA zTrackSegmentO,Y \ Set VV+2 to the 3D x-coordinate of the outer segment STA VV+2 \ vector for the car object \ Note that VV+1 still contains the y-coordinate for the \ inner segment vector, which we can reuse as the height \ of the track from side-to-side is always the same, \ i.e. yTrackSegmentI = yTrackSegmentO for the same \ vector which means the track is always level along the \ y-axis LDX #0 \ We are about to work our way through the three axes, \ so set X = 0 to use as an axis counter, working \ through the three axes x, y, z using X = 0, 1, 2 \ \ The comments below are for the x-axis LDA UU \ Set U = UU STA U \ = the car's current racing line .bcar5 LDA #0 \ Set (V A) = 0 STA V LDA VV,X \ Set A to the x-coordinate of the outer segment vector \ We now calculate (V A T) = A * U, making sure we get \ the signs right BPL bcar6 \ If A is positive, jump to bcar6 to multiply A and U as \ they are \ If we get here then A is negative, so we need to apply \ the correct sign to the multiplication EOR #&FF \ Negate A (so it is now positive) CLC ADC #1 JSR Multiply8x8 \ Set (A T) = A * U EOR #&FF \ Negate A again (so it is now the correct sign for the CLC \ multiplication) ADC #1 BCS bcar7 \ If the addition just overflowed, then the result is \ now positive, which means V is already the correct \ high byte for (V A T), so jump to bcar7 DEC V \ Otherwise, decrement V to &FF so it's the correct \ high byte for (V A T) BCC bcar7 \ Jump to bcar7 (this BCC is effectively a JMP as we \ just passed through a BCS) .bcar6 JSR Multiply8x8 \ Set (A T) = A * U .bcar7 \ By this point, we have the following, signed result: \ \ (V A T) = A * U \ = xTrackSegmentO * carRacingLine ASL A \ Set (V A) = (V A) * 4 ROL V \ = xTrackSegmentO * carRacingLine * 4 ASL A ROL V CLC \ Set (xCoord2Hi xCoord2Lo) = (xCoord2Hi xCoord2Lo) ADC xCoord2Lo,X \ + (V A) STA xCoord2Lo,X \ \ starting with the low bytes LDA xCoord2Hi,X \ And then the high bytes ADC V STA xCoord2Hi,X INX \ Set X = X + 2, so we skip the y-axis INX CPX #4 \ Loop back to bcar5 until we have processed the x-axis BNE bcar5 \ (for X = 0) and z-axis (for X = 2) \ Finally, we add 144 to the y-coordinate LDA yCoord2Lo \ Set (yCoord2Hi yCoord2Lo) += 144 CLC \ ADC #144 \ starting with the low bytes STA yCoord2Lo BCC bcar8 \ And then the high bytes INC yCoord2Hi
Name: BuildCarObjects (Part 3 of 3) [Show more] Type: Subroutine Category: 3D objects Summary: Calculate the screen coordinates of all the objects in the specified car Deep dive: Drawing a 3D car from 2D parts
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

Now that we have the car's 3D coordinates in xCoord2, we calculate the car's yaw and pitch angles, and use them to create either one car object, or four car objects if this is the four-object car. If this is the four-object car (i.e. the car is directly in front of us, is close enough and is visible), then we calculate the coordinates for the three extra objects as follows: [ xCoord2 ] [ xTrackSegmentI ] Front tyres = [ yCoord2 ] + [ yTrackSegmentI ] / 2 [ zCoord2 ] [ zTrackSegmentI ] [ xCoord2 ] [ xTrackSegmentI ] Body and helmet = [ yCoord2 ] + [ yTrackSegmentI ] / 4 [ zCoord2 ] [ zTrackSegmentI ] [ xCoord2 ] [ xTrackSegmentI ] Rear tyres = [ yCoord2 ] + [ yTrackSegmentI ] / 8 [ zCoord2 ] [ zTrackSegmentI ]
.bcar8 LDA #4 \ Set A = 4, to use as the object type for the car (we \ start with the object type of the standard car, and \ change this later if required) JSR GetObjectAngles-2 \ Calculate the object's yaw and pitch angles, using the \ coordinates in xCoord2, and set the object's \ visibility, scale and type LDX thisDriver \ Set X = thisDriver (driver number of car we are \ driving) LDA objectDistanceHi \ Set A to the high byte of the distance of the object CMP #3 \ If A >= 3, then the car is not close enough to be the BCS bcar11 \ four-object car, so jump to bcar11 to check whether it \ should be built as a distant car object \ If we get here then A <= 2, so the car is close enough \ to consider building as a four-object car LDA thisPosition \ If the car we are building is not the car just ahead CMP positionAhead \ of us in the race, then it can't be the four-object BNE bcar10 \ car, so jump to bcar10 to return from the subroutine \ with the car built as a standard car LDA objectStatus,X \ If bit 7 of the car's object status byte is set, then BMI bcar9 \ the car is not visible, so jump to bcar9 to skip the \ following instruction (which leaves the car object as \ a standard car, but still builds the other three car \ objects) \ If we get here then the car we are building is the \ nearest car in front of us, it's close and it is \ visible, so we draw this car as the four-object car DEC objectStatus,X \ The object type is stored in bits 0-3 of objectStatus, \ so this decrements the car's object type from 4 (the \ standard car) to 3 (the rear wing in the four-object \ car) .bcar9 LDY vectorNumber \ Set Y to the segment vector number that we stored \ above JSR GetSegmentVector \ Fetch the inner segment vector for the part of the \ track that the car is on: \ \ [ (SS T) ] [ xTrackSegmentI ] \ [ (TT U) ] = [ yTrackSegmentI ] \ [ (UU V) ] [ yTrackSegmentI ] \ \ So this contains the direction of the track where the \ car is JSR HalveCoordinate \ Halve the coordinate in (SS T), (TT U) and (UU V) LDY #&FD \ Set Y = &FD so the call to AddVectors uses xCoord2 LDX #&FA \ Set X = &FA so the call to AddVectors uses xCoord1 JSR AddVectors \ Set: \ \ [ (SS T) ] \ xCoord1 = xCoord2 + [ (TT U) ] / 2 \ [ (UU V) ] \ \ So xCoord1 contains the 3D coordinates of the front \ tyres of the four-object car JSR HalveCoordinate \ Halve the coordinate in (SS T), (TT U) and (UU V) LDX #&F4 \ Set X = &F4 so the call to AddVectors uses \ xHelmetCoord JSR AddVectors \ Set: \ \ [ (SS T) ] \ xHelmetCoord = xCoord2 + [ (TT U) ] / 4 \ [ (UU V) ] \ \ So xHelmetCoord contains the 3D coordinates of the \ helmet and body of the four-object car JSR HalveCoordinate \ Halve the coordinate in (SS T), (TT U) and (UU V) LDX #&FD \ Set X = &FD so the call to AddVectors uses xCoord2 JSR AddVectors \ Set: \ \ [ (SS T) ] \ xCoord2 = xCoord2 + [ (TT U) ] / 8 \ [ (UU V) ] \ \ So xCoord2 contains the 3D coordinates of the rear \ tyres of the four-object car \ Now that we have the 3D coordinates of the extra three \ parts of the four-object car, we can calculate the \ object's yaw and pitch angles, and store the details \ in objects 20, 21 and 22 (for the rear tyres, \ body/helmet and front tyres respectively) LDA #20 \ Set objectNumber = 20, to use as then object number STA objectNumber \ for the rear tyres in the four-object car LDA #2 \ Set A = 2, to use as the object type for the rear \ tyres in the four-object car JSR GetObjectAngles-2 \ Calculate the object's yaw and pitch angles, using the \ coordinates of the rear tyres in xCoord2, and set the \ object's visibility, scale and type LDA #21 \ Set objectNumber = 21, to use as then object number STA objectNumber \ for the body and helmet in the four-object car LDA #1 \ Set A = 1, to use as the object type for the body and \ helmet in the four-object car LDX #&F4 \ Set X = &F4 so the call to GetObjectAngles uses \ xHelmetCoord JSR GetObjectAngles \ Calculate the object's yaw and pitch angles, using the \ coordinates of the body and helmet in xHelmetCoord, \ and set the object's visibility, scale and type LDA #22 \ Set objectNumber = 22, to use as then object number STA objectNumber \ for the front tyres in the four-object car LDA #0 \ Set A = 0, to use as the object type for the rear \ tyres in the four-object car LDX #&FA \ Set X = &FA so the call to GetObjectAngles uses \ xCoord1 JSR GetObjectAngles \ Calculate the object's yaw and pitch angles, using the \ coordinates of the front tyres using xCoord1, and set \ the object's visibility, scale and type .bcar10 LDX thisDriver \ Set X to the driver number that we stored at the start \ of the BuildVisibleCar routine RTS \ Return from the subroutine .bcar11 \ We jump here when objectDistanceHi >= 3, so we now \ need to check whether the car is far enough away for \ us to change it to a distant car object \ \ We jump here with A set to objectDistanceHi CMP #5 \ If A < 5, i.e. objectDistanceHi = 4, then the car is BCC bcar10 \ close enough to stay as a standard car object, so jump \ to bcar10 to return from the subroutine LDA objectStatus,X \ If bit 7 of the car's object status byte is set, then BMI bcar10 \ the car is not visible, so jump to bcar10 to return \ from the subroutine \ If we get here then objectDistanceHi >= 5 and the car \ is visible, so the car is far enough away to be a \ distant car object INC objectStatus,X \ The object type is stored in bits 0-3 of objectStatus, \ so this increments the car's object type from 4 (the \ standard car) to 5 (the distant car) RTS \ Return from the subroutine
Name: GetObjectAngles [Show more] Type: Subroutine Category: 3D objects Summary: Calculate the object's yaw and pitch angles, and set the object's visibility, scale and type Deep dive: Pitch and yaw angles
Context: See this subroutine on its own page References: This subroutine is called as follows: * BuildCarObjects (Part 3 of 3) calls GetObjectAngles * BuildCarObjects (Part 3 of 3) calls entry point GetObjectAngles-2

Arguments: A Object type X The offset of the variable to use for the object's 3D coordinates in the GetObjYawAngle routine: * &F4 = xHelmetCoord * &FA = xCoord1 * &FD = xCoord2 Other entry points: GetObjectAngles-2 Use xCoord2 for the object's 3D coordinates in the call to GetObjYawAngle
LDX #&FD \ Set X = &FD so the calls to GetObjYawAngle and \ GetObjPitchAngle use xCoord2 and yCoord2 for the \ object's 3D coordinates .GetObjectAngles STA objectType \ Store the object type in objectType JSR GetObjYawAngle-2 \ Calculate the object's yaw angle, from the point of \ view of the player, returning it in (JJ II) LDY objectNumber \ Set Y to the number of the object we are processing LDA II \ Set the yaw angle for this object in (objYawAngleHi STA objYawAngleLo,Y \ objYawAngleLo) to (JJ II) LDA JJ STA objYawAngleHi,Y JSR CheckForContact-2 \ Check to see if the object and the player's car are \ close enough for contact, specifically if they are \ within a distance of 37 from each other JSR GetObjPitchAngle-2 \ Calculate the object's pitch angle, from the point \ of view of the player, 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 \ routine \ Fall through into SetObjectDetails to set the object's \ visibility, scale and type
Name: SetObjectDetails [Show more] Type: Subroutine Category: 3D objects Summary: Set an object's visibility, scale and type
Context: See this subroutine on its own page References: This subroutine is called as follows: * BuildRoadSign calls SetObjectDetails

Arguments: A The object's pitch angle, as returned by GetObjPitchAngle C flag If the C flag is set, hide the object scaleUp The scale up factor, as returned by GetObjPitchAngle scaleDown The scale down factor, as returned by GetObjPitchAngle
.SetObjectDetails LDY objectNumber \ Set Y to the number of the object BCS HideObject \ If the C flag is set, jump to HideObject to hide the \ object and return from the subroutine using a tail \ call SEC \ Set A = A - 1 SBC #1 BMI HideObject \ If the result is negative, jump to HideObject to hide \ the object and return from the subroutine using a tail \ call STA objectPitchAngle,Y \ Store the object's pitch angle in objectPitchAngle \ We now set the object's scaleUp value (i.e. its size) \ to the following: \ \ scaleUp / 2 ^ (scaleDown - 10) \ \ where scaleUp and scaleDown were set by the call to \ GetObjPitchAngle LDA scaleDown \ Set X = scaleDown - 9 SEC SBC #9 TAX LDA scaleUp \ Set A = scaleUp DEX \ Set X = X - 1 \ = scaleDown - 10 BEQ sobj3 \ If X = 0, jump to sobj3 to set the object's scaleUp \ to A BPL sobj2 \ If X > 0, jump to sobj3 to set the object's scaleUp \ to A << X \ Otherwise X < 0, so we calculate A >> X to set as the \ the object's scaleUp .sobj1 LSR A \ Set A = A >> 1 INX \ Increment the shift counter in X BNE sobj1 \ Loop back to sobj1 until we have shifted by X places BEQ sobj3 \ Jump to sobj3 (this BEQ is effectively a JMP as we \ just passed through a BNE) .sobj2 ASL A \ Set A = A << 1 DEX \ Decrement the shift counter in X BNE sobj2 \ Loop back to sobj2 until we have shifted by X places .sobj3 STA objectSize,Y \ Set the object's size to the scaled value of scaleUp LDA objectStatus,Y \ Set A to the object's status byte AND #%01110000 \ Clear bit 7 to make the object visible, and set bits ORA objectType \ 0-3 to the object type JMP SetObjectStatus \ Jump to SetObjectStatus to store the updated object \ status byte, returning from the subroutine using a \ tail call
Name: HideObject [Show more] Type: Subroutine Category: 3D objects Summary: Set an object to be hidden
Context: See this subroutine on its own page References: This subroutine is called as follows: * BuildVisibleCar calls HideObject * SetObjectDetails calls HideObject

Arguments: objectNumber The number of the object to hide
.HideObject LDY objectNumber \ Set Y to the number of the object to hide LDA objectStatus,Y \ Set A to the object's status byte ORA #%10000000 \ Set bit 7 in the object's status byte in A, which \ marks the object as hidden \ Fall through into SetObjectStatus to store the updated \ object status byte
Name: SetObjectStatus [Show more] Type: Subroutine Category: 3D objects Summary: Set an object's status byte
Context: See this subroutine on its own page References: This subroutine is called as follows: * SetObjectDetails calls SetObjectStatus

Arguments: A The object status byte Y The number of the object
.SetObjectStatus STA objectStatus,Y \ Set object Y's status byte to the value in A RTS \ Return from the subroutine
Name: CheckForContact [Show more] Type: Subroutine Category: Car geometry Summary: Check to see if the object is close enough to the player car to make contact
Context: See this subroutine on its own page References: This subroutine is called as follows: * BuildRoadSign calls CheckForContact * GetObjectAngles calls entry point CheckForContact-2

Arguments: Y Contact is made if objectDistanceLo <= Y (J I) max(|x-delta|, |z-delta|) (H G) min(|x-delta|, |z-delta|) M The smaller yaw angle of the object, where 0 to 255 represents 0 to 45 degrees objectNumber The number of the object being checked for contact Other entry points: CheckForContact-2 Set Y = 37, so we make contact if (L K) <= 37
LDY #37 \ Set Y = 37, so we make contact if (L K) <= 37 .CheckForContact JSR GetObjectDistance \ Set (L K) to the distance between the object and the \ player's car LDA L \ Set objectDistanceHi to the high byte of (L K) STA objectDistanceHi BNE ccon1 \ If objectDistanceHi is non-zero then the objects are \ too far apart for a collision, so jump to ccon1 to \ return from the subroutine CPY K \ If K > Y, then the objects are too far apart for a BCC ccon1 \ collision, this time in terms of the low byte of the \ distance, so jump to ccon1 to return from the \ subroutine \ If we get here then K <= Y, so the objects are close \ enough for a collision DEC processContact \ Decrement processContact so it is non-zero, so we \ check for contact between this car and the player's \ car in the ProcessContact routine LDA K \ Set (objectDistanceHi objectDistanceLo) = (L K) STA objectDistanceLo LDA objectNumber \ Store the number of the other object in STA collisionDriver \ collisionDriver, so we know who's crashing .ccon1 RTS \ Return from the subroutine
Name: DrawCarInPosition [Show more] Type: Subroutine Category: Drawing objects Summary: Draw the car in a specified race position
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawCars calls DrawCarInPosition

Arguments: X The race position of the car to draw Returns: X X is preserved
.DrawCarInPosition STX xStoreDraw \ Store X in xStoreDraw so it can be retrieved at the \ end of the DrawCarOrSign routine LDA driversInOrder,X \ Set X to the number of the driver in position X TAX \ Fall through into DrawCarOrSign to draw the car whose \ driver number we just looked up, i.e. the car in \ position X
Name: DrawCarOrSign [Show more] Type: Subroutine Category: Drawing objects Summary: Draw a car or sign Deep dive: Object definitions Drawing a 3D car from 2D parts Road signs
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawCars calls DrawCarOrSign * MainDrivingLoop (Part 2 of 5) calls DrawCarOrSign

This routine is used to draw road signs and cars. Arguments: X The car or sign to draw: * 0-19 = Draw the car for this driver number * 20-22 = Draw one of the three extra objects that make up the four-object car: * 20 = rear wing * 21 = body and helmet * 22 = front tyres * 23 = Draw the road sign xStoreDraw The value to restore into X at the end of the routine Returns: X X is set to xStoreDraw
.DrawCarOrSign LDA objectStatus,X \ Set A to this object's status byte BMI dcas3 \ If bit 7 is set then the object is not visible, so \ jump to dcas3 to return from the subroutine without \ drawing anything AND #%00001111 \ Extract the object's object type from bits 0-3 and STA objectType \ store it in objectType LDA objYawAngleLo,X \ Set (A T) = objYawAngle for this object SEC \ - playerYawAngle SBC playerYawAngleLo \ STA T \ starting with the low bytes LDA objYawAngleHi,X \ And then the high bytes SBC playerYawAngleHi \ \ So (A T) now contains the amount that the object we \ are drawing is to the left or right of the player's \ car BPL dcas1 \ If the result is positive, jump to dcas1 to perform a \ positive comparison CMP #&E0 \ The result is negative, so check to see if A < -32, BCC dcas3 \ and if so, jump to dcas3 to return from the subroutine \ without drawing anything BCS dcas2 \ Jump to dcas2 (this BCS is effectively a JMP as we \ just passed through a BCC) .dcas1 CMP #32 \ The result is positive, so check to see if A >= 32, BCS dcas3 \ and if so, jump to dcas3 to return from the subroutine \ without drawing anything .dcas2 \ If we get here then -32 <= A < 32 ASL T \ Set (A T) = (A T) * 4 ROL A \ ASL T \ so -128 <= A < 128 ROL A CLC \ Set xPixelCoord = 80 + A ADC #80 \ STA xPixelCoord \ This moves xPixelCoord so that it is centred on the \ screen, as the centre x-coordinate of the screen is \ at 80 pixels LDA objectPitchAngle,X \ Set yPixelCoord to this object's pitch angle STA yPixelCoord LDA objectSize,X \ Set scaleUp to this object's size (i.e. the size of STA scaleUp \ the car) JSR DrawObject \ Draw the object on-screen .dcas3 LDX xStoreDraw \ Set X = xStoreDraw so X is unchanged by the routine \ call RTS \ Return from the subroutine
Name: HalveCoordinate [Show more] Type: Subroutine Category: Maths (Geometry) Summary: Halve a coordinate with three 16-bit axes
Context: See this subroutine on its own page References: This subroutine is called as follows: * BuildCarObjects (Part 3 of 3) calls HalveCoordinate

Given a three-axis variable, this routine halves each axis in-place: [ (SS T) ] [ (SS T) ] [ (TT U) ] = [ (TT U) ] / 2 [ (UU V) ] [ (UU V) ] Arguments: (SS T) The value of the coordinate's first axis (TT U) The value of the coordinate's second axis (UU V) The value of the coordinate's third axis
.HalveCoordinate LDX #2 \ We are about to right-shift the following 16-bit \ variables: \ \ (SS T) \ (TT U) \ (UU V) \ \ so set a counter in X to use as an index that loops \ through 2, 1 and 0, as: \ \ (TT U) = (SS+1 U+1) \ (UU V) = (SS+2 U+2) \ \ The following comments are for (SS T), but the same \ process applies for (TT U) and (UU V) .halc1 LDA SS,X \ Set A to the high byte of (SS T) CLC \ If A is negative, set the C flag, otherwise clear the BPL halc2 \ C flag, so this sets the C flag to the sign of (SS T) SEC .halc2 ROR SS,X \ Set (SS T) = (SS T) >> 1 ROR T,X DEX \ Decrement the loop counter to move on to the next \ variable BPL halc1 \ Loop back until we have shifted all three 16-bit \ variables to the right RTS \ Return from the subroutine
Name: vergeTableHi [Show more] Type: Variable Category: Drawing the track Summary: High bytes of the addresses of the four verge tables
Context: See this variable on its own page References: This variable is used as follows: * DrawVergeEdge calls vergeTableHi
.vergeTableHi EQUB HI(leftVergeStart) EQUB HI(leftTrackStart) EQUB HI(rightVergeStart) EQUB HI(rightGrassStart)
Name: vergeTableLo [Show more] Type: Variable Category: Drawing the track Summary: Low bytes of the addresses of the four verge tables
Context: See this variable on its own page References: This variable is used as follows: * DrawVergeEdge calls vergeTableLo
.vergeTableLo EQUB LO(leftVergeStart) EQUB LO(leftTrackStart) EQUB LO(rightVergeStart) EQUB LO(rightGrassStart)
Name: DrawSegmentEdge (Part 1 of 7) [Show more] Type: Subroutine Category: Drawing the track Summary: Draw a single segment's edge as part of a whole track verge edge Deep Dive: Drawing the track verges
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawVergeEdge calls DrawSegmentEdge

This part of the routine checks whether the edge is on-screen, off-screen or partially on-screen, and stores the yaw and pitch angles of the edge in W and RR respectively. Arguments: A Index into the vergePixelMask table for the pixel bytes that make up the colours of this edge in this segment X Index in the verge buffer of the track segment list entry for the verge, pointing to either the inner edge or outer edge of the verge mark, depending on the verge table we are drawing (so we can use this for fetching yaw angles from the track segment list for the verge) Y Index in the verge buffer of the track segment list entry for the verge, for the inner edge of the verge mark (so we can use this for fetching pitch angles from the track segment list for the verge) C flag Flag to control whether we draw the edge, or just set up variables for the next call: * Clear = draw this edge * Set = do not draw this edge, but do set up the variables to pass to the next call M Yaw angle from the previous call to DrawSegmentEdge N Pitch angle from the previous call to DrawSegmentEdge prevYawIndex Same as X for the first call, or the yaw angle index of the previous call (i.e. the previous segment) if this is not the first call Returns: X X is unchanged Y Y is unchanged M Set to 128 + edge's yaw angle * 4, to carry through to the next call to DrawSegmentEdge N Set to the edge's pitch angle to carry through to the next call to DrawSegmentEdge
.DrawSegmentEdge PHP \ Store the C flag on the stack so we can retrieve it \ later STA pixelMaskIndex \ Store the index into the vergePixelMask table in \ pixelMaskIndex so we can use it later LDA #0 \ Set vergeOnScreenEdge = 0 to denote that the edge is STA vergeOnScreenEdge \ fully on-screen or fully off-screen (we update this \ below if it turns out not to be the case) LDA yVergeRight,Y \ Set A to the pitch angle - 1 of the verge edge we are SEC \ drawing SBC #1 CMP #78 \ If A >= 78, jump to dver2 with the C flag set to BCS dver2 \ indicate that this verge edge is off-screen LDA xVergeRightHi,X \ Set A to the high byte of the yaw angle of the verge \ edge we are drawing BPL dver1 \ If A is positive, jump to dver1 to skip the following EOR #&FF \ Set A = ~A \ = -A - 1 \ = |A| - 1 \ \ So A is now positive is approximately |A| .dver1 CMP #20 \ If A >= 20, set the C flag, otherwise clear the C flag \ \ Because the field of view is 20 degrees, the C flag is \ now set if the verge edge is off-screen, or clear if \ it is on-screen .dver2 ROR GG \ Rotate the C flag into bit 7 of RR, so bit 7 is set if \ the edge is off-screen, or clear if it is on-screen \ \ If this is not the first call to DrawSegmentEdge, then \ bit 6 will now contain the on-screen/off-screen bit \ for the previous segment's verge edge LDA xVergeRightHi,X \ Set (W A) to the yaw angle of the verge edge we are STA W \ drawing LDA xVergeRightLo,X ASL A \ Set (W A) = (W A) * 4 ROL W \ ASL A \ So W is the high byte of the yaw angle * 4 ROL W LDA W \ Set W = W + 128 CLC \ ADC #128 \ So W contains 128 + yaw angle * 4 (if we drop the low STA W \ byte, which is the fractional part) \ \ Adding 128 moves the angle range from -128 to +128, to \ 0 to 255, so when we calculate the difference between \ the yaw angles for two segments by subtracting them \ below, the C flag will be set correctly LDA yVergeRight,Y \ Set RR to the pitch angle of the verge edge we are STA RR \ drawing STX thisYawIndex \ Store the index of the yaw angle in the track segment \ list for this verge in thisYawIndex so we can use it \ later STY thisPitchIndex \ Store the index of the pitch angle in the track \ segment list for this verge in thisPitchIndex so we \ can use it later PLP \ Retrieve the C flag that we passed to the routine and \ stored on the stack earlier BCS dver8 \ If the C flag is set then we don't draw this edge, so \ jump to dver28 via dver8 to clean up and return from \ the subroutine BIT GG \ If bit 6 of GG is clear, then the previous segment's BVC dver3 \ verge edge from the last call to DrawSegmentEdge was \ on-screen, so jump to dver3 \ If we get here then previous segment's verge edge from \ the last call to DrawSegmentEdge was off-screen BMI dver8 \ If bit 7 of GG is set, then this segment's verge edge, \ which we are now drawing, is also off-screen, so jump \ to dver28 via dver8 to clean up and return from the \ subroutine \ If we get here then the previous verge edge was \ off-screen but this one is on-screen \ We now swap the values of M and W, and the values of \ N and RR, which sets W and RR to the yaw and pitch \ angles of the previous edge LDX M \ Set X = M and Y = N LDY N LDA W \ Set M = W STA M LDA RR \ Set N = RR STA N STX W \ Set W = X \ = M STY RR \ Set RR = Y \ = N DEC vergeOnScreenEdge \ Set vergeOnScreenEdge = &FF (as it was set to 0 above) \ to flag that this edge is partially off-screen and \ that we have swapped the angles over \ By this point, we have the following: \ \ * W = the yaw angle of the edge to draw \ \ * RR = the pitch angle of the edge to draw \ \ * M = the yaw angle of the previous edge \ \ * N = the pitch angle of the previous edge \ \ We only get to this point if this is not the first \ call to DrawSegmentEdge for this verge edge, so we \ know that M and N are defined
Name: DrawSegmentEdge (Part 2 of 7) [Show more] Type: Subroutine Category: Drawing the track Summary: Set up the edge's gradient Deep Dive: Drawing the track verges
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

This part calculates the edge's gradient in terms of the change in yaw and pitch angles, and stores them in SS and TT, with SS containing the yaw delta, and TT containing the pitch delta.
.dver3 LDA RR \ Set A = RR - N SEC SBC N STA WW \ Set WW = A \ \ So WW contains the current edge's pitch angle minus \ the previous edge's pitch angle, so: \ \ * WW is positive if the current pitch angle is \ greater than the previous pitch angle, so the \ current edge is higher up the screen, so the edge \ is moving up the screen \ \ * WW is negative if the current pitch angle is \ less than the previous pitch angle, so the \ current edge is lower down the screen, so the edge \ is moving down the screen BPL dver4 \ If A is positive, jump to dver4 LDA #0 \ Set A = -WW SEC \ = -A SBC WW \ \ So A is now positive, i.e. A = |RR - N| .dver4 STA TT \ Set TT = A \ = |RR - N| \ \ So TT contains the difference in pitch angle between \ the previous edge and the current edge, so let's call \ it dPitch: \ \ TT = |dPitch| LDA GG \ If bits 6 and 7 of GG are clear, then both this and AND #%11000000 \ the previous segment's verge edges were on-screen, so BEQ dver9 \ jump to dver9 to calculate the difference in yaw \ angles in SS \ If we get here then one of the previous segment's \ verge edges were off-screen, so we need to interpolate \ the angles to calculate where the edge meets the \ screen edge LDY thisYawIndex \ Set Y to the index of the yaw angle in the track \ segment list for this verge LDX prevYawIndex \ Set X to the index of the yaw angle in the track \ segment list for the segment from the previous call LDA xVergeRightLo,Y \ Set (VV T) to the difference in yaw angle between the SEC \ current segment and the previous segment, starting SBC xVergeRightLo,X \ with the low bytes STA T LDA xVergeRightHi,Y \ And then the high bytes SBC xVergeRightHi,X STA VV \ This also sets the sign of VV as follows: \ \ * VV is positive if the previous yaw angle is less \ than the current yaw angle, so the edge is moving \ from left to right \ \ * VV is negative if the previous yaw angle is \ greater than the current yaw angle, so the edge is \ moving from right to left JSR Absolute16Bit \ Set (A T) = |A T| \ \ So (A T) contains the difference in yaw angle between \ the previous edge and the current edge, so let's call \ it dYaw: \ \ (A T) = |dYaw| \ We now scale (A T) and TT, scaling (A T) up while \ scaling TT down, up to a maximum of two shifts up and \ down, as follows: \ \ * If |dYaw| >= 64, |dPitch| / 4 \ \ * If |dYaw| >= 32, |dYaw| * 2, |dPitch| / 2 \ \ * If |dYaw| < 16, |dYaw| * 4 \ \ This scales |dYaw| to be as large as possible while \ simultaneously making |dPitch| as small as possible CMP #64 \ If A >= 64, jump to dver5 to divide TT by 4 BCS dver5 ASL T \ Set (A T) = (A T) * 2 ROL A \ = |dYaw| * 2 CMP #64 \ If A >= 64, jump to dver6 to divide TT by 2 BCS dver6 ASL T \ Set (A T) = (A T) * 2 ROL A \ = |dYaw| * 4 BPL dver7 \ Jump to dver7 (this BPL is effectively a JMP as bit 7 \ of A will never be set, as the maximum value of A \ before the last ROR was 63, and 63 << 1 = 126, which \ has bit 7 clear) .dver5 LSR TT \ Set TT = TT / 2 \ = |dPitch| / 2 .dver6 LSR TT \ Set TT = TT / 2 \ = |dPitch| / 2 .dver7 STA SS \ Set SS = A \ = scaled |dYaw| LDA VV \ If vergeOnScreenEdge = &FF, then this sets VV to ~VV, EOR vergeOnScreenEdge \ which flips bit 7 (which we will use later) STA VV LDA SS \ Set A = SS JMP dver10 \ Jump to dver10 to finish setting up SS and TT .dver8 JMP dver28 \ Jump to dver28 (this is used as a way for branch \ instructions to jump to dver28, when it is too far \ for a branch instruction to reach .dver9 \ If we get here then both the previous and current \ verge edges are on-screen, so we need to set SS to the \ difference in yaw angle between the previous and \ current edges \ \ Note that we added 128 to each yaw angle to convert \ them in the range 0 to 255 (rather than -128 to +127) \ so the subtraction will set the C flag correctly LDA M \ Set A = M - W SEC \ SBC W \ So A contains the difference in yaw angle between the \ previous edge and the current edge, so let's call it \ dYaw ROR VV \ Rotate the C flag into bit 7 of VV, so bit 7 is set if \ M >= W, or clear if M < W BMI dver10 \ If A is negative, jump to dver10 to finish setting up \ SS and TT EOR #&FF \ Negate A using two's complement, so A is negative, CLC \ i.e. A = -|A| ADC #1 \ We now fall through into dver10 to set SS to A, which \ contains -|M - W|, or -|dYaw| .dver10 STA SS \ Set SS = A BNE dver11 \ If A is non-zero, jump to dver11 ORA TT \ A is zero, so this sets A to TT BEQ dver8 \ If A is zero (which means both SS and TT are zero), \ jump to dver28 via dver8 to clean up and return from \ the subroutine \ By this point, we have the following: \ \ * SS = the scaled yaw delta along the edge to draw \ (scaled to be as large as possible) \ \ * TT = the scaled pitch delta along the edge to draw \ (scaled to be as small as possible) \ \ * VV = the left-right direction of the edge \ \ * Positive = left to right \ \ * Negative = right to left \ \ * WW = the up-down direction of the edge \ \ * Positive = line is heading up the screen \ \ * Negative = line is heading down the screen \ \ So SS and TT are set to the gradient of the edge that \ we are drawing, and WW and VV are set to the direction \ of the edge
Name: DrawSegmentEdge (Part 3 of 7) [Show more] Type: Subroutine Category: Drawing the track Summary: Modify the drawing routines according to the edge gradient Deep Dive: Drawing the track verges
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

This part modifies the edge-drawing routines for the verge so they move in the right directions depending on the signs of the deltas calculated previously, and set up pixel bytes and masks for the correct colour scheme depending on which verge edge is being drawn.
.dver11 LDA GG \ Set A to bits 6 and 7 of GG AND #%11000000 BEQ dver12 \ If bits 6 and 7 of GG are clear, then both this \ segment's verge edge and the previous segment's verge \ edge were on-screen, so jump to dver12 \ If we get here then either this segment's verge edge \ or the previous segment's verge edge was off-screen \ \ We also set bit 7 of VV above to the following: \ \ * Bit 7 is clear if this yaw angle >= previous yaw \ angle \ \ * Bit 7 is set if this yaw angle < previous yaw \ angle \ \ with these flipped if vergeOnScreenEdge is &FF, which \ means the edge is partially on-screen \ \ So bit 7 is set if: \ \ * The edge is not partially on-screen and this yaw \ angle < previous yaw angle, in which case we have \ just moved left, falling off the left edge of the \ screen \ \ * The edge is partially on-screen and this yaw \ angle >= previous yaw angle, in which case we have \ just moved right onto the screen from the left \ edge \ \ In either case we are drawing over the left edge of \ the screen, so we need will need to update the \ background colour for this track line LDA VV \ Set A to bit 7 of VV to store in updateBackground, so AND #%10000000 \ A is non-zero if we are drawing on the left edge of \ the screen .dver12 STA updateBackground \ Store A in updateBackground, so we update the \ background colour table in the StopDrawingEdge routine \ We set WW above to the previous edge's pitch angle \ minus this edge's pitch angle, so it is positive if \ the edge is heading up the screen, negative if it is \ heading down the screen LDA WW \ If WW is non-zero, jump to dver13 BNE dver13 \ If we get here then this edge and the previous edge \ have the same pitch angle LDA vergeOnScreenEdge \ Set WW = ~vergeOnScreenEdge EOR #&FF \ STA WW \ So WW is 0 if the edge is partially on-screen, or &FF \ otherwise .dver13 \ We now modify both the DrawVergeByteLeft and \ DrawVergeByteRight routines so they do nothing on \ entry, and either increment or decrement Y on exit, \ depending on the polarity of WW \ \ We modify the routines as follows: \ \ * If WW is positive, so the edge is heading up the \ screen, then do NOP on entry and INY on exit, so \ we increase the track line as we draw the edge \ \ * If WW is negative, so the edge is heading down the \ screen, then do NOP on entry and DEY on exit, so \ we decrease the track line as we draw the edge \ \ This ensures that when we call these routines, they \ increase or decrease the track line in the appropriate \ direction for the line we are drawing BPL dver14 \ If WW is positive, jump to dver14 LDA #&88 \ Set A to the opcode for the DEY instruction BNE dver15 \ Jump to dver15 (this BNE is effectively a JMP as A is \ never zero) .dver14 LDA #&C8 \ Set A to the opcode for the INY instruction .dver15 STA verl5 \ Modify the instructions at verl5 and verb5 as follows: STA verb5 \ \ * INY when WW is positive \ \ * DEY when WW is negative \ \ So the track line in Y increases when WW is positive \ and the line is heading up the screen LDA #&EA \ Set A to the opcode for the NOP instruction STA verl1 \ Modify the instruction at verl1 to NOP, so the \ DrawVergeByteLeft routine doesn't update Y on entry STA verb1 \ Modify the instruction at verb1 to NOP, so the \ DrawVergeByteRight routine doesn't update Y on entry LDY pixelMaskIndex \ Set Y = pixelMaskIndex so we can use it to fetch the \ correct pixel bytes from vergePixelMask for this edge LDX #0 \ We are about to populate four bytes in objectPalette \ and vergeEdgeRight, so set X as a loop counter that \ starts at 0 \ \ We populate objectPalette with the four bytes at \ offset Y in vergePixelMask, and vergeEdgeRight with \ the same bytes, but masked to only include the \ rightmost 4, 3, 2 and 1 pixels \ \ This means the objectPalette table actually contains \ pixel bytes at this point, rather than full colour \ four-pixel bytes as it does in the rest of the code, \ but we can still think of it as containing the \ "palette" for the track verges, it's just a more \ sophisticated palette - stripey toothpaste is still \ toothpaste, after all .dver16 LDA vergePixelMask,Y \ Set A to the Y-th verge pixel mask in vergePixelMask STA objectPalette,X \ Store the full pixel mask in objectPalette AND pixelsEdgeRight,X \ Store the pixel mask in vergeEdgeRight, with all the STA vergeEdgeRight,X \ pixels to the right of position X set, plus pixel X INY \ Increment Y so we fetch the next verge pixel mask INX \ Increment X so we store in the next byte of four CPX #4 \ Loop back until we have populated all four bytes BNE dver16
Name: DrawSegmentEdge (Part 4 of 7) [Show more] Type: Subroutine Category: Drawing the track Summary: Set variables to use when updating the background colour Deep Dive: Drawing the track verges
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

This part sets variables for use when updating the background colour table, and clips the pitch angle in RR to fit into a track line.
LDA vergeType \ Set T = vergeType << 3 ASL A \ ASL A \ So T is as follows: ASL A \ STA T \ * %00000000 if we are drawing leftVergeStart (%00) \ \ * %00001000 if we are drawing leftTrackStart (%01) \ \ * %00010000 if we are drawing rightVergeStart (%10) \ \ * %00011000 if we are drawing rightGrassStart (%11) LDA objectPalette \ Set A to the first verge pixel mask that we stored in \ part 3, which is always a four-pixel, single colour \ pixel byte of the form %aaaabbbb, where the pixel \ colour is %ab LSR A \ Set A = A >> 3 AND %11 LSR A \ = %aaaab AND %11 LSR A \ = %ab AND #%00000011 \ \ So A contains the colour of the pixels in the first \ verge pixel mask that we stored in part 3 ORA T \ Set A = A OR T \ = %ab OR %vv000 \ = %vv0ab \ \ where %vv is the verge we are currently drawing, from \ vergeType ORA #%01000000 \ Set backgroundRight = %010vv0ab STA backgroundRight \ \ So backgroundRight contains the following data: \ \ * %vv is the verge we are currently drawing, from \ vergeType \ \ * %ab is the colour in the first verge pixel mask \ for the verge \ \ * %010xx0xx denotes that this value (if used) gets \ stored in the backgroundColour table by the \ UpdateBackground routine \ \ We use backgroundRight in the UpdateBackground routine \ when updating the background colour for the track line LDA objectPalette \ Set A to the first verge pixel mask that we stored in \ part 3, which is always a four-pixel, single colour \ pixel byte BNE dver17 \ If A is non-zero, then the colour is not black, so \ jump to dver17 LDA #&55 \ A is zero, which is a four-pixel byte in black, so STA objectPalette \ store &55 in objectPalette, as &55 in the screen \ buffer represents black .dver17 STA JJ \ Store A in JJ, so it can be used as the right pixel \ byte when drawing the edge in the screen buffer, i.e. \ the fill byte to the right of the edge LDA objectPalette+3 \ Set A to the fourth verge pixel mask that we stored in \ part 3, which is in the form %aaaxbbbx, where %a is \ the first bit of the left colour in the mask, and %b \ is the second bit \ \ This works because the fourth entry in each block in \ vergePixelMask has pixels 0 to 2 set to the left \ colour within the four-pixel block, and only pixel 3 \ set to the other colour, and the following batch of \ bit-shuffling extracts the colour of pixel 2 LSR A \ Set bit 0 of A to bit 1 of the pixel mask AND #%00000001 BIT objectPalette+3 \ If bit 7 of the pixel mask is clear, jump to dver18 to BPL dver18 \ skip the following instruction ORA #%00000010 \ Set bit 1 of A, so bit 1 of A is the same as bit 7 of \ the fourth pixel mask \ \ In other words, A = %000000ab .dver18 ORA #%10000000 \ Set A = %100000ab ORA T \ Set A = A OR T \ = %100000ab OR %vv000 \ = %100vv0ab \ \ where %vv is the verge we are currently drawing, from \ vergeType STA backgroundLeft \ Set backgroundLeft = %100vv0ab \ \ So backgroundLeft contains the following data: \ \ * %vv is the verge we are currently drawing, from \ vergeType \ \ * %ab is the colour of the first three pixels in the \ verge pixel mask for the verge in objectPalette+3 \ \ * %010xx0xx denotes that this value (if used) gets \ stored in the backgroundColour table by the \ UpdateBackground routine \ \ We use backgroundLeft in the UpdateBackground routine \ when updating the background colour for the track \ line, and in part 6 where we extract the colour bits \ in %ab LDA thisPitchIndex \ If thisPitchIndex + 1 = vergeBufferEnd, then this is CLC \ the last entry in the verge buffer, so jump to dver19 ADC #1 \ to skip the following CMP vergeBufferEnd BEQ dver19 \ We now clip the pitch angle in RR so it fits into the \ track line range of 0 to 79 LDA RR \ If RR < 80, then this is a valid track line number, so CMP #80 \ jump to dver20 to leave it alone BCC dver20 .dver19 \ If we get here then RR is outside the correct range \ for track lines of 0 to 79, so we need to clip it to \ 0 or 79, depending on which way the line is heading LDA #0 \ Set A = 0 to use as the new value of R if WW is \ negative BIT WW \ If WW is negative, then the line is heading down the BMI dver20 \ screen, so jump to dver20 to set RR = 0, which clips \ RR to the bottom of the track view LDA #79 \ WW is positive, so the line is heading up the \ screen, so set A = 79 to use as the new value of R, \ which clips RR to the top of the track view .dver20 STA RR \ Update RR to be within the range 0 to 79
Name: DrawSegmentEdge (Part 5 of 7) [Show more] Type: Subroutine Category: Drawing the track Summary: Calculate the dash data block and screen addresses for the edge Deep Dive: Drawing the track verges
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

This part calculates the dash data block for the edge, and the corresponding addresses within the screen buffer.
\ At this point, M is set to 128 + yaw angle * 4 for \ the previous edge LDA M \ Set U = M - 48 SEC \ SBC #48 \ So U and A now contain the pixel x-coordinate of the STA U \ previous edge, i.e. of the pixel at yaw angle M, \ because: \ \ M - 48 \ = 128 + (yaw angle * 4) - 48 \ = 80 + (yaw angle * 4) \ \ 80 is the x-coordinate of the middle of the screen, so \ this calculates the pixel x-coordinate for this yaw \ angle, in the range 0 to 159 LSR A \ Set UU = A / 4 LSR A \ STA UU \ So UU and A now contain the dash data block number for \ the pixel at yaw angle M, as each dash data block is \ four pixels wide CMP #40 \ If A >= 40, then this is not a valid dash data block BCS dver24 \ number, as they are in the range 0 to 39, so jump to \ dver28 via dver24 to clean up and return from the \ subroutine LSR A \ Halve A, as there are two dash data blocks in every \ page of memory, so A is now the page number of the \ relevant dash data block CLC \ Set Q = dashData + A ADC #HI(dashData) \ STA Q \ So Q is the high byte of the address of the dash data \ block STA S \ Set S = Q CLC \ Set NN = S + 1 ADC #1 STA NN LDA U \ Set X = U mod 8 AND #7 \ TAX \ So X contains the pixel number within two pixel bytes, \ i.e. in the range 0 to 7, as each pixel byte contains \ four pixels \ \ We pass this to the drawing routines so they start \ drawing from the correct pixel LDY N \ Set Y = N, which is the pitch angle of the previous \ edge \ By this point we have the following addresses set up: \ \ * (S R) = address of the first dash data block in \ the memory page containing the pixels at \ yaw angle M (as R = 0), i.e. of the \ previous edge \ \ * (Q P) = address of the second dash data block in \ the memory page containing the pixels at \ yaw angle M (as P = &80), i.e. of the \ previous edge \ \ * (NN MM) = address of the third dash data block in \ this sequence, i.e. the first memory \ page of the next dash data block (as \ NN = SS + 1) \ \ and the following variables: \ \ * X = pixel number (0 to 7) \ \ * Y = pitch angle of previous edge \ \ * UU = the number of the dash data block containing \ the previous edge, i.e. the dash data block \ where we start drawing the new edge \ \ * JJ = The right pixel byte when drawing the edge in \ the screen buffer, i.e. the fill byte to the \ right of the edge \ \ * RR = the pitch angle of the edge to draw, clipped \ to the range 0 to 79 (to map onto a track \ line)
Name: DrawSegmentEdge (Part 6 of 7) [Show more] Type: Subroutine Category: Drawing the track Summary: Calculate the dash data block for the edge Deep Dive: Drawing the track verges
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

This part draws the edge for this segment by jumping to the correct drawing routine, depending on the gradient and direction of the edge.
LDA SS \ If SS < TT, then the edge has a steep gradient, so CMP TT \ jump to dver26 to jump to the correct drawing routine BCC dver26 LDA objectPalette \ Set A to the first verge pixel mask that we stored in \ part 3, which is always a four-pixel, single colour \ pixel byte CMP #%11111111 \ If the background colour of the verge pixel mask is BEQ dver21 \ green, jump to dver21 to skip the following LDA backgroundLeft \ Extract bits 0-1 from backgroundLeft, which contain AND #%00000011 \ the colour from the left side of the verge pixel mask, \ i.e. the pixels on the left side of this verge edge CMP #3 \ If %ab = 3, so the colour to the left of the verge BEQ dver21 \ edge is green, jump to dver21 to skip the following \ Otherwise, disable the DrawGreass routines LDA #&60 \ Set A to the opcode for the RTS instruction STA DrawGrassLeft \ Modify the DrawGrassLeft routine to start with an RTS, \ so it returns without doing anything (so it's \ effectively disabled) STA DrawGrassRight \ Modify the DrawGrassRight routine to start with an \ RTS, so it returns without doing anything (so it's \ effectively disabled) BNE dver23 \ Jump to dver23 (this BNE is effectively a JMP as A is \ never zero) .dver21 LDA #&E0 \ Set A to the opcode for the CPX #128 instruction STA DrawGrassLeft \ Revert the DrawGrassLeft routine to start with a CPX \ instruction, so it runs as normal STA DrawGrassRight \ Revert the DrawGrassRight routine start with a CPX \ instruction, so it runs as normal LDA vergeType \ Set A to the type of verge we are drawing CMP #2 \ Set bit 7 of A if vergeType >= 2, so we get: ROR A \ \ * Bit 7 clear when we are drawing leftVergeStart or \ leftTrackStart \ \ * Bit 7 set when we are drawing rightVergeStart or \ rightGrassStart EOR VV \ If bit 7 of VV is the same as bit 7 of A, then: BPL dver23 \ \ * We are drawing the left track verge and drawing \ the edge from left to right (both bits are clear) \ \ * We are drawing the right track verge and drawing \ the edge from right to left (both bits are set) \ \ In either case, jump to dver23 to skip modifying the \ DrawVergeByteLeft and DrawVergeByteRight routines \ If we get here, then: \ \ * We are drawing the left track verge and drawing \ the edge from right to left \ \ * We are drawing the right track verge and drawing \ the edge from left to right \ \ We now modify both the DrawVergeByteLeft and \ DrawVergeByteRight routines to increment or decrement \ Y on entry, and to do nothing on exit (which flips the \ modifications we did in part 3, which made them do \ nothing on entry and the update of Y on exit) LDA verl5 \ Fetch the instruction that the DrawVergeByteLeft and \ DrawVergeByteRight routines currently execute on exit \ (which will be INY or DEY) STA verl1 \ Modify the instruction at verl1, so the \ DrawVergeByteLeft routine now does this action on \ entry instead STA verb1 \ Modify the instruction at verb1, so the \ DrawVergeByteRight routine now does this action on \ entry instead LDA #&EA \ Set A to the opcode for the NOP instruction STA verl5 \ Modify the instruction at verl5, so the \ DrawVergeByteLeft routine now does nothing on exit STA verb5 \ Modify the instruction at verb5, so the \ DrawVergeByteRight routine now does nothing on exit \ We also do the first increment or decrement, to get \ things set up correctly, depending on the sign in WW, \ which determines whether the edge is going up or down \ the screen LDA WW \ If WW is positive, then the line is heading up the BPL dver22 \ screen, to jump to dver22 to decrement Y before \ drawing the edge INY \ WW is negative, so the line is heading down the \ screen, so increment Y JMP dver23 \ Jump to dver23 to draw the edge .dver22 DEY \ WW is positive, so decrement Y and fall through into \ dver23 to draw the edge .dver23 \ If we get here then SS >= TT, so the edge has a \ gradient shallow LDA VV \ If VV is positive, jump to dver25 to draw the edge BPL dver25 \ from left to right JSR DrawShallowToLeft \ VV is negative, so draw the edge from right to left .dver24 JMP dver28 \ Jump to dver28 to clean up and return from the \ subroutine .dver25 JSR DrawShallowToRight \ VV is positive, so draw the edge from left to right JMP dver28 \ Jump to dver28 to clean up and return from the \ subroutine .dver26 \ If we get here then SS < TT, so the edge has a steep \ gradient LDA VV \ If VV is positive, jump to dver27 to draw the edge BPL dver27 \ from left to right JSR DrawSteepToLeft \ VV is negative, so draw the edge from right to left JMP dver28 \ Jump to dver28 to clean up and return from the \ subroutine .dver27 JSR DrawSteepToRight \ VV is positive, so draw the edge from left to right
Name: DrawSegmentEdge (Part 7 of 7) [Show more] Type: Subroutine Category: Drawing the track Summary: Save the angles for the next call to DrawSegmentEdge and return from the subroutine Deep Dive: Drawing the track verges
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

This part gets things ready for the next call to DrawSegmentEdge, when we draw the next segment's verge edge, and returns from the subroutine.
.dver28 LDA vergeOnScreenEdge \ If bit 7 of vergeOnScreenEdge is set then the edge we BMI dver29 \ are drawing is partially off-screen, so jump to dver29 \ to skip the following \ If we get here then the edge we are drawing is wholly \ on-screen, so we can store the edge's angles in M and \ N to be picked up in the next call to DrawSegmentEdge LDA W \ Set M = W, so we pass on 128 + yaw angle * 4 STA M LDA RR \ Set N = RR, so we pass on the pitch angle STA N .dver29 LDX thisYawIndex \ Set X to the original yaw angle index that we stored \ above, so X is preserved through calls to the routine LDY thisPitchIndex \ Set Y to the original pitch angle index that we stored \ above, so Y is preserved through calls to the routine RTS \ Return from the subroutine EQUB &A5, &53 \ These bytes appear to be unused EQUB &F0, &EB EQUB &20, &12 EQUB &2F, &4C EQUB &FC, &2C
Name: DrawShallowToRight [Show more] Type: Subroutine Category: Drawing the track Summary: Draw a verge edge with a shallow gradient from left to right Deep Dive: Drawing the track verges
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawSegmentEdge (Part 6 of 7) calls DrawShallowToRight

Arguments: X The pixel number to start drawing from, in the range 0 to 7, as it is measured across the two edge bytes that we draw in this routine Y The track line to draw on (0 to 79) SS The scaled yaw delta along the edge to draw TT The scaled pitch delta along the edge to draw RR The pitch angle of the current segment (we stop drawing the edge when Y reaches this value) UU The number of the dash data block where we start drawing JJ The right pixel byte when drawing the edge in the screen buffer, i.e. the fill byte to the right of the edge (S R) Address of the first dash data block in the memory page containing the pixels at the start of the previous edge (Q P) Address of the second dash data block in the memory page containing the pixels at the start of the previous edge (NN MM) Address of the third dash data block in this sequence, i.e. the first memory page of the next dash data block
.DrawShallowToRight LDA jumpShallowRight,X \ Modify the BCC instruction at shlr1 below so that STA shlr1+1 \ it jumps to the destination given in the X-th entry in \ the jumpShallowRight lookup table LDX #128 \ Set X = 128, so we can detect at the end of the \ routine whether X has been altered (which only happens \ if we draw something) LDA SS \ Set A = -SS EOR #&FF \ CLC \ This is the starting value for the cumulative slope ADC #1 \ error, which we tally up in A CLC \ Clear the C flag so the next instruction effectively \ becomes a JMP .shlr1 BCC shlr2 \ This instruction was modified above, so it jumps to \ the address specified in the jumpShallowRight table, \ as follows: \ \ * shlr4 when X = 0 \ * shlr6 when X = 1 \ * shlr8 when X = 2 \ * shlr10 when X = 3 \ * shlr13 when X = 4 \ * shlr15 when X = 5 \ * shlr17 when X = 6 \ * shlr19 when X = 7 \ \ These jump-points start by drawing a pixel byte before \ moving on to the slope error calculations \ \ The following entry points are also supported, though \ these don't appear to be used, as the routine is only \ called with X in the range 0 to 7: \ \ * shlr3 when X = 8 \ * shlr5 when X = 9 \ * shlr7 when X = 10 \ * shlr9 when X = 11 \ * shlr12 when X = 12 \ * shlr14 when X = 13 \ * shlr16 when X = 14 \ * shlr18 when X = 15 \ \ These jump-points start with the slope error \ calculations rather than a call to the draw routine, \ they would presumably omit the first pixel byte of the \ line, were they to be used .shlr2 LDX #128 \ Set X = 128, so we can detect at the end of the \ routine whether X has been altered (which only happens \ if we draw something) .shlr3 \ We start with the slope error calculation for pixel 0 \ of the first dash data block ADC TT \ Set A = A + TT, to add the pitch delta to the slope \ error BCC shlr5 \ If the addition didn't overflow, skip to the next \ pixel to step along the x-axis, as the cumulative \ pitch deltas haven't yet added up to a multiple of SS \ If we get here then the pitch deltas have added up to \ a whole line and the addition has overflowed, so we \ need to draw a pixel SBC SS \ If we get here then the cumulative pitch deltas in TT \ have added up to a multiple of the yaw delta in SS, so \ we subtract SS from the slope error (which we can do \ we we know the C flag is set) .shlr4 LDX #0 \ Draw the edge in pixel 0 of the first dash data block JSR DrawVergeByteLeft \ (the leftmost pixel) and draw a fill byte in the \ second dash data block \ \ If we have reached the pitch angle of the current \ segment, then we have reached the end of the edge to \ draw, in which case DrawVergeByteLeft will jump to \ StopDrawingEdge to abort this edge and return us two \ routines up the call stack, to DrawSegmentEdge .shlr5 ADC TT \ Repeat the slope error calculation for pixel 1 BCC shlr7 \ of the first dash data block SBC SS .shlr6 LDX #1 \ Draw the edge in pixel 1 of the first dash data block JSR DrawVergeByteLeft \ (the second pixel) and draw a fill byte in the second \ dash data block, or abort the drawing and return to \ DrawSegmentEdge if we've finished drawing the edge .shlr7 ADC TT \ Repeat the slope error calculation for pixel 2 BCC shlr9 \ of the first dash data block SBC SS .shlr8 LDX #2 \ Draw the edge in pixel 2 of the first dash data block JSR DrawVergeByteLeft \ (the third pixel) and draw a fill byte in the second \ dash data block, or abort the drawing and return to \ DrawSegmentEdge if we've finished drawing the edge .shlr9 ADC TT \ Repeat the slope error calculation for pixel 3 BCC shlr11 \ of the first dash data block SBC SS .shlr10 LDX #3 \ Draw the edge in pixel 3 of the first dash data block JSR DrawVergeByteLeft \ (the rightmost pixel) and draw a fill byte in the \ second dash data block, or abort the drawing and \ return to DrawSegmentEdge if we've finished drawing \ the edge .shlr11 JSR DrawGrassLeft \ If nothing has been drawn in the first dash data \ block (in which case X will still be 128), then fill \ the byte with green grass INC UU \ Increment UU so we now draw in the right pair of dash \ data blocks (i.e. the second and third dash data \ blocks of the three that we set up) .shlr12 ADC TT \ Repeat the slope error calculation for pixel 0 BCC shlr14 \ of the second dash data block SBC SS .shlr13 LDX #0 \ Draw the edge in pixel 0 of the second dash data block JSR DrawVergeByteRight \ (the leftmost pixel) and draw a fill byte in the third \ dash data block, or abort the drawing and return to \ DrawSegmentEdge if we've finished drawing the edge .shlr14 ADC TT \ Repeat the slope error calculation for pixel 1 BCC shlr16 \ of the second dash data block SBC SS .shlr15 LDX #1 \ Draw the edge in pixel 1 of the second dash data block JSR DrawVergeByteRight \ (the second pixel) and draw a fill byte in the third \ dash data block, or abort the drawing and return to \ DrawSegmentEdge if we've finished drawing the edge .shlr16 ADC TT \ Repeat the slope error calculation for pixel 2 BCC shlr18 \ of the second dash data block SBC SS .shlr17 LDX #2 \ Draw the edge in pixel 2 of the second dash data block JSR DrawVergeByteRight \ (the third pixel) and draw a fill byte in the third \ dash data block, or abort the drawing and return to \ DrawSegmentEdge if we've finished drawing the edge .shlr18 ADC TT \ Repeat the slope error calculation for pixel 3 BCC shlr20 \ of the second dash data block SBC SS .shlr19 LDX #3 \ Draw the edge in pixel 3 of the second dash data block JSR DrawVergeByteRight \ (the rightmost pixel) and draw a fill byte in the \ third dash dash data block, or abort the drawing and \ return to DrawSegmentEdge if we've finished drawing \ the edge .shlr20 JSR DrawGrassRight \ If nothing has been drawn in the second dash data \ block (in which case X will still be 128), then fill \ the byte with green grass INC S \ Increment S to move (S R) on to the next page, to move \ it right by two dash data blocks INC Q \ Increment Q to move (Q P) on to the next page, to move \ it right by two dash data blocks INC NN \ Increment NN to move (NN MM) on to the next page, to \ move it right by two dash data blocks INC UU \ Increment UU to the number of the next dash data block \ to the right LDX S \ If (S R) hasn't yet reached the rightmost dash data CPX #HI(dashData39)+1 \ block (i.e. it hasn't gone past block 39, which is at BNE shlr2 \ the right edge of the screen), then jump back to shlr2 \ to keep drawing RTS \ Return from the subroutine
Name: DrawShallowToLeft [Show more] Type: Subroutine Category: Drawing the track Summary: Draw a verge edge with a shallow gradient from right to left Deep Dive: Drawing the track verges
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawSegmentEdge (Part 6 of 7) calls DrawShallowToLeft

Arguments: X The pixel number to start drawing from, in the range 0 to 7, as it is measured across the two edge bytes that we draw in this routine Y The track line to draw on (0 to 79) SS The scaled yaw delta along the edge to draw TT The scaled pitch delta along the edge to draw RR The pitch angle of the current segment (we stop drawing the edge when Y reaches this value) UU The number of the dash data block where we start drawing JJ The right pixel byte when drawing the edge in the screen buffer, i.e. the fill byte to the right of the edge (S R) Address of the first dash data block in the memory page containing the pixels at the start of the previous edge (Q P) Address of the second dash data block in the memory page containing the pixels at the start of the previous edge (NN MM) Address of the third dash data block in this sequence, i.e. the first memory page of the next dash data block
.DrawShallowToLeft LDA jumpShallowLeft,X \ Modify the BCC instruction at shrl1 below so that STA shrl1+1 \ it jumps to the destination given in the X-th entry in \ the jumpShallowLeft lookup table LDX #128 \ Set X = 128, so we can detect at the end of the \ routine whether X has been altered (which only happens \ if we draw something) LDA SS \ Set A = -SS EOR #&FF \ CLC \ This is the starting value for the cumulative slope ADC #1 \ error, which we tally up in A CLC \ Clear the C flag so the next instruction effectively \ becomes a JMP .shrl1 BCC shrl2 \ This instruction was modified above, so it jumps to \ the address specified in the jumpShallowLeft table, as \ follows: \ \ * shrl19 when X = 0 \ * shrl17 when X = 1 \ * shrl15 when X = 2 \ * shrl13 when X = 3 \ * shrl10 when X = 4 \ * shrl8 when X = 5 \ * shrl6 when X = 6 \ * shrl4 when X = 7 \ \ These jump-points start by drawing a pixel byte before \ moving on to the slope error calculations \ \ The following entry points are also supported, though \ these don't appear to be used, as the routine is only \ called with X in the range 0 to 7: \ \ * shrl18 when X = 8 \ * shrl16 when X = 9 \ * shrl14 when X = 10 \ * shrl12 when X = 11 \ * shrl9 when X = 12 \ * shrl7 when X = 13 \ * shrl5 when X = 14 \ * shrl3 when X = 15 \ \ These jump-points start with the slope error \ calculations rather than a call to the draw routine, \ they would presumably omit the first pixel byte of the \ line, were they to be used .shrl2 LDX #128 \ Set X = 128, so we can detect at the end of the \ routine whether X has been altered (which only happens \ if we draw something) .shrl3 \ We start with the slope error calculation for pixel 3 \ of the second dash data block ADC TT \ Set A = A + TT, to add the pitch delta to the slope \ error BCC shrl5 \ If the addition didn't overflow, skip to the next \ pixel to step along the x-axis, as the cumulative \ pitch deltas haven't yet added up to a multiple of SS \ If we get here then the pitch deltas have added up to \ a whole line and the addition has overflowed, so we \ need to draw a pixel SBC SS \ If we get here then the cumulative pitch deltas in TT \ have added up to a multiple of the yaw delta in SS, so \ we subtract SS from the slope error (which we can do \ we we know the C flag is set) .shrl4 LDX #3 \ Draw the edge in pixel 3 of the second dash data block JSR DrawVergeByteRight \ (the rightmost pixel) and draw a fill byte in the \ third dash dash data block \ \ If we have reached the pitch angle of the current \ segment, then we have reached the end of the edge to \ draw, in which case DrawVergeByteRight will jump to \ StopDrawingEdge to abort this edge and return us two \ routines up the call stack, to DrawSegmentEdge .shrl5 ADC TT \ Repeat the slope error calculation for pixel 2 BCC shrl7 \ of the second dash data block SBC SS .shrl6 LDX #2 \ Draw the edge in pixel 2 of the second dash data block JSR DrawVergeByteRight \ (the third pixel) and draw a fill byte in the third \ dash data block, or abort the drawing and return to \ DrawSegmentEdge if we've finished drawing the edge .shrl7 ADC TT \ Repeat the slope error calculation for pixel 1 BCC shrl9 \ of the second dash data block SBC SS .shrl8 LDX #1 \ Draw the edge in pixel 1 of the second dash data block JSR DrawVergeByteRight \ (the second pixel) and draw a fill byte in the third \ dash data block, or abort the drawing and return to \ DrawSegmentEdge if we've finished drawing the edge .shrl9 ADC TT \ Repeat the slope error calculation for pixel 0 BCC shrl11 \ of the second dash data block SBC SS .shrl10 LDX #0 \ Draw the edge in pixel 0 of the second dash data block JSR DrawVergeByteRight \ (the leftmost pixel) and draw a fill byte in the third \ dash data block, or abort the drawing and return to \ DrawSegmentEdge if we've finished drawing the edge .shrl11 JSR DrawGrassRight \ If nothing has been drawn in the second dash data \ block (in which case X will still be 128), then fill \ the byte with green grass DEC UU \ Decrement UU so we now draw in the left pair of dash \ data blocks (i.e. the first and second dash data \ blocks of the three that we set up) .shrl12 ADC TT \ Repeat the slope error calculation for pixel 3 BCC shrl14 \ of the first dash data block SBC SS .shrl13 LDX #3 \ Draw the edge in pixel 3 of the first dash data block JSR DrawVergeByteLeft \ (the rightmost pixel) and draw a fill byte in the \ second dash data block, or abort the drawing and \ return to DrawSegmentEdge if we've finished drawing \ the edge .shrl14 ADC TT \ Repeat the slope error calculation for pixel 2 BCC shrl16 \ of the first dash data block SBC SS .shrl15 LDX #2 \ Draw the edge in pixel 2 of the first dash data block JSR DrawVergeByteLeft \ (the third pixel) and draw a fill byte in the second \ dash data block, or abort the drawing and return to \ DrawSegmentEdge if we've finished drawing the edge .shrl16 ADC TT \ Repeat the slope error calculation for pixel 1 BCC shrl18 \ of the first dash data block SBC SS .shrl17 LDX #1 \ Draw the edge in pixel 1 of the first dash data block JSR DrawVergeByteLeft \ (the second pixel) and draw a fill byte in the second \ dash data block, or abort the drawing and return to \ DrawSegmentEdge if we've finished drawing the edge .shrl18 ADC TT \ Repeat the slope error calculation for pixel 0 BCC shrl20 \ of the first dash data block SBC SS .shrl19 LDX #0 \ Draw the edge in pixel 0 of the first dash data block JSR DrawVergeByteLeft \ (the leftmost pixel) and draw a fill byte in the \ second dash data block, or abort the drawing and \ return to DrawSegmentEdge if we've finished drawing \ the edge .shrl20 JSR DrawGrassLeft \ If nothing has been drawn in the first dash data \ block (in which case X will still be 128), then fill \ the byte with green grass DEC S \ Decrement S to move (S R) on to the previous page, to \ move it left by two dash data blocks DEC Q \ Decrement Q to move (Q P) on to the previous page, to \ move it left by two dash data blocks DEC NN \ Decrement NN to move (NN MM) on to the previous page, \ to move it left by two dash data blocks DEC UU \ Decrement UU to the number of the previous dash data \ block to the left LDX S \ If (S R) hasn't yet reached the leftmost dash data CPX #HI(dashData0)-1 \ block (i.e. it hasn't gone past block 0, which is at CLC \ the left edge of the screen), then jump back to shrl2 BNE shrl2 \ to keep drawing (with the C flag cleared, as it gets \ set by the comparison) JMP strl10 \ Jump to strl10 to finish drawing the right-hand pixel \ byte of the pair, as the first dash data block in \ (S R) might be off-screen, but the second one in (Q P) \ isn't
Name: DrawSteepToRight [Show more] Type: Subroutine Category: Drawing the track Summary: Draw a verge edge with a steep gradient from left to right Deep Dive: Drawing the track verges
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawSegmentEdge (Part 6 of 7) calls DrawSteepToRight

Arguments: X The pixel number to start drawing from, in the range 0 to 7, as it is measured across the two edge bytes that we draw in this routine Y The track line to draw on (0 to 79) SS The scaled yaw delta along the edge to draw TT The scaled pitch delta along the edge to draw RR The pitch angle of the current segment (we stop drawing the edge when Y reaches this value) UU The number of the dash data block where we start drawing JJ The right pixel byte when drawing the edge in the screen buffer, i.e. the fill byte to the right of the edge (S R) Address of the first dash data block in the memory page containing the pixels at the start of the previous edge (Q P) Address of the second dash data block in the memory page containing the pixels at the start of the previous edge (NN MM) Address of the third dash data block in this sequence, i.e. the first memory page of the next dash data block
.DrawSteepToRight LDA jumpSteepRight,X \ Modify the BCC instruction at stlr1 below so that STA stlr1+1 \ it jumps to the destination given in the X-th entry in \ the jumpSteepRight lookup table LDA TT \ Set A = -TT EOR #&FF \ CLC \ This is the starting value for the cumulative slope ADC #1 \ error, which we tally up in A CLC \ Clear the C flag so the next instruction effectively \ becomes a JMP .stlr1 BCC stlr2 \ This instruction was modified above, so it jumps to \ the address specified in the jumpSteepRight table, as \ follows: \ \ * stlr2 when X = 0 \ * stlr3 when X = 1 \ * stlr4 when X = 2 \ * stlr5 when X = 3 \ * stlr6 when X = 4 \ * stlr7 when X = 5 \ * stlr8 when X = 6 \ * stlr9 when X = 7 .stlr2 LDX #0 \ Draw the edge in pixel 0 of the first dash data block JSR DrawVergeByteLeft \ (the leftmost pixel) and draw a fill byte in the \ second dash data block \ \ If we have reached the pitch angle of the current \ segment, then we have reached the end of the edge to \ draw, in which case DrawVergeByteLeft will jump to \ StopDrawingEdge to abort this edge and return us two \ routines up the call stack, to DrawSegmentEdge \ We now do the slope error calculation for pixel 0 of \ the first dash data block ADC SS \ Set A = A + SS, to add the yaw delta to the slope \ error BCC stlr2 \ If the addition didn't overflow, skip to the next \ pixel to step along the y-axis, as the cumulative \ pitch deltas haven't yet added up to a multiple of TT \ If we get here then the pitch deltas have added up to \ a whole line and the addition has overflowed, so we \ need to draw a pixel SBC TT \ If we get here then the cumulative yaw deltas in SS \ have added up to a multiple of the pitch delta in TT, \ so we subtract TT from the slope error (which we can \ do we we know the C flag is set) .stlr3 LDX #1 \ Draw the edge in pixel 1 of the first dash data block JSR DrawVergeByteLeft \ (the second pixel) and draw a fill byte in the second \ dash data block, or abort the drawing and return to \ DrawSegmentEdge if we've finished drawing the edge ADC SS \ Repeat the slope error calculation for pixel 1 BCC stlr3 \ of the first dash data block SBC TT .stlr4 LDX #2 \ Draw the edge in pixel 2 of the first dash data block JSR DrawVergeByteLeft \ (the third pixel) and draw a fill byte in the second \ dash data block, or abort the drawing and return to \ DrawSegmentEdge if we've finished drawing the edge ADC SS \ Repeat the slope error calculation for pixel 2 BCC stlr4 \ of the first dash data block SBC TT .stlr5 LDX #3 \ Draw the edge in pixel 3 of the first dash data block JSR DrawVergeByteLeft \ (the rightmost pixel) and draw a fill byte in the \ second dash data block, or abort the drawing and \ return to DrawSegmentEdge if we've finished drawing \ the edge ADC SS \ Repeat the slope error calculation for pixel 3 BCC stlr5 \ of the first dash data block SBC TT INC UU \ Increment UU so we now draw in the right pair of dash \ data blocks (i.e. the second and third dash data \ blocks of the three that we set up) .stlr6 LDX #0 \ Draw the edge in pixel 0 of the second dash data block JSR DrawVergeByteRight \ (the leftmost pixel) and draw a fill byte in the third \ dash data block, or abort the drawing and return to \ DrawSegmentEdge if we've finished drawing the edge ADC SS \ Repeat the slope error calculation for pixel 0 BCC stlr6 \ of the second dash data block SBC TT .stlr7 LDX #1 \ Draw the edge in pixel 1 of the second dash data block JSR DrawVergeByteRight \ (the second pixel) and draw a fill byte in the third \ dash data block, or abort the drawing and return to \ DrawSegmentEdge if we've finished drawing the edge ADC SS \ Repeat the slope error calculation for pixel 1 BCC stlr7 \ of the second dash data block SBC TT .stlr8 LDX #2 \ Draw the edge in pixel 2 of the second dash data block JSR DrawVergeByteRight \ (the third pixel) and draw a fill byte in the third \ dash data block, or abort the drawing and return to \ DrawSegmentEdge if we've finished drawing the edge ADC SS \ Repeat the slope error calculation for pixel 2 BCC stlr8 \ of the second dash data block SBC TT .stlr9 LDX #3 \ Draw the edge in pixel 3 of the second dash data block JSR DrawVergeByteRight \ (the rightmost pixel) and draw a fill byte in the \ third dash dash data block, or abort the drawing and \ return to DrawSegmentEdge if we've finished drawing \ the edge ADC SS \ Repeat the slope error calculation for pixel 2 BCC stlr9 \ of the second dash data block SBC TT INC S \ Increment S to move (S R) on to the next page, to move \ it right by two dash data blocks INC Q \ Increment Q to move (Q P) on to the next page, to move \ it right by two dash data blocks INC NN \ Increment NN to move (NN MM) on to the next page, to \ move it right by two dash data blocks INC UU \ Increment UU to the number of the next dash data block \ to the right LDX S \ If (S R) hasn't yet reached the rightmost dash data CPX #HI(dashData39)+1 \ block (i.e. it hasn't gone past block 39, which is at BNE stlr2 \ the right edge of the screen), then jump back to shlr2 \ to keep drawing RTS \ Return from the subroutine
Name: DrawSteepToLeft [Show more] Type: Subroutine Category: Drawing the track Summary: Draw a verge edge with a steep gradient from right to left Deep Dive: Drawing the track verges
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawSegmentEdge (Part 6 of 7) calls DrawSteepToLeft

Arguments: X The pixel number to start drawing from, in the range 0 to 7, as it is measured across the two edge bytes that we draw in this routine Y The track line to draw on (0 to 79) SS The scaled yaw delta along the edge to draw TT The scaled pitch delta along the edge to draw RR The pitch angle of the current segment (we stop drawing the edge when Y reaches this value) UU The number of the dash data block where we start drawing JJ The right pixel byte when drawing the edge in the screen buffer, i.e. the fill byte to the right of the edge (S R) Address of the first dash data block in the memory page containing the pixels at the start of the previous edge (Q P) Address of the second dash data block in the memory page containing the pixels at the start of the previous edge (NN MM) Address of the third dash data block in this sequence, i.e. the first memory page of the next dash data block
.DrawSteepToLeft LDA jumpSteepLeft,X \ Modify the BCC instruction at strl1 below so that STA strl1+1 \ it jumps to the destination given in the X-th entry in \ the jumpSteepLeft lookup table LDA TT \ Set A = -TT EOR #&FF \ CLC \ This is the starting value for the cumulative slope ADC #1 \ error, which we tally up in A CLC \ Clear the C flag so the next instruction effectively \ becomes a JMP .strl1 BCC strl2 \ This instruction was modified above, so it jumps to \ the address specified in the jumpSteepLeft table, as \ follows: \ \ * strl9 when X = 0 \ * strl8 when X = 1 \ * strl7 when X = 2 \ * strl6 when X = 3 \ * strl5 when X = 4 \ * strl4 when X = 5 \ * strl3 when X = 6 \ * strl2 when X = 7 .strl2 LDX #3 \ Draw the edge in pixel 3 of the second dash data block JSR DrawVergeByteRight \ (the rightmost pixel) and draw a fill byte in the \ third dash dash data block \ \ If we have reached the pitch angle of the current \ segment, then we have reached the end of the edge to \ draw, in which case DrawVergeByteRight will jump to \ StopDrawingEdge to abort this edge and return us two \ routines up the call stack, to DrawSegmentEdge \ We now do the slope error calculation for pixel 3 of \ the second dash data block ADC SS \ Set A = A + SS, to add the yaw delta to the slope \ error BCC strl2 \ If the addition didn't overflow, skip to the next \ pixel to step along the y-axis, as the cumulative \ pitch deltas haven't yet added up to a multiple of TT \ If we get here then the pitch deltas have added up to \ a whole line and the addition has overflowed, so we \ need to draw a pixel SBC TT \ If we get here then the cumulative yaw deltas in SS \ have added up to a multiple of the pitch delta in TT, \ so we subtract TT from the slope error (which we can \ do we we know the C flag is set) .strl3 LDX #2 \ Draw the edge in pixel 2 of the second dash data block JSR DrawVergeByteRight \ (the third pixel) and draw a fill byte in the third \ dash data block, or abort the drawing and return to \ DrawSegmentEdge if we've finished drawing the edge ADC SS \ Repeat the slope error calculation for pixel 2 BCC strl3 \ of the second dash data block SBC TT .strl4 LDX #1 \ Draw the edge in pixel 1 of the second dash data block JSR DrawVergeByteRight \ (the second pixel) and draw a fill byte in the third \ dash data block, or abort the drawing and return to \ DrawSegmentEdge if we've finished drawing the edge ADC SS \ Repeat the slope error calculation for pixel 1 BCC strl4 \ of the second dash data block SBC TT .strl5 LDX #0 \ Draw the edge in pixel 0 of the second dash data block JSR DrawVergeByteRight \ (the leftmost pixel) and draw a fill byte in the third \ dash data block, or abort the drawing and return to \ DrawSegmentEdge if we've finished drawing the edge ADC SS \ Repeat the slope error calculation for pixel 0 BCC strl5 \ of the second dash data block SBC TT DEC UU \ Decrement UU so we now draw in the left pair of dash \ data blocks (i.e. the first and second dash data \ blocks of the three that we set up) .strl6 LDX #3 \ Draw the edge in pixel 3 of the first dash data block JSR DrawVergeByteLeft \ (the rightmost pixel) and draw a fill byte in the \ second dash data block, or abort the drawing and \ return to DrawSegmentEdge if we've finished drawing \ the edge ADC SS \ Repeat the slope error calculation for pixel 3 BCC strl6 \ of the first dash data block SBC TT .strl7 LDX #2 \ Draw the edge in pixel 2 of the first dash data block JSR DrawVergeByteLeft \ (the third pixel) and draw a fill byte in the second \ dash data block, or abort the drawing and return to \ DrawSegmentEdge if we've finished drawing the edge ADC SS \ Repeat the slope error calculation for pixel 2 BCC strl7 \ of the first dash data block SBC TT .strl8 LDX #1 \ Draw the edge in pixel 1 of the first dash data block JSR DrawVergeByteLeft \ (the second pixel) and draw a fill byte in the second \ dash data block, or abort the drawing and return to \ DrawSegmentEdge if we've finished drawing the edge ADC SS \ Repeat the slope error calculation for pixel 1 BCC strl8 \ of the first dash data block SBC TT .strl9 LDX #0 \ Draw the edge in pixel 0 of the first dash data block JSR DrawVergeByteLeft \ (the leftmost pixel) and draw a fill byte in the \ second dash data block, or abort the drawing and \ return to DrawSegmentEdge if we've finished drawing \ the edge ADC SS \ Repeat the slope error calculation for pixel 0 BCC strl9 \ of the first dash data block SBC TT DEC S \ Decrement S to move (S R) on to the previous page, to \ move it left by two dash data blocks DEC Q \ Decrement Q to move (Q P) on to the previous page, to \ move it left by two dash data blocks DEC NN \ Decrement NN to move (NN MM) on to the previous page, \ to move it left by two dash data blocks DEC UU \ Decrement UU to the number of the previous dash data \ block to the left LDX S \ If (S R) hasn't yet reached the leftmost dash data CPX #HI(dashData0)-1 \ block (i.e. it hasn't gone past block 0, which is at CLC \ the left edge of the screen), then jump back to strl2 BNE strl2 \ to keep drawing (with the C flag cleared, as it gets \ set by the comparison) .strl10 \ If we get here then we have drawn our steep edge to \ the left, all the way to the left edge of the screen, \ so we now need to update the background table for this \ track line \ \ First, though, we need to alter the track line in Y so \ it points to the correct line, which we do by moving \ the track line up or down by one step, in the same \ direction as the edge we are drawing LDA verl1 \ Fetch the instruction that is run at the start of the \ DrawVergeByteLeft routine, which will be an INY or DEY \ instruction (this is the instruction that is run on \ entry into the DrawVergeByteLeft routine, and which \ moves the track line on to the next line in the \ correct up/down direction for the edge we are drawing) STA strl11 \ Store the instruction we just fetched into the next \ address, so we execute the same instruction before \ falling into UpdateBackground .strl11 NOP \ This instruction is modified by the above code to be \ the same INY or DEY that is executed at the start of \ the DrawVergeByteLeft routine, so it moves the track \ line on by one in the correct direction for the edge \ we are drawing \ Fall through into UpdateBackground to update the \ background colour table
Name: UpdateBackground [Show more] Type: Subroutine Category: Drawing the track Summary: Update the background colour table for when we draw a verge edge off the left edge of the screen
Context: See this subroutine on its own page References: This subroutine is called as follows: * StopDrawingEdge calls UpdateBackground

Arguments: Y The track line we are drawing on (0 to 79)
.UpdateBackground LDA vergeOnScreenEdge \ If bit 7 of vergeOnScreenEdge is set then the edge we BMI upba1 \ are drawing is partially off-screen, so jump to upba1 \ to skip the following \ If we get here then the edge we are drawing is fully \ on-screen LDA backgroundRight \ Set A = backgroundRight, which is in the format \ %010vv0ab: \ \ * %vv is the verge we are currently drawing, from \ vergeType \ \ * %ab is the colour in the first verge pixel mask \ for the verge \ \ * %010xx0xx denotes that this value is being set to \ backgroundRight by the UpdateBackground routine JMP upba2 \ Jump to upba2 .upba1 \ If we get here then the edge we are drawing is \ partially off-screen DEY \ Decrement the track line in Y LDA backgroundColour,Y \ Set A to the contents of the background colour table \ for track line Y BNE upba4 \ If the background colour currently in the table is \ non-zero, jump to upba4 to return from the subroutine LDA backgroundLeft \ The background colour currently in the table is zero, \ so set A = backgroundLeft, which is in the format \ %100vv0ab: \ \ * %vv is the verge we are currently drawing, from \ vergeType \ \ * %ab is the colour of the first three pixels in the \ verge pixel mask for the verge in objectPalette+3 \ \ * %100xx0xx denotes that this value is being set to \ backgroundLeft by the UpdateBackground routine .upba2 CPY #80 \ If Y >= 80, then this is not a valid track line BCS upba4 \ number, so jump to upba4 to return from the subroutine LDX playerSideways \ If playerSideways < 40, then the player's car is CPX #40 \ facing along the track rather than sideways, so jump BCC upba3 \ to upba3 to store A in the background colour table \ If we get here then the player's car is facing \ sideways, relative to the track direction, so we set \ the background colour to either black or green, \ ignoring the colour of the verge marks STA T \ Store A in T so we can retrieve it below AND #%00000011 \ If bits 0-1 of A >= 3, i.e. bits 0-1 of A = 3, set the CMP #3 \ C flag LDA T \ Retrieve the value of A that we stored in T above BCS upba3 \ If bits 0-1 of A = 3, jump to upba3 to store green as \ the background colour AND #%11111100 \ Otherwise clear bits 0 and 1 of A, to store black as \ the background colour .upba3 STA backgroundColour,Y \ Store A as the background colour for track line Y .upba4 RTS \ Return from the subroutine
Name: DrawVergeByteLeft [Show more] Type: Subroutine Category: Drawing the track Summary: Draw two bytes into the screen buffer in the first and second dash data blocks for the edge Deep Dive: Drawing the track verges
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawShallowToLeft calls DrawVergeByteLeft * DrawShallowToRight calls DrawVergeByteLeft * DrawSteepToLeft calls DrawVergeByteLeft * DrawSteepToRight calls DrawVergeByteLeft

This routine draws a single byte of a verge edge in the first dash data block of the three that we set up in DrawSegmentEdge. It also draws a second byte to the right, in the second dash data block, using the fill colour so the screen gets filled to the right of the verge edge. Arguments: X The pixel number of the edge within the pixel byte, with 0 being the first pixel (at the left end of the byte) to 3 as the last pixel (at the right end of the byte) Y The track line to draw on (0 to 79) RR The pitch angle of the current segment (we stop drawing the edge when Y reaches this value) UU The number of the dash data block where we start drawing JJ The right pixel byte when drawing the edge in the screen buffer, i.e. the fill byte to the right of the edge (S R) Address of the first dash data block in the memory page containing the pixels at the start of the previous edge (Q P) Address of the second dash data block in the memory page containing the pixels at the start of the previous edge Returns: C flag The C flag is cleared Y Incremented or decremented, depending on which way the verge goes (decremented if the verge goes down, incremented is it goes up) A A is unchanged
.DrawVergeByteLeft STA II \ Store A in II so we can retrieve it later .verl1 NOP \ This instruction is modified by the DrawSegmentEdge \ routine, to be NOP, INY or DEY, to move on to the next \ track line as appropriate CPY RR \ If Y = RR, we have reached the pitch angle of the BEQ StopDrawingEdge \ current edge, so jump to StopDrawingEdge to stop \ drawing the edge LDA UU \ Set A = UU, the number of the dash data block where we \ want to draw .verl2 STA &7000,Y \ This instruction is modified by the DrawVergeEdge \ routine to point to the relevant table for the edge \ we are drawing: \ \ * leftVergeStart \ \ * leftTrackStart \ \ * rightVergeStart \ \ * rightGrassStart \ \ So this stores the dash data block number for the \ previous edge in the relevant verge table for track \ line Y LDA (R),Y \ Set A to the current contents of track line Y in \ (S R), which is the dash data block containing the \ previous edge BNE verl6 \ If the current contents of the screen buffer is \ non-zero, then it already contains something, so jump \ to verl6 to merge our edge with the existing pixels LDA objectPalette,X \ Set A to the X-th entry from the object palette, which \ contains a background colour bytes with the first X \ pixels in the foreground colour .verl3 STA (R),Y \ Store the pixel byte in the dash data block LDA JJ \ Store the pixel byte in JJ in the next dash data block STA (P),Y \ to fill the pixel byte to the right of the edge byte \ with the background colour in JJ (so this fills to the \ right of the edge as we draw it) .verl4 LDA II \ Retrieve the value of A we stored above, so A is \ unchanged by the routine .verl5 INY \ This instruction is modified by the DrawSegmentEdge \ routine, to be NOP, INY or DEY, to move on to the next \ track line as appropriate CLC \ Clear the C flag RTS \ Return from the subroutine .verl6 \ If we get here then the pixel byte we are drawing into \ is non-empty CPY #44 \ If Y >= 44, jump to verl7 to skip the following check, BCS verl7 \ as offsets of 44 and above are always within a dash \ data block JSR CheckDashData \ Check whether offset Y points to dash data within \ block UU, clearing the C flag if it does BCC verl4 \ If offset Y does not point to dash data, jump to verl4 \ to return from the subroutine without drawing anything .verl7 \ If we get here then the pixel byte we are drawing into \ is non-empty and is a valid part of the screen buffer CMP #&55 \ If the current contents of the screen buffer is not BNE verl8 \ &55, then it does not denote black, so skip the \ following LDA #0 \ The current contents of the screen buffer is &55, \ which denotes black, so set A to 0 so we merge with a \ pixel byte of black .verl8 AND pixelsToLeft,X \ AND the current contents with the X-th pixel mask from \ pixelsToLeft, which is a pixel byte with all the \ pixels set to the left of the X-th pixel, so this \ clears all pixels in the existing screen buffer byte \ from the X-th pixel and to the right \ \ In other words, this clears the track edge and to the \ right, but keeps content to the left of the edge ORA vergeEdgeRight,X \ We set up vergeEdgeRight to contain the pixel bytes \ from objectPalette, but masked to only include the \ rightmost 4, 3, 2 and 1 pixels, so this inserts the \ edge and all the pixels to the right of the edge into \ the pixels that we just cleared, thus drawing the edge \ on top of what's already on-screen BNE verl3 \ If the resulting pixel byte in A is non-zero, then we \ are ready to poke it into the screen buffer, so jump \ to verl3 LDA #&55 \ The resulting pixel byte is zero, which is a black \ pixel byte, and we represent this in the screen buffer \ with &55, so set A to &55 so we poke this into the \ screen buffer instead of zero BNE verl3 \ Jump to verl3 (this BNE is effectively a JMP as A is \ never zero)
Name: StopDrawingEdge [Show more] Type: Subroutine Category: Drawing the track Summary: Stop drawing the current segment's verge edge
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawVergeByteLeft calls StopDrawingEdge * DrawVergeByteRight calls StopDrawingEdge

This routine stops drawing the current segment's edge when called from DrawVergeByteLeft or DrawVergeByteRight.
.StopDrawingEdge TSX \ These instructions remove two bytes from the top of INX \ the stack so the RTS below returns an extra level up INX \ the call chain TXS \ \ We jump to this routine from the DrawVergeByteLeft and \ DrawVergeByteRight routines. These are only called \ from the DrawShallowToLeft, DrawShallowToRight, \ DrawSteepToLeft or DrawSteepToRight routines, which in \ turn are only called from DrawSegmentEdge, so this \ returns us to DrawSegmentEdge to stop drawing the \ current segment's verge edge and move on to the next \ segment LDA updateBackground \ If updateBackground is non-zero, then we just drew a BNE UpdateBackground \ verge on the start of the left edge of the screen, so \ call UpdateBackground to update the background colour \ for this track line, returning from the subroutine \ using a tail call RTS \ Return from the subroutine
Name: DrawVergeByteRight [Show more] Type: Subroutine Category: Drawing the track Summary: Draw two bytes into the screen buffer in the second and third dash data blocks for the edge Deep Dive: Drawing the track verges
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawShallowToLeft calls DrawVergeByteRight * DrawShallowToRight calls DrawVergeByteRight * DrawSteepToLeft calls DrawVergeByteRight * DrawSteepToRight calls DrawVergeByteRight

This routine draws a single byte of a verge edge in the second dash data block of the three that we set up in DrawSegmentEdge. It also draws a second byte to the right, in the third dash data block, using the fill colour so the screen gets filled to the right of the verge edge. Arguments: X The pixel number of the edge within the pixel byte, with 0 being the first pixel (at the left end of the byte) to 3 as the last pixel (at the right end of the byte) Y The track line to draw on (0 to 79) RR The pitch angle of the current segment (we stop drawing the edge when Y reaches this value) UU The number of the dash data block where we start drawing JJ The right pixel byte when drawing the edge in the screen buffer, i.e. the fill byte to the right of the edge (Q P) Address of the second dash data block in the memory page containing the pixels at the start of the previous edge (NN MM) Address of the third dash data block in this sequence, i.e. the first memory page of the next dash data block Returns: C flag The C flag is cleared Y Incremented or decremented, depending on which way the verge goes (decremented if the verge goes down, incremented is it goes up) A A is unchanged
.DrawVergeByteRight STA II \ Store A in II so we can retrieve it later .verb1 NOP \ This instruction is modified by the DrawSegmentEdge \ routine, to be NOP, INY or DEY, to move on to the next \ track line as appropriate CPY RR \ If Y = RR, we have reached the pitch angle of the BEQ StopDrawingEdge \ current edge, so jump to StopDrawingEdge to stop \ drawing the edge LDA UU \ Set A = UU, the number of the dash data block where we \ want to draw .verb2 STA &7000,Y \ This instruction is modified by the DrawVergeEdge \ routine to point to the relevant table for the edge \ we are drawing: \ \ * leftVergeStart \ \ * leftTrackStart \ \ * rightVergeStart \ \ * rightGrassStart \ \ So this stores the dash data block number for the \ previous edge in the relevant verge table for track \ line Y LDA (P),Y \ Set A to the current contents of track line Y in \ (Q P), which is the dash data block containing the \ previous edge BNE verb6 \ If the current contents of the screen buffer is \ non-zero, then it already contains something, so jump \ to verb6 to merge our edge with the existing pixels LDA objectPalette,X \ Set A to the X-th entry from the object palette, which \ contains a background colour bytes with the first X \ pixels in the foreground colour .verb3 STA (P),Y \ Store the pixel byte in the dash data block LDA JJ \ Store the pixel byte in JJ in the next dash data block STA (MM),Y \ to fill the pixel byte to the right of the edge byte \ with the background colour in JJ (so this fills to the \ right of the edge as we draw it) .verb4 LDA II \ Retrieve the value of A we stored above, so A is \ unchanged by the routine .verb5 INY \ This instruction is modified by the DrawSegmentEdge \ routine, to be NOP, INY or DEY, to move on to the next \ track line as appropriate CLC \ Clear the C flag RTS \ Return from the subroutine .verb6 \ If we get here then the pixel byte we are drawing into \ is non-empty CPY #44 \ If Y >= 44, jump to verb7 to skip the following check, BCS verb7 \ as offsets of 44 and above are always within a dash \ data block JSR CheckDashData \ Check whether offset Y points to dash data within \ block UU, clearing the C flag if it does BCC verb4 \ If offset Y does not point to dash data, jump to verb4 \ to return from the subroutine without drawing anything .verb7 \ If we get here then the pixel byte we are drawing into \ is non-empty and is a valid part of the screen buffer CMP #&55 \ If the current contents of the screen buffer is not BNE verb8 \ &55, then it does not denote black, so skip the \ following LDA #0 \ The current contents of the screen buffer is &55, \ which denotes black, so set A to 0 so we merge with a \ pixel byte of black .verb8 AND pixelsToLeft,X \ AND the current contents with the X-th pixel mask from \ pixelsToLeft, which is a pixel byte with all the \ pixels set to the left of the X-th pixel, so this \ clears all pixels in the existing screen buffer byte \ from the X-th pixel and to the right \ \ In other words, this clears the track edge and to the \ right, but keeps content to the left of the edge ORA vergeEdgeRight,X \ We set up vergeEdgeRight to contain the pixel bytes \ from objectPalette, but masked to only include the \ rightmost 4, 3, 2 and 1 pixels, so this inserts the \ edge and all the pixels to the right of the edge into \ the pixels that we just cleared, thus drawing the edge \ on top of what's already on-screen BNE verb3 \ If the resulting pixel byte in A is non-zero, then we \ are ready to poke it into the screen buffer, so jump \ to verb3 LDA #&55 \ The resulting pixel byte is zero, which is a black \ pixel byte, and we represent this in the screen buffer \ with &55, so set A to &55 so we poke this into the \ screen buffer instead of zero BNE verb3 \ Jump to verb3 (this BNE is effectively a JMP as A is \ never zero)
Name: DrawGrassRight [Show more] Type: Subroutine Category: Drawing the track Summary: Draw a green byte into the screen buffer in the second dash data block for the edge Deep Dive: Drawing the track verges
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawSegmentEdge (Part 6 of 7) calls DrawGrassRight * DrawShallowToLeft calls DrawGrassRight * DrawShallowToRight calls DrawGrassRight

Arguments: X The value of X from the current drawing routine: * 0 to 3 if a pixel byte has already been drawn * 128 is no pixel byte has been drawn so far Y The track line to draw on (0 to 79) (Q P) Address of the second dash data block in the memory page containing the pixels at the start of the previous edge Returns: X 128 A A is unchanged C flag Clear
.DrawGrassRight CPX #128 \ If X <> 128 then the pixel byte has already been drawn BNE grar2 \ in the calling routine (i.e. DrawShallowToLeft or \ DrawShallowToRight), so jump to grar2 to return from \ the subroutine CPY #44 \ If Y >= 44, jump to grar1 to skip the following check, BCS grar1 \ as offsets of 44 and above are always within a dash \ data block JSR CheckDashData \ Check whether offset Y points to dash data within \ block UU, clearing the C flag if it does BCC grar2 \ If offset Y does not point to dash data, jump to grar2 \ to return from the subroutine .grar1 TAX \ Y points into dash data, so set the Y-th byte of (Q P) LDA #%11111111 \ to four pixels of colour 3 (green) STA (P),Y TXA .grar2 LDX #128 \ Set X = 128 to reset the draw detection logic for the \ next byte to draw CLC \ Clear the C flag RTS \ Return from the subroutine
Name: DrawGrassLeft [Show more] Type: Subroutine Category: Drawing the track Summary: Draw a green byte into the screen buffer in the first dash data block for the edge Deep Dive: Drawing the track verges
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawSegmentEdge (Part 6 of 7) calls DrawGrassLeft * DrawShallowToLeft calls DrawGrassLeft * DrawShallowToRight calls DrawGrassLeft

Arguments: X The value of X from the current drawing routine: * 0 to 3 if a pixel byte has already been drawn * 128 is no pixel byte has been drawn so far Y The track line to draw on (0 to 79) (S R) Address of the first dash data block in the memory page containing the pixels at the start of the previous edge Returns: X 128 A A is unchanged C flag Clear
.DrawGrassLeft CPX #128 \ If X <> 128 then the pixel byte has already been drawn BNE gral2 \ in the calling routine (i.e. DrawShallowToLeft or \ DrawShallowToRight), so jump to gral2 to return from \ the subroutine CPY #44 \ If Y >= 44, jump to gral1 to skip the following check, BCS gral1 \ as offsets of 44 and above are always within a dash \ data block JSR CheckDashData \ Check whether offset Y points to dash data within \ block UU, clearing the C flag if it does BCC gral2 \ If offset Y does not point to dash data, jump to gral2 \ to return from the subroutine .gral1 TAX \ Y points into dash data, so set the Y-th byte of (S R) LDA #%11111111 \ to four pixels of colour 3 (green) STA (R),Y TXA .gral2 LDX #128 \ Set X = 128 to reset the draw detection logic for the \ next byte to draw CLC \ Clear the C flag RTS \ Return from the subroutine
Name: CheckDashData [Show more] Type: Subroutine Category: Screen buffer Summary: Check whether a dash data block index is pointing to dash data
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawGrassLeft calls CheckDashData * DrawGrassRight calls CheckDashData * DrawVergeByteLeft calls CheckDashData * DrawVergeByteRight calls CheckDashData

This routine checks whether an index in Y, which is relative to the start of a dash data block, is pointing to dash data within the block. Arguments: UU Dash data block number (0 to 39) Y The index from the start of the dash data block Returns: C flag The result, as follows: * Clear if offset Y does not point to dash data * Set if offset Y does point to dash data A A is unchanged X X is unchanged
.CheckDashData STA T \ Store A and X in T and U so we can retrieve them below STX U LDX UU \ Set X to the dash data block number TYA \ Set the C flag as follows: CMP dashDataOffset,X \ \ * C flag clear if Y < dashDataOffset,X \ \ * C flag set if Y >= dashDataOffset,X BNE cdas1 \ If Y <> the dashDataOffset of block X, skip the \ following instruction CLC \ If we get here then Y = the dashDataOffset of block X, \ so clear the C flag \ So by this point we have: \ \ * C flag clear if Y <= dashDataOffset,X \ \ * C flag set if Y > dashDataOffset,X \ \ As the dash data lives at the top of each dash data \ block, this gives us the result we want (as Y is \ pointing to data when Y > dashDataOffset,X) .cdas1 LDA T \ Restore the values of A and X that we stored above LDX U RTS \ Return from the subroutine
Name: token26 [Show more] Type: Variable Category: Text Summary: Text for recursive token 26 Deep dive: Text tokens
Context: See this variable on its own page References: This variable is used as follows: * tokenHi calls token26 * tokenLo calls token26
.token26 EQUB 200 + 54 \ Print token 54 ("FORMULA 3 CHAMPIONSHIP" header) EQUB 31, 10, 12 \ Move text cursor to column 10, row 12 EQUB 131 \ Set foreground colour to yellow alphanumeric EQUS "STANDARD OF" \ Print "STANDARD OF" EQUB 200 + 15 \ Print token 15 (" RACE") EQUB 31, 14, 14 \ Move text cursor to column 14, row 14 EQUB 255 \ End token
Name: token4 [Show more] Type: Variable Category: Text Summary: Text for recursive token 4 Deep dive: Text tokens
Context: See this variable on its own page References: This variable is used as follows: * tokenHi calls token4 * tokenLo calls token4
.token4 EQUB 200 + 52 \ Print token 52 (multicoloured "REVS") EQUB 160 + 3 \ Print 3 spaces EQUB 200 + 52 \ Print token 52 (multicoloured "REVS") EQUB 160 + 3 \ Print 3 spaces EQUB 200 + 52 \ Print token 52 (multicoloured "REVS") EQUB 160 + 1 \ Print 1 space EQUB 255 \ End token
Name: dashData0 [Show more] Type: Variable Category: Screen buffer Summary: Contains code that gets moved into screen memory Deep dive: The jigsaw puzzle binary
Context: See this variable on its own page References: This variable is used as follows: * DrawShallowToLeft calls dashData0 * DrawSteepToLeft calls dashData0 * dashDataOffset calls dashData0 * fillDataOffset calls dashData0
.dashData0 SKIP 52 \ Populated with code from &7FCC to &7FFF
Name: tyreEdgeIndex [Show more] Type: Variable Category: Dashboard Summary: Index of the mask and pixel bytes for the tyre edges on a specific track line Deep dive: Drawing around the dashboard
Context: See this variable on its own page References: This variable is used as follows: * DrawTrackView (Part 3 of 4) calls tyreEdgeIndex

This table points to the index of the mask or pixel byte to use from the leftTyreMask/rightTyreMask and leftTyrePixels/rightTyrePixels tables for a specific track line. This is used when clipping track lines to the tyre edges. There is a byte for each track line from 27 (the track line at the top of the tyres) down to 3 (the lowest track line, just above where the wing mirror joins the car body). Lines 0 to 2 are not used.
.tyreEdgeIndex EQUB 0 \ Line 0 EQUB 0 \ Line 1 EQUB 0 \ Line 2 EQUB 0 \ Line 3 EQUB 6 \ Line 4 EQUB 6 \ Line 5 EQUB 6 \ Line 6 EQUB 6 \ Line 7 EQUB 6 \ Line 8 EQUB 6 \ Line 9 EQUB 6 \ Line 10 EQUB 3 \ Line 11 EQUB 3 \ Line 12 EQUB 3 \ Line 13 EQUB 3 \ Line 14 EQUB 3 \ Line 15 EQUB 1 \ Line 16 EQUB 1 \ Line 17 EQUB 1 \ Line 18 EQUB 0 \ Line 19 EQUB 0 \ Line 20 EQUB 0 \ Line 21 EQUB 5 \ Line 22 EQUB 4 \ Line 23 EQUB 3 \ Line 24 EQUB 2 \ Line 25 EQUB 1 \ Line 26 EQUB 0 \ Line 27
Name: segmentFlagMask [Show more] Type: Variable Category: Track geometry Summary: A mask for extracting bits from the segment flag byte when processing the track verge and corner markers
Context: See this variable on its own page References: This variable is used as follows: * GetVergeAndMarkers (Part 1 of 4) calls segmentFlagMask
.segmentFlagMask EQUB %00101101 \ Mask for the right verge, to keep the following from \ the segment flags: \ \ * Bit 0 (section shape) \ * Bit 2 (colour of right verge marks) \ * Bit 3 (show right corner markers) \ * Bit 5 (corner marker colours) EQUB %00110011 \ Mask for the left verge, to keep the following from \ the segment flags: \ \ * Bit 0 (section shape) \ * Bit 1 (colour of left verge marks) \ * Bit 4 (show left corner markers) \ * Bit 5 (corner marker colours)
Name: vergeColour [Show more] Type: Variable Category: Track geometry Summary: Lookup table for converting bits 0-2 of the segment flag byte into the verge colour scheme
Context: See this variable on its own page References: This variable is used as follows: * GetVergeAndMarkers (Part 1 of 4) calls vergeColour
.vergeColour EQUB 0 \ * 0 = %000 = black right, black left, straight EQUB 0 \ * 1 = %001 = black right, black left, curve EQUB 1 \ * 2 = %010 = black right, red left, straight EQUB 1 \ * 3 = %011 = black right, red left, curve EQUB 1 \ * 4 = %100 = red right, black left, straight EQUB 1 \ * 5 = %101 = red right, black left, curve EQUB 1 \ * 6 = %110 = red right, red left, straight EQUB 1 \ * 7 = %111 = red right, red left, curve
Name: vergeScale [Show more] Type: Variable Category: Track geometry Summary: Scale factors for the track verge width for different types of verge (larger value = smaller verge width)
Context: See this variable on its own page References: This variable is used as follows: * GetVergeAndMarkers (Part 2 of 4) calls vergeScale
.vergeScale EQUB 5 \ * 0 = %000 = black right, black left, straight EQUB 5 \ * 1 = %001 = black right, black left, curve EQUB 3 \ * 2 = %010 = black right, red left, straight EQUB 4 \ * 3 = %011 = black right, red left, curve EQUB 3 \ * 4 = %100 = red right, black left, straight EQUB 4 \ * 5 = %101 = red right, black left, curve EQUB 4 \ * 6 = %110 = red right, red left, straight EQUB 4 \ * 7 = %111 = red right, red left, curve EQUB &38, &38 \ These bytes appear to be unused
Name: staDrawByteTyre [Show more] Type: Variable Category: Screen buffer Summary: Low address bytes of the STA instructions in the DRAW_BYTE macros, for use when drawing track lines around the tyres
Context: See this variable on its own page References: This variable is used as follows: * DrawTrackView (Part 3 of 4) calls staDrawByteTyre

This table contains the low byte offset of the address of the STA (P),Y instruction for the track line, which we convert into an RTS when drawing the track line up against the right tyre, so we stop in time. As the tyres are reflections of each other, we can also use this to calculate the starting point for the line that starts at the left tyre: see part 3 of DrawTrackView for details.
.staDrawByteTyre EQUB 8 * 17 + 15 \ Line 0 = LO(address) of STA (P),Y in DRAW_BYTE 34 EQUB 8 * 17 + 15 \ Line 1 = LO(address) of STA (P),Y in DRAW_BYTE 34 EQUB 8 * 17 + 15 \ Line 2 = LO(address) of STA (P),Y in DRAW_BYTE 34 EQUB 8 * 17 + 15 \ Line 3 = LO(address) of STA (P),Y in DRAW_BYTE 34 EQUB 9 * 17 + 15 \ Line 4 = LO(address) of STA (P),Y in DRAW_BYTE 35 EQUB 9 * 17 + 15 \ Line 5 = LO(address) of STA (P),Y in DRAW_BYTE 35 EQUB 9 * 17 + 15 \ Line 6 = LO(address) of STA (P),Y in DRAW_BYTE 35 EQUB 9 * 17 + 15 \ Line 7 = LO(address) of STA (P),Y in DRAW_BYTE 35 EQUB 9 * 17 + 15 \ Line 8 = LO(address) of STA (P),Y in DRAW_BYTE 35 EQUB 9 * 17 + 15 \ Line 9 = LO(address) of STA (P),Y in DRAW_BYTE 35 EQUB 9 * 17 + 15 \ Line 10 = LO(address) of STA (P),Y in DRAW_BYTE 35 EQUB 9 * 17 + 15 \ Line 11 = LO(address) of STA (P),Y in DRAW_BYTE 35 EQUB 9 * 17 + 15 \ Line 12 = LO(address) of STA (P),Y in DRAW_BYTE 35 EQUB 9 * 17 + 15 \ Line 13 = LO(address) of STA (P),Y in DRAW_BYTE 35 EQUB 9 * 17 + 15 \ Line 14 = LO(address) of STA (P),Y in DRAW_BYTE 35 EQUB 9 * 17 + 15 \ Line 15 = LO(address) of STA (P),Y in DRAW_BYTE 35 EQUB 9 * 17 + 15 \ Line 16 = LO(address) of STA (P),Y in DRAW_BYTE 35 EQUB 9 * 17 + 15 \ Line 17 = LO(address) of STA (P),Y in DRAW_BYTE 35 EQUB 9 * 17 + 15 \ Line 18 = LO(address) of STA (P),Y in DRAW_BYTE 35 EQUB 9 * 17 + 15 \ Line 19 = LO(address) of STA (P),Y in DRAW_BYTE 35 EQUB 9 * 17 + 15 \ Line 20 = LO(address) of STA (P),Y in DRAW_BYTE 35 EQUB 9 * 17 + 15 \ Line 21 = LO(address) of STA (P),Y in DRAW_BYTE 35 EQUB 10 * 17 + 15 \ Line 22 = LO(address) of STA (P),Y in DRAW_BYTE 36 EQUB 10 * 17 + 15 \ Line 23 = LO(address) of STA (P),Y in DRAW_BYTE 36 EQUB 10 * 17 + 15 \ Line 24 = LO(address) of STA (P),Y in DRAW_BYTE 36 EQUB 10 * 17 + 15 \ Line 25 = LO(address) of STA (P),Y in DRAW_BYTE 36 EQUB 10 * 17 + 15 \ Line 26 = LO(address) of STA (P),Y in DRAW_BYTE 36 EQUB 10 * 17 + 15 \ Line 27 = LO(address) of STA (P),Y in DRAW_BYTE 36
Name: dashData1 [Show more] Type: Variable Category: Screen buffer Summary: Contains code that gets moved into screen memory Deep dive: The jigsaw puzzle binary
Context: See this variable on its own page References: This variable is used as follows: * dashDataOffset calls dashData1 * fillDataOffset calls dashData1
.dashData1 SKIP 52 \ Populated with code from &7F98 to &7FCB
Name: ldaDrawByte [Show more] Type: Variable Category: Screen buffer Summary: Low address bytes of the LDA #0 instructions in the DRAW_BYTE macros, for use when drawing track lines around the dashboard
Context: See this variable on its own page References: This variable is used as follows: * DrawTrackView (Part 2 of 4) calls ldaDrawByte * DrawTrackView (Part 3 of 4) calls ldaDrawByte
.ldaDrawByte IF _ACORNSOFT OR _4TRACKS EQUB &1C \ Line 0 is unused and contains workspace noise ELIF _SUPERIOR OR _REVSPLUS EQUB &28 \ Line 0 is unused and contains workspace noise ENDIF EQUB 8 * 17 + 5 \ Line 1 = LO(address) of LDA #0 in DRAW_BYTE 34 EQUB 7 * 17 + 5 \ Line 2 = LO(address) of LDA #0 in DRAW_BYTE 33 EQUB 7 * 17 + 5 \ Line 3 = LO(address) of LDA #0 in DRAW_BYTE 33 EQUB 7 * 17 + 5 \ Line 4 = LO(address) of LDA #0 in DRAW_BYTE 33 EQUB 7 * 17 + 5 \ Line 5 = LO(address) of LDA #0 in DRAW_BYTE 33 EQUB 7 * 17 + 5 \ Line 6 = LO(address) of LDA #0 in DRAW_BYTE 33 EQUB 6 * 17 + 5 \ Line 7 = LO(address) of LDA #0 in DRAW_BYTE 32 EQUB 6 * 17 + 5 \ Line 8 = LO(address) of LDA #0 in DRAW_BYTE 32 EQUB 6 * 17 + 5 \ Line 9 = LO(address) of LDA #0 in DRAW_BYTE 32 EQUB 6 * 17 + 5 \ Line 10 = LO(address) of LDA #0 in DRAW_BYTE 32 EQUB 6 * 17 + 5 \ Line 11 = LO(address) of LDA #0 in DRAW_BYTE 32 EQUB 5 * 17 + 5 \ Line 12 = LO(address) of LDA #0 in DRAW_BYTE 31 EQUB 5 * 17 + 5 \ Line 13 = LO(address) of LDA #0 in DRAW_BYTE 31 EQUB 5 * 17 + 5 \ Line 14 = LO(address) of LDA #0 in DRAW_BYTE 31 EQUB 5 * 17 + 5 \ Line 15 = LO(address) of LDA #0 in DRAW_BYTE 31 EQUB 4 * 17 + 5 \ Line 16 = LO(address) of LDA #0 in DRAW_BYTE 30 EQUB 4 * 17 + 5 \ Line 17 = LO(address) of LDA #0 in DRAW_BYTE 30 EQUB 4 * 17 + 5 \ Line 18 = LO(address) of LDA #0 in DRAW_BYTE 30 EQUB 4 * 17 + 5 \ Line 19 = LO(address) of LDA #0 in DRAW_BYTE 30 EQUB 3 * 17 + 5 \ Line 20 = LO(address) of LDA #0 in DRAW_BYTE 29 EQUB 3 * 17 + 5 \ Line 21 = LO(address) of LDA #0 in DRAW_BYTE 29 EQUB 3 * 17 + 5 \ Line 22 = LO(address) of LDA #0 in DRAW_BYTE 29 EQUB 3 * 17 + 5 \ Line 23 = LO(address) of LDA #0 in DRAW_BYTE 29 EQUB 2 * 17 + 5 \ Line 24 = LO(address) of LDA #0 in DRAW_BYTE 28 EQUB 2 * 17 + 5 \ Line 25 = LO(address) of LDA #0 in DRAW_BYTE 28 EQUB 2 * 17 + 5 \ Line 26 = LO(address) of LDA #0 in DRAW_BYTE 28 EQUB 2 * 17 + 5 \ Line 27 = LO(address) of LDA #0 in DRAW_BYTE 28 EQUB 1 * 17 + 5 \ Line 28 = LO(address) of LDA #0 in DRAW_BYTE 27 EQUB 1 * 17 + 5 \ Line 29 = LO(address) of LDA #0 in DRAW_BYTE 27 EQUB 1 * 17 + 5 \ Line 30 = LO(address) of LDA #0 in DRAW_BYTE 27 EQUB 1 * 17 + 5 \ Line 31 = LO(address) of LDA #0 in DRAW_BYTE 27 EQUB 1 * 17 + 5 \ Line 32 = LO(address) of LDA #0 in DRAW_BYTE 27 EQUB 1 * 17 + 5 \ Line 33 = LO(address) of LDA #0 in DRAW_BYTE 27 EQUB 1 * 17 + 5 \ Line 34 = LO(address) of LDA #0 in DRAW_BYTE 27 EQUB 1 * 17 + 5 \ Line 35 = LO(address) of LDA #0 in DRAW_BYTE 27 EQUB 1 * 17 + 5 \ Line 36 = LO(address) of LDA #0 in DRAW_BYTE 27 EQUB 1 * 17 + 5 \ Line 37 = LO(address) of LDA #0 in DRAW_BYTE 27 EQUB 1 * 17 + 5 \ Line 38 = LO(address) of LDA #0 in DRAW_BYTE 27 EQUB 0 * 17 + 5 \ Line 39 = LO(address) of LDA #0 in DRAW_BYTE 26 EQUB 0 * 17 + 5 \ Line 40 = LO(address) of LDA #0 in DRAW_BYTE 26 EQUB 0 * 17 + 5 \ Line 41 = LO(address) of LDA #0 in DRAW_BYTE 26 EQUB 0 * 17 + 5 \ Line 42 = LO(address) of LDA #0 in DRAW_BYTE 26 EQUB 0 * 17 + 5 \ Line 43 = LO(address) of LDA #0 in DRAW_BYTE 26
Name: vergeEdgeInOut [Show more] Type: Variable Category: Drawing the track Summary: Table for mapping the verge tables to the outer and inner edges of the verge marks
Context: See this variable on its own page References: This variable is used as follows: * DrawVergeEdge calls vergeEdgeInOut
.vergeEdgeInOut EQUB 16 \ leftVergeStart is an outer edge of the verge mark EQUB 0 \ leftTrackStart is an inner edge of the verge mark EQUB 0 \ rightVergeStart is an inner edge of the verge mark EQUB 16 \ rightGrassStart is an outer edge of the verge mark
Name: handPixels [Show more] Type: Variable Category: Dashboard Summary: The number of pixels in the longest axis for the rev counter hand at various points in a half-quadrant Deep dive: Trigonometry
Context: See this variable on its own page References: This variable is used as follows: * DrawRevCounter calls handPixels

This table contains values that are used to calculate the coordinates of the end of the hand in the rev counter. The contents of the table are very close to the following (the values from the following calculation are shown in the comments below - they are close, but not quite a perfect match, so I haven't got this exactly right): FOR I%, 0, 21 EQUB INT(0.5 + 28 * COS((PI/4) * I% / 21)) NEXT This gives the length of the adjacent side of a right-angled triangle, with a hypotenuse of length 28, and an angle ranging from 0 to PI/4 (i.e. one eighth of a circle), split up into 21 points per eighth of a circle. In other words, if we have a clock whose centre is at the origin, then this table contains the x-coordinate of the end of a clock hand of length 28 as it moves from 3 o'clock to half past 4.
.handPixels EQUB 28 \ INT(0.5 + 28.00) = 28 EQUB 28 \ INT(0.5 + 27.98) = 28 EQUB 28 \ INT(0.5 + 27.92) = 28 EQUB 28 \ INT(0.5 + 27.82) = 28 EQUB 28 \ INT(0.5 + 27.69) = 28 EQUB 27 \ INT(0.5 + 27.51) = 28 (doesn't match) EQUB 27 \ INT(0.5 + 27.30) = 27 EQUB 27 \ INT(0.5 + 27.05) = 27 EQUB 27 \ INT(0.5 + 26.76) = 27 EQUB 26 \ INT(0.5 + 26.43) = 26 EQUB 26 \ INT(0.5 + 26.06) = 26 EQUB 26 \ INT(0.5 + 25.66) = 26 EQUB 25 \ INT(0.5 + 25.23) = 25 EQUB 25 \ INT(0.5 + 24.76) = 25 EQUB 24 \ INT(0.5 + 24.25) = 24 EQUB 24 \ INT(0.5 + 23.71) = 24 EQUB 23 \ INT(0.5 + 23.13) = 23 EQUB 22 \ INT(0.5 + 22.53) = 23 (doesn't match) EQUB 21 \ INT(0.5 + 21.89) = 22 (doesn't match) EQUB 20 \ INT(0.5 + 21.22) = 21 (doesn't match) EQUB 20 \ INT(0.5 + 20.53) = 21 (doesn't match) EQUB 20 \ INT(0.5 + 19.80) = 20 EQUB &81, &81 \ These bytes appear to be unused EQUB &81, &81 EQUB &81, &81
Name: dashData2 [Show more] Type: Variable Category: Screen buffer Summary: Contains code that gets moved into screen memory Deep dive: The jigsaw puzzle binary
Context: See this variable on its own page References: This variable is used as follows: * dashDataOffset calls dashData2 * fillDataOffset calls dashData2
.dashData2 SKIP 52 \ Populated with code from &7F64 to &7F97
Name: staDrawByte [Show more] Type: Variable Category: Screen buffer Summary: Low address bytes of the STA instructions in the DRAW_BYTE macros, for use when drawing track lines around the dashboard
Context: See this variable on its own page References: This variable is used as follows: * DrawTrackView (Part 2 of 4) calls staDrawByte * DrawTrackView (Part 3 of 4) calls staDrawByte
.staDrawByte EQUB 3 * 17 + 15 \ Line 0 = LO(address) of STA (P),Y in DRAW_BYTE 3 EQUB 5 * 17 + 15 \ Line 1 = LO(address) of STA (P),Y in DRAW_BYTE 5 EQUB 6 * 17 + 15 \ Line 2 = LO(address) of STA (P),Y in DRAW_BYTE 6 EQUB 6 * 17 + 15 \ Line 3 = LO(address) of STA (P),Y in DRAW_BYTE 6 EQUB 6 * 17 + 15 \ Line 4 = LO(address) of STA (P),Y in DRAW_BYTE 6 EQUB 6 * 17 + 15 \ Line 5 = LO(address) of STA (P),Y in DRAW_BYTE 6 EQUB 6 * 17 + 15 \ Line 6 = LO(address) of STA (P),Y in DRAW_BYTE 6 EQUB 7 * 17 + 15 \ Line 7 = LO(address) of STA (P),Y in DRAW_BYTE 7 EQUB 7 * 17 + 15 \ Line 8 = LO(address) of STA (P),Y in DRAW_BYTE 7 EQUB 7 * 17 + 15 \ Line 9 = LO(address) of STA (P),Y in DRAW_BYTE 7 EQUB 7 * 17 + 15 \ Line 10 = LO(address) of STA (P),Y in DRAW_BYTE 7 EQUB 7 * 17 + 15 \ Line 11 = LO(address) of STA (P),Y in DRAW_BYTE 7 EQUB 8 * 17 + 15 \ Line 12 = LO(address) of STA (P),Y in DRAW_BYTE 8 EQUB 8 * 17 + 15 \ Line 13 = LO(address) of STA (P),Y in DRAW_BYTE 8 EQUB 8 * 17 + 15 \ Line 14 = LO(address) of STA (P),Y in DRAW_BYTE 8 EQUB 8 * 17 + 15 \ Line 15 = LO(address) of STA (P),Y in DRAW_BYTE 8 EQUB 9 * 17 + 15 \ Line 16 = LO(address) of STA (P),Y in DRAW_BYTE 9 EQUB 9 * 17 + 15 \ Line 17 = LO(address) of STA (P),Y in DRAW_BYTE 9 EQUB 9 * 17 + 15 \ Line 18 = LO(address) of STA (P),Y in DRAW_BYTE 9 EQUB 9 * 17 + 15 \ Line 19 = LO(address) of STA (P),Y in DRAW_BYTE 9 EQUB 10 * 17 + 15 \ Line 20 = LO(address) of STA (P),Y in DRAW_BYTE 10 EQUB 10 * 17 + 15 \ Line 21 = LO(address) of STA (P),Y in DRAW_BYTE 10 EQUB 10 * 17 + 15 \ Line 22 = LO(address) of STA (P),Y in DRAW_BYTE 10 EQUB 10 * 17 + 15 \ Line 23 = LO(address) of STA (P),Y in DRAW_BYTE 10 EQUB 11 * 17 + 15 \ Line 24 = LO(address) of STA (P),Y in DRAW_BYTE 11 EQUB 11 * 17 + 15 \ Line 25 = LO(address) of STA (P),Y in DRAW_BYTE 11 EQUB 11 * 17 + 15 \ Line 26 = LO(address) of STA (P),Y in DRAW_BYTE 11 EQUB 11 * 17 + 15 \ Line 27 = LO(address) of STA (P),Y in DRAW_BYTE 11 EQUB 12 * 17 + 15 \ Line 28 = LO(address) of STA (P),Y in DRAW_BYTE 12 EQUB 12 * 17 + 15 \ Line 29 = LO(address) of STA (P),Y in DRAW_BYTE 12 EQUB 12 * 17 + 15 \ Line 30 = LO(address) of STA (P),Y in DRAW_BYTE 12 EQUB 12 * 17 + 15 \ Line 31 = LO(address) of STA (P),Y in DRAW_BYTE 12 EQUB 12 * 17 + 15 \ Line 32 = LO(address) of STA (P),Y in DRAW_BYTE 12 EQUB 12 * 17 + 15 \ Line 33 = LO(address) of STA (P),Y in DRAW_BYTE 12 EQUB 12 * 17 + 15 \ Line 34 = LO(address) of STA (P),Y in DRAW_BYTE 12 EQUB 12 * 17 + 15 \ Line 35 = LO(address) of STA (P),Y in DRAW_BYTE 12 EQUB 12 * 17 + 15 \ Line 36 = LO(address) of STA (P),Y in DRAW_BYTE 12 EQUB 12 * 17 + 15 \ Line 37 = LO(address) of STA (P),Y in DRAW_BYTE 12 EQUB 12 * 17 + 15 \ Line 38 = LO(address) of STA (P),Y in DRAW_BYTE 12 EQUB 13 * 17 + 15 \ Line 39 = LO(address) of STA (P),Y in DRAW_BYTE 13 EQUB 13 * 17 + 15 \ Line 40 = LO(address) of STA (P),Y in DRAW_BYTE 13 EQUB 13 * 17 + 15 \ Line 41 = LO(address) of STA (P),Y in DRAW_BYTE 13 EQUB 13 * 17 + 15 \ Line 42 = LO(address) of STA (P),Y in DRAW_BYTE 13 EQUB 13 * 17 + 15 \ Line 43 = LO(address) of STA (P),Y in DRAW_BYTE 13 EQUB &28, &28 \ These bytes appear to be unused EQUB &00, &00 EQUB &81, &81 EQUB &81, &81 EQUB &81, &81 EQUB &81, &81 EQUB &81, &81 EQUB &81, &81 EQUB &81, &81 EQUB &81, &81 EQUB &81, &81 EQUB &81, &81 EQUB &81, &81
Name: dashData3 [Show more] Type: Variable Category: Screen buffer Summary: Contains code that gets moved into screen memory Deep dive: The jigsaw puzzle binary
Context: See this variable on its own page References: This variable is used as follows: * dashDataOffset calls dashData3 * fillDataOffset calls dashData3
.dashData3 SKIP 58 \ Populated with code from &7F2A to &7F63
Name: DrawFence (Part 2 of 2) [Show more] Type: Subroutine Category: Screen buffer Summary: Draw the fence that we crash into when running off the track
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.fenc2 LDX #3 \ Set X = 3, to use as an index into the fencePixelsSky \ and fencePixelsGrass tables, so we draw pixel bytes \ repeatedly from these two tables to build up the fence \ effect .fenc3 CPY horizonLine \ If Y < horizonLine, then this pixel row is below the BCC fenc4 \ horizon, so jump to fenc4 to draw the fence with green \ grass behind it LDA fencePixelsSky,X \ Otherwise this pixel row is above the horizon, so set \ A to the correct pixel byte for the fence with blue \ sky behind it BNE fenc5 \ Jump to fenc5 (this BNE is effectively a JMP as A is \ never zero) .fenc4 LDA fencePixelsGrass,X \ Set A to the correct pixel byte for the fence with \ green grass behind it .fenc5 STA (P),Y \ Store A in the dash data block at (Q P), to draw this \ four-pixel part of the fence in the track view STA tyreRightEdge,Y \ Store A in the tyreRightEdge and dashRightEdge STA dashRightEdge,Y \ entries for this row, so the drawing routines can wrap \ the fence correctly around the dashboard and tyres DEX \ Decrement X to point to the next pixel byte in the \ fence pixel byte tables BPL fenc6 \ If we just decremented X to -1, set it back to 3, so LDX #3 \ X goes 3, 2, 1, 0, then 3, 2, 1, 0, and so on .fenc6 DEY \ Decrement the byte counter in Y to point to the next \ byte in the dash data block CPY U \ If Y <> U then we have not yet drawn the fence in all BNE fenc3 \ the bytes in the dash data block (as U contains the \ dashDataOffset for this block, which contains the \ offset of the last byte that we need to fill), so loop \ back to draw the next byte of the fence \ If we get here then we have drawn fence through the \ whole dash data block, so now we move on to the next \ block by updating the counter in T and pointing (Q P) \ to the next dash data block INC T \ Increment the loop counter to point to the next dash \ data block LDA P \ Set (Q P) = (Q P) + &80 EOR #&80 \ STA P \ starting with the low bytes \ \ We can do the addition more efficiently by using EOR \ to flip between &xx00 and &xx80, as the dash data \ blocks always start at these addresses BMI fenc7 \ We then increment the high byte, but only if the EOR INC Q \ set the low byte to &00 rather than &80 (if we just \ set it to the latter, the BMI will skip the INC) .fenc7 JMP fenc1 \ Loop back to part 1 to draw the fence in the next dash \ data block EQUB 0 \ This byte appears to be unused
Name: token18 [Show more] Type: Variable Category: Text Summary: Text for recursive token 18 Deep dive: Text tokens
Context: See this variable on its own page References: This variable is used as follows: * tokenHi calls token18 * tokenLo calls token18
.token18 EQUS " 5" \ Print " 5" EQUB 255 \ End token EQUB &81 \ This byte appears to be unused
Name: dashData4 [Show more] Type: Variable Category: Screen buffer Summary: Contains code that gets moved into screen memory Deep dive: The jigsaw puzzle binary
Context: See this variable on its own page References: This variable is used as follows: * dashDataOffset calls dashData4 * fillDataOffset calls dashData4
.dashData4 SKIP 76 \ Populated with code from &7EDE to &7F29
Name: PrintDriverName [Show more] Type: Subroutine Category: Text Summary: Print a driver's name
Context: See this subroutine on its own page References: This subroutine is called as follows: * PrintDriverPrompt calls PrintDriverName * PrintPositionName calls PrintDriverName

Arguments: (Y A) Address of 12-character driver name
.PrintDriverName STY S \ Set (S R) = (Y A) STA R LDY #0 \ Set a character counter in Y .name1 LDA (R),Y \ Set A to the Y-th character from (S R) JSR PrintCharacter \ Print the character in A INY \ Increment the character counter CPY #12 \ Loop back to print the next character until we have BNE name1 \ printed all 12 characters RTS \ Return from the subroutine
Name: CheckRestartKeys [Show more] Type: Subroutine Category: Keyboard Summary: If the restart keys are being pressed, restart the game
Context: See this subroutine on its own page References: This subroutine is called as follows: * GetMenuOption calls CheckRestartKeys * WaitForSpaceReturn calls CheckRestartKeys * HeadToTrack calls entry point RestartGame

Other entry points: RestartGame Restart the game, putting the C flag into bit 7 of pressingShiftArrow
.CheckRestartKeys LDX #&FF \ Scan the keyboard to see if SHIFT is being pressed JSR ScanKeyboard BNE rest1 \ If SHIFT is not being pressed, jump to rest1 LDX #&86 \ Scan the keyboard to see if right arrow is being JSR ScanKeyboard \ pressed (if it is this will also set the C flag) BNE rest1 \ If right arrow is not being pressed, jump to rest1 BIT pressingShiftArrow \ If bit 7 of pressingShiftArrow is set, then we are BMI rest2 \ still pressing Shift-arrow from a previous restart, so \ jump to rest2 to return from the subroutine without \ anything doing \ If we get here then SHIFT-arrow is being pressed and \ bit 7 of pressingShiftArrow is clear, so this is a \ new pressing SHIFT-arrow, so we fall through into \ RestartGame with the C flag set to restart the game \ and set bit 7 of pressingShiftArrow .RestartGame LDX startingStack \ Set the stack pointer to the value it had when the TXS \ game started, which clears any stored addresses put on \ the stack by the code we are now exiting from ROR pressingShiftArrow \ Shift the C flag into bit 7 of pressingShiftArrow JMP MainLoop \ Jump to the start of the main gane loop to restart the \ game .rest1 LSR pressingShiftArrow \ Clear bit 7 of pressingShiftArrow to indicate that we \ are no longer pressing SHIFT-arrow .rest2 RTS \ Return from the subroutine EQUB &00, &00 \ These bytes appear to be unused
Name: token19 [Show more] Type: Variable Category: Text Summary: Text for recursive token 19 Deep dive: Text tokens
Context: See this variable on its own page References: This variable is used as follows: * tokenHi calls token19 * tokenLo calls token19
.token19 EQUS "10" \ Print "10" EQUB 255 \ End token
Name: dashData5 [Show more] Type: Variable Category: Screen buffer Summary: Contains code that gets moved into screen memory Deep dive: The jigsaw puzzle binary
Context: See this variable on its own page References: This variable is used as follows: * dashDataOffset calls dashData5 * fillDataOffset calls dashData5
.dashData5 SKIP 77 \ Populated with code from &7E91 to &7EDD
Name: GetNumberFromText [Show more] Type: Subroutine Category: Text Summary: Convert a two-digit string into a number
Context: See this subroutine on its own page References: This subroutine is called as follows: * GetNumberInput calls GetNumberFromText

Arguments: T The first digit of the number, as text U The second digit of the number, as text Returns: A The numerical value of the number C flag The status of the conversion: * Clear if the string is a valid number and A <= 40 * Set if string is not a valid number, or A > 40
.GetNumberFromText LDA T \ Set A to the character containing the first digit CMP #' ' \ If the first digit is not a space, skip the following BNE tnum1 \ instruction LDA #'0' \ The first digit is a space, so convert it to a "0" .tnum1 SEC \ Subtract the ASCII value for "0" to get the numerical SBC #'0' \ value of the first digit into A CMP #10 \ If the value of the first digit is greater than 10, BCS tnum2 \ then this is not a valid number, so jump to tnum2 to \ return from the subroutine with the C flag set, to \ indicate an error STA T \ Set T = the value of the first digit LDX U \ Set X to the character containing the second digit CPX #' ' \ If the second digit is a space, then jump to tnum2 to CLC \ return from the subroutine with the value of the first BEQ tnum2 \ digit in A and the C flag clear, to indicate success ASL A \ Set T = (A << 2 + T) << 1 ASL A \ = (A * 4 + A) * 2 ADC T \ = 10 * A ASL A \ STA T \ So T contains 10 * the numerical value of the first \ digit TXA \ Set A to the character containing the second digit SEC \ Subtract the ASCII value for "0" to get the numerical SBC #'0' \ value of the second digit CMP #10 \ If the value of the second digit is greater than 10, BCS tnum2 \ then this is not a valid number, so jump to tnum2 to \ return from the subroutine with the C flag set, to \ indicate an error ADC T \ Set A = A + T \ = the numerical value of the second digit \ + 10 * the numerical value of the first digit \ \ which is the numerical value of the two-digit string CMP #41 \ If A < 41, clear the C flag, otherwise set it .tnum2 RTS \ Return from the subroutine
Name: startDialLo [Show more] Type: Variable Category: Drawing pixels Summary: The low byte of the screen address of the start of the dial hand on the rev counter
Context: See this variable on its own page References: This variable is used as follows: * DrawRevCounter calls startDialLo
.startDialLo EQUB &66 \ Quadrant 0 (12:00 to 3:00) = &7566 EQUB &67 \ Quadrant 1 (3:00 to 6:00) = &7567 EQUB &5F \ Quadrant 2 (6:00 to 9:00) = &755F EQUB &5E \ Quadrant 3 (9:00 to 12:00) = &755E
Name: token20 [Show more] Type: Variable Category: Text Summary: Text for recursive token 20 Deep dive: Text tokens
Context: See this variable on its own page References: This variable is used as follows: * tokenHi calls token20 * tokenLo calls token20
.token20 EQUS "20" \ Print "20" EQUB 255 \ End token
Name: dashData6 [Show more] Type: Variable Category: Screen buffer Summary: Contains code that gets moved into screen memory Deep dive: The jigsaw puzzle binary
Context: See this variable on its own page References: This variable is used as follows: * dashDataOffset calls dashData6 * fillDataOffset calls dashData6
.dashData6 SKIP 77 \ Populated with code from &7E44 to &7E90
Name: leftDashPixels [Show more] Type: Variable Category: Dashboard Summary: Pixels along the left edge of the dashboard Deep dive: Drawing around the dashboard
Context: See this variable on its own page References: This variable is used as follows: * DrawTrackView (Part 2 of 4) calls leftDashPixels * DrawTrackView (Part 3 of 4) calls leftDashPixels

Contains a pixel byte for the white border (colour 2) along the left edge of the dashboard. There is a byte for each track line from 43 (the track line at the top of the dashboard) down to 3 (the lowest track line, just above where the wing mirror joins the car body). Lines 0 to 2 are not used. Each pixel is a colour 2 pixel, so the high nibble contains a 1 and the low nibble contains a 0, to give colour %10. Colour 2 is mapped to white at this point of the custom screen.
.leftDashPixels EQUB %00000000 \ Line 0 EQUB %00000000 \ Line 1 EQUB %11110000 \ Line 2 EQUB %01110000 \ Line 3 EQUB %00110000 \ Line 4 EQUB %00010000 \ Line 5 EQUB %00000000 \ Line 6 EQUB %01110000 \ Line 7 EQUB %01110000 \ Line 8 EQUB %00110000 \ Line 9 EQUB %00010000 \ Line 10 EQUB %00000000 \ Line 11 EQUB %01110000 \ Line 12 EQUB %00110000 \ Line 13 EQUB %00010000 \ Line 14 EQUB %00000000 \ Line 15 EQUB %01110000 \ Line 16 EQUB %00110000 \ Line 17 EQUB %00010000 \ Line 18 EQUB %00000000 \ Line 19 EQUB %01110000 \ Line 20 EQUB %00110000 \ Line 21 EQUB %00010000 \ Line 22 EQUB %00000000 \ Line 23 EQUB %01110000 \ Line 24 EQUB %00110000 \ Line 25 EQUB %00010000 \ Line 26 EQUB %00000000 \ Line 27 EQUB %01110000 \ Line 28 EQUB %01000000 \ Line 29 EQUB %00110000 \ Line 30 EQUB %00100000 \ Line 31 EQUB %00110000 \ Line 32 EQUB %00010000 \ Line 33 EQUB %00010000 \ Line 34 EQUB %00010000 \ Line 35 EQUB %00000000 \ Line 36 EQUB %00000000 \ Line 37 EQUB %00000000 \ Line 38 EQUB %01000000 \ Line 39 EQUB %01110000 \ Line 40 EQUB %01000000 \ Line 41 EQUB %00110000 \ Line 42 EQUB %00110000 \ Line 43
Name: pixelsToLeft [Show more] Type: Variable Category: Drawing pixels Summary: Pixel byte with all the pixels to the left of position X set
Context: See this variable on its own page References: This variable is used as follows: * DrawObjectEdge (Part 3 of 5) calls pixelsToLeft * DrawVergeByteLeft calls pixelsToLeft * DrawVergeByteRight calls pixelsToLeft
.pixelsToLeft EQUB %00000000 EQUB %10001000 EQUB %11001100 EQUB %11101110 EQUB &81, &81 \ These bytes appear to be unused EQUB &81, &81 EQUB &81, &81 EQUB &81
Name: dashData7 [Show more] Type: Variable Category: Screen buffer Summary: Contains code that gets moved into screen memory Deep dive: The jigsaw puzzle binary
Context: See this variable on its own page References: This variable is used as follows: * dashDataOffset calls dashData7 * fillDataOffset calls dashData7
.dashData7 SKIP 73 \ Populated with code from &7DFB to &7E43
Name: rightDashPixels [Show more] Type: Variable Category: Dashboard Summary: Pixels along the right edge of the dashboard Deep dive: Drawing around the dashboard
Context: See this variable on its own page References: This variable is used as follows: * DrawTrackView (Part 2 of 4) calls rightDashPixels * DrawTrackView (Part 3 of 4) calls rightDashPixels

Contains a pixel byte for the white border (colour 2) along the right edge of the dashboard. There is a byte for each track line from 43 (the track line at the top of the dashboard) down to 3 (the lowest track line, just above where the wing mirror joins the car body). Lines 0 to 2 are not used. Each pixel is a colour 2 pixel, so the high nibble contains a 1 and the low nibble contains a 0, to give colour %10. Colour 2 is mapped to white at this point of the custom screen.
.rightDashPixels EQUB %00000000 \ Line 0 EQUB %00000000 \ Line 1 EQUB %11110000 \ Line 2 EQUB %11100000 \ Line 3 EQUB %11000000 \ Line 4 EQUB %10000000 \ Line 5 EQUB %00000000 \ Line 6 EQUB %11100000 \ Line 7 EQUB %11100000 \ Line 8 EQUB %11000000 \ Line 9 EQUB %10000000 \ Line 10 EQUB %00000000 \ Line 11 EQUB %11100000 \ Line 12 EQUB %11000000 \ Line 13 EQUB %10000000 \ Line 14 EQUB %00000000 \ Line 15 EQUB %11100000 \ Line 16 EQUB %11000000 \ Line 17 EQUB %10000000 \ Line 18 EQUB %00000000 \ Line 19 EQUB %11100000 \ Line 20 EQUB %11000000 \ Line 21 EQUB %10000000 \ Line 22 EQUB %00000000 \ Line 23 EQUB %11100000 \ Line 24 EQUB %11000000 \ Line 25 EQUB %10000000 \ Line 26 EQUB %00000000 \ Line 27 EQUB %11100000 \ Line 28 EQUB %00100000 \ Line 29 EQUB %11000000 \ Line 30 EQUB %01000000 \ Line 31 EQUB %11000000 \ Line 32 EQUB %10000000 \ Line 33 EQUB %10000000 \ Line 34 EQUB %10000000 \ Line 35 EQUB %00000000 \ Line 36 EQUB %00000000 \ Line 37 EQUB %00000000 \ Line 38 EQUB %00100000 \ Line 39 EQUB %11100000 \ Line 40 EQUB %00100000 \ Line 41 EQUB %11000000 \ Line 42 EQUB %11000000 \ Line 43
Name: pixelsEdgeRight [Show more] Type: Variable Category: Drawing pixels Summary: Pixel byte with all the pixels to the right of position X set, plus pixel X
Context: See this variable on its own page References: This variable is used as follows: * DrawObjectEdge (Part 3 of 5) calls pixelsEdgeRight * DrawSegmentEdge (Part 3 of 7) calls pixelsEdgeRight
.pixelsEdgeRight EQUB %11111111 EQUB %01110111 EQUB %00110011 EQUB %00010001
Name: token11 [Show more] Type: Variable Category: Text Summary: Text for recursive token 11 Deep dive: Text tokens
Context: See this variable on its own page References: This variable is used as follows: * tokenHi calls token11 * tokenLo calls token11
.token11 EQUS "ENTER " \ Print "ENTER " EQUB 255 \ End token EQUB &81, &81 \ These bytes appear to be unused EQUB &81, &81 EQUB &81
Name: dashData8 [Show more] Type: Variable Category: Screen buffer Summary: Contains code that gets moved into screen memory Deep dive: The jigsaw puzzle binary
Context: See this variable on its own page References: This variable is used as follows: * dashDataOffset calls dashData8 * fillDataOffset calls dashData8
.dashData8 SKIP 68 \ Populated with code from &7DB7 to &7DFA
Name: Absolute8Bit [Show more] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate the absolute value (modulus) of an 8-bit number
This routine returns |A|. It can also return A * abs(n), where A is given the sign of n. Arguments: A The number to make positive N flag Controls the sign to be applied: * If we want to calculate |A|, do an LDA or equivalent before calling the routine * If we want to calculate A * abs(n), do a BIT n before calling the routine * If we want to set the sign of A, then call with: * N flag clear to calculate A * 1 * N flag set to calculate A * -1
.Absolute8Bit BPL aval1 \ If A is positive then it already contains its absolute \ value, so jump to aval1 to return from the subroutine EOR #&FF \ Negate the value in A using two's complement, as the CLC \ following is true when A is negative: ADC #1 \ \ |A| = -A \ = ~A + 1 .aval1 RTS \ Return from the subroutine
Name: paletteSection2 [Show more] Type: Variable Category: Screen mode Summary: Colour palette for screen section 2 in the custom screen mode (part of the mode 5 portion) 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 calls paletteSection2

Palette data is given as a set of bytes, with each byte mapping a logical colour to a physical one. In each byte, the logical colour is given in bits 4-7 and the physical colour in bits 0-3. See p.379 of the Advanced User Guide for details of how palette mapping works, as in modes 4 and 5 we have to do multiple palette commands to change the colours correctly, and the physical colour value is EOR'd with 7, just to make things even more confusing. Each of these mappings requires six calls to SHEILA &21 - see p.379 of the Advanced User Guide for an explanation.
.paletteSection2 EQUB &07, &17 \ Map logical colour 0 to physical colour 0 (black) EQUB &47, &57 EQUB &23, &33 \ Map logical colour 1 to physical colour 4 (blue) EQUB &63, &73 EQUB &80, &90 \ Map logical colour 2 to physical colour 7 (white) EQUB &C0, &D0 EQUB &A5, &B5 \ Map logical colour 3 to physical colour 2 (green) EQUB &E5, &F5
Name: paletteSection0 [Show more] Type: Variable Category: Screen mode Summary: Colour palette for screen section 0 in the custom screen mode (the mode 4 portion) 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 calls paletteSection0
.paletteSection0 EQUB &03, &13 \ Map logical colour 0 to physical colour 4 (blue) EQUB &23, &33 EQUB &43, &53 EQUB &63, &73 EQUB &84, &94 \ Map logical colour 1 to physical colour 3 (yellow) EQUB &A4, &B4 EQUB &C4, &D4 EQUB &E4, &F4
Name: paletteSection3 [Show more] Type: Variable Category: Screen mode Summary: Colour palette for screen section 3 in the custom screen mode (part of the mode 5 portion) 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 calls paletteSection3
.paletteSection3 EQUB &26, &36 \ Map logical colour 1 to physical colour 1 (red) EQUB &66, &76
Name: paletteSection4 [Show more] Type: Variable Category: Screen mode Summary: Colour palette for screen section 4 in the custom screen mode (part of the mode 5 portion) 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 calls paletteSection4
.paletteSection4 EQUB &A1, &B1 \ Map logical colour 3 to physical colour 6 (cyan) EQUB &E1, &F1
Name: token25 [Show more] Type: Variable Category: Text Summary: Text for recursive token 25 Deep dive: Text tokens
Context: See this variable on its own page References: This variable is used as follows: * tokenHi calls token25 * tokenLo calls token25
.token25 EQUB 31, 13, 18 \ Move text cursor to column 13, row 18 EQUS "front" \ Print "front" EQUB 160 + 2 \ Print 2 spaces EQUB 133 \ Set foreground colour to magenta alphanumeric EQUB 200 + 16 \ Print token 16 (" > ") EQUB 255 \ End token EQUB &81, &81 \ These bytes appear to be unused EQUB &81, &81
Name: dashData9 [Show more] Type: Variable Category: Screen buffer Summary: Contains code that gets moved into screen memory Deep dive: The jigsaw puzzle binary
Context: See this variable on its own page References: This variable is used as follows: * dashDataOffset calls dashData9 * fillDataOffset calls dashData9
.dashData9 SKIP 64 \ Populated with code from &7D77 to &7DB6
Name: WaitForSpace [Show more] Type: Subroutine Category: Keyboard Summary: Print a prompt, wait for the SPACE key to be released, and wait for SPACE to be pressed
Context: See this subroutine on its own page References: This subroutine is called as follows: * GetWingSettings calls WaitForSpace * MainLoop (Part 5 of 6) calls WaitForSpace * PrintDriverPrompt calls WaitForSpace
.WaitForSpace LDA #0 \ Set A = 0 so WaitForSpaceReturn waits for SPACE to be \ pressed \ Fall through into WaitForSpaceReturn to print the \ prompt, wait for the SPACE key to be released, and \ wait for SPACE to be pressed
Name: WaitForSpaceReturn [Show more] Type: Subroutine Category: Keyboard Summary: Print a prompt, wait for the SPACE key to be released, and wait for either SPACE or RETURN to be pressed
Context: See this subroutine on its own page References: This subroutine is called as follows: * PrintDriverTable calls WaitForSpaceReturn

Arguments: A Determines the key to wait for: * Bit 7 clear = wait for SPACE to be pressed * Bit 7 set = wait for SPACE or RETURN to be pressed Returns: G If bit 7 was set on entry, then it is cleared if RETURN was pressed, but remains set if SPACE was pressed
.WaitForSpaceReturn STA G \ Store A in G so we can check the value of bit 7 below LDX #30 \ Print token 30 ("PRESS SPACE BAR TO CONTINUE" in cyan JSR PrintToken \ at column 5, row 24) .wait1 LDX #&9D \ Scan the keyboard to see if SPACE is being pressed JSR ScanKeyboard BEQ wait1 \ If SPACE is being pressed, loop back to wait1 until \ it is released .wait2 LDX #&9D \ Scan the keyboard to see if SPACE is being pressed JSR ScanKeyboard BEQ wait3 \ If SPACE is being pressed, jump to wait3 to return \ from the subroutine JSR CheckRestartKeys \ Check whether the restart keys are being pressed, and \ if they are, restart the game (the restart keys are \ SHIFT and right arrow) BIT G \ If bit 7 of G is clear, jump back to wait2 to wait for BPL wait2 \ SPACE to be pressed LDX #&B6 \ Scan the keyboard to see if RETURN is being pressed JSR ScanKeyboard BNE wait2 \ If RETURN is not being pressed, jump back to wait2 to \ wait for RETURN is being pressed LSR G \ Clear bit 7 of G .wait3 RTS \ Return from the subroutine
Name: pixelByte [Show more] Type: Variable Category: Drawing pixels Summary: A table of pixel bytes with individual pixels set
Context: See this variable on its own page References: This variable is used as follows: * DrawDashboardLine calls pixelByte
.pixelByte EQUB %10000000 \ Pixel byte with the first pixel set to colour 2 EQUB %01000000 \ Pixel byte with the second pixel set to colour 2 EQUB %00100000 \ Pixel byte with the third pixel set to colour 2 EQUB %00010000 \ Pixel byte with the fourth pixel set to colour 2 EQUB %00000000 \ Pixel byte with the first pixel set to colour 0 EQUB %00000000 \ Pixel byte with the second pixel set to colour 0 EQUB %00000000 \ Pixel byte with the third pixel set to colour 0 EQUB %00000000 \ Pixel byte with the fourth pixel set to colour 0
Name: token8 [Show more] Type: Variable Category: Text Summary: Text for recursive token 8 Deep dive: Text tokens
Context: See this variable on its own page References: This variable is used as follows: * tokenHi calls token8 * tokenLo calls token8
.token8 EQUS "Amateur" \ Print "Amateur" EQUB 255 \ End token
Name: token51 [Show more] Type: Variable Category: Text Summary: Text for recursive token 51 Deep dive: Text tokens
Context: See this variable on its own page References: This variable is used as follows: * tokenHi calls token51 * tokenLo calls token51
.token51 EQUS " POINTS" \ Print " POINTS" EQUB 255 \ End token EQUB &81, &81 \ These bytes appear to be unused EQUB &81, &81
Name: dashData10 [Show more] Type: Variable Category: Screen buffer Summary: Contains code that gets moved into screen memory Deep dive: The jigsaw puzzle binary
Context: See this variable on its own page References: This variable is used as follows: * dashDataOffset calls dashData10 * fillDataOffset calls dashData10
.dashData10 SKIP 60 \ Populated with code from &7D3B to &7D76
Name: objectTop [Show more] Type: Variable Category: 3D objects Summary: Scaffold measurements for the top of each object part Deep dive: Object definitions Scaling objects with scaffolds
Context: See this variable on its own page References: This variable is used as follows: * DrawObjectEdges calls objectTop

Entries contain indexes into the scaledScaffold table. n + 8 points to the negative value of n (as scaledScaffold+8 is filled with the negative of scaledScaffold).
.objectTop EQUB 7 + 8 \ Object 0, Part 0: Scaffolds: (-7, -6, -1, -2) \ Coordinates: (-4, -5, -22, -18) EQUB 7 + 8 \ Object 0, Part 1: Scaffolds: (-7, -6, 2, 1) \ Coordinates: (-4, -5, 18, 22) EQUB 6 + 8 \ Object 0, Part 2: Scaffolds: (-6, -3, -0, -4) \ Coordinates: (-5, -17, -24, -16) EQUB 6 + 8 \ Object 0, Part 3: Scaffolds: (-6, -3, 4, 0) \ Coordinates: (-5, -17, 16, 24) EQUB 5 + 8 \ Object 0, Part 4: Scaffolds: (-5, -3, -2, 2) \ Coordinates: (-8, -17, -18, 18) EQUB 5 \ Object 1, Part 0: Scaffolds: (5, 6, -5, 5) \ Coordinates: (5, 2, -5, 5) EQUB 6 \ Object 1, Part 1: Scaffolds: (6, -7, -3, 3) \ Coordinates: (2, -1, -8, 8) EQUB 7 + 8 \ Object 1, Part 2: Scaffolds: (-7, -5, -1, 1) \ Coordinates: (-1, -5, -12, 12) EQUB 4 \ Object 1, Part 3: Scaffolds: (4, 5, -6, 6) \ Coordinates: (6, 5, -2, 2) EQUB 6 + 8 \ Object 2, Part 0: Scaffolds: (-6, -5, -0, -4) \ Coordinates: (-3, -5, -26, -16) EQUB 0 \ Extra edges: Scaffolds: 0, 4 \ Coordinates: 26, 16 EQUB 5 + 8 \ Object 2, Part 2: Scaffolds: (-5, -3, -0, 0) \ Coordinates: (-5, -17, -26, 26) EQUB 7 + 8 \ Object 2, Part 3: Scaffolds: (-7, -6, -1, -2) \ Coordinates: (-2, -3, -24, -18) EQUB 7 + 8 \ Object 2, Part 4: Scaffolds: (-7, -6, 2, 1) \ Coordinates: (-2, -3, 18, 24) EQUB 2 \ Object 3, Part 0: Scaffolds: (2, 3, -0, 0) \ Coordinates: (6, 4, -16, 16) EQUB 3 \ Object 3, Part 1: Scaffolds: (3, 4, -0, 0) \ Coordinates: (4, 3, -16, 16) EQUB 4 \ Object 3, Part 2: Scaffolds: (4, -1, -5, 5) \ Coordinates: (3, -10, -1, 1) EQUB 1 + 8 \ Object 3, Part 3: Scaffolds: (-1, -0, -5, 5) \ Coordinates: (-10, -16, -1, 1) EQUB 6 \ Object 4, Part 0: Scaffolds: (6, -7, -4, 4) \ Coordinates: (3, -1, -6, 6) EQUB 7 + 8 \ Object 4, Part 1: Scaffolds: (-7, -5, -3, 3) \ Coordinates: (-1, -5, -12, 12) EQUB 6 + 8 \ Object 4, Part 2: Scaffolds: (-6, -5, -0, -2) \ Coordinates: (-3, -5, -26, -16) EQUB 0 \ Extra edges: Scaffolds: 0, 4 \ Coordinates: 26, 16 EQUB 5 + 8 \ Object 4, Part 4: Scaffolds: (-5, -1, -0, 0) \ Coordinates: (-5, -17, -26, 26) EQUB 7 \ Object 4, Part 5: Scaffolds: (7, -5, -7, 7) \ Coordinates: (1, -5, -1, 1) EQUB 4 \ Object 4, Part 6: Scaffolds: (4, 6, -2, 2) \ Coordinates: (6, 3, -16, 16) EQUB 2 + 8 \ Object 5, Part 0: Scaffolds: (-2, -1, -0, 0) \ Coordinates: (-3, -17, -26, 26) EQUB 0 \ Object 6, Part 0: Scaffolds: (0, 2, -1, 1) \ Coordinates: (16, 1, -10, 10) EQUB 1 \ Object 7, Part 0: Scaffolds: (1, -4, -0, 0) \ Coordinates: (20, -8, -28, 28) EQUB 4 + 8 \ Object 7, Part 1: Scaffolds: (-4, -2, -1, -3) \ Coordinates: (-8, -18, -20, -16) EQUB 4 + 8 \ Object 7, Part 2: Scaffolds: (-4, -2, 3, 1) \ Coordinates: (-8, -18, 16, 20) EQUB 2 \ Object 8, Part 0: Scaffolds: (2, -0, -3, 3) \ Coordinates: (3, -18, -2, 2) EQUB 1 \ Object 8, Part 1: Scaffolds: (1, 2, -3, 1) \ Coordinates: (16, 3, -2, 16) EQUB 1 \ Object 9, Part 0: Scaffolds: (1, -2, -0, 0) \ Coordinates: (12, -10, -16, 16) EQUB 2 + 8 \ Object 9, Part 1: Scaffolds: (-2, -0, -3, 3) \ Coordinates: (-10, -16, -3, 3) EQUB 3 \ Object 10, Part 0: Scaffolds: (3, 4, -0, 0) \ Coordinates: (4, 1, -10, 10) EQUB 4 \ Object 10, Part 1: Scaffolds: (4, -2, -0, -1) \ Coordinates: (1, -6, -10, -9) EQUB 0 \ Object 10, Part 2: Scaffolds: (0, 3, 1, 0) \ Coordinates: (10, 4, 9, 10) EQUB 1 \ Object 11, Part 0: Scaffolds: (1, 3, -0, 1) \ Coordinates: (8, 5, -10, 8) EQUB 3 \ Object 11, Part 1: Scaffolds: (3, -2, -0, -1) \ Coordinates: (5, -6, -10, -8) EQUB 1 \ Object 12, Part 0: Scaffolds: (1, 3, -1, 0) \ Coordinates: (8, 5, -8, 10) EQUB 3 \ Object 12, Part 1: Scaffolds: (3, -2, 1, 0) \ Coordinates: (5, -6, 8, 10)
Name: leftTyrePixels [Show more] Type: Variable Category: Dashboard Summary: Pixels along the edge of the left tyre Deep dive: Drawing around the dashboard
Context: See this variable on its own page References: This variable is used as follows: * DrawTrackView (Part 3 of 4) calls leftTyrePixels

Contains a pixel byte for the white border (colour 2) along the edge of the left tyre. The tyreEdgeIndex table maps track line numbers to entries in this table. Each pixel is a colour 2 pixel, so the high nibble contains a 1 and the low nibble contains a 0, to give colour %10. Colour 2 is mapped to white at this point of the custom screen.
.leftTyrePixels EQUB %00000000 EQUB %10000000 EQUB %11000000 EQUB %01000000 EQUB %01100000 EQUB %11100000 EQUB %00100000
Name: token31 [Show more] Type: Variable Category: Text Summary: Text for recursive token 31 Deep dive: Text tokens
Context: See this variable on its own page References: This variable is used as follows: *