Skip to navigation

Revs on the BBC Micro

Revs I source

Name: GetTextInput [Show more] Type: Subroutine Category: Keyboard Summary: Fetch a string from the keyboard, padded with spaces if required
Context: See this subroutine on its own page References: This subroutine is called as follows: * GetDriverName calls GetTextInput * GetNumberInput calls GetTextInput

This routine fetches a string of characters from the keyboard and stores the result in memory. The string is entered by pressing RETURN, at which point the string in memory is padded with spaces so that it meets the required length. The DELETE key is supported, leading spaces are ignored, and the ESCAPE key is trapped and has no effect.
Arguments: (Y A) The address where the string should be stored X The length of string that we require
Returns: Y The number of characters entered, before any padding is applied
.GetTextInput STA P \ Set (Q P) = (Y A) STY Q STX W \ Store the required length of input in W LDA #2 \ Call OSBYTE with A = 2 and X = 0 to select the LDX #0 \ keyboard as the input stream and disable RS423 JSR OSBYTE LDA #21 \ Call OSBYTE with A = 21 and X = 0 to flush the LDX #0 \ keyboard buffer JSR OSBYTE .text1 LDY #0 \ Set Y = 0 to use as a counter for the number of \ characters entered .text2 JSR OSRDCH \ Call OSRDCH to read a character from the currently \ selected input stream (i.e. the keyboard) into A BCS text7 \ If the call to OSRDCH set the C flag then there was an \ error (probably caused by pressing ESCAPE), so jump to \ text7 to process this CMP #13 \ If RETURN was pressed, jump to text9 BEQ text9 CMP #' ' \ If a control character was entered (i.e. with an ASCII BCC text2 \ code less than that of " "), jump back to text2 to \ ignore it and wait for another key press BNE text3 \ If a key other than SPACE was pressed, jump to text3 \ skip the next two instructions CPY #0 \ SPACE was pressed, so if no other characters have been BEQ text2 \ (i.e. Y = 0), jump back to text2 to ignore it .text3 CMP #127 \ If the character entered has an ASCII value < 127, BCC text4 \ jump to text4 to process it as a valid character BNE text2 \ If the character entered has an ASCII value > 127, \ jump back to text2 to ignore it \ If we get here then the DELETE key was pressed, which \ has an ASCII value of 127 DEY \ Decrement the number of characters entered in Y, to \ process the deletion BPL text6 \ If Y is still positive, jump to text6 to print the \ delete character, which will delete the last character \ entered on-screen BMI text1 \ Y is negative, so we just deleted past the start of \ the entered string, so jump to text1 to set Y to 0 and \ start again (this BMI is effectively a JMP as we just \ passed through a BPL) .text4 \ If we get here then a valid character was entered CPY W \ If the number of characters entered in Y is not yet BNE text5 \ the required number in W, jump to text5 to store the \ new character LDA #7 \ Otherwise set A = 7 (the ASCII code for a beep) and BNE text6 \ jump to text6 to skip storing the new character and \ make a beep, as we already have enough characters .text5 \ If we get here then we have successfully fetched a new \ character, so now we store it STA (P),Y \ Store the character entered in the Y-th byte of (Q P) INY \ Increment the character counter in Y .text6 JSR OSWRCH \ Print the character in A, which will either be the new \ character, or a beep, or a delete JMP text2 \ Jump up to text2 to fetch the next character .text7 \ If we get here then ESCAPE was pressed TYA \ Store the character count in Y on the stack PHA LDA #126 \ Call OSBYTE with A = 126 to acknowledge the ESCAPE JSR OSBYTE \ condition PLA \ Retrieve the character count from the stack into Y TAY JMP text2 \ Jump up to text2 to fetch the next character .text8 INY \ We get here from below after appending a space to the \ stored string, so we increment Y and repeat the \ padding process until the string is full .text9 \ If we get here then RETURN was pressed CPY W \ If the number of characters entered in Y is not yet BNE text10 \ the required number in W, jump to text10 to pad out \ the string with spaces RTS \ Otherwise the string is the correct size, so we now \ return from the subroutine .text10 LDA #' ' \ Append a space to the end of the stored string STA (P),Y BNE text8 \ Jump back to text8 to keep padding the string with \ spaces (this BNE is effectively a JMP as A is never \ zero)
Name: SetDriverSpeed [Show more] Type: Subroutine Category: Drivers Summary: Set the speed for a specific driver Deep dive: Tactics of the non-player drivers
Context: See this subroutine on its own page References: This subroutine is called as follows: * InitialiseDrivers calls SetDriverSpeed * ProcessTime calls SetDriverSpeed

Arguments: setSpeedForDriver The number of the driver to set the speed for
Returns: X The number of the previous driver setSpeedForDriver Points to the next driver, ready for the next call to SetDriverSpeed
.SetDriverSpeed LDX setSpeedForDriver \ Set X to the driver number to initialise \ We now start a lengthy calculation of a figure in A \ that we will add to the track's base speed to \ determine the speed for this driver, with a higher \ figure in A giving the car a higher speed in the race 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 PHP \ Store the processor flags from the random timer value \ on the stack AND #%01111111 \ Clear bit 0 of the random number in A, so A is now in \ the range 0 to 127 \ The following calculates the following: \ \ * If A is in the range 0 to 63, A = A mod 4 \ \ * If A is in the range 64 to 127, A = (A - 64) mod 7 \ \ Given that A starts out as a random number, this will \ produce a random number with the following chances: \ \ 50% of the time, 25% chance of 0 to 3 \ 50% of the time, 12.5% chance of 0 to 7 \ \ So the probability of getting each of the numbers from \ 0 to 7 is: \ \ 0 to 3 = 0.5 * 0.25 + 0.5 * 0.125 = 0.1875 = 18.75% \ 4 to 7 = 0.5 * 0.125 = 0.0625 = 6.25% \ \ So we're three times more likely to get a number in \ the range 0 to 3 as in the range 4 to 7 LDY #16 \ Set a loop counter in Y to subtract 16 lots of 4 .fast1 CMP #4 \ If A < 4, jump to fast3, as A now contains the BCC fast3 \ original value of A mod 4 SBC #4 \ A >= 4, so set A = A - 4 DEY \ Decrement the loop counter BNE fast1 \ Loop back until we have either reduced A to be less \ than 4, or we have subtracted 16 * 4 = 64 \ If we get here then the original A was 64 or more, \ 64, and A is now in the range 0 to 63 LDY #9 \ Set a loop counter in Y to subtract 9 lots of 7 .fast2 CMP #7 \ If A < 7, jump to fast3, as A now contains the BCC fast3 \ original value of (A - 64) mod 7 SBC #7 \ A >= 7, so set A = A - 7 DEY \ Decrement the loop counter BNE fast2 \ Loop back until we have either reduced A to be less \ than 7, or we have subtracted 9 * 7 = 63 \ If we get here then the original A was 127, and we \ first subtracted 64 and then 63 to give us 0 .fast3 \ We now have our random number in the range 0 to 7, \ with 0 to 3 more likely than 4 to 7 PLP \ Retrieve the processor flags from the random timer \ value that we put on the stack above, which sets the \ N flag randomly (amongst others) JSR Absolute8Bit \ The first instruction of Absolute8Bit is a BPL, which \ normally skips negation for positive numbers, but in \ this case it means the Absolute8Bit routine randomly \ changes the sign of A, so A is now in the range \ -7 to +7, with -3 to +3 more likely than -7 to -4 or \ 4 to 7 ASL A \ Set A = A << 1, so A is now in the range -14 to +14, \ with -6 to +6 more likely than -14 to -7 or 7 to 14 SEC \ Set A = A - driverGridRow for this driver SBC driverGridRow,X \ \ So A is left alone for the two cars at the front of \ the grid, is reduced by 1 for the next two cars, and \ is reduced by 9 for the two cars at the back STA T \ Set T = A \ By this point, the value in A (and T) is in the range: \ \ * -14 to +14 for the front two cars \ * -15 to +13 for the next two cars \ ... \ * -23 to +4 for the last two cars \ \ We now alter this according to the race class LDY raceClass \ Set Y to the race class DEY \ Decrement Y, so it will be -1 for Novice, 0 for \ Amateur and 1 for Professional BEQ fast5 \ If Y = 0 (Amateur), jump to fast5 to leave A alone BPL fast4 \ If Y = 1 (Professional), jump to fast4 \ If we get here, then the race class is Novice ASL A \ Set A = A << 1 \ \ so A is now in the range: \ \ * -28 to +28 for the front two cars \ * -30 to +26 for the next two cars \ ... \ * -46 to +8 for the last two cars \ \ This makes the range of speeds less tightly bunched, \ so the race is less intense JMP fast5 \ Jump to fast5 to skip the following .fast4 \ If we get here, then the race class is Professional ROL T \ Shift bit 7 of T into the C flag, and because T = A, \ this puts bit 7 of A into the C flag ROR A \ Shift A right while inserting a copy of bit 7 into \ bit 7, so this effectively divides A by two while \ keeping the sign intact: \ \ A = A / 2 \ \ So A is now in the range: \ \ * -7 to +7 for the front two cars \ * -8 to +6 for the next two cars \ ... \ * -12 to +2 for the last two cars \ \ This makes the range of speeds more tightly bunched, \ so the race is more intense .fast5 \ By this point we have our value A, which determines \ the speed of the driver based on our random number, \ the car's grid position and the race class, so now we \ add this to the track's base speed to get the driver's \ speed for the race \ \ The track's base speed is different depending on the \ race class, so taking Silverstone as an example, we \ get these final ranges for the front two cars: \ \ * Novice = 134 plus -28 to +28 = 106 to 162 \ * Amateur = 146 plus -14 to +14 = 132 to 160 \ * Professional = 153 plus -7 to +7 = 146 to 160 \ \ and these final ranges for the two cars at the back: \ \ * Novice = 134 plus -46 to +8 = 88 to 142 \ * Amateur = 146 plus -23 to +4 = 123 to 150 \ * Professional = 153 plus -12 to +2 = 141 to 155 CLC \ Set the driverSpeed for driver X to baseSpeed + A ADC baseSpeed STA driverSpeed,X JSR GetPositionAhead \ Set X to the number of the position ahead of the \ driver whose speed we just set STX setSpeedForDriver \ Set setSpeedForDriver = X \ \ So the next call to the routine will set the speed for \ the next driver ahead RTS \ Return from the subroutine
Name: SetPlayerPositions [Show more] Type: Subroutine Category: Drivers Summary: Set the current player's position, plus the positions behind and in front
Context: See this subroutine on its own page References: This subroutine is called as follows: * FinishRace calls SetPlayerPositions * MoveAndDrawCars calls SetPlayerPositions * ResetVariables calls SetPlayerPositions * SortDrivers calls SetPlayerPositions
.SetPlayerPositions LDA currentPlayer \ Set A to the number of the current player LDX #19 \ We are about to work our way through the ordered list \ of drivers in driversInOrder, so set a loop counter \ in X, starting at the end of the list (i.e. last \ place) .ppos1 CMP driversInOrder,X \ If the driver in position X in the list matches the BEQ ppos2 \ current player, jump to ppos2 DEX \ Decrement the driver number BPL ppos1 \ Loop back until we have gone through the whole table .ppos2 \ By this point, X contains the position within the \ driversInOrder list of the current player (or -1 if \ the current player doesn't appear in the list) STX currentPosition \ Store the current player's position in currentPosition JSR GetPositionBehind \ Set X to the number of the position behind this one STX positionBehind \ Store the position behind the current player in \ positionBehind LDX currentPosition \ Set X to the number of the position ahead of the JSR GetPositionAhead \ current player's position STX positionAhead \ Store the position ahead of the current player in \ positionAhead RTS \ Return from the subroutine
Name: Protect [Show more] Type: Subroutine Category: Setup Summary: Decrypt or unprotect the game code (disabled)
Context: See this subroutine on its own page References: This subroutine is called as follows: * MoveCode calls Protect
IF _SUPERIOR OR _REVSPLUS .Protect JMP SetupGame \ Jump to SetupGame to continue setting up the game NOP \ Presumably this contained some kind of copy protection NOP \ or decryption code that has been replaced by NOPs in NOP \ this unprotected version of the game NOP NOP ENDIF
Name: GetSteeringAssist [Show more] Type: Subroutine Category: Tactics Summary: Fetch the current computer assisted steering (CAS) status and show or hide the CAS indicator Deep dive: Computer assisted steering (CAS)
Context: See this subroutine on its own page References: This subroutine is called as follows: * AssistSteering calls GetSteeringAssist

The computer assisted steering (CAS) indicator is in the centre-bottom of the rev counter, and is made up of four pixels in colour 2 (white) as follows: ...xx... ..x..x.. which would be encoded in mode 5 screen memory as follows: %00010000 %10000000 %00100000 %01000000 This routine shows or hides the indicator according to the current setting of configAssist, returning the value of configAssist in X.
Returns: X The current value of configAssist: * %10000000 if computer assisted steering is enabled * 0 if computer assisted steering is not enabled A A is unchanged C flag Set to bit 7 of directionFacing (clear if our car is facing forwards, set if we are facing backwards)
IF _SUPERIOR OR _REVSPLUS .GetSteeringAssist PHA \ Store A on the stack so we can retrieve it below LDA configAssist \ Set A to configAssist, which has the following value: \ \ * %10000000 if computer assisted steering is enabled \ \ * 0 if computer assisted steering is not enabled \ \ The following updates screen memory to add a small \ "hat" marker to the centre-bottom of the rev counter \ when CAS is enabled (or to remove the marker when it \ isn't enabled) STA assistRight1 \ Set assistRight1 = 0 or %10000000 LSR A \ Set assistRight2 = 0 or %01000000 STA assistRight2 LSR A \ Set assistLeft2 = 0 or %00100000 STA assistLeft2 LSR A \ Set assistLeft1 = 0 or %00010000 STA assistLeft1 LDA directionFacing \ Set the C flag to bit 7 of directionFacing, which we ROL A \ return from the subroutine PLA \ Restore the value of A that we put on the stack above LDX configAssist \ Set X to configAssist RTS \ Return from the subroutine ENDIF
Name: SetupGame [Show more] Type: Subroutine Category: Setup Summary: Decrypt or unprotect the game code (disabled)
Context: See this subroutine on its own page References: This subroutine is called as follows: * MoveCode calls SetupGame * Protect calls SetupGame
IF _SUPERIOR OR _REVSPLUS .SuperiorSetupGame CLEAR &3850, &3880 \ In the Superior Software release of Revs, the routines ORG &3850 \ for computer assisted steering (CAS) take up extra \ memory, so we need to claw back some memory from \ somewhere \ \ The clever solution is to move the SetupGame routine, \ which is run when the game loads, but is never needed \ again, so in the Superior version, SetupGame is put \ into the same block of memory as the carSpeedLo, \ totalPointsLo and totalPointsLo variables, which are \ only used after the game has started \ \ These lines rewind BeebAsm's assembly back to \ carSpeedLo (which is at address &3850), and clear the \ block that is occupied by these three variables, so we \ can assemble SetupGame in the right place while \ retaining the correct addresses for the three \ variables \ \ We also make a note of the current address, so we can \ ORG back to it after assembling SetupGame ENDIF .SetupGame LDA #4 \ Call OSBYTE with A = 4, X = 1 and Y = 0 to disable LDY #0 \ cursor editing LDX #1 JSR OSBYTE JSR SetScreenMode7 \ Change to screen mode 7 and hide the cursor LDX #9 \ We now zero the ten bytes starting at configStop, so \ set a loop counter in X LDA #0 \ Set lineBufferSize = 0, to reset the line buffer STA lineBufferSize .setp1 STA configStop,X \ Zero the X-th byte at configStop \ \ The address in this instruction gets modified DEX \ Decrement the loop counter BPL setp1 \ Loop back until we have zeroed all ten bytes LDA #246 \ Set volumeLevel = -10, which sets the sound level to STA volumeLevel \ medium (-15 is full volume, 0 is silent) TSX \ Store the stack pointer in startingStack so we can STX startingStack \ restore it when restarting the game JSR CallTrackHook \ Call the hook code in the track file (for Silverstone \ the hook routine is just an RTS, so this does nothing) IF _ACORNSOFT OR _4TRACKS \ Fall through into the main game loop to start the game ELIF _SUPERIOR OR _REVSPLUS LDA #190 \ Call OSBYTE with A = 190, X = %00100000 and Y = 0 to LDY #0 \ configure the digital joystick port on the BBC Master LDX #%00100000 \ Compact conversion type to 32-bit conversion (as the JSR OSBYTE \ Superior Software release was updated to work on this \ machine) \ \ The configuration does the following: \ \ * Bit 7 clear = update channel values from cursor \ keys and/or digital joystick \ \ * Bit 6 clear = do not simulate key presses from the \ digital joystick \ \ * Bit 5 set = return fixed values to channels 1 and \ 2 as follows: \ \ Left = &FFFF to channel 1 \ Centre (horizontal) = &7FFF to channel 1 \ Right = 0 to channel 1 \ Down = &FFFF to channel 2 \ Centre (vertical) = &7FFF to channel 2 \ Up = 0 to channel 2 \ \ * Bit 4 is unused \ \ * Bits 0-3 clear = emulate the analogue speed of \ joystick movement by returning slowly changing \ values related to the joystick movement JMP MainLoop \ Jump to the main game loop to start the game EQUB &FF \ This byte appears to be unused ORG SuperiorSetupGame \ Switch back to the original address, so we can \ continue with the assembly ENDIF
Name: MainLoop (Part 1 of 6) [Show more] Type: Subroutine Category: Main loop Summary: The main game loop: practice laps Deep dive: Program flow of the main game loop
Context: See this subroutine on its own page References: This subroutine is called as follows: * CheckRestartKeys calls MainLoop * SetupGame calls MainLoop

This part of the main loop implements practice laps.
.MainLoop LDX #0 \ Set configStop = 0 so we clear out any existing STX configStop \ stop-related key presses JSR InitialiseDrivers \ Initialise all 20 drivers LDX #4 \ Print "REVS REVS REVS" as a double-height header JSR PrintHeader \ at column 0, row 4, with the colours of each letter in \ REVS set to magenta/yellow/cyan/green JSR PrintHeaderChecks \ Print chequered lines above and below the header LDX #39 \ Print token 39, which shows a menu with the following JSR PrintToken \ options: \ \ 1 = PRACTICE \ \ 2 = COMPETITION LDX #2 \ Fetch the menu choice into X (0 to 1) JSR GetMenuOption CPX #1 \ If X >= 1, then the choice was competition, so jump to BCS game1 \ game1 to start setting up the competition races STX currentPlayer \ Otherwise X = 0 and the choice was practice, so set \ currentPlayer = 0 DEX \ Set qualifyingTime = 255, so that the time we spend STX qualifyingTime \ practicing is as long as we want JSR ResetBestLapTimes \ Reset the best lap times to 10:00.0 for all drivers JSR HeadToTrack \ Head to the track to choose the wing settings and \ start the practice laps, which the player exits by \ pressing SHIFT and right arrow to restart the game \ (so we don't return from this call)
Name: MainLoop (Part 2 of 6) [Show more] Type: Subroutine Category: Main loop Summary: The main game loop: competition setup Deep dive: Program flow of the main game loop
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

This part of the main loop gets all the general information required for the competition: the race class and the duration of qualifying laps.
.game1 LDA #0 \ Set competitionStarted = 0, to indicate that the STA competitionStarted \ competition hasn't started yet (so we still need to \ get the race class, the number of laps, and the \ players' names) LDX #21 \ Print token 21, which shows a menu with the following JSR PrintToken \ options: \ \ Prompt = SELECT THE CLASS OF RACE \ \ 1 = Novice \ \ 2 = Amateur \ \ 3 = Professional LDX #3 \ Fetch the menu choice into X (0 to 2) JSR GetMenuOption STX raceClass \ Set raceClass to the chosen race class (0 to 2) JSR GetSectionSteering \ Set up the optimum steering for each section for this \ race class, storing the results in sectionSteering .game2 LDX #22 \ Print token 22, which shows a menu with the following JSR PrintToken \ options: \ \ Prompt = SELECT DURATION OF QUALIFYING LAPS \ \ 1 = 5 mins \ \ 2 = 10 mins \ \ 3 = 20 mins LDX #3 \ Fetch the menu choice into X (0 to 2) JSR GetMenuOption LDA timeFromOption,X \ Set the value of qualifyingTime to 4, 9 or 25, which STA qualifyingTime \ should be the number of minutes of qualifying time \ minus one, but the latter seems to be a bug
Name: MainLoop (Part 3 of 6) [Show more] Type: Subroutine Category: Main loop Summary: The main game loop: qualifying laps Deep dive: Program flow of the main game loop
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

This part of the main loop gets the players' driver names, and heads to the track for their qualifying laps.
JSR ResetBestLapTimes \ Reset the best lap times to 10:00.0 for all drivers LDA #20 \ Set currentPlayer = 20, so the first human player will STA currentPlayer \ be number 19, the next one will be 18, and so on .game3 DEC currentPlayer \ Decrement currentPlayer so it points to the next human \ player LDX currentPlayer \ Set X to the new player number JSR ResetBestLapTime \ Reset the best lap time to 10:00.0 for the new player LDA competitionStarted \ If competitionStarted = 0, jump to game4 to ask for BEQ game4 \ the player's name, as the competition hasn't started \ yet and we are still running qualifying laps \ If we get here then the competition is in full swing, \ so we need to run qualifying laps for all the human \ players JSR PrintDriverPrompt \ Print the "DRIVER ->" prompt and the player's name JSR HeadToTrack \ Head to the track to choose the wing settings and \ drive the qualifying laps, returning here when the \ laps are finished LDA currentPlayer \ If currentPlayer <> lowestPlayerNumber, then we still CMP lowestPlayerNumber \ have more qualifying laps to get, so jump back to BNE game3 \ game3 for the next player's qualifying laps \ By this point we have all the qualifying times for the \ human players LDA #0 \ Sort the drivers by lap time, putting the results into JSR SortDrivers \ positionNumber and driversInOrder JMP game9 \ Jump down to game9 to print the driver table, showing \ the grid positions for the race .game4 \ If we get here then we need to get the player's name, \ as the competition has not yet started LDX #23 \ Print token 23, which shows the following prompt: JSR PrintToken \ \ ENTER NAME OF DRIVER \ \ and a text prompt: \ \ > \ ------------ JSR GetDriverName \ Fetch the player's name from the keyboard JSR HeadToTrack \ Head to the track to choose the wing settings and \ start the qualifying laps, returning here when the \ laps are finished LDX currentPlayer \ If currentPlayer = 0 then we have got as many players BEQ game5 \ as we can handle (20 of them), so jump to game5 to \ skip asking for another driver LDX #27 \ Print token 27, which shows a menu with the following JSR PrintToken \ options: \ \ 1 = ENTER ANOTHER DRIVER \ \ 2 = START RACE LDX #2 \ Fetch the menu choice into X (0 to 1) JSR GetMenuOption CPX #0 \ If X = 0, then the choice was to enter another driver, BEQ game3 \ so jump back to game3 to add the new player and head \ to the track for their qualifying laps
Name: MainLoop (Part 4 of 6) [Show more] Type: Subroutine Category: Main loop Summary: The main game loop: the competition race Deep dive: Program flow of the main game loop
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

This part of the main loop checks the slowest qualifying lap times to see if we should make the game easier.
.game5 LDA currentPlayer \ We have now got all the player names and qualifying STA lowestPlayerNumber \ times that we need, so store the player number in \ lowestPlayerNumber, which will contain 19 if there is \ one player, 18 if there are two, and so on \ \ Human players take the place of drivers with higher \ numbers, so the first player takes the place of driver \ 19 (the aptly called Dummy Driver, as they never get \ to race), and the second player takes the place of \ driver 18 (Peter Out), the third player replaces \ driver 17 (Rick Shaw) and so on \ \ So this not only represents the lowest player number, \ but also the highest non-human driver number (which is \ lowestPlayerNumber - 1) LDA #0 \ Sort the drivers by best lap time, putting the results JSR SortDrivers \ into positionNumber and driversInOrder, so they now \ contain the order of the drivers on the grid for the \ coming race \ We now adjust the class of the race to cater for the \ player's qualifying lap time, which makes things more \ fun for a mixed group of player skills LDX #0 \ Set X to the race class number for Novice .game6 LDY driversInOrder+19 \ Set Y to the driver number with the slowest lap time CPY lowestPlayerNumber \ If Y < lowestPlayerNumber, then driver Y is one of the BCC game8 \ computer-controlled drivers and they have the slowest \ lap time, so jump to game8 as the race class doesn't \ need changing \ Otherwise the slowest lap time is by one of the human \ players, so we now set the race class (i.e. the race \ difficulty setting) according to the figures in the \ track data) LDA bestLapSeconds,Y \ Calculate the slowest lap time minus the time for SEC \ class X from the track data, starting with the seconds SBC trackLapTimeSec,X LDA bestLapMinutes,Y \ And then the minutes SBC trackLapTimeMin,X \ Note that for X = 2 (professional), the track data \ figure is 0, so the C flag will always be set BCS game7 \ If the slowest lap time is longer than trackLapTimeMin \ from the track data, jump to game7 to consider setting \ the class to X, if it is easier than the current class \ \ In other words, if the slowest lap time is really slow \ and is by a human player, this can make the game \ easier if the race class isn't already on Novice \ \ For example, for Silverstone: \ \ * We will consider setting the class to Novice if \ the longest lap is > 1:33 and by a human player \ \ * We will consider setting the class to Amateur if \ the longest lap is <= 1:33 and > 1:29 and by a \ human player \ \ * We will consider setting the class to Professional \ if the longest lap is <= 1:29 and by a human \ player \ If we get here, the slowest lap time is quicker than \ the figure from the track data, so we now check the \ next figure from the track data and try again INX \ Increment X to the next difficulty level BNE game6 \ Jump back to game6 to check the next race class (this \ BNE is effectively a JMP as X is never zero) .game7 CPX raceClass \ If X >= raceClass then X is the same or more difficult BCS game8 \ than the current setting, so jump to game8 to leave \ the class unchanged STX raceClass \ Otherwise set the race class to the easier class in X
Name: MainLoop (Part 5 of 6) [Show more] Type: Subroutine Category: Main loop Summary: The main game loop: the competition race Deep dive: Program flow of the main game loop
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

This part of the main loop heads to the track to run the actual race. We print the driver table showing the grid positions, and set the grid row for any human drivers. We then start a loop, running a race for each human player, and asking for the number of laps in the race for the first such race. The loop concludes in the next part of the main loop.
.game8 LDX raceClass \ Set X to the race class JSR GetSectionSteering \ Set up the optimum steering for each section for this \ race class, storing the results in sectionSteering LDX #26 \ Print token 26, which is a double-height header with JSR PrintToken \ the text "STANDARD OF RACE" JSR PrintRaceClass \ Print the race class JSR WaitForSpace \ Print a prompt and wait for SPACE to be pressed .game9 LDX #2 \ Print the driver table, showing lap times, under the LDA #0 \ heading "GRID POSITIONS", so this shows the drivers JSR PrintDriverTable \ in their starting positions on the grid \ We now make a copy of the driversInOrder list into \ driversInOrder2, so we can retrieve it below, and at \ the same time we set the correct grid row for any \ human players, depending on their starting position \ on the grid (there are two cars per grid row) LDY #19 \ Set up a counter in Y so we can work through the \ drivers in order, from the back of the grid to the \ front .game10 LDA driversInOrder,Y \ Copy the Y-th position from driversInOrder to STA driversInOrder2,Y \ driversInOrder2, setting A to the number of the driver \ in position Y CMP lowestPlayerNumber \ If A < lowestPlayerNumber, then driver A is one of the BCC game11 \ computer-controlled drivers, so jump to game11 to skip \ setting the grid number for the driver \ If we get here then driver A is a human player TAX \ Set X = the player's driver number LDA positionNumber,Y \ Set A = the position of the player on the grid LSR A \ Set the driver's grid row to A / 2, so the first two STA driverGridRow,X \ drivers are on grid row 0, then the next two are on \ grid row 1, and so on .game11 DEY \ Decrement the counter BPL game10 \ Loop back until we have saved all the positions LDA competitionStarted \ If competitionStarted <> 0, then the competition has BNE game12 \ already started, so jump to game12 to skip the lap \ selection process LDX #28 \ Print token 28, which shows a menu with the following JSR PrintToken \ options: \ \ Prompt = SELECT NUMBER OF LAPS \ \ 1 = 5 laps \ \ 2 = 10 laps \ \ 3 = 20 laps LDA #20 \ Set numberOfPlayers = 20 - lowestPlayerNumber SEC \ SBC lowestPlayerNumber \ so numberOfPlayers is 1 if there is one player, 2 if STA numberOfPlayers \ there are two players, and so on LDX #3 \ Fetch the menu choice into X (0 to 2) JSR GetMenuOption LDA lapsFromOption,X \ Convert the menu choice (0 to 2) into the number of STA numberOfLaps \ laps (5, 10, 20) using the lapsFromOption lookup, and \ store the result in numberOfLaps STX lapsMenuOption \ Store the menu choice (0 to 2) in lapsMenuOption .game12 LDA #20 \ We now work our way through the human players so each STA currentPlayer \ one can race in turn, so set currentPlayer to 20 so \ it gets decremented to 19 for the first player .game13 DEC currentPlayer \ Decrement currentPlayer to move on to the next player JSR PrintDriverPrompt \ Print the "DRIVER ->" prompt and the player's name LDX #19 \ We now restore the grid positions we saved above, so \ set a counter in X .game14 LDA driversInOrder2,X \ Restore the X-th position from driversInOrder2 to STA driversInOrder,X \ driversInOrder DEX \ Decrement the counter BPL game14 \ Loop back until we have restored all the positions JSR ResetBestLapTimes \ Reset the best lap times to 10:00.0 for all drivers LDA #%10000000 \ Head to the track to choose the wing settings and JSR HeadToTrack+2 \ start the race (as bit 7 of A is set), returning here \ when the race is finished
Name: MainLoop (Part 6 of 6) [Show more] Type: Subroutine Category: Main loop Summary: The main game loop: race points and competition results Deep dive: Program flow of the main game loop
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

This part of the main loop processes the points from the race and displays the driver tables, and loops back to the previous part if there are more human players in the race. When all the players have raced, we loop back to part 2 for the next race in the championship.
LDA #%10000000 \ Sort the drivers by total race time, putting the JSR SortDrivers \ results into positionNumber and driversInOrder LDX #5 \ We now award points to the top six drivers in the \ race, so set a counter in X .game15 JSR AwardRacePoints \ Award points to the driver in race position X DEX \ Decrement the counter BPL game15 \ Loop back until we have awarded points to the top six \ drivers LDA #0 \ Sort the drivers by best lap time, putting the results JSR SortDrivers \ into positionNumber and driversInOrder LDX #6 \ Award a point to the driver with the fastest lap JSR AwardRacePoints .game16 LDA #%10000000 \ Sort the drivers by total race time, putting the JSR SortDrivers \ results into positionNumber and driversInOrder LDX #1 \ Print the driver table, showing points awarded in the LDA #4 \ last race, under the heading "POINTS", so this shows JSR PrintDriverTable \ the best drivers from the last race, along with the \ points awarded to the fastest six drivers LDA #0 \ Sort the drivers by best lap time, putting the results JSR SortDrivers \ into positionNumber and driversInOrder LDX #6 \ Print the driver table, showing lap times, under the LDA #0 \ heading "BEST LAP TIMES", so this shows the lap times JSR PrintDriverTable \ from the race LDA #%01000000 \ Sort the drivers by accumulated points, putting the JSR SortDrivers \ results into positionNumber and driversInOrder LDX #3 \ Set competitionStarted = 3, which is non-zero, so this STX competitionStarted \ indicates that the competition has started (so we \ don't get asked to choose the number of laps or player \ names) LDA #&88 \ Print the driver table, showing accumulated points, JSR PrintDriverTable \ under the heading "ACCUMULATED POINTS", so this shows \ the cumulative results of all races BIT G \ If bit 7 of G is clear, then RETURN was pressed, so BPL game16 \ jump back to game16 to show the driver tables again LDA currentPlayer \ If currentPlayer <> lowestPlayerNumber, then we still CMP lowestPlayerNumber \ have more players to race, so jump back to game13 to BNE game13 \ start the next player's race JMP game2 \ Jump back to game2 to move on to the next race in the \ competition
Name: HeadToTrack [Show more] Type: Subroutine Category: Main Loop Summary: Get the wing settings and start a race, practice or qualifying lap Deep dive: Program flow of the main game loop
Context: See this subroutine on its own page References: This subroutine is called as follows: * MainLoop (Part 1 of 6) calls HeadToTrack * MainLoop (Part 3 of 6) calls HeadToTrack * MainLoop (Part 5 of 6) calls via HeadToTrack+2

Other entry points: HeadToTrack+2 Called with A = %10000000 to start a race, as opposed to practice or a qualifying lap
.HeadToTrack LDA #%00101000 \ Set A to the value for practice or a qualifying lap \ (this instruction will be skipped when starting a race \ by calling HeadToTrack+2) STA raceStarted \ Set raceStarted to the value of A, so bit 7 gets set \ if this is a race, but is clear for practice or a \ qualifying lap STA raceStarting \ Set raceStarting to the value of A, which will be 128 \ if this is a race, so the starting lights get shown \ and the restrictions of the starting grid are applied \ (i.e. can't move the car, timers are disabled) \ \ If this not a race then we clear bit 7 of raceStarting \ so the restrictions are not applied .race1 JSR GetWingSettings \ Get the front and rear wing settings from the player JSR MainDrivingLoop \ Call the main driving loop to switch to the custom \ mode, implement the driving part of the game, and \ return here with the screen mode back to mode 7 BIT configStop \ If bit 6 of configStop is set then we are returning to BVS race1 \ the track after visiting the pits, so loop back to \ race1 to get new wing settings before rejoining the \ driving loop BPL race2 \ If bit 7 of configStop is clear then we did not use \ SHIFT and right arrow to exit the main driving loop, \ so jump to race2 to return from the subroutine \ If we get here then bit 6 of configStop is clear and \ bit 7 of configStop is set, which means we presses \ SHIFT and right arrow to exit the main driving loop, \ which is the key combination for restarting the whole \ game JSR RestartGame \ Jump to RestartGame to restart the game, which removes \ the return address from the stack and jumps to the \ main loop, so this call acts like JMP RestartGame .race2 RTS \ Return from the subroutine
Name: GetMenuOption [Show more] Type: Subroutine Category: Keyboard Summary: Scan the keyboard for a menu entry number, highlight the choice, show the SPACE bar message and return the choice number
Context: See this subroutine on its own page References: This subroutine is called as follows: * MainLoop (Part 1 of 6) calls GetMenuOption * MainLoop (Part 2 of 6) calls GetMenuOption * MainLoop (Part 3 of 6) calls GetMenuOption * MainLoop (Part 5 of 6) calls GetMenuOption

Arguments: X The number of entries in the menu
Returns: X The chosen option, zero-based (so the first option is 0, the second option is 1, and so on)
.GetMenuOption LDY #0 \ Set W = 0, to indicate that we have not yet chosen an STY W \ option from the menu STX U \ Store the number of entries in U .mopt1 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) LDY U \ We now loop through each valid menu option for this \ menu and check whether the relevant key is being \ pressed, so we set a loop counter in U to start from \ the menu size and loop down to zero .mopt2 STY V \ Store the loop counter in V so we can retrieve it \ below IF _ACORNSOFT OR _4TRACKS LDX menuKeys,Y \ Fetch the key number for menu option Y ELIF _SUPERIOR OR _REVSPLUS LDX menuKeysSup,Y \ Fetch the key number for menu option Y ENDIF JSR ScanKeyboard \ Scan the keyboard to see if this key is being pressed BEQ mopt3 \ If this key is being pressed, jump to mopt3 LDY V \ Retrieve the value of the loop counter DEY \ Decrement the loop counter to scan for the next menu \ option BPL mopt2 \ Loop back to check the key for the next option BMI mopt1 \ Loop back to mopt1 to keep checking through the option \ keys (this BMI is effectively a JMP as we just passed \ through a BPL) .mopt3 LDY V \ Set Y to the menu option that was chosen BNE mopt4 \ If Y is non-zero, jump to mopt4 to process the choice \ If we get here, SPACE was pressed LDA W \ If W = 0 then we have not yet chosen an option from BEQ mopt1 \ the menu, so jump back to mopt1 to keep checking for \ key presses, as SPACE is only a valid key press when \ we have chosen an option \ If we get here then we have already chosen an option \ from the menu, and SPACE has been pressed LDA #152 \ Poke the mode 7 conceal character into screen memory, STA row24_column5 \ to hide row 24 from column 5 onwards, i.e. hide the \ "PRESS SPACE BAR TO CONTINUE" prompt LDX G \ Set X = G - 1, to return as the zero-based choice DEX \ number RTS \ Return from the subroutine .mopt4 STY G \ Set G to the number of the choice we just made LDA W \ If W is non-zero, jump to mopt5 to skip the following BNE mopt5 \ three instructions LDX #30 \ Set X = 30 to pass to PrintToken below STX W \ Set W = 30, so W is now non-zero and denotes that we \ have made a choice JSR PrintToken \ Print token 30 ("PRESS SPACE BAR TO CONTINUE" in cyan \ at column 5, row 24) .mopt5 \ We now work our way through the menu, setting each \ entry's background colour according to the choice made \ (the chosen entry is set to red, while other entries \ are set to blue) LDX #0 \ Set an offset counter in X to step through the screen \ address of the on-screen number for each menu entry, \ starting at an offset of 0 (the offset is added to \ row18_column5 in the loop below) LDY #1 \ Set a counter in Y to count through the menu entries .mopt6 LDA #132 \ Set A to the mode 7 control code for blue, to set as \ the background colour for the unselected menu entries CPY G \ If Y <> G then this is not the chosen entry, so skip BNE mopt7 \ the following instruction to leave A as blue LDA #129 \ Set A to the mode 7 control code for red, to set as \ the background colour for the selected menu entry .mopt7 STA row18_column5,X \ Poke the colour in A into screen memory at offset X \ from column 5 on row 18, which is the screen address \ of the number for the first menu entry (so this sets \ the background colour of the current entry to A) TXA \ Set X = X + 80 CLC \ ADC #80 \ so X now points to the next menu entry, as 80 is two TAX \ lines of mode 7 characters, and the menu entries are \ shown on every other line INY \ Increment the option counter in Y CPY U \ If Y <= U then loop back to set the background colour BCC mopt6 \ for the next option, until we have done all of them BEQ mopt6 BNE mopt1 \ Jump back to mopt1 to keep checking for key presses, \ so we can change the option, or press SPACE to lock in \ our choice (this BNE is effectively a JMP as we just \ passed through a BEQ)
Name: ConvertNumberToBCD [Show more] Type: Subroutine Category: Maths (Arithmetic) Summary: Convert a number into binary coded decimal (BCD), for printing
Context: See this subroutine on its own page References: This subroutine is called as follows: * PrintDriverTable calls ConvertNumberToBCD * ResetVariables calls ConvertNumberToBCD * UpdateLapTimers calls ConvertNumberToBCD

This routine converts a number in the range 0 to 19 into a BCD number in the range 1 to 20, so the number can be printed.
Arguments: A The number to be converted into BCD (0 to 19)
Returns: A The number in BCD (1 to 20)
.ConvertNumberToBCD CMP #10 \ If A < 10, skip the following instruction as A is in BCC ibcd1 \ the range 0 to 9, which is the same number in BCD ADC #5 \ A >= 10, so set A = A + 6 (as the C flag is set) to \ convert the number into BCD, like this: \ \ * 10 = &0A -> &10 (i.e. 10 in BCD) \ * 11 = &0B -> &11 (i.e. 11 in BCD) \ * 12 = &0C -> &12 (i.e. 12 in BCD) \ * 13 = &0D -> &13 (i.e. 13 in BCD) \ * 14 = &0E -> &14 (i.e. 14 in BCD) \ * 15 = &0F -> &15 (i.e. 15 in BCD) \ * 16 = &10 -> &16 (i.e. 16 in BCD) \ * 17 = &11 -> &17 (i.e. 17 in BCD) \ * 18 = &12 -> &18 (i.e. 18 in BCD) \ * 19 = &13 -> &19 (i.e. 19 in BCD) .ibcd1 SED \ Set the D flag to switch arithmetic to Binary Coded \ Decimal (BCD) ADC #1 \ Increment A in BCD mode, so the result is in the \ range 1 to 20 CLD \ Clear the D flag to switch arithmetic to normal RTS \ Return from the subroutine
Name: PrintDriverTable [Show more] Type: Subroutine Category: Text Summary: Print the table of drivers
Context: See this subroutine on its own page References: This subroutine is called as follows: * MainLoop (Part 5 of 6) calls PrintDriverTable * MainLoop (Part 6 of 6) calls PrintDriverTable

The driver table consists of the following: * A header, as specified by the argument in X * A table with one row for each driver, showing a number, a driver name and a third column as specified by the argument in A * A "PRESS SPACE TO CONTINUE" prompt below the table If the table is being shown after practice or qualifying, the drivers are shown in driver order, from 1 to 20, but if it is shown after a race, the first column shows the numbers from the positionNumber table, the second column shows the drivers in the order that they appear in the driversInOrder list, and the race class is printed above the table. The routine also waits for SPACE or RETURN to be pressed before returning.
Arguments: X The number of the token to print as the header (see PrintHeader for more details): * 1 = "POINTS" * 2 = "GRID POSITIONS" * 3 = "ACCUMULATED POINTS" * 6 = "BEST LAP TIMES" A Defines what to show in the third column in the table: * 0 = lap times * 4 = points awarded in the last race * &88 = accumulated points positionNumber A list of position numbers (for race tables only), which contains the numbers 0 to 19 in sequence, with tied positions represented by shared position numbers
Returns: G Bit 7 is clear if RETURN was pressed, set if SPACE was pressed
.PrintDriverTable PHA \ Store the value of A on the stack so we can retrieve \ it below AND #%00001111 \ Set colourScheme to the lower nibble of A, which STA colourScheme \ contains the colour scheme to use for the table, so we \ can pass it to SetRowColours below JSR PrintHeader \ Print the header specified in X LDY #0 \ We are about to print a table containing 20 rows, one \ for each driver, so set a row counter in Y .dtab1 STY rowCounter \ Store the row counter in rowCounter LDA #%00000000 \ Set G = 0 so the call to Print2DigitBCD below will STA G \ print the second digit and will not print leading \ zeroes when printing the driver number JSR SetRowColours \ Set the colours in token 31 according to the colour \ scheme we stored in colourScheme above, so they can be \ used to set the colours of each table cell LDX #32 \ Print token 32, which prints two spaces and backspaces JSR PrintToken \ followed by token 31, so this sets up the colours for \ the first column LDY rowCounter \ Set Y to the table row number LDA positionNumber,Y \ Set A to the positionNumber for this row, to show in \ the first column BIT raceStarted \ If bit 7 of raceStarted is set, that means the results BMI dtab2 \ are from a finished race, so jump to dtab2 to skip the \ following instruction, so we print the numbers from \ positionNumber in the first column TYA \ Set A to the row number, so we print the row number in \ the first column (i.e. 1 to 20, as ) .dtab2 JSR ConvertNumberToBCD \ Convert the number in A into binary coded decimal \ (BCD), adding 1 in the process JSR Print2DigitBCD \ Print the binary coded decimal (BCD) number in A, and \ because we set G to 0 above, it will print the second \ digit and will not print leading zeroes LDX #31 \ Print token 31, which prints two spaces and sets the JSR PrintToken \ colours as configured above, so this inserts a black \ gap between the first and second table columns LDY rowCounter \ Set Y to the table row number JSR PrintPositionName \ Print the name of the driver in position Y, so row Y \ of the table contains the details of the driver in \ position Y, and set driverPrinted to the number of the \ driver that was printed LDX #31 \ Print token 31, which prints two spaces and sets the JSR PrintToken \ colours as configured above, so this inserts a black \ gap between the second and third table columns LDX driverPrinted \ Set X to the number of the driver we just printed, so \ the call to PrintTimer prints the lap time for driver \ X PLA \ If the value of A that we stored on the stack at the PHA \ start of the routine is non-zero, jump to dtab3 to BNE dtab3 \ skip the following \ If we get here then the value of A passed to the \ routine was 0, so we now print the third column \ containing the driver's best lap time LDA #%00100110 \ Print the lap time for driver X in the following JSR PrintTimer \ format: \ \ * %00 Minutes: No leading zeroes, print both digits \ * %10 Seconds: Leading zeroes, print both digits \ * %0 Tenths: Print tenths of a second \ * %11 Tenths: Leading zeroes, no second digit JMP dtab6 \ Jump down to dtab6 to move on to the next table row .dtab3 \ If we get here then the value of A passed to the \ routine was non-zero (i.e. 4 or &88) BMI dtab4 \ If bit 7 of A is set (i.e. A = &88), jump to dtab4 \ If we get here then the value of A passed to the \ routine was 4, so we now print the third column \ containing the points awarded to the driver in the \ last race \ \ Only the top six drivers from each race get points, \ so we print a blank column for the other drivers LDA rowCounter \ Set A = rowCounter + 20 CLC ADC #20 CMP #26 \ Set X = A, and if A < 26 (so rowCounter is 0 to 5), TAX \ jump to dtab5 to print the race points BCC dtab5 LDA #7 \ A >= 26 (so rowCounter is 6 to 19), so print seven JSR PrintSpaces \ spaces in the last column BEQ dtab6 \ Jump to dtab6 to move on to the next table row (this \ BEQ is effectively a JMP, as PrintSpaces sets the Z \ flag) .dtab4 \ If we get here then the value of A passed to the \ routine had bit 7 set, so it must have been &88, so \ we now print the third column containing the driver's \ accumulated points LDA #%00101000 \ Set G, so the next three calls to Print2DigitBCD do STA G \ the following: \ \ * No leading zeroes, print second digit \ * Leading zeroes, print second digit \ * Leading zeroes, print second digit \ \ The second and third two calls to Print2DigitBCD are \ in the Print4DigitBCD routine below LDA totalPointsTop,X \ If the top byte of the driver's total points is zero, BEQ dtab5 \ jump to dtab5 JSR Print2DigitBCD \ Otherwise print the top byte of the driver's total \ points, which is a binary coded decimal (BCD) number LDA totalPointsHi,X \ Fetch the high byte of the driver's total points, to \ pass to Print4DigitBCD JSR Print4DigitBCD \ Print both the high and low bytes of the driver's \ total points in full, followed by a space JMP dtab6 \ Jump to dtab6 to move on to the next table row .dtab5 JSR Print234DigitBCD \ Print the high and low bytes of the driver's total \ points, replacing leading zeroes with spaces, and \ followed by a space .dtab6 \ If we get here then we have finished printing the \ current table row, so now we move on to the next row LDY rowCounter \ Set Y to the table row number INY \ Increment the table row number CPY #20 \ Loop back to print the next table row, until we have BNE dtab1 \ printed all 20 LDA #3 \ Print three spaces to pad out the final row JSR PrintSpaces LDA #156 \ Print ASCII 156 to switch to a black background JSR OSWRCH LDA raceStarted \ If bit 7 of raceStarted is clear, that means the BPL dtab7 \ results are from qualifying or practice, so jump to \ dtab7 to skip the following so we do not print the \ race class above the table \ We now print the race class and number of laps in the \ gap between the page header and the top of the table LDX #49 \ Print token 49, which moves the cursor to column 9, JSR PrintToken \ row 2 JSR PrintRaceClass \ Print the race class LDA lapsMenuOption \ Set the configurable token in token 50 to 218 plus the CLC \ figure in lapsMenuOption, to give 218, 219 or 220, ADC #218 \ which correspond to the tokens for " 5", "10" and "20" STA token50+3 LDX #50 \ Print token 50, which is "n laps", where n is the JSR PrintToken \ number of laps we just configured .dtab7 PLA \ Retrieve the value of A that we stored on the stack at \ the start of the routine JSR WaitForSpaceReturn \ Print a prompt and wait for SPACE or RETURN to be \ pressed, depending on bit 7 of A RTS \ Return from the subroutine
Name: PrintNearestDriver [Show more] Type: Subroutine Category: Text Summary: Print a driver's name in the "In front" or "Behind" slot in the header
Context: See this subroutine on its own page References: This subroutine is called as follows: * UpdatePositionInfo calls PrintNearestDriver

Arguments: Y The position of the driver whose name we print A The pixel row on which to print the driver name: * 24 = the first line of text at the top of the screen (i.e. the "In front:" section of token 43) * 33 = the second line of text at the top of the screen (i.e. the "Behind:" section of token 44)
.PrintNearestDriver STA yCursor \ Move the cursor to the pixel row in A LDA #27 \ Move the cursor to character column 27 STA xCursor \ Fall through into PrintPositionName to print the \ driver name at column 27 on the specified row
Name: PrintPositionName [Show more] Type: Subroutine Category: Text Summary: Print the name of the driver in a specific position in the driver position list
Context: See this subroutine on its own page References: This subroutine is called as follows: * PrintDriverTable calls PrintPositionName

Arguments: Y The position of the driver whose name we print
Returns: driverPrinted The number of the driver that we printed
.PrintPositionName LDX driversInOrder,Y \ Set X to the number of the driver in position Y STX driverPrinted \ Store the driver number in driverPrinted, so we can \ return it JSR GetDriverAddress \ Set (Y A) to the address of driver X's name JSR PrintDriverName \ Print the name of the driver at address (Y A) RTS \ Return from the subroutine
Name: PrintDriverPrompt [Show more] Type: Subroutine Category: Text Summary: Print the "DRIVER ->" prompt and a driver's name, to show whose turn it is next when playing a multi-player game
Context: See this subroutine on its own page References: This subroutine is called as follows: * MainLoop (Part 3 of 6) calls PrintDriverPrompt * MainLoop (Part 5 of 6) calls PrintDriverPrompt
.PrintDriverPrompt LDX #29 \ Print token 29, which clears the screen, displays the JSR PrintToken \ F3 header, and shows a " DRIVER -> " prompt LDX currentPlayer \ Set X to the driver number of the current player JSR GetDriverAddress \ Set (Y A) to the address of driver X's name JSR PrintDriverName \ Print the name of the driver at address (Y A) JSR WaitForSpace \ Print a prompt and wait for SPACE to be pressed RTS \ Return from the subroutine
Name: AddRacePoints [Show more] Type: Subroutine Category: Drivers Summary: Add the race points to the driver's total points
Context: See this subroutine on its own page References: This subroutine is called as follows: * AwardRacePoints calls AddRacePoints

Arguments: X The race position whose points should be added Y The driver who receives those points, i.e. who has then added to their total accumulated points
.AddRacePoints SED \ Set the D flag to switch arithmetic to Binary Coded \ Decimal (BCD) LDA totalPointsLo,Y \ Add (0 racePointsHi racePointsLo) for position X to CLC \ (totalPointsTop totalPointsHi totalPointsLo) for ADC racePointsLo,X \ driver Y, starting with the low bytes STA totalPointsLo,Y LDA totalPointsHi,Y \ And then the high bytes ADC racePointsHi,X STA totalPointsHi,Y LDA totalPointsTop,Y \ And then the top bytes ADC #0 STA totalPointsTop,Y CLD \ Clear the D flag to switch arithmetic to normal RTS \ Return from the subroutine
Name: ResetTrackLines [Show more] Type: Subroutine Category: Screen buffer Summary: Reset the track lines below the horizon in the track view
Context: See this subroutine on its own page References: This subroutine is called as follows: * MainDrivingLoop (Part 2 of 5) calls ResetTrackLines * ProcessShiftedKeys calls ResetTrackLines

This routine does the following: * Set horizonLine+1 bytes at rightGrassStart to &80 * Set horizonLine+1 bytes at leftVergeStart to &80 * Set horizonLine+1 bytes at rightVergeStart to &80 * Set horizonLine+1 bytes at leftTrackStart to &80 * Set 80 bytes at backgroundColour to 0 Setting each of the four verge edges to &80 effectively pushes the edges off the right edge of the screen, which resets the verge edges on that track line. By resetting the first horizonLine+1 bytes in each verge edge block, we are resetting all the edges for track lines below the horizon, i.e. from track line 0 at the bottom of the screen to track line horizonLine+1 on the horizon. Setting the 80 bytes at backgroundColour to zero resets the background colour for all track lines.
.ResetTrackLines LDX horizonLine \ We start by setting horizonLine+1 bytes at \ leftVergeStart, rightGrassStart rightVergeStart and \ leftTrackStart to &80, so set a byte counter in X LDA #&80 \ Set A = &80 to use as our reset value .resl1 STA leftVergeStart,X \ Set the X-th byte of leftVergeStart to &80 STA leftTrackStart,X \ Set the X-th byte of leftTrackStart to &80 STA rightVergeStart,X \ Set the X-th byte of rightVergeStart to &80 STA rightGrassStart,X \ Set the X-th byte of rightGrassStart to &80 DEX \ Decrement the byte counter BPL resl1 \ Loop back until we have zeroed all horizonLine+1 bytes LDX #79 \ We now zero the 80 bytes at backgroundColour, so set a \ byte counter in X LDA #0 \ Set A = 0 to use as our zero value .resl2 STA backgroundColour,X \ Zero the X-th byte of backgroundColour DEX \ Decrement the byte counter BPL resl2 \ Loop back until we have zeroed all 80 bytes RTS \ Return from the subroutine
Name: GetDriverName [Show more] Type: Subroutine Category: Keyboard Summary: Fetch a player's name from the keyboard
Context: See this subroutine on its own page References: This subroutine is called as follows: * MainLoop (Part 3 of 6) calls GetDriverName
.GetDriverName LDX currentPlayer \ Set X to the driver number of the current player JSR GetDriverAddress \ Set (Y A) to the address of driver X's name LDX #12 \ Fetch a string of length 12 from the keyboard and JSR GetTextInput \ store it in (Y A), padding the string out with spaces \ if required RTS \ Return from the subroutine
Name: DrawCars [Show more] Type: Subroutine Category: Drawing objects Summary: Draw all the car objects, with four objects for the closest car in front of us
Context: See this subroutine on its own page References: This subroutine is called as follows: * MoveAndDrawCars calls DrawCars
.DrawCars LDX currentPosition \ Set X to the current player's position, so we work our \ way backwards through the pack, starting with the car \ behind the current player, and wrapping round to the \ cars in front, working our way towards the player's \ car in the order in which they should be drawn (with \ distant cars first) BPL cars2 \ If X is positive, jump to cars2 to skip the following \ instruction .cars1 JSR DrawCarInPosition \ Draw the car in position X .cars2 JSR GetPositionBehind \ Set X to the number of the position behind position X, \ so we work our way back through the pack CPX positionAhead \ Loop back to cars1 until we have reached the position BNE cars1 \ ahead of the current player \ We now draw the car that's just in front of us, which \ is made up of four objects that can be skewed to make \ it look like the car is steering LDX #22 \ The four objects are the front tyres, body, rear tyres \ and rear wing, so set up a counter in X to work \ through the first three in the order 22, 21 and 20, to \ pass to DrawCarOrSign in turn so they get drawn in \ that order: front tyres, body/helmet and rear tyres .cars3 STX xStoreDraw \ Store X in xStoreDraw so it gets preserved through \ the call to DrawCarOrSign JSR DrawCarOrSign \ Draw the specified part of the four-object car just \ in front of us DEX \ Decrement the object counter CPX #20 \ Loop back until we have drawn all three objects BCS cars3 LDX positionAhead \ Set X to the position ahead of the current player JSR DrawCarInPosition \ Draw the car in position X, which draws the rear wing \ as the last (and closest) of the four objects RTS \ Return from the subroutine
Name: dashData42 [Show more] Type: Variable Category: Screen buffer Summary: Contains part of the dashboard image that gets moved into screen memory
Context: See this variable on its own page References: No direct references to this variable in this source file
ORG &6C00 .dashData42 SKIP 2853
Name: UpdateMirrors [Show more] Type: Subroutine Category: Dashboard Summary: Update the wing mirrors to show any cars behind us Deep dive: Wing mirrors
Context: See this subroutine on its own page References: This subroutine is called as follows: * MainDrivingLoop (Part 2 of 5) calls UpdateMirrors
ORG &7B00 .UpdateMirrors LDY positionBehind \ Set Y to the position of the driver behind us LDX driversInOrder,Y \ Set X to the number of the driver behind us LDA objectStatus,X \ If the object status byte for the car behind us has BMI upmi1 \ bit 7 set, then it is hidden, so jump to upmi1 to \ clear the mirror (as V will be set to a negative \ value, and this will never match any values from \ mirrorSegment, as all the mirrorSegment entries are \ positive) \ We now calculate the size of the car to draw in the \ mirror (in other words, the height of the block we \ draw) \ \ We do this by taking the scale factor for the driver \ behind (which is the same as the size of the car) and \ dividing by 8 to give us half the number of pixel \ lines to draw, in T (so if T = 1, we draw a block \ that's two pixels high, and if T = 4, we draw a block \ that's eight pixels high) \ \ We then calculate the upper and lower offsets within \ the mirror segment, by taking the offset of the middle \ row in the segment, and adding and subtracting T to \ give us T rows either side of &B6 in TT and N \ \ We then pass N and TT (the latter via A) into the \ DrawCarInMirror routine LDA objectSize,X \ Set A = the size of the object for the driver behind LSR A \ Set T = A / 8 LSR A \ = objectSize / 8 LSR A STA T CLC \ Set TT = &B6 + T ADC #&B6 STA TT LDA #&B6 \ Set N = &B6 - T SEC SBC T STA N \ Next we calculate the mirror segment that the car \ should appear in, based on the difference in yaw angle \ between the car (whose angle is in objYawAngleHi), and \ the player (whose angle is in playerYawAngleHi), \ storing the result in A \ \ This gives us the amount of distance that the car \ behind the player is to either side of the player, in \ terms of yaw angle \ \ This can then be matched with the values in the \ mirrorSegment table to see which segment we should \ show the car in, with larger yaw angles mapping to \ segments towards the outside of the mirrors LDA objYawAngleHi,X \ Set A = objYawAngleHi for the driver behind SEC \ Set A = A - playerYawAngleHi - 4 SBC playerYawAngleHi \ = objYawAngleHi - playerYawAngleHi - 4 SEC SBC #4 LSR A \ Set A = A >> 3 LSR A \ = A div 8 LSR A .upmi1 STA V \ Set V = A \ So by this point: \ \ * V is negative if there is no car in the mirror (so \ we must have jumped here from the BMI at the start \ of the routine) \ \ * Otherwise V is a yaw angle that potentially maps to \ a mirror segment number LDY #5 \ We now loop through the six mirror segments, either \ clearing or drawing each of them, so we set up a loop \ counter in Y to count from 5 to 0 .upmi2 LDA V \ If V matches this segment's mirrorSegment value, then CMP mirrorSegment,Y \ we can see a car in this segment, so jump to upmi3 to BEQ upmi3 \ set A = TT (which we calculated above to denote the \ size of the car) and send this to mirrorContents and \ DrawCarInMirror \ If we get here then we can't see a car in this mirror \ segment, so we need to clear the mirror to white LDA mirrorContents,Y \ If this segment's mirrorContents value is 0, then BEQ upmi5 \ there is no car being shown in this segment, so jump \ to upmi5 to move on to the next segment, as the \ mirror segment is already clear LDA #0 \ Otherwise we need to clear this segment, so set A = 0 BEQ upmi4 \ and jump to upmi4 to send this to mirrorContents and \ DrawCarInMirror (this BEQ is effectively a JMP as A is \ always zero) .upmi3 LDA TT \ Set A = TT to store in mirrorContents and pass to \ DrawCarInMirror .upmi4 STA mirrorContents,Y \ Store A in the Y-th entry in mirrorContents, which \ will be zero if there is no car in this mirror \ segment, non-zero if there is JSR DrawCarInMirror \ Draw the car in the specified mirror segment, between \ the upper and lower offsets in A and N .upmi5 DEY \ Decrement the loop counter BPL upmi2 \ Loop back until we have looped through 5 to 0 RTS \ Return from the subroutine
Name: ShowStartingLights [Show more] Type: Subroutine Category: Dashboard Summary: Show the lights at the start of the race Deep dive: Starting lights
Context: See this subroutine on its own page References: This subroutine is called as follows: * MainDrivingLoop (Part 2 of 5) calls ShowStartingLights

The lights at the start of the race follow this pattern: * The lights are initially off, so they show as three lights with a white border and black interior. * Once we have started our engine, the timer starts and after a pause the lights turn blue. During this time we can't move, but we can rev the engine. * After another pause, the lights turn green and we can start the race. * After another pause, the lights disappear. This is implemented by the counter in raceStarting, which works like this. When a race is about to start, raceStarting is set to 128, and stays on this value until the engine is started, at which point it starts to count down, with one tick per iteration of the main loop, working through the following sequence: * 128 = show black lights, engine not yet started * Start counting down from 240 once engine starts * 240-192 = show black lights * 191-161 = show blue lights * 160 = keep showing blue lights and stop counting down until main loop counter is a multiple of 64 * Start counting down from 40 once loop counter is a multiple of 64 * 40-1 = show green lights * 0 = show no lights (race has started)
.ShowStartingLights LDA raceStarted \ If bit 7 of raceStarted is clear then this is either BPL star9 \ a practice or qualifying lap, so jump to star9 to \ return from the subroutine, as there is no need for \ starting lights LDX raceStarting \ If raceStarting = 0, then the race is not in the BEQ star9 \ process of starting (i.e. it's already started), so \ jump to star9 to return from the subroutine, as there \ is no need for starting lights BPL star2 \ If raceStarting <= 127, jump to star2 to decrement \ the raceStarting counter (this instruction isn't \ necessary as the following comparison covers the same \ test, so this branch could be removed) CPX #128 \ If raceStarting <> 128, jump to star2 to decrement BNE star2 \ the raceStarting counter \ If we get here then raceStarting = 128, which means \ the lights are showing as black, and we are waiting \ for the engine to start to continue the countdown BIT engineStatus \ If bit 7 of engineStatus is clear, then the engine is BPL star1 \ not yet on, so jump to star1 to keep the value of X \ (and therefore the value of raceStarting) at 128, as \ we only move on to the second stage once we have \ started our engine LDX #240 \ If we get here then the engine has started, so set \ X = 240 to store as the updated value of raceStarting, \ which can now start ticking down .star1 \ If we get here then the lights are off LDY #0 \ Set Y to the EOR pattern that doesn't flip any pixels, \ \ See star8 below for an explanation of the EOR pattern LDA #%10000000 \ Set A to a pixel byte containing four pixels with \ colours 2, 0, 0, 0 (white, black, black, black) BNE star6 \ Jump to star6 to store the updated value of X in \ raceStarting and draw the lights (this BNE is \ effectively a JMP as A is never zero) .star2 \ If we get here, then X is non-zero and X <> 128, so \ the engine has started and the light sequence has \ started CPX #160 \ If X = 160, jump to star4 to consider switching from BEQ star4 \ the blue lights to the green lights DEX \ Decrement X BPL star5 \ If X is positive, jump to star5 to display the green \ lights CPX #192 \ If X >= 192, jump to star1 BCS star1 .star3 \ If we get here then the lights are blue (the second \ stage) LDA #%10100101 \ Set A to a pixel byte containing four pixels with \ colours 2, 1, 2, 1 (white, blue, white, blue) LDY #%01110111 \ Set Y to the EOR pattern that flips the above to \ %11010010, i.e. colours 2, 2, 1, 2 (white, white, \ blue, white) \ \ See star8 below for an explanation of the EOR pattern BNE star6 \ Jump to star6 to store the updated value of X in \ raceStarting and draw the lights (this BNE is \ effectively a JMP as A is never zero) .star4 LDA mainLoopCounterLo \ If mainLoopCounterLo mod 64 <> 0, which will be true AND #63 \ for 63 out of 64 iterations round the main loop, jump BNE star3 \ to star3 to display the blue lights \ Otherwise it is time to switch on the green lights LDX #40 \ Set X = 40 .star5 \ If we get here then the lights are green (the third \ and final stage) LDA #%11110010 \ Set A to a pixel byte containing four pixels with \ colours 2, 2, 3, 2 (white, white, green, white) LDY #%00000101 \ Set Y to the EOR pattern that flips the above to \ %11110111, i.e. colours 2, 3, 3, 3 (white, green, \ green, green) \ \ See star8 below for an explanation of the EOR pattern .star6 STX raceStarting \ Store the updated value of X in raceStarting \ We now draw the lights, starting with the white border \ lines, and then the lights themselves using the pixel \ byte in A and the EOR pattern in Y \ \ We only need to draw the left set of lights, as the \ screen buffer will replicate the other two sets to the \ right \ \ The whole light takes up ten pixel rows: two rows for \ the top edge, six rows for the light bulb, and another \ two rows for the bottom edge PHA \ Store the pixel byte in A on the stack, so we can \ retrieve it below when drawing the lights STY T \ Store the EOR pattern in T, so we can retrieve it when \ drawing the lights LDA #%11110000 \ Set A to a pixel byte containing four pixels with \ colours 2, 2, 2, 2 (white, white, white, white), for \ drawing the edges, which we actually draw as a full \ block of white, and then fill in the centre later LDX #9 \ The light contains ten pixel rows, so set a row \ counter in X .star7 STA dashData37+36,X \ Store the X-th row of the light in the screen buffer, \ in dash data block 37 (the screen drawing routine will \ replicate the light in blocks 38 and 39) DEX \ Decrement the pixel row counter BPL star7 \ Loop back until we have drawn all ten rows of the \ light PLA \ Set A to the pixel byte for the lights that we stored \ on the stack above LDX #5 \ The central bulb of the light is six pixel rows, so \ set a row counter in X .star8 STA dashData37+38,X \ Store the X-th row of the light in the screen buffer, \ again in dash data block 37, starting two pixel rows \ after the edge that we drew above EOR T \ Flip the pixels by EOR'ing with T, so the pattern of \ the bulb pixels flips with each row required \ \ This means the green light looks like this: \ \ white, white, green, white ..x. \ white, green, green, green .xxx \ white, white, green, white ..x. \ white, green, green, green .xxx \ white, white, green, white ..x. \ white, green, green, green .xxx \ \ and the blue light looks like this: \ \ white, blue, white, blue .x.x \ white, white, blue, white ..x. \ white, blue, white, blue .x.x \ white, white, blue, white ..x. \ white, blue, white, blue .x.x \ white, white, blue, white ..x. \ \ The EOR pattern for the black lights is 0, so there is \ no flipping and every row is the same: \ \ white, black, black, black .xxx \ white, black, black, black .xxx \ white, black, black, black .xxx \ white, black, black, black .xxx \ white, black, black, black .xxx \ white, black, black, black .xxx DEX \ Decrement the pixel row counter BPL star8 \ Loop back until we have drawn all six rows of the \ bulb .star9 RTS \ Return from the subroutine
Name: PrintTimer [Show more] Type: Subroutine Category: Text Summary: Print the specified timer
Context: See this subroutine on its own page References: This subroutine is called as follows: * PrintBestLapTime calls PrintTimer * PrintDriverTable calls PrintTimer * PrintLapTime calls PrintTimer

Arguments: X The lap time to print: * 0 to 19: Lap time for the specified driver * 20 = the clock timer (clockMinutes clockSeconds clockTenths) * 21 = the lap timer (lapMinutes lapSeconds lapTenths) A Flags to control how the time is printed: * Bit 7: clear = do not print leading zeroes in mins set = print leading zeroes in mins * Bit 6: clear = print second digit in mins set = do not print second digit in mins * Bit 5: clear = do not print leading zeroes in secs set = print leading zeroes in secs * Bit 4: clear = print second digit in secs set = do not print second digit in secs * Bit 3: clear = print tenths of a second set = do not print tenths of a second * Bit 2: clear = do not print leading zeroes in tenths set = print leading zeroes in tenths * Bit 1: clear = print second digit in tenths set = do not print second digit in tenths
.PrintTimer STA G \ Store A in G so we can check the value of bit 7 below LDA bestLapMinutes,X \ Print the number of minutes in driver X's best lap JSR Print2DigitBCD \ time LDA #&3A \ Print ":" JSR PrintCharacter LDA bestLapSeconds,X \ Print the number of seconds in driver X's best lap JSR Print2DigitBCD \ time ASL G \ If bit 7 of G is set, we do not want to print tenths BCS plap1 \ of a second, so jump to plap1 to return from the \ subroutine LDA #&2E \ Print "." JSR PrintCharacter LDA bestLapTenths,X \ Print the number of tenths of a second in driver X's JSR Print2DigitBCD \ best lap time .plap1 RTS \ Return from the subroutine
Name: DrawTrackView (Part 4 of 4) [Show more] Type: Subroutine Category: Screen buffer Summary: Revert all the code modifications made by the DrawTrackView routine Deep dive: Drawing around the dashboard Drawing the track view
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.view19 LDA view3+1 \ Modify the instruction at view20 to use the low byte STA view20+1 \ of the address from view3 \ \ In part 2 we modified the instruction at view3 to \ revert the specified instruction back to STA (P),Y, \ ready for a loop-back that never happened, so view20 \ will now revert this change instead LDA view8+1 \ Modify the instruction at view21 to use the low byte STA view21+1 \ of the address from view8 \ \ In part 3 we modified the instruction at view8 to \ revert the specified instruction back to STA (P),Y, \ ready for a loop-back that never happened, so view21 \ will now revert this change instead LDA view14+1 \ Modify the instruction at view22 to use the low byte STA view22+1 \ of the address from view14 \ \ In part 3 we modified the instruction at view14 to \ revert the specified instruction back to STA (P),Y, \ ready for a loop-back that never happened, so view22 \ will now revert this change instead LDA #&91 \ Set A to the opcode for the STA (P),Y instruction .view20 STA DrawTrackBytes+15 \ Revert the instruction that view3 would have reverted .view21 STA DrawTrackBytes+15 \ Revert the instruction that view8 would have reverted .view22 STA byte2+15 \ Revert the instruction that view14 would have reverted LDA #&E0 \ Set A to the opcode for the CPX #44 instruction STA byte3 \ Revert the instruction at byte3 to CPX #44 RTS \ Return from the subroutine
Name: DrawTrackView (Part 1 of 4) [Show more] Type: Subroutine Category: Screen buffer Summary: Draw the top part of the track view using the data in the dash data blocks Deep dive: Drawing around the dashboard Drawing the track view The track verges
Context: See this subroutine on its own page References: This subroutine is called as follows: * MainDrivingLoop (Part 1 of 5) calls DrawTrackView * MainDrivingLoop (Part 2 of 5) calls DrawTrackView
.DrawTrackView LDA #0 \ Set (Q P) = &6700 and (S R) = &6800, ready to pass to STA P \ the DrawTrackLine routine (so the track view gets STA R \ drawn at the correct place on screen, from &6701 LDX #&67 \ onwards, as the first line is drawn at (Q P) + 1) STX Q INX STX S LDX #79 \ Set X = 79, to point to the first byte to draw from \ each dash data block (i.e. the byte at the end of the \ data block, at offset 79) JSR DrawTrackLine \ Draw one-pixel high lines that correspond to dash data \ offsets 79 to 44, returning from the subroutine after \ drawing the line specified by offset X = 44 (so this \ draws all the lines from the top of the track view, \ down to the line just above the top of the dashboard) JMP view1 \ Jump to part 2 to draw the rest of the track view from \ offsets 43 to 3, modifying the code so it draws the \ rest of the lines around the shape of the dashboard
Name: DrawTrackLine (Part 2 of 2) [Show more] Type: Subroutine Category: Screen buffer Summary: Draw a pixel line across the screen in the track view Deep dive: Drawing around the dashboard Drawing the track view
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.prow2 \ At this point, X contains the offset within the dash \ data of the pixel line to be drawn across the screen LDA backgroundColour,X \ Fetch the colour of the first byte on the line from AND #%00000011 \ the X-th entry in backgroundColour (bits 0 to 2) TAY LDA colourPalette,Y \ Set A to logical colour Y from the colour palette, \ to use as the first byte on the line \ Fall through into DrawTrackBytes to draw the pixel \ bytes that make up the line we want to draw \ \ If we are drawing the lines above the top of the \ dashboard, then the following then loops back to the \ start of the DrawTrackLine routine to keep drawing \ lines until we do reach the top of the dashboard, at \ which point we return from the DrawTrackLine routine \ to part 2 of DrawTrackView, which modifies the code to \ draw subsequent lines around the shape of the \ dashboard
Name: DRAW_BYTE [Show more] Type: Macro Category: Screen buffer Summary: Draw a pixel byte as part of a horizontal line when drawing the track view
Context: See this macro on its own page References: This macro is used as follows: * DrawTrackBytes (Part 1 of 3) uses DRAW_BYTE * DrawTrackBytes (Part 2 of 3) uses DRAW_BYTE

This routine draws a row of four pixels (i.e. one byte) as part of the track view. The track view is drawn one line at a time, with each line being one pixel high. Each of these lines is drawn as a sequence of bytes, with each byte containing four pixels. Each macro instance draws one pixel byte into screen memory, so that's one four-pixel block within the horizontal line. So the full sequence of macros, from DRAW_BYTE 0 through DRAW_BYTE 39, draws a one-pixel high line across the full width of the screen. In other words, each DRAW_BYTE macro draws a character block's worth of line, as the screen is 40 character blocks wide. Each macro instance draws one pixel byte, from offset X within dash data block I%, into screen memory. The offset X is decremented for each run through the sequence of macros, as data is stored at the end of each dash data block. So as each pixel line is drawn, moving down the screen, X decrements down from 79 (the start of each dash data block) as we work our way through the data. The destination address in screen memory for the data is governed by (Q P), which points to the address of the pixel byte to update in the first byte (i.e. the address of the first pixel in the line across the screen). If the dash data byte is zero, then the current value of A is stored in screen memory (i.e. the same value that was stored in the previous byte). If the dash data byte is non-zero, then this is stored in A and screen memory, unless it is &55, in which a zero is stored in A and screen memory. As it is copied, the source dash data byte is zeroed, so the macro effectively moves a byte into screen memory, clearing it in the process.
Arguments: I% The pixel byte number (0 to 39) X The offset within the dash data of the data to be drawn on the screen (from &7F down, as the dash data lives at the end of each dash data block) A The value stored in screen memory in the previous pixel byte (or the starting value if this is pixel byte 0) (Q P) The screen address of the leftmost character block to update (i.e. the screen address of the pixel byte to draw in the first character block on the line, so this is the address of the start of the horizontal line that the sequential macros draw) (S R) Contains (Q P) + &100, to be used instead of (Q P) for pixel bytes 32 to 39
MACRO DRAW_BYTE I% LDY dashData+&80*I%,X \ Set Y to the X-th byte of dash data block I% BEQ P%+10 \ If Y = 0, skip the next three instructions (i.e. jump \ to the LDY #LO(8*I%) instruction to leave the value of \ A alone) \ Otherwise Y is non-zero, and we do the following in \ the next three instructions: \ \ * Zero the location in code block I% from which we \ just read a byte \ \ * Set A to the byte we just read from code block I%, \ unless the value is &55, in which case set A = 0 LDA #0 \ Zero the X-th byte of dash data block I% STA dashData+&80*I%,X LDA zeroIfYIs55,Y \ Set A to the Y-th byte from zeroIfYIs55 \ \ This sets A = Y, unless Y = &55, in which case it sets \ Y = 0, so that's: \ \ If Y = &55, set A = 0, otherwise set A = Y \ \ The zeroIfYIs55 table exists just to enable us to do \ this logic in one instruction \ If Y = 0 above, this is where we jump to LDY #LO(8*I%) \ Store A in character block I% in screen memory, as an IF I% < 32 \ offset from (Q P) STA (P),Y \ ELSE \ (S R) is used instead of (Q P) for pixel bytes 32 to STA (R),Y \ 39, which saves us from having to increment Q to cross ENDIF \ the page boundary, as (S R) = (Q P) + &100 \ Fall through into the next DRAW_BYTE macro to draw \ the next pixel byte along to the right, continuing the \ horizontal line of pixels ENDMACRO
Name: DrawTrackBytes (Part 1 of 3) [Show more] Type: Subroutine Category: Screen buffer Summary: Draw the pixel bytes that make up the track view (0 to 15) Deep dive: Drawing around the dashboard Drawing the track view
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawTrackView (Part 2 of 4) calls DrawTrackBytes * DrawTrackView (Part 3 of 4) calls DrawTrackBytes * DrawTrackView (Part 4 of 4) calls DrawTrackBytes

This routine draws pixel bytes 0 to 39, which draws a one-pixel high line in the track view. Note that this routine starts on a page boundary (DrawTrackBytes = &7C00). This is important as it means the code can be modified at a specific address using only the low byte of that address, as we know that high byte is the same throughout the routine. This is why the staDrawByte and ldaDrawByte lookup tables only need to store the low bytes of the addresses for instructions that we need to modify.
.DrawTrackBytes DRAW_BYTE 0 \ Draw pixel bytes 0 to 15 DRAW_BYTE 1 DRAW_BYTE 2 DRAW_BYTE 3 DRAW_BYTE 4 DRAW_BYTE 5 DRAW_BYTE 6 DRAW_BYTE 7 DRAW_BYTE 8 DRAW_BYTE 9 DRAW_BYTE 10 DRAW_BYTE 11 DRAW_BYTE 12 DRAW_BYTE 13 DRAW_BYTE 14 DRAW_BYTE 15 JMP byte1 \ Jump to part 2 to continue with pixel byte 16
Name: DrawTrackView (Part 2 of 4) [Show more] Type: Subroutine Category: Screen buffer Summary: Draw the part of the track view that fits around the dashboard Deep dive: Drawing around the dashboard Drawing the track view
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

This routine modifies the DrawTrackBytes routine so that it draws all the remaining lines in the track view so they fit around the shape of the dashboard.
.view1 \ We get here with X = 44, as in part 1 we drew the \ lines specified by dash data offsets 79 to 44 \ \ In the following loop, we draw the lines specified by \ dash data offsets 43 to 28 LDA #&60 \ Set A to the opcode for the RTS instruction STA byte3 \ Modify the following instruction in DrawTrackBytes \ (part 3): \ \ CPX #44 -> RTS \ \ so that calls to DrawTrackLine and DrawTrackBytes from \ now on will draw individual lines rather than looping \ back to DrawTrackLine as they did for the lines above \ the dashboard .view2 DEX \ Decrement the dash data block pointer to point to \ the data for the next line LDY staDrawByte,X \ Set Y to the X-th entry in staDrawByte, which contains \ the low byte of the address of the STA (P),Y \ instruction in the DRAW_BYTE macro given in the \ table CPY view3+1 \ If the instruction at view3 has already been modified BEQ view5 \ to this address, jump to view5 to skip the following \ modifications, as they have already been done on the \ previous iteration of the loop LDA #&91 \ Set A to the opcode for the STA (P),Y instruction .view3 STA DrawTrackBytes+15 \ Modify the specified instruction back to STA (P),Y \ (the address of the modified instruction is set by the \ following, so the first time we run this line it has \ no effect) STY view4+1 \ Modify the instruction at view4 to change the low byte \ of the address to the X-th entry in staDrawByte, so \ the instruction at view4 changes the STA (P),Y \ instruction to an RTS in the DRAW_BYTE macro given \ in staDrawByte STY view3+1 \ Modify the instruction at view3 to change the low byte \ of the address to the X-th entry in staDrawByte, so \ the instruction at view3 changes the RTS instruction \ back to STA (P),Y when we loop back around LDA #&60 \ Set A to the opcode for the RTS instruction .view4 STA DrawTrackBytes \ Modify the specified instruction to an RTS so the next \ call to DrawTrackLine will return at that point (the \ address of the modified instruction is set above) LDY ldaDrawByte,X \ Set Y to the X-th entry in ldaDrawByte, which contains \ the low byte of the LDA #0 instruction in the specific \ DRAW_BYTE macro, as given in the table STY view6+1 \ Modify the instruction at view6 to change the low byte \ of the address to the X-th entry in ldaDrawByte, so \ the instruction at view6 changes so it jumps to the \ LDA #0 instruction in the DRAW_BYTE macro specified in \ the table .view5 JSR DrawTrackLine \ Draw the left portion of this track line \ \ This routine was modified above to return from the \ subroutine at the STA instruction in the DRAW_BYTE \ macro specified in the staDrawByte table, so this \ returns the last pixel byte of this portion of the \ line in A, i.e. the rightmost byte of the left portion \ of the track line, where the line meets the left \ border of the central part of the dashboard AND leftDashMask,X \ We now merge the track byte in A with the left edge ORA leftDashPixels,X \ of the dashboard, by masking out the pixels in A that STA (P),Y \ are hidden by the dashboard (with AND leftDashMask), \ and replacing them with the pixels from the left edge \ of the dashboard (with ORA leftDashPixels) LDA dashRightEdge,X \ Fetch the track pixel byte that would be shown along \ the right edge of the dashboard, i.e. the leftmost \ byte of the right portion of the track line, where the \ line meets the right border of the central part of the \ dashboard AND rightDashMask,X \ We now merge the track byte in A with the right edge ORA rightDashPixels,X \ of the dashboard, by masking out the pixels in A that \ are hidden by the dashboard (with AND rightDashMask), \ and replacing them with the pixels from the left edge \ of the dashboard (with ORA rightDashPixels) TAY \ Copy the pixel byte into Y, because the following JSR \ jumps straight to the LDA #0 instruction within the \ DRAW_BYTE macro, and at that point the macro expects \ the pixel byte to be in Y rather than A .view6 JSR byte2 \ Draw the right portion of this track line \ \ This JSR was modified above to jump to the LDA #0 \ instruction in the DRAW_BYTE macro specified in the \ ldaDrawByte table CPX #28 \ Loop back to keep drawing lines, working our way down BNE view2 \ through the dash data from entry 44 down to entry 28 JMP view7 \ Jump to part 3 to draw the rest of the track view from \ offsets 27 to 3, modifying the code so it draws the \ rest of the lines around the shape of the dashboard \ and the shape of the tyres
Name: DrawTrackBytes (Part 2 of 3) [Show more] Type: Subroutine Category: Screen buffer Summary: Draw the pixel bytes that make up the track view (16 to 39) Deep dive: Drawing around the dashboard Drawing the track view
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawTrackView (Part 2 of 4) calls via byte2 * DrawTrackView (Part 3 of 4) calls via byte2 * DrawTrackView (Part 4 of 4) calls via byte2

Note that the latter half of this routine, from .byte2 onwards, starts on a page boundary (byte2 = &7E00). This is important as it means the code can be modified at a specific address using only the low byte of that address, as we know that high byte is the same throughout the routine. This is why the lookup tables at staDrawByte and ldaDrawByte only need to store the low bytes of the addresses for instructions that we need to modify.
Other entry points: byte2 Only draw pixel bytes 26 to 39
.byte1 DRAW_BYTE 16 \ Draw pixel bytes 16 to 25 DRAW_BYTE 17 DRAW_BYTE 18 DRAW_BYTE 19 DRAW_BYTE 20 DRAW_BYTE 21 DRAW_BYTE 22 DRAW_BYTE 23 DRAW_BYTE 24 DRAW_BYTE 25 .byte2 DRAW_BYTE 26 \ Draw pixel bytes 26 to 39 DRAW_BYTE 27 DRAW_BYTE 28 DRAW_BYTE 29 DRAW_BYTE 30 DRAW_BYTE 31 DRAW_BYTE 32 DRAW_BYTE 33 DRAW_BYTE 34 DRAW_BYTE 35 DRAW_BYTE 36 DRAW_BYTE 37 DRAW_BYTE 38 DRAW_BYTE 39 .byte3 CPX #44 \ If X = 44, then we have just drawn the last pixel BEQ byte4 \ line above the top of the dashboard, so return from \ the subroutine so we can modify the routine to draw \ subsequent lines in two parts, to fit around the \ dashboard (as byte4 contains an RTS) DEX \ Decrement the dash data pointer in X to move on to the \ next pixel line \ Fall through into DrawTrackLine to draw the next line
Name: DrawTrackLine (Part 1 of 2) [Show more] Type: Subroutine Category: Screen buffer Summary: Draw a pixel line across the screen in the track view, broken up into bytes Deep dive: Drawing around the dashboard Drawing the track view
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawTrackView (Part 1 of 4) calls DrawTrackLine * DrawTrackView (Part 2 of 4) calls DrawTrackLine

Arguments: X The offset within the dash data of the data to be drawn on the screen (from &7F down, as the dash data lives at the end of each dash data block) (Q P) The screen address of the leftmost pixel of the line above where we want to draw the horizontal pixel line (S R) Contains (Q P) + &100
.DrawTrackLine \ We start by incrementing (Q P) and (S R) to point to \ the next pixel row down the screen LDY P \ Set Y = P + 1, which is the low byte of (Q P) + 1 INY TYA \ If Y mod 8 = 0 then incrementing (Q P) will take us AND #&07 \ into the next character block (i.e. from pixel row 7 BEQ prow1 \ to pixel row 8), so jump to prow1 to update the screen \ addresses accordingly STY P \ Otherwise set the low bytes of (Q P) and (S R) to Y, STY R \ so this does: \ \ (Q P) = (Q P) + 1 \ \ (S R) = (S R) + 1 \ \ so they point to the next pixel row down the screen JMP prow2 \ Jump to part 2 to draw this pixel row .prow1 TYA \ Set (Q P) = Y + &138 CLC \ ADC #&38 \ starting with the low bytes STA P STA R LDA Q \ And then the high bytes, so (Q P) points to the start ADC #&01 \ of the character block on the next character row STA Q \ (i.e. the next pixel row down) ADC #&01 \ Set (S R) = (Q P) + &100 STA S JMP prow2 \ Jump to part 2 to draw this pixel row
Name: DrawTrackBytes (Part 3 of 3) [Show more] Type: Subroutine Category: Screen buffer Summary: Return from the subroutine Deep dive: Drawing around the dashboard Drawing the track view
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.byte4 RTS \ Return from the subroutine
Name: DrawTrackView (Part 3 of 4) [Show more] Type: Subroutine Category: Screen buffer Summary: Draw the part of the track view that fits around the dashboard and tyres Deep dive: Drawing around the dashboard Drawing the track view
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

This routine modifies the DrawTrackBytes routine so that it draws all the remaining lines in the track view so they fit around the shape of the dashboard and the tyres.
\ We get here with X = 28, as in part 1 we drew the \ lines specified by dash data offsets 79 to 44, and in \ part 2 we drew the lines specified by dash data \ offsets 43 to 28 \ \ In the following loop, we draw the lines specified by \ dash data offsets 27 to 3 .view7 DEX \ Decrement the dash data block pointer to point to \ the data for the next line LDY staDrawByte,X \ Set Y to the X-th entry in staDrawByte, which contains \ the low byte of the address of the STA (P),Y \ instruction in the DRAW_BYTE macro given in the \ table CPY view8+1 \ If the instruction at view8 has already been modified BEQ view10 \ to this address, jump to view10 to skip the following \ modifications, as they have already been done on the \ previous iteration of the loop LDA #&91 \ Set A to the opcode for the STA (P),Y instruction .view8 STA DrawTrackBytes+15 \ Modify the specified instruction back to STA (P),Y \ (the address of the modified instruction is set by the \ following, so the first time we run this line it has \ no effect) STY view9+1 \ Modify the instruction at view9 to change the low byte \ of the address to the X-th entry in staDrawByte, so \ the instruction at view9 changes the STA (P),Y \ instruction to an RTS in the DRAW_BYTE macro given \ in staDrawByte STY view8+1 \ Modify the instruction at view8 to change the low byte \ of the address to the X-th entry in staDrawByte, so \ the instruction at view8 changes the RTS instruction \ back to STA (P),Y when we loop back around LDA #&60 \ Set A to the opcode for the RTS instruction .view9 STA DrawTrackBytes \ Modify the specified instruction to an RTS so the next \ call to DrawTrackLine will return at that point (the \ address of the modified instruction is set above) .view10 \ The following code is normally run at the start of the \ DrawTrackLine routine, but we are going to call the \ DrawTrackBytes routine to draw our line instead, so \ we can skip the bytes that are hidden behind the left \ tyre \ \ So we repeat the code here, which increments the \ screen addresses in (Q P) and (S R) to point to the \ next pixel row down the screen LDY P \ Set Y = P + 1, which is the low byte of (Q P) + 1 INY TYA \ If Y mod 8 <> 0 then incrementing (Q P) will keep us AND #&07 \ within the current character block (i.e. the new pixel BNE view11 \ row will be 7 or less), so jump to view11 to store the \ incremented values \ Otherwise incrementing (Q P) will take us into the \ next character block (i.e. from pixel row 7 to pixel \ row 8), so we need to update the screen addresses to \ jump to the next character row TYA \ Set (Q P) = Y + &138 CLC \ ADC #&38 \ starting with the low bytes STA P STA R LDA Q \ And then the high bytes, so (Q P) points to start of ADC #&01 \ the character block on the next character row (i.e. STA Q \ the next pixel row down) ADC #&01 \ Set (S R) = (Q P) + &100 STA S BCC view12 \ Jump to view12 to skip the following .view11 \ If we get here then incrementing the screen addresses \ keeps us within the current character block, so we can \ store the incremented addresses in (Q P) and (S R) STY P \ Set the low bytes of (Q P) and (S R) to Y, so this STY R \ does: \ \ (Q P) = (Q P) + 1 \ \ (S R) = (S R) + 1 \ \ so they point to the next pixel row down the screen .view12 \ The staDrawByteTyre 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 (see the code that modifies view14 and view15 \ below) \ \ As the tyres are reflections of each other, we can \ also use this value to calculate the starting point \ for the line that starts at the left tyre, which is \ what we do now LDA #LO(byte3)+3 \ Set A = LO(byte3) + 3 - staDrawByteTyre SEC \ SBC staDrawByteTyre,X \ There are 14 instances of the DRAW_BYTE macro between \ byte2 and byte3, ranging from DRAW_BYTE 26 up to \ DRAW_BYTE 39 \ \ This calculation converts the low address byte from \ the staDrawByteTyre table so that instead of pointing \ to the LDA #0 instruction in the n-th DRAW_BYTE macro, \ it points to the LDA #0 instruction in the 14-n-th \ macro, as an offset from byte2 \ \ This effectively takes the end point given in the \ staDrawByteTyre table and returns the start point if \ the range DRAW_BYTE 26 to DRAW_BYTE 39 were \ "reflected" into DRAW_BYTE 39 to DRAW_BYTE 26 \ \ This enables us to calculate the offset of the start \ point's macro for the left tyre, as an offset from \ DrawTrackBytes, which is what we want STA view13+1 \ Modify the instruction at view13 to change the low \ byte of the address to A, so the instruction at view13 \ changes so it jumps to the LDA #0 instruction in the \ DRAW_BYTE macro specified in the table LDY tyreEdgeIndex,X \ Set Y to the index of the mask and pixel bytes for the \ tyre edge for this track line, so we can use it below \ to fetch the correct entries from leftTyreMask and \ leftTyrePixels LDA tyreRightEdge,X \ Fetch the track pixel byte that would be shown along \ the right edge of the left tyre, i.e. the leftmost \ byte of the track line, where the line meets the left \ tyre AND leftTyreMask,Y \ We now merge the track byte in A with the edge of the ORA leftTyrePixels,Y \ left tyre, by masking out the pixels in A that are \ hidden by the tyre (with AND leftTyreMask), and \ replacing them with the pixels from the edge of the \ left tyre (with ORA leftTyrePixels) TAY \ Copy the pixel byte into Y, because the following JSR \ jumps straight to the LDA #0 instruction within the \ DRAW_BYTE macro, and at that point the macro expects \ the pixel byte to be in Y rather than A .view13 JSR DrawTrackBytes \ Draw the left portion of this track line \ \ This routine was modified above to return from the \ subroutine at the STA instruction in the DRAW_BYTE \ macro specified in the staDrawByte table, so this \ returns the last pixel byte of this portion of the \ line in A, i.e. the rightmost byte of the left portion \ of the track line, where the line meets the left \ border of the central part of the dashboard AND leftDashMask,X \ We now merge the track byte in A with the left edge ORA leftDashPixels,X \ of the dashboard, by masking out the pixels in A that \ are hidden by the dashboard (with AND leftDashMask), \ and replacing them with the pixels from the left edge \ of the dashboard (with ORA leftDashPixels) STA (P),Y \ Write the merged pixel byte into screen memory LDY staDrawByteTyre,X \ Set Y to the X-th entry in staDrawByteTyre, which \ contains the low byte of the address of the STA (P),Y \ instruction in the DRAW_BYTE macro given in the \ table CPY view14+1 \ If the instruction at view14 has already been modified BEQ view16 \ to this address, jump to view16 to skip the following \ modifications, as they have already been done on the \ previous iteration of the loop LDA #&91 \ Set A to the opcode for the STA (P),Y instruction .view14 STA byte2+15 \ Modify the specified instruction back to STA (P),Y \ (the address of the modified instruction is set by the \ following, so the first time we run this line it has \ no effect) STY view15+1 \ Modify the instruction at view15 to change the low \ byte of the address to the X-th entry in \ staDrawByteTyre, so the instruction at view15 changes \ the STA (P),Y instruction to an RTS in the DRAW_BYTE \ macro given in staDrawByteTyre STY view14+1 \ Modify the instruction at view14 to change the low \ byte of the address to the X-th entry in \ staDrawByteTyre, so the instruction at view14 changes \ the STA (P),Y instruction to an RTS in the DRAW_BYTE \ macro given in staDrawByteTyre LDA #&60 \ Set A to the opcode for the RTS instruction .view15 STA byte2 \ Modify the specified instruction to an RTS so the next \ call to byte2 will return at that point (the address \ of the modified instruction is set above) .view16 LDY ldaDrawByte,X \ Set Y to the X-th entry in ldaDrawByte, which contains \ the low byte of the LDA #0 instruction in the specific \ DRAW_BYTE macro, as given in the table STY view17+1 \ Modify the instruction at view17 to change the low \ byte of the address to the X-th entry in ldaDrawByte, \ so the instruction at view17 changes so it jumps to \ the LDA #0 instruction in the DRAW_BYTE macro \ specified in the table LDA dashRightEdge,X \ Fetch the track pixel byte that would be shown along \ the right edge of the dashboard, i.e. the leftmost \ byte of the right portion of the track line, where the \ line meets the right border of the central part of the \ dashboard AND rightDashMask,X \ We now merge the track byte in A with the right edge ORA rightDashPixels,X \ of the dashboard, by masking out the pixels in A that \ are hidden by the dashboard (with AND rightDashMask), \ and replacing them with the pixels from the left edge \ of the dashboard (with ORA rightDashPixels) TAY \ Copy the pixel byte into Y, because the following JSR \ jumps straight to the LDA #0 instruction within the \ DRAW_BYTE macro, and at that point the macro expects \ the pixel byte to be in Y rather than A .view17 JSR byte2 \ Draw the right portion of this track line \ \ This JSR was modified above to jump to the LDA #0 \ instruction in the DRAW_BYTE macro specified in the \ ldaDrawByte table STY U \ Store Y in U so we can retrieve it below LDY tyreEdgeIndex,X \ Set Y to the index of the mask and pixel bytes for the \ tyre edge for this track line, so we can use it to \ fetch the correct entries from rightTyreMask and \ rightTyrePixels AND rightTyreMask,Y \ We now merge the track byte in A with the edge of the ORA rightTyrePixels,Y \ right tyre, by masking out the pixels in A that are \ hidden by the tyre (with AND rightTyreMask), and \ replacing them with the pixels from the edge of the \ right tyre (with ORA rightTyrePixels) LDY U \ Retrieve the value of Y that we stored above STA (R),Y \ Write the merged pixel byte into screen memory, using \ (S R) as the screen address as this is at the right \ end of the track line CPX #3 \ If we just drew the line at dash data entry 3, jump BEQ view18 \ to view18 to stop drawing track lines JMP view7 \ Loop back to keep drawing lines, working our way down \ through the dash data from entry 27 down to entry 3 .view18 JMP view19 \ Jump to part 4 to reverse our code modifications
Name: DrawCarInMirror [Show more] Type: Subroutine Category: Dashboard Summary: Draw a car in a specified segment of one of the wing mirrors, or clear a specified segment Deep dive: Wing mirrors
Context: See this subroutine on its own page References: This subroutine is called as follows: * UpdateMirrors calls DrawCarInMirror

Arguments: Y Mirror segment (0 to 5) * 0 = left mirror, outer segment * 1 = left mirror, middle segment * 2 = left mirror, inner segment * 3 = right mirror, inner segment * 4 = right mirror, middle segment * 5 = right mirror, outer segment N Start offset within the segment for the car lines A End offset within the segment for the car lines (or 0 to clear the mirror segment)
Returns: Y Y is unchanged
.DrawCarInMirror STA RR \ Store A in RR STY G \ Store Y in G so we can retrieve it before returning \ from the subroutine LDA mirrorAddressHi,Y \ Set (Q P) to the base screen address of this mirror STA Q \ segment (to which we add the following offsets to get LDA mirrorAddressLo,Y \ the screen address for this particular segment) STA P LDA startMirror,Y \ Set W to the offset of the first pixel byte in this STA W \ mirror segment LDA endMirror,Y \ Set Y to the offset of the first pixel byte in this TAY \ mirror segment .mirr1 \ We now work our way through the mirror segment pixel \ bytes, going backwards from the end byte to the start \ byte, either removing the car or drawing the car with \ added random blurriness LDA #%11110000 \ Set A to the pixel byte containing four pixels of \ colour 2 (white) CPY RR \ If Y >= RR, jump to mirr2 to draw a white pixel byte BCS mirr2 CPY N \ If Y < N, jump to mirr2 to draw a white pixel byte BCC mirr2 \ If we get here then N <= Y < RR, so we draw a pixel \ byte of black pixels to represent the car, with the \ pixels randomised but tending to black when the \ engine is on (if the engine is off, then all the \ pixels are black, so this part simulates the mirror \ shuddering when the engine is on) LDX 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 &2000,X \ There is game code at location &2000, so this randomly \ switches some of the white pixels (colour 2) to black \ (colour 0) in the pixel byte in A AND engineStatus \ If our engine is off, then engineStatus is zero and \ all the pixels are set to black, but if the engine is \ on, engineStatus is &FF so this instruction has no \ effect, leaving the image randomised .mirr2 STA (P),Y \ Draw the pixel byte in A at screen address (Q P) + Y DEY \ Decrement Y to point to the pixel byte above BPL mirr3 \ If bit 7 of Y is clear, jump to mirr3 to move on to \ the next pixel byte TYA \ If Y mod 8 < 7 then jump to mirr3 to move on to the AND #7 \ next pixel byte CMP #7 BCC mirr3 \ If we get here then Y has bit 7 set and Y mod 8 = 7, \ so we have gone past the top of the current character \ row and need to move up to the last pixel row in the \ character block above row instead \ \ There are &140 bytes per row, and we want to move from \ the top of the character block on this row to the \ bottom of the character block on the row above, so we \ subtract &140 to go up to the top of the character \ block on the row above, and then add 8 to jump down to \ the bottom row, i.e. we subtract &140 - 8 = &138 LDA P \ Set (Q P) = (Q P) - &138 SEC \ SBC #&38 \ starting with the low bytes STA P LDA Q \ And then the high bytes, so (Q P) points to the end of SBC #&01 \ the character block on the previous character row STA Q \ (i.e. the next pixel row up) .mirr3 CPY W \ Loop back to draw the next pixel byte, until Y < W BCS mirr1 LDY G \ Retrieve the value of Y that we stored in G, so that \ Y is preserved through the call to the routine RTS \ Return from the subroutine EQUB &FF \ This byte appears to be unused
Save Revs.bin For an explanation of the following, see the deep dive on "The jigsaw puzzle binary"
ORG &9000 INCBIN "1-source-files/images/dashboard.bin" COPYBLOCK &9EF6, &9EF6+10, dashData25 \ Step 1: Insert the dashboard image COPYBLOCK &9ECD, &9EF6, dashData26 \ into the game code, split into 18 COPYBLOCK &9E99, &9ECD, dashData27 \ pieces COPYBLOCK &9E61, &9E99, dashData28 COPYBLOCK &9E25, &9E61, dashData29 COPYBLOCK &9DE5, &9E25, dashData30 COPYBLOCK &9DA1, &9DE5, dashData31 COPYBLOCK &9D58, &9DA1, dashData32 COPYBLOCK &9D0B, &9D58, dashData33 COPYBLOCK &9CBE, &9D0B, dashData34 COPYBLOCK &9C72, &9CBE, dashData35 COPYBLOCK &9C38, &9C72, dashData36 COPYBLOCK &9C04, &9C38, dashData37 COPYBLOCK &9BD0, &9C04, dashData38 COPYBLOCK &9B9C, &9BD0, dashData39 COPYBLOCK &9B68, &9B9C, dashData40 COPYBLOCK &9B25, &9B68, dashData41 COPYBLOCK &9000, &9B25, dashData42 COPYBLOCK &7FCC, &8000, dashData0 \ Step 2: Insert the code that runs COPYBLOCK &7F98, &7FCC, dashData1 \ in screen memory into the game COPYBLOCK &7F64, &7F98, dashData2 \ code, split into 26 pieces COPYBLOCK &7F2A, &7F64, dashData3 COPYBLOCK &7EDE, &7F2A, dashData4 COPYBLOCK &7E91, &7EDE, dashData5 COPYBLOCK &7E44, &7E91, dashData6 COPYBLOCK &7DFB, &7E44, dashData7 COPYBLOCK &7DB7, &7DFB, dashData8 COPYBLOCK &7D77, &7DB7, dashData9 COPYBLOCK &7D3B, &7D77, dashData10 COPYBLOCK &7D03, &7D3B, dashData11 COPYBLOCK &7CCF, &7D03, dashData12 COPYBLOCK &7CA6, &7CCF, dashData13 COPYBLOCK &7C82, &7CA6, dashData14 COPYBLOCK &7C5E, &7C82, dashData15 COPYBLOCK &7C3A, &7C5E, dashData16 COPYBLOCK &7C16, &7C3A, dashData17 COPYBLOCK &7BF2, &7C16, dashData18 COPYBLOCK &7BCE, &7BF2, dashData19 COPYBLOCK &7BAA, &7BCE, dashData20 COPYBLOCK &7B86, &7BAA, dashData21 COPYBLOCK &7B62, &7B86, dashData22 COPYBLOCK &7B3E, &7B62, dashData23 COPYBLOCK &7B1A, &7B3E, dashData24 COPYBLOCK &7AF6+10, &7B1A, dashData25+10 COPYBLOCK &5FD0, &6700, &64D0 \ 3: Split the game code into the COPYBLOCK &0D00, &16DC, &5A80 \ parts that make up the game binary COPYBLOCK &7000, &70DB, &1500 \ file and pack them together in the COPYBLOCK &70DB, &7725, &5300 \ correct order COPYBLOCK &0B00, &0D00, &1300 COPYBLOCK &7900, &7A00, &1200 CLEAR &645C, &64D0 ORG &15DB CLEAR &15DB, &16DC EQUB &20, &00 \ 4: Add workspace noise to match EQUB &63, &60 \ the final game binary EQUB &A6, &03 EQUB &10, &03 EQUB &20, &CB EQUB &2A, &20 EQUB &84, &50 EQUB &E4, &4D EQUB &D0, &F6 EQUB &A2, &16 EQUB &86, &45 EQUB &20, &D1 EQUB &2A, &CA EQUB &E0, &14 EQUB &B0, &F6 EQUB &A6, &4D EQUB &20, &CB EQUB &2A, &60 EQUB &20, &0E EQUB &2B, &A2 EQUB &F4, &20 EQUB &CC, &0B EQUB &20, &0E EQUB &2B, &A2 EQUB &FD, &20 EQUB &CC, &0B EQUB &A9, &14 EQUB &85, &42 EQUB &A9, &02 EQUB &20, &5D EQUB &2A, &A9 EQUB &15, &85 EQUB &42, &A9 EQUB &01, &A2 EQUB &F4, &20 EQUB &5F, &2A EQUB &A9, &16 EQUB &85, &42 EQUB &A9, &00 EQUB &A2, &FA EQUB &20, &5F EQUB &2A, &A6 EQUB &45, &60 EQUB &C9, &05 EQUB &90, &F9 EQUB &BD, &8C EQUB &01, &30 EQUB &F4, &FE EQUB &8C, &01 EQUB &60, &A2 EQUB &FD, &85 EQUB &37, &20 EQUB &45, &21 EQUB &A4, &42 EQUB &A5, &8A EQUB &99, &80 EQUB &03, &A5 EQUB &8B, &99 EQUB &98, &03 EQUB &20, &B1 EQUB &2A, &20 EQUB &85, &22 EQUB &A4, &42 EQUB &B0, &2C EQUB &38, &E9 EQUB &01, &30 EQUB &27, &99 EQUB &B0, &03 EQUB &A5, &2B EQUB &38, &E9 EQUB &09, &AA EQUB &A5, &2A EQUB &CA, &F0 EQUB &0C, &10 EQUB &06, &4A EQUB &E8, &D0 EQUB &FC, &F0 EQUB &04, &0A EQUB &CA, &D0 EQUB &FC, &99 EQUB &C8, &03 EQUB &B9, &8C EQUB &01, &29 EQUB &70, &05 EQUB &37, &4C EQUB &AD, &2A EQUB &A4, &42 EQUB &B9, &8C EQUB &01, &09 EQUB &80, &99 EQUB &8C, &01 EQUB &60, &A0 EQUB &25, &20 EQUB &A5, &0C EQUB &A5, &7D EQUB &85, &55 EQUB &D0, &0E EQUB &C4, &7C EQUB &90, &0A EQUB &C6, &68 EQUB &A5, &7C EQUB &85, &41 EQUB &A5, &42 EQUB &85, &67 EQUB &60, &86 EQUB &45, &BD EQUB &3C, &01 EQUB &AA, &BD EQUB &8C, &01 EQUB &30, &35 EQUB &29, &0F EQUB &85, &37 EQUB &BD, &80 EQUB &03, &38 EQUB &E5, &0A EQUB &85, &74 EQUB &BD, &98 EQUB &03, &E5 EQUB &0B, &10 EQUB &06, &C9 EQUB &E0, &90 EQUB &1E, &B0 EQUB &04, &C9 EQUB &20, &B0 EQUB &18, &06 EQUB &74, &2A EQUB &06, &74 EQUB &2A, &18 EQUB &69 SAVE "3-assembled-output/Revs2.bin", LOAD%, LOAD_END%