Skip to navigation

Revs on the BBC Micro

Revs B source

Name: MainDrivingLoop (Part 1 of 5) [Show more] Type: Subroutine Category: Main loop Summary: Main driving loop: Switch to the track and start the main loop Deep dive: Program flow of the main game loop
Context: See this subroutine on its own page References: This subroutine is called as follows: * HeadToTrack calls MainDrivingLoop

Returns: configStop Contains details of why we are exiting the driving loop: * Bit 5 set = retire from race/lap (SHIFT-f7 pressed) * Bit 7 and bit 6 set = pit stop (SHIFT-f0 pressed) * Bit 7 set and bit 6 clear = restart game (SHIFT and right arrow pressed)
.MainDrivingLoop JSR SetCustomScreen \ Switch to the custom screen mode, which also sets \ screenSection to -2, so the interrupt handler doesn't \ do any palette switching just yet, but leaves the \ palette mapped to black, so the screen is blank LDA #0 \ Set printMode = 0 so text printing pokes directly into STA printMode \ screen memory JSR CopyDashData \ Copy the dash data from the main game code to screen \ memory JSR DrawTrackView \ Copy the data from the dash data blocks to the screen \ to draw the track view \ \ As the screen is currently mapped to black, this \ doesn't show anything, but it does zero all the dash \ data blocks, so they are ready to be filled with the \ track view BIT configStop \ If bit 6 of configStop is set then we are returning to BVS main4 \ the track after visiting the pits, so jump to main4 \ to reset the pit stop flag and enter the driving loop .main1 \ We jump back here when restarting practice laps LDX #0 \ Zero the clock timer, as there is no time limit on the JSR ZeroTimer \ practice session .main2 \ We jump back here when restarting qualifying laps JSR ResetVariables \ Reset a number of variables for driving, and print the \ top two text lines .main3 \ We jump back here when restarting a Novice race JSR BuildPlayerCar \ Build the objects for the player's car .main4 LDA #0 \ Set configStop = 0 so we clear out any existing STA configStop \ stop-related key presses JSR ScaleWingSettings \ Scale the wing settings and calculate the wing balance \ for use in the driving model
Name: MainDrivingLoop (Part 2 of 5) [Show more] Type: Subroutine Category: Main loop Summary: Main driving loop: The body of the main loop 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
.main5 \ The main driving loop starts here, and we loop back to \ here from part 5 JSR ProcessTime \ Increment the timers and the main loop counter, and \ set the speed for the next non-player drivers JSR ShowStartingLights \ If this is a race, show the starting lights on the \ right of the screen JSR ProcessDrivingKeys \ Check for and process the main driving keys JSR ApplyDrivingModel \ Apply the driving model to the player's car JSR GetTrackAndMarkers \ Calculate the coordinates for the track sides and \ corner markers JSR MovePlayerOnTrack \ Update the position of the player's car within the \ current track segment JSR MovePlayerSegment \ Move the player's car into the correct segment JSR UpdateLapTimers \ Update the lap timers and display timer-related \ messages at the top of the screen JSR MakeDrivingSounds \ Make the relevant sounds for the engine and tyres JSR ResetTrackLines \ Reset the track lines below the horizon in the track \ view JSR DrawTrack \ Draw the track into the screen buffer JSR MakeDrivingSounds \ Make the relevant sounds for the engine and tyres JSR SetBackground \ Set the background colour for any track lines in the \ track view that do not currently have a background set JSR BuildRoadSign \ Build the road sign (if one is visible) LDX #23 \ Draw the road sign we just built JSR DrawCarOrSign JSR DrawCornerMarkers \ Draw any visible corner markers JSR MoveAndDrawCars \ Move the cars around the track and draw any that are \ visible, up to a maximum of five JSR CopyTyreDashEdges \ Copy the pixels from the edges of the left tyre and \ right dashboard so they can be used when drawing the \ track view around the tyres and dashboard, and fill \ the blocks to the right of the edges with the \ appropriate content JSR UpdateMirrors \ Update the view in the wing mirrors JSR MakeDrivingSounds \ Make the relevant sounds for the engine and tyres JSR MoveHorizon \ Move the position of the horizon palette switch up or \ down, depending on the current track pitch angle JSR ProcessContact \ Process any car-on-car contact, if there has been any JSR CheckForCrash \ Check to see if we have crashed into the fence, and if \ so, display the fence, make the crash sound and set \ crashedIntoFence = &FF JSR DrawTrackView \ Copy the data from the dash data blocks to the screen \ to draw the track view, fitting it around the tyres \ and dashboard
Name: MainDrivingLoop (Part 3 of 5) [Show more] Type: Subroutine Category: Main loop Summary: Main driving loop: Process rejoining the race or lap after a crash 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
LDA screenSection \ If screenSection is positive, jump to main6 to skip BPL main6 \ the following instruction INC screenSection \ If screenSection is negative, then we increment it \ \ This kickstarts the custom screen interrupt handler \ on the first time round the main driving loop, as the \ call to SetCustomScreen at the start of the routine \ sets screenSection to -2, and this increment bumps it \ up to -1, which makes the screen handler start \ applying the custom screen effect \ \ In other words, this displays the driving screen for \ the first time, after waiting for all the drawing \ routines in part 2 to finish, so we don't get to see \ the screen being drawn .main6 LDA crashedIntoFence \ If crashedIntoFence = 0 then we have not crashed into BEQ main10 \ the fence, so jump to main10 to continue with the main \ driving loop in part 5 INC crashedIntoFence \ Otherwise crashedIntoFence must be &FF, which means we \ have crashed into the fence, so increment it back to \ zero, to clear the "we have crashed" flag \ We now pause for a few seconds, before jumping back to \ the relevant starting point for the loop LDA #156 \ Set irqCounter = 156 STA irqCounter .main7 LDA irqCounter \ Fetch irqCounter, which gets incremented every time \ the IRQ routine reaches section 4 of the custom screen BMI main7 \ Loop back to main7 until irqCounter increments round \ to zero (so we wait for it to go from 156 to 0, which \ takes around three seconds at 50 ticks per second) .main8 LDA qualifyingTime \ If bit 7 of qualifyingTime is set then this is a BMI main1 \ practice lap (i.e. qualifyingTime = 255), so jump back \ to main1 LDA raceStarted \ If bit 7 of raceStarted is clear then this is either BPL main2 \ a practice or qualifying lap, but we didn't just jump \ to main1, so this must be qualifying, so jump back to \ main2 LDA raceClass \ If raceClass = 0 then this is a Novice race, so jump BEQ main3 \ back to main3 \ Otherwise this is an Amateur or a Professional race, \ and not a Novice race, practice or a qualifying lap
Name: MainDrivingLoop (Part 4 of 5) [Show more] Type: Subroutine Category: Main loop Summary: Main driving loop: Leave the track 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
.main9 \ If we get here then either: \ \ * We have quit the race or lap by pressing SHIFT-f7 \ (in which case we jumped here from part 5) \ \ * This is either an Amateur or a Professional race \ and we crashed (in which case we fell through from \ part 3) \ \ * leaveTrackTimer = 1 (in which case we jumped here \ from part 5 after the leave track timer ran down) \ \ In all cases, we are done racing and need to leave \ the track JSR FlushSoundBuffers \ Flush all four sound channel buffers LDA qualifyingTime \ If bit 7 of qualifyingTime is set then this is a BMI main8 \ practice lap (i.e. qualifyingTime = 255), so jump to \ main1 via main8, so we start a new practice lap LDX #48 \ Blank out the first text line at the top of the screen JSR PrintSecondLineGap \ and print token 48 on the second line, to give: \ \ " " \ " PLEASE WAIT " JSR FinishRace \ Continue running the race until all the non-player \ drivers have finished and we have a result LDA configStop \ If bit 7 of configStop is set then we must be pressing BMI main13 \ either SHIFT-f0 for a pit stop or SHIFT and right \ arrow to restart the game, so jump to main13 to leave \ the track LDA #%00100000 \ Set bit 5 of configStop to indicate that we have STA configStop \ retired from the race (so we leave the track \ permanently rather than just visiting the pits) BNE main13 \ Jump to main13 to leave the track (this BNE is \ effectively a JMP as A is never zero)
Name: MainDrivingLoop (Part 5 of 5) [Show more] Type: Subroutine Category: Main loop Summary: Main driving loop: Process driving keys, potentially leaving the track, and loop back to part 2 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
.main10 IF _ACORNSOFT OR _4TRACKS LDY #9 \ Check for all the shifted keys (i.e. those that need JSR ProcessShiftedKeys \ SHIFT holding down to trigger) and process them \ accordingly ELIF _SUPERIOR OR _REVSPLUS LDY #11 \ Check for all the shifted keys (i.e. those that need JSR ProcessShiftedKeys \ SHIFT holding down to trigger) and process them \ accordingly ENDIF LDA configStop \ If configStop = 0, then we aren't pressing one of the BEQ main11 \ keys that stops the race, so jump to main11 to keep \ iterating round the main driving loop BPL main9 \ If bit 7 of configStop is clear then we must be \ pressing SHIFT-f7 to retire from the race, so jump to \ main9 to leave the track AND #%01000000 \ If bit 6 of configStop is clear (and we know bit 7 is BEQ main13 \ set), then we must be pressing SHIFT and right arrow, \ so jump to main13 to leave the track and restart the \ game \ If we get here then we know both bit 6 and bit 7 must \ be set, so we must be pressing SHIFT-f0 to return to \ the pits LDA playerMoving \ If playerMoving = 0 then the player's car is BEQ main13 \ stationary, so jump to main13 to leave the track and \ return to the pits LDA #0 \ We can't enter the pits if the car is moving, so set STA configStop \ configStop = 0 so we clear out the SHIFT-f4 key press .main11 LDX leaveTrackTimer \ Set X to the current value of the leave track timer BEQ main12 \ If X = 0 then the leave track timer is not running, so \ jump to main12 to continue with the main driving loop DEX \ The leave track timer is running, so decrement the \ timer value in X BEQ main9 \ If X = 0 then the leave track timer was 1 before the \ decrement and has now run down, so jump to main9 to \ leave the track STX leaveTrackTimer \ Store the decremented leave track timer so that it \ continues counting down towards 1 .main12 JSR MakeDrivingSounds \ Make the relevant sounds for the engine and tyres JSR UpdateDashboard \ Update the rev counter on the dashboard JMP main5 \ Loop back to main5 to repeat the main driving loop .main13 \ If we get here then we leave the track and switch back \ to mode 7, either to visit the pits or because the \ driving is done LDA #%10000000 \ Copy the dash data from screen memory back to the main JSR CopyDashData \ game code JSR KillCustomScreen \ Disable the custom screen mode and switch to mode 7 RTS \ Return from the subroutine
Name: AddTimeToTimer [Show more] Type: Subroutine Category: Drivers Summary: Add time to the specified timer Deep dive: Scheduling tasks in the main loop
Context: See this subroutine on its own page References: This subroutine is called as follows: * ProcessTime calls AddTimeToTimer * UpdateLapTimers calls AddTimeToTimer

Arguments: X The timer to increment: * 0 = the clock timer (clockMinutes clockSeconds clockTenths) * 1 = the lap timer (lapMinutes lapSeconds lapTenths) Returns: C flag Denotes whether the number of seconds has changed: * Set if the time just ticked on to the next second * Clear if the time is unchanged
.AddTimeToTimer SED \ Set the D flag to switch arithmetic to Binary Coded \ Decimal (BCD) LDA #&09 \ Set A = &09, so we add 9/100 of a second below LDY timerAdjust \ If timerAdjust <> trackTimerAdjust (which is 24 for CPY trackTimerAdjust \ the Silverstone track), jump to time1 to skip the BNE time1 \ following \ If we get here then timerAdjust = trackTimerAdjust, so \ we need to apply the clock adjustment, as this gets \ applied every trackTimerAdjust iterations around the \ main driving loop \ \ The clock adjustment speeds the clock up by advancing \ the clock twice as fast as usual, for this tick only LDA #&18 \ Set A = &18, so we add 18/100 of a second below .time1 CLC \ Add A to the tenths for the timer ADC clockTenths,X \ STA clockTenths,X \ starting with the tenths of a second PHP \ Store the C flag on the stack, so we can return it \ from the subroutine below (the C flag will be set \ if the lap time just ticked on to the next second) LDA clockSeconds,X \ Then we add the seconds into A ADC #0 CMP #&60 \ If A < &60, then the number of seconds is still valid, BCC time2 \ so jump to time2 to skip the following instruction LDA #0 \ Otherwise set A = 0, so 60 seconds on the timer \ increments back round to 0 seconds .time2 STA clockSeconds,X \ Update the seconds value for the timer LDA clockMinutes,X \ Finally, we add the minutes ADC #0 STA clockMinutes,X BPL time3 \ If the updates minutes value for the timer is \ positive, jump to time3 to skip the following JSR ZeroTimer \ Otherwise the timer just reached the maximum possible \ value, so wrap it back round to zero LDY currentPlayer \ Set the total race time for the current player to -1, LDA #&80 \ as otherwise the driver might finish with a very low STA totalRaceMinutes,Y \ time just because the race timer looped back to zero .time3 PLP \ Retrieve the C flag from the stack, which will be set \ if the addition of tenths overflowed (in other words, \ if the lap time just ticked on to the next second) CLD \ Clear the D flag to switch arithmetic to normal RTS \ Return from the subroutine
Name: PrintSecondLineGap [Show more] Type: Subroutine Category: Text Summary: Prints a text token on the second text line at the top of the driving screen, with an empty gap on the line above
Context: See this subroutine on its own page References: This subroutine is called as follows: * MainDrivingLoop (Part 4 of 5) calls PrintSecondLineGap * ResetVariables calls PrintSecondLineGap * UpdateLapTimers calls PrintSecondLineGap

Arguments: X The token number (0 to 54) to print on the second text line at the top of the screen
.PrintSecondLineGap JSR PrintSecondLine \ Print token X on the second text line at the top of \ the screen LDX #45 \ Print token 45 (38 spaces) on the first text line of JSR PrintFirstLine \ at the top of the screen, which blanks the top line RTS \ Return from the subroutine
Name: ResetVariables [Show more] Type: Subroutine Category: Main Loop Summary: Reset a number of variables for driving, and print the top two text lines
Context: See this subroutine on its own page References: This subroutine is called as follows: * MainDrivingLoop (Part 1 of 5) calls ResetVariables
.ResetVariables LDA #0 \ Set A = 0, so we can use it to reset variables to zero \ in the following loops LDX #&68 \ We start by zeroing all zero-page variables from \ playerMoving to processContact, so set up a loop \ counter in X .rese1 STA playerMoving,X \ Zero the X-th byte from playerMoving DEX \ Decrement the loop counter BPL rese1 \ Loop back until we have zeroed all variables from \ playerMoving to processContact LDX #&7F \ We now zero all variables from xPlayerCoordHi to \ zTyreForceBoth, so set up a loop counter in X .rese2 STA xPlayerCoordHi,X \ Zero the X-th byte from xPlayerCoordHi DEX \ Decrement the loop counter BPL rese2 \ Loop back until we have zeroed all variables from \ xPlayerCoordHi to zTyreForceBoth JSR DefineEnvelope \ Define the first (and only) sound envelope LDX #23 \ We now zero the 24-byte blocks at objTrackSection and \ objSectionSegmt, and initialise all 24 bytes in \ (objectSegmentHi objectSegmentLo), so set up a loop \ counter in X STX previousSignNumber \ Set previousSignNumber = 23 .rese3 LDA trackStartLine+1 \ Set the X-th byte of (objectSegmentHi objectSegmentLo) STA objectSegmentHi,X \ to the value of trackStartLine, which is 843 for the LDA trackStartLine \ Silverstone track and contains the segment number of STA objectSegmentLo,X \ the starting line, expressed as the number of segments \ between the starting line and the start of section 0, \ counting forwards round the track LDA #0 \ Zero the X-th byte of objTrackSection STA objTrackSection,X STA objSectionSegmt,X \ Zero the X-th byte of objSectionSegmt DEX \ Decrement the loop counter BPL rese3 \ Loop back until we have zeroed or copied all 24 \ variable bytes JSR SetPlayerPositions \ Set the current player's position in currentPosition, \ plus the number of the position ahead in positionAhead \ and number of the position behind in positionBehind LDA #1 \ Set A = 1, to pass to PlaceCarsOnTrack as the spacing \ for the cars when this is a race BIT raceStarted \ If bit 7 of raceStarted is set then this is a race BMI rese4 \ rather than practice or qualifying, so jump to rese4 \ to skip the following \ This is a practice or qualifying lap, so we set the \ player's position as specified in the track data LDX trackStartPosition \ Set X to trackStartPosition, the starting position for \ practice or qualifying laps, which is 4 for the \ Silverstone track LDY currentPosition \ Set Y to the current player's position JSR SwapDriverPosition \ Swap the positions of drivers in positions X and Y in \ the driversInOrder table, so for Silverstone this sets \ the current player's position to 4 (i.e. fifth place) JSR SetPlayerPositions \ Set the current player's position in currentPosition, \ plus the number of the position ahead in positionAhead \ and number of the position behind in positionBehind LDA trackCarSpacing \ Set A to trackCarSpacing, to pass to PlaceCarsOnTrack \ as the spacing for the cars when this is a qualifying \ lap (this value is 40 for the Silverstone track) .rese4 \ By this point, A = 1 if this is a race, or the value \ of trackCarSpacing if this is practice or qualifying \ (40 for Silverstone) JSR PlaceCarsOnTrack \ Reset the cars on the track, placing them on the \ starting grid if this is a race, or spread out along \ the track if this is a qualifying lap LDX #19 \ We now zero the 20-byte blocks at driverLapNumber, \ carSteering, carProgress, (carSpeedHi carSpeedLo) and \ carStatus, and initialise the 20-byte blocks at \ objectStatus, totalRaceMinutes and carSectionSpeed, \ so set up a loop counter in X .rese5 LDA #%10000000 \ Set the X-th byte of objectStatus to %10000000, so the STA objectStatus,X \ object is hidden STA totalRaceMinutes,X \ Set the X-th byte of totalRaceMinutes to -1 LDA #0 \ Zero the X-th byte of driverLapNumber STA driverLapNumber,X STA carSteering,X \ Zero the X-th byte of carSteering STA carProgress,X \ Zero the X-th byte of carProgress STA carSpeedHi,X \ Zero the X-th byte of carSpeedHi STA carStatus,X \ Zero the X-th byte of carStatus STA carSpeedLo,X \ Zero the X-th byte of carSpeedLo LDA #255 \ Set the X-th byte of carSectionSpeed to 255 STA carSectionSpeed,X DEX \ Decrement the loop counter BPL rese5 \ Loop back until we have zeroed or initialised all 20 \ bytes in each block LDA #1 \ Set gearNumber = 1, to set the gears to neutral STA gearNumber STA pastHalfway \ Set pastHalfway = 1, so the player is in the second \ half of the track (which is true for both practice \ and qualifying, as well as the starting grid) LDX #7 \ Set oddsOfEngineStart = 7 STX oddsOfEngineStart DEX \ Set sectionListValid = 6 STX sectionListValid STX sectionListPointer \ Set sectionListPointer = 6 DEX \ We now zero the six bytes at mirrorContents to clear \ all six segments of the wing mirrors, so set X = 5 to \ use as a loop counter .rese6 STA mirrorContents,X \ Clear the contents of the X-th wing mirror segment DEX \ Decrement the loop counter BPL rese6 \ Loop back until we have zeroed all six mirror segments JSR PrintGearNumber \ Print the new gear number on the gear stick (neutral) LDA raceStarted \ If bit 7 of raceStarted is set then this is a race BMI rese7 \ rather than practice or qualifying, so jump to rese7 \ with bit 7 of A set and bit 6 of A clear, to print the \ race header at the top of the screen LDX #40 \ Blank out the first text line at the top of the screen JSR PrintSecondLineGap \ and print token 40 on the second line, to give: \ \ " " \ "Lap Time : Best Time " LDX #1 \ Zero the lap timer JSR ZeroTimer JSR PrintBestLapTime \ Print the best lap time and the current lap time at \ the top of the screen LDA #&DF \ Set firstLapStarted = -33 STA firstLapStarted RTS \ Return from the subroutine .rese7 STA updateDrivingInfo \ Set bit 7 and clear bit 6 of updateDrivingInfo so the \ lap number gets printed at the top of the screen STA updateDriverInfo \ Set bit 7 of updateDriverInfo so the driver names get \ printed at the top of the screen LDX #43 \ Print token 43 on the first text line at the top of JSR PrintFirstLine \ the screen and token 44 on the second line, to give: LDX #44 \ JSR PrintSecondLine \ "Position In front: " \ "Laps to go Behind: " \ \ Token 44 includes five extra spaces at the end, though \ I'm not sure why LDA currentPosition \ Set A to the current player's position JSR ConvertNumberToBCD \ Convert the number in A into binary coded decimal \ (BCD), adding 1 in the process STA positionChangeBCD \ Set positionChangeBCD to the current player's position \ in BCD RTS \ Return from the subroutine
Name: SetBackground [Show more] Type: Subroutine Category: Screen buffer Summary: Set the background colour for any track lines that have not yet had a background colour set Deep dive: Drawing the track view
Context: See this subroutine on its own page References: This subroutine is called as follows: * MainDrivingLoop (Part 2 of 5) calls SetBackground
.SetBackground \ We start by setting the colour of the track line that \ contains the horizon LDX horizonListIndex \ Set X = horizonListIndex \ \ So X is the index in the track verge buffer for the \ horizon line's right verge LDY horizonLine \ Set Y to the track line number of the horizon LDA xVergeLeftHi,X \ Set A = X-th entry in xVergeLeftHi + 20 CLC ADC #20 BPL bgnd1 \ If A is positive, then the left edge of the verge is \ on-screen, so jump to bgnd1 to set the lines below \ the horizon to the colour of grass LDA xVergeRightHi,X \ Set A = X-th entry in xVergeRightHi + 20 CLC ADC #20 BMI bgnd1 \ If A is negative, then the right edge of the verge is \ on-screen, jump to bgnd1 to set the lines below \ the horizon to the colour of grass \ If we get to this point, then: \ \ * xVergeRightHi + 20 is positive \ \ * xVergeLeftHi + 20 is negative \ \ Adding 20 degrees to the yaw angles will move them to \ the right by half the screen width, so this is the \ same as moving the angles from the left edge of the \ screen to the middle \ \ We then check whether moving the angles to the centre \ pushes the rightmost verge edge in A past the centre \ (i.e. positive), while still leaving the leftmost edge \ in the left half (i.e. negative) \ \ If so, then this means the verge at the horizon is \ straddling the left edge of the screen, so we need to \ set the background colour for the horizon track line \ to black LDA #%00100000 \ Set A = %00100000 (colour 0, black) for the horizon \ track line, where: \ \ * %00 in bits 0-1 is logical colour 0 \ \ * %001xx0xx denotes that this value was stored in \ the backgroundColour table by the SetBackground \ routine BNE bgnd2 \ Jump to bgnd2 (this BNE is effectively a JMP as A is \ never zero) .bgnd1 LDA #%00100011 \ Set A = %00100011 (colour 3, green) for the horizon \ track line, where: \ \ * %11 in bits 0-1 is logical colour 3 \ \ * %001xx0xx denotes that this value was stored in \ the backgroundColour table by the SetBackground \ routine .bgnd2 STA backgroundColour,Y \ Set the colour of the horizon line to A \ We now work our way through the whole backgroundColour \ table, filling in any entries that are zero (and which \ have therefore not been set yet) with blue LDA #%00100001 \ Set A = %00100001 (colour 1, blue) to use as the line \ colour for lines above the horizon, i.e. the sky, \ where: \ \ * %01 in bits 0-1 is logical colour 1 \ \ * %001xx0xx denotes that this value was stored in \ the backgroundColour table by the SetBackground \ routine LDY #79 \ We now loop through all the track lines, starting from \ line 79 at the top of the track view down to 0 at the \ bottom, so set a counter in Y \ \ If any values are zero, then we set them to blue .bgnd3 LDX backgroundColour,Y \ Set X to the background colour for track line Y BEQ bgnd4 \ If X is zero, then this track line doesn't currently \ have a background colour set, so jump to bgnd4 to set \ the track line to blue TXA \ If we get here then X is non-zero, so there is already \ a backgroundColour value for this track line, so copy \ this value to A so the following STA instruction \ doesn't change anything .bgnd4 STA backgroundColour,Y \ Set the background colour for track line Y to A DEY \ Decrement the loop counter to move down to the next \ track line BPL bgnd3 \ Loop back until we have set the line colour for all \ 80 track lines RTS \ Return from the subroutine
Name: CopyDashData [Show more] Type: Subroutine Category: Screen buffer Summary: Copy the dash data from the main game code to screen memory, and vice versa Deep dive: The jigsaw puzzle binary
Context: See this subroutine on its own page References: This subroutine is called as follows: * MainDrivingLoop (Part 1 of 5) calls CopyDashData * MainDrivingLoop (Part 5 of 5) calls CopyDashData

Arguments: A The direction of the copy: * Bit 7 clear = copy from game code to screen memory * Bit 7 set = copy from screen memory to game code
.CopyDashData STA T \ Store A in T, so bit 7 of T determines the direction \ of the copy LDX #3 \ First, we copy the four bytes at dashDataAddress to \ P, Q, R and S, so set a loop counter in X for the four \ bytes .dash1 LDA dashDataAddress,X \ Copy the X-th byte of dashDataAddress to the X-th STA P,X \ byte of P DEX \ Decrement the loop counter BPL dash1 \ Loop back until we have copied all four bytes \ So we now have the following: \ \ (Q P) = the location of the first block in the main \ game code \ \ (S R) = the location of the first block in screen \ memory (i.e. at the end of screen memory, as \ the first block in the game code is the last \ block in screen memory) \ \ We now copy 41 blocks of memory from one address to \ the other, with the direction determined by the value \ of bit 7 in T (which we set above) \ \ We work through each block by starting at offset 79 \ from the start of (Q P) and (S R), and decrementing \ the offset until it matches the dashDataOffset value \ for this block \ \ We store the block number (which goes from 0 to 40) \ in X, and the offset (which goes from 79 down to \ dashDataOffset,X + 1) in Y LDX #0 \ Set a block counter in X to count through 41 blocks .dash2 LDY #79 \ Each block we want to copy ends at the start address \ plus 79, so set an index counter in Y, which we can \ use to work our way backwards through each byte in \ the block LDA #0 \ Set V = 0 to act as a byte counter to go from 0 to the STA V \ number of bytes copied .dash3 BIT T \ If bit 7 of T is set, skip the following two BMI dash4 \ instructions so we copy from (S R) to (Q P) LDA (P),Y \ Copy the Y-th byte of (Q P) to the Y-th byte of (S R) STA (R),Y .dash4 LDA (R),Y \ Copy the Y-th byte of (S R) to the Y-th byte of (Q P) STA (P),Y INC V \ Increment the byte counter DEY \ Decrement the index counter TYA \ If Y <> the dashDataOffset value for block X, loop CMP dashDataOffset,X \ to keep copying the contents of this block BNE dash3 \ We have copied a block of memory, so we now need to \ update (Q P) and (S R) to point to the next block to \ copy \ \ When in screen memory, the blocks are stored one after \ the other, in reverse order, so the address of the \ next block to copy is the start address of the block \ we just copied in (S R), minus the size of the block \ we just copied, which is in V, so the next block will \ be at (S R) - V LDA R \ Set (S R) = (S R) - V SEC \ SBC V \ starting with the low bytes STA R BCS dash5 \ And decrementing the high byte of (S R) if the low DEC S \ byte wraps around .dash5 \ When in the main game code, the blocks are stored \ every &80 bytes, so the address of the next block is \ (Q P) + &80 \ \ Note that each block takes up a different amount of \ memory, as follows: \ \ Block starts at: (Q P) + dashDataOffset,X + 1 \ Block ends at: (Q P) + 79 \ \ It's the value of (Q P) that is spaced out by &80 for \ each block, rather than the actual data in the block \ (for each block, (Q P) to (Q P) + dashDataOffset,X \ is used for other purposes) LDA P \ Set (Q P) = (Q P) + &80 CLC \ ADC #&80 \ starting with the low bytes STA P BCC dash6 \ And incrementing the high byte of (Q P) if the low INC Q \ byte overflows .dash6 INX \ Increment the block counter to point to the next block \ to copy CPX #41 \ Loop back to copy the next block until we have copied BNE dash2 \ all 41 blocks RTS \ Return from the subroutine
Name: dashDataAddress [Show more] Type: Variable Category: Screen buffer Summary: Addresses for copying the first block of dash data between the main game code and screen memory Deep dive: The jigsaw puzzle binary
Context: See this variable on its own page References: This variable is used as follows: * CopyDashData calls dashDataAddress
.dashDataAddress EQUW dashData \ The location of the first block in the game code EQUW &8000 - 80 \ The location of the first block in screen memory
Name: CheckVergeOnScreen [Show more] Type: Subroutine Category: Drawing the track Summary: Check whether a verge coordinate is on-screen Deep Dive: Drawing the track verges
Context: See this subroutine on its own page References: This subroutine is called as follows: * MapSegmentsToLines calls CheckVergeOnScreen

This routine tests whether the magnitude of a signed yaw angle is < 20. In other words, given a signed yaw angle x, this tests whether |x| < 20. As the field of view in Revs is 20 degrees, this tests whether or not a yaw angle is visible on-screen. It does this by adding 20 and then testing against 40, which gives the result we want as the following are all equivalent: -20 < x < 20 -20 + 20 < x < 20 + 20 0 < x < 40 Arguments: X The index within the track verge buffer of the verge to check: * horizonListIndex + 40 for the left verge * horizonListIndex for the right verge Returns: V The result, where x is the verge yaw angle: * Bit 7 is clear if |x| < 20 (visible on-screen) * Bit 7 is set if |x| >= 20 (not visible)
.CheckVergeOnScreen LDA xVergeRightHi,X \ Set A to the x-coordinate of the X-th entry in the \ track segment list CLC \ Set A = A + 20 ADC #20 CMP #40 \ Set bit 7 if A >= 40, clear it if A < 40 ROR V RTS \ Return from the subroutine
Name: MapSegmentsToLines [Show more] Type: Subroutine Category: Drawing the track Summary: Map verges in the track segment list to track lines in the track view Deep Dive: Drawing the track verges
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawTrack calls MapSegmentsToLines

This routine populates the leftSegment or rightSegment table (depending on the arguments). It does this by working its way through the verge buffer, from distant entries at the start of the buffer, to closer entries at the end of the buffer. As it goes, it looks at the pitch angles of the entries, which equate to track lines in the track view, with high track lines matching high pitch values (which are higher up the screen). It then fills the corresponding entries in the leftSegment or rightSegment table, which have one entry per track line, with the index numbers of the relevant entries from the verge buffer. In other words, if we fill five entries in the rightSegment table with an index n, then that means that the segment that's mapped to entry n in the verge buffer will take up five track lines on-screen, so it will be five pixels tall. It's worth reiterating that the track verge buffer stores distant segments first, coming towards us as we progress through the list, so leftSegment and rightSegment are the reverse of this, with closest segments at the start, furthest segments at the end. This matches the track lines, where small numbers are at the bottom of the screen (i.e. close), high numbers are up the screen (i.e. further away). Arguments: A The low byte of the table address to populate: * LO(rightSegment) = populate rightSegment * LO(leftSegment) = populate leftSegment Y Index of the last entry in the track verge buffer: * segmentListRight for the right verge * segmentListPointer for the left verge X The index within the track verge buffer of the horizon: * horizonListIndex for the right verge * horizonListIndex + 40 for the left verge Returns: vergeDepthOfField Updated to skip any segments that are hidden, so we do not waste time drawing their verges N Contains the pitch angle of the last segment to be mapped, which is the closest one to the player
.MapSegmentsToLines STA maps5+1 \ Modify the instruction at maps5 to use the low byte of \ the address in A, so the STA instruction writes to the \ table specified by A STY vergeBufferEnd \ Set vergeBufferEnd to the index in Y, which points to \ the last entry in the track verge buffer for this side \ of the track DEY \ Set U = Y - 1 STY U \ \ In the following loop, X iterates up to U - 1, so this \ ensures that it increments to the end of the track \ verge buffer, but not past the end JSR CheckVergeOnScreen \ Set bit 7 of V if the segment in X is off-screen, or \ clear bit 7 if it is on-screen LDY horizonLine \ Set Y to the track line number of the horizon JMP maps7 \ Jump to maps7 to join the loop below \ \ The loop below uses two loop counters: \ \ * Y is an index into leftSegment or rightSegment, \ starting at horizonLine and decrementing down \ through the track lines, i.e. the ones that show \ land rather than sky \ \ Y decrements from the horizon track line to 1 \ \ * X is an index into xVergeRightHi and yVergeRight, \ starting at horizonListIndex (the entry in the \ verge buffer that's on the horizon) and \ incrementing though the verge buffer, going from \ distant segments back towards the player \ \ X increments from the horizon entry in the verge \ buffer, up to the end of the verge buffer (as per \ the value that we gave U above) \ \ In other words, we work our way through the verge \ buffer from the horizon towards the player, inserting \ values into the leftSegment or rightSegment table \ for each corresponding track line, starting at the \ horizon track line and working down the screen \ \ We also enter the loop with V set to the visibility of \ the horizon's entry in the verge buffer .maps1 \ We loop back here when X is incremented to point to \ the next entry in the verge buffer LDA V \ If bit 7 of V is clear, then we have already reached a BPL maps2 \ visible entry in the verge buffer, so jump to maps2 \ Bit 7 of V is set, so we haven't yet reached a visible \ entry in the verge buffer, so we test the new entry in \ X to see if it is visible \ \ In this way, V is a flag that records when we reach \ the visible entries in the buffer, at which point we \ flip bit 7 of V from set to clear, and stop checking JSR CheckVergeOnScreen \ Set bit 7 of V if the segment in X is off-screen, or \ clear bit 7 if it is on-screen .maps2 LDA yVergeRight,X \ Set A to the pitch angle of the current entry in the \ verge buffer CMP #80 \ If A >= 80, then this pitch angle maps to a track line BCS maps9 \ that's off the top of the screen, so jump to maps9 to \ pad out the rest of the leftSegment or rightSegment \ table and finish off BIT V \ If bit 7 of V is clear, then we have already reached a BPL maps3 \ visible entry in the verge buffer, so jump to maps3 \ If we get here then bit 7 of V is set, so the current \ entry in the verge buffer is not on-screen in the \ x-axis, though it is on-screen in the y-axis as we \ know A < 80 CMP yVergeRight+1,X \ If the pitch angle of this entry in the verge buffer BEQ maps14 \ is the same as the angle of the next entry (i.e. \ the next entry closer to the player), then jump to \ maps14 to set bit 7 of this entry's vergeDataRight \ and move on to the next entry in the verge buffer .maps3 \ If we get here then either this entry in the verge \ buffer is on-screen, or it's off-screen but is at a \ different pitch angle to the next closest entry CMP N \ If A >= N, then the entry is at a higher pitch angle BCS maps14 \ than the current track line in N, so jump to maps14 \ to set bit 7 of the X-th vergeDataRight and on to \ maps8 to move on to the next X \ Otherwise we fall through to fill index Y down to \ index A + 1 with the value in X, which sets the \ leftSegment or rightSegment entries for track lines \ Y down to A + 1 with the index in X .maps4 \ If we get here then we fill index Y down to index \ A + 1 with the value in X STA RR \ Set RR = A TXA \ Set A = X JMP maps6 \ Jump to maps6 to fill index Y down to index RR + 1 \ with the value in A .maps5 STA leftSegment,Y \ Store A in the Y-th entry in leftSegment or \ rightSegment DEY \ Decrement the loop counter in Y to point to the next \ track line down the screen .maps6 \ This loop fills leftSegment or rightSegment with the \ value in A, from index Y down to index RR + 1 CPY RR \ Loop back to maps5 until we have filled from index Y BNE maps5 \ to RR + 1 with the value in A .maps7 \ This is where we first join the loop STY N \ Set N = Y, so N contains the number of the track line \ we are currently processing .maps8 INX \ Increment the loop counter in X to point to the next \ entry in the verge buffer BMI maps11 \ If bit 7 of X is set, then we must have called the \ loop from maps10 below, so we have now finished \ filling the leftSegment or rightSegment table all the \ way back to index 1, so jump to maps11 to exit the \ loop as we are done filling CPX U \ If X < U, then we still have entries in the verge BCC maps1 \ buffer to process, so loop back to maps1 .maps9 \ If we get here then we are nearly done, and just need \ to pad out the rest of the leftSegment or rightSegment \ table with the current entry's index with bit 7 set, \ working back to position 1 in the table \ First we cap the pitch angle of the current entry in \ the verge buffer to a maximum of N LDA yVergeRight,X \ Set A to the pitch angle of the current entry in the \ verge buffer (i.e. the last entry we will fill, which \ is the closest track line to the player) BMI maps10 \ If A is negative, jump to maps10 CMP N \ If A < N, jump to maps10 BCC maps10 \ If we get here then A is positive and A >= N LDA N \ Set the pitch angle of the current entry in the verge STA yVergeRight,X \ buffer to N .maps10 \ We now get ready to loop back to the fill loop above, \ to pad out the remainder of the leftSegment or \ rightSegment table TXA \ Set bit 7 of X, so when we jump back to the fill loop ORA #%10000000 \ via maps4, we fill the rest of the leftSegment or TAX \ rightSegment table with this value, and then exit the \ loop by jumping to maps11 LDA #0 \ Set A = 0, so we fill the leftSegment or rightSegment \ table back to position 1 with the value in X BEQ maps4 \ Jump to maps4 to fill index Y down to index A + 1 with \ the value in X (this BEQ is effectively a JMP as A is \ always zero) .maps11 \ Now we loop X from vergeDepthOfField up until we find \ the first vergeDataRight entry with bit 7 clear, and \ set vergeDepthOfField to the updated X \ \ This moves through the verge buffer, towards the \ player, starting at the horizon line, and skipping any \ entries that have bit 7 set in vergeDataRight, so we \ set the depth of field to skip these segments \ \ We set bit 7 of vergeDataRight for any entries whose \ pitch angles were higher than the current track line \ as we worked down the screen and towards the player in \ the verge buffer \ \ So this stops us from displaying verges on any hills \ between the horizon and the player LDX vergeDepthOfField \ Set X = to the current verge depth of field, which is \ the index within the verge buffer beyond which we do \ not draw verge marks .maps12 LDA vergeDataRight,X \ If bit 7 the X-th vergeDataRight is clear, jump to BPL maps13 \ maps13 to store X in vergeDepthOfField INX \ Increment the loop counter in X CPX U \ If X < U, loop back to maps12 BCC maps12 .maps13 STX vergeDepthOfField \ Set vergeDepthOfField = X RTS \ Return from the subroutine .maps14 LDA #%10000000 \ Set bit 7 of vergeDataRight for the current entry in ORA vergeDataRight,X \ the verge buffer STA vergeDataRight,X BMI maps8 \ Jump to maps8 (this BMI is effectively a JMP as bit 7 \ of A is always set)
Name: DrawVergeEdge [Show more] Type: Subroutine Category: Drawing the track Summary: Draw one of the four track verge edges into the screen buffer Deep Dive: Drawing the track verges
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawTrack calls DrawVergeEdge

Arguments: A The index in the verge buffer where we start drawing the track verges (so this is essentially the depth of field for this verge) Y Determines the type of verge that we are drawing: * 0 = leftVergeStart, the left edge of the left verge * 1 = leftTrackStart, the right edge of the left verge * 2 = rightVergeStart, the left edge of the right verge * 3 = rightGrassStart, the right edge of the right verge vergeBufferEnd The index of the last entry in the track verge buffer for the side of the track we are currently drawing pixelMaskVerge Offset within the vergePixelMask table for segments that are close enough to show a verge * 0 for leftVergeStart * 8 for leftTrackStart * 16 for rightVergeStart * 28 for rightGrassStart pixelMaskNoVerge Offset within the vergePixelMask table for segments that are too far away to show a verge * n/a for leftVergeStart * 0 for leftTrackStart * 28 for rightVergeStart * 28 for rightGrassStart MM Set to 0, to act as the low byte of (NN MM) R Set to 0, to act as the low byte of (S R) P Set to &80, to use as the low byte of (Q P)
.DrawVergeEdge STY vergeType \ Set vergeType to the type of verge that we are \ going to draw STA prevPitchIndex \ Set prevPitchIndex to the verge buffer index in A, to \ use as the starting index into the buffer for the \ drawing process CMP vergeBufferEnd \ If A >= vergeBufferEnd, then it points to an index BCS vedg6 \ after the end of the verge buffer data for this side \ of the track, so jump to vedg6 to return from the \ subroutine CLC \ Set: ADC vergeEdgeInOut,Y \ STA prevYawIndex \ prevYawIndex = A + vergeEdgeInOut \ \ for the type of verge we are drawing \ \ This sets prevYawIndex to the following: \ \ * A for the inner edges of the verge mark in \ leftTrackStart and rightVergeStart \ \ * A + 16 for the outer edges of the verge mark in \ leftVergeStart and rightGrassStart \ \ This points prevYawIndex to the correct part of the \ track segment list for this entry in the buffer, as \ the angles for the outer edge of the verge mark are \ stored 16 bytes after the inner edge angles (the inner \ edge angles are in bytes 6 to 21 of the track segment \ list, while the outer edge angles are in bytes 22 to \ 37) LDA vergeTableHi,Y \ Modify the following instructions at verl2 and verb2, STA verl2+2 \ depending on the type of verge in Y: STA verb2+2 \ LDA vergeTableLo,Y \ * 0 = STA &7000,Y -> STA leftVergeStart,Y STA verl2+1 \ * 1 = STA &7000,Y -> STA leftTrackStart,Y STA verb2+1 \ * 2 = STA &7000,Y -> STA rightVergeStart,Y \ * 3 = STA &7000,Y -> STA rightGrassStart,Y \ \ So this modifies the DrawVergeByteLeft and \ DrawVergeByteRight routines to draw the verge \ specified in Y LDX prevYawIndex \ Set X = prevYawIndex, to use as the index to the \ verge's yaw angles LDY prevPitchIndex \ Set Y = prevPitchIndex, to use as the index to the \ verge's pitch angles SEC \ Set the C flag to pass to DrawSegmentEdge so it does \ not draw the edge (as this is the first call for this \ edge so we just set up the variables, ready for the \ next call) JSR DrawSegmentEdge \ Draw the verge edge for this entry (i.e. this segment) \ in the verge buffer .vedg1 INX \ Increment X to the index of the next yaw angle in the \ track segment list INY \ Increment Y to the index of the next pitch angle in \ the track segment list CPY vergeBufferEnd \ If Y >= vergeBufferEnd then we have processed all the BCS vedg6 \ entries in the verge buffer, so jump to vedg6 to \ return from the subroutine LDA vergeDataRight,Y \ If bit 7 is set in vergeDataRight for the new segment BMI vedg1 \ from the track segment list, then this segment is \ hidden behind a hill, so jump to vedg1 move on to the \ next segment LDA prevPitchIndex \ If prevPitchIndex < vergeDepthOfField, then the new CMP vergeDepthOfField \ segment is further away from the player than the depth BCC vedg2 \ of field index in vergeDepthOfField, so jump to vedg2 \ to draw the track edges without verge marks BNE vedg3 \ If prevPitchIndex <> vergeDepthOfField, which means \ prevPitchIndex > vergeDepthOfField, jump to vedg3 \ If we get here then prevPitchIndex = vergeDepthOfField \ so the current segment is right at the point where we \ stop drawing verge marks LDA vergeDataRight-1,Y \ Set A to the colour of the previous entry in the verge AND #3 \ buffer, which is stored in bits 0-2 of vergeDataRight \ (the -1 points to the previous entry, which will be \ one step further away from the player) \ \ The colour values are: \ \ * 0 = black \ * 1 = red \ * 2 = white BNE vedg4 \ If the verge of the previous entry is red or white, \ jump to vedg4 to set A = pixelMaskVerge + A * 4 and \ draw the edge \ If we get here then the verge of the previous entry \ is black STY vergeDepthOfField \ Set Y = vergeDepthOfField SEC \ Set the C flag to pass to DrawSegmentEdge so it does \ not draw the edge LDA vergeType \ Set A to the type of verge we are drawing BEQ vedg5 \ If we are drawing leftVergeStart, jump to vedg5 with \ the C flag set and A = 0 to draw the edge .vedg2 \ If we get here then one of these is true: \ \ * prevPitchIndex < vergeDepthOfField, so the current \ segment is further away from the player than the \ depth of field index in vergeDepthOfField, so we \ draw all verge marks as black \ \ * prevPitchIndex = vergeDepthOfField and the \ previous segment's verge edge is red or white and \ we are not drawing leftVergeStart, so we want to \ draw a black verge mark \ \ In both cases we want to draw black verge marks LDA pixelMaskNoVerge \ Set A = pixelMaskNoVerge \ \ pixelMaskNoVerge always points to a pixel byte that's \ green and black, so is this how we draw the track for \ segments that are beyond the verge depth of field CLC \ Set the C flag to pass to DrawSegmentEdge so it draws \ the edge BCC vedg5 \ Jump to vedg5 with the C flag clear and A set to \ pixelMaskNoVerge to draw the edge (this BCC is \ effectively a JMP as the C flag is always clear) .vedg3 \ If we get here then prevPitchIndex > vergeDepthOfField \ so the current segment is closer to the player than \ the depth of field index in vergeDepthOfField, so we \ draw the verge mark LDA vergeDataRight-1,Y \ Set A to the colour of the previous entry in the verge AND #3 \ buffer, which is stored in bits 0-2 of vergeDataRight \ (the -1 points to the previous entry, which will be \ one step further away from the player) \ \ The colour values are: \ \ * 0 = black \ * 1 = red \ * 2 = white BNE vedg4 \ If the verge of the previous entry is red or white, \ jump to vedg4 to set: \ \ * A = pixelMaskVerge + 4 for red \ \ * A = pixelMaskVerge + 8 for white \ \ And then pass this to DrawSegmentEdge LDA vergeType \ Set A to the type of verge we are drawing CMP #1 \ If we are drawing leftTrackStart, jump to vedg5 with BEQ vedg5 \ the C flag set and A = 1, so it doesn't draw the edge \ but just sets up the variables CMP #2 \ If we are drawing rightVergeStart, jump to vedg5 with BEQ vedg5 \ the C flag set and A = 2, so it doesn't draw the edge \ but just sets up the variables LDA #0 \ Set A = 0, so we pass A = pixelMaskVerge to \ DrawSegmentEdge .vedg4 ASL A \ Set A = pixelMaskVerge + A * 4 ASL A \ CLC \ This also clears the C flag, so the call to ADC pixelMaskVerge \ DrawSegmentEdge draws the verge edge .vedg5 JSR DrawSegmentEdge \ Draw the verge edge for this entry (i.e. this segment) \ in the verge buffer STY prevPitchIndex \ Update the value of prevPitchIndex to point to the \ entry we just processed, which is now the previous \ entry STX prevYawIndex \ Update the value of prevYawIndex to point to the entry \ we just processed, which is now the previous entry JMP vedg1 \ Loop back to vedg1 to process the next entry .vedg6 RTS \ Return from the subroutine
Name: DrawTrack [Show more] Type: Subroutine Category: Drawing the track Summary: Draw the track into the screen buffer Deep Dive: Drawing the track verges
Context: See this subroutine on its own page References: This subroutine is called as follows: * MainDrivingLoop (Part 2 of 5) calls DrawTrack
.DrawTrack LDA #&80 \ Set P = &80, to use as the low byte of (Q P) in the STA P \ DrawVergeEdge that we call below LDA horizonListIndex \ Set A = X = horizonListIndex + 40 CLC \ ADC #40 \ So X is the index within the track verge buffer of the TAX \ horizon line's left verge (adding 40 moves the index \ from xVergeRightHi to xVergeLeftHi, which are 40 bytes \ apart in memory) CMP #49 \ If A >= 49, jump to dtra1 BCS dtra1 LDA #49 \ Set A = 49, so A has a minimum value of 49 .dtra1 STA vergeDepthOfField \ Set vergeDepthOfField = A \ = max(49, horizonListIndex + 40) \ = max(9, horizonListIndex) + 40 \ \ This sets the depth of field for displaying verge \ marks to at least entry 9 in the verge buffer, which \ is past all the sections (0 to 5) and past the first \ three segments (6 to 8) \ \ Adding 40 then points the index to the left verge LDA #LO(leftSegment) \ Set A to the low byte of leftSegment, so the call to \ MapSegmentsToLines populates the leftSegment table \ The following set R and MM to zero, to use as the low \ bytes in (S R) and (NN MM), though this is only \ because LO(leftSegment) happens to be zero; moving \ leftSegment off a page boundary would break this code STA R \ Set R = 0 to use as the low byte in (S R) STA MM \ Set MM = 0 to use as the low byte in (NN MM) LDY segmentListPointer \ Set Y to index of the last entry in the track segment \ list for the left side of the track JSR MapSegmentsToLines \ Populate the leftSegment table with a mapping of track \ lines on-screen to the verge buffer for the left side \ of the track LDY #0 \ Set Y = 0, so the call to DrawVergeEdge draws the \ leftVergeStart verge (the left edge of the left verge) STY pixelMaskVerge \ Set pixelMaskVerge = 0 LDA vergeDepthOfField \ Set A = vergeDepthOfField, so we only draw the next \ verge for segments that are within the verge depth of \ field JSR DrawVergeEdge \ Draw the left edge of the left verge (leftVergeStart) LDA #8 \ Set pixelMaskVerge = 8 STA pixelMaskVerge LDY #0 \ Set pixelMaskNoVerge = 0 STY pixelMaskNoVerge INY \ Set Y = 1, so the call to DrawVergeEdge draws the \ leftTrackStart verge (the right edge of the left \ verge) LDA horizonListIndex \ Set A = horizonListIndex + 40, so we draw the next CLC \ verge all the way from the horizon to the player ADC #40 JSR DrawVergeEdge \ Draw the right edge of the left verge (leftTrackStart) LDA vergeDepthOfField \ Set A = vergeDepthOfField to pass to the \ SetVergeBackground routine LDX #%00000100 \ Set X = %00000100 to pass to SetVergeBackground as a \ bit mask to use when calculating the background \ colours JSR SetVergeBackground \ Update the background colour table for any verges that \ overlap the left edge of the screen STY vergeTopLeft \ SetVergeBackground sets Y to the track line just above \ the segment at vergeDepthOfField (i.e. the furthest \ segment that might contain a verge), so store this in \ vergeTopLeft LDA horizonListIndex \ Set A = horizonListIndex TAX \ Set X = horizonListIndex CMP #9 \ If A >= 9, jump to dtra1 BCS dtra2 LDA #9 \ Set A = 9, so A has a minimum value of 9 .dtra2 STA vergeDepthOfField \ Set vergeDepthOfField = A \ = max(9, horizonListIndex) + 40 \ \ This sets the depth of field for displaying verge \ marks to at least entry 9 in the verge buffer, which \ is past all the sections (0 to 5) and past the first \ three segments (6 to 8) LDX horizonListIndex \ Set X = horizonListIndex \ \ So X is the index in the track verge buffer for the \ horizon line's right verge LDA #LO(rightSegment) \ Set A to the low byte of rightSegment, so the call to \ MapSegmentsToLines populates the rightSegment table LDY segmentListRight \ Set Y to index of the last entry in the track segment \ list for the right side of the track JSR MapSegmentsToLines \ Populate the rightSegment table with a mapping of \ track lines on-screen to the verge buffer for the \ right side of the track LDA #28 \ Set pixelMaskNoVerge = 28 STA pixelMaskNoVerge LDA #16 \ Set pixelMaskVerge = 16 STA pixelMaskVerge LDY #2 \ Set Y = 2, so the call to DrawVergeEdge draws the \ rightVergeStart verge (the left edge of the right \ verge) LDA horizonListIndex \ Set A = horizonListIndex so we draw the next verge all \ the way from the horizon to the player JSR DrawVergeEdge \ Draw the left edge of the right verge \ (rightVergeStart) LDA #28 \ Set pixelMaskVerge = 28 STA pixelMaskVerge LDY #3 \ Set Y = 3, so the call to DrawVergeEdge draws the \ rightGrassStart verge (the right edge of the right \ verge) LDA vergeDepthOfField \ Set A = vergeDepthOfField, so we only draw the next \ verge for segments that are within the verge depth of \ field JSR DrawVergeEdge \ Draw the right edge of the right verge \ (rightGrassStart) LDA vergeDepthOfField \ Set A = vergeDepthOfField to pass to the \ SetVergeBackground routine LDX #%00010100 \ Set X = %00010100 to pass to SetVergeBackground as a \ bit mask to use when calculating the background \ colours JSR SetVergeBackground \ Update the background colour table for any verges that \ overlap the left edge of the screen STY vergeTopRight \ SetVergeBackground sets Y to the track line just above \ the segment at vergeDepthOfField (i.e. the furthest \ segment that might contain a verge), so store this in \ vergeTopRight RTS \ Return from the subroutine
Name: SetVergeBackground [Show more] Type: Subroutine Category: Drawing the track Summary: Update the background colour table for any verges that overlap the left edge of the screen Deep Dive: Drawing the track verges
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawTrack calls SetVergeBackground

This routine works through any track lines that might contain a verge, and checks whether the verge crosses the left edge of the screen, updating the relevant entry in the background colour table if it does. Arguments: A Index of the first entry to check in the verge buffer, typically set to vergeDepthOfField so only entries that might contain a verge are checked X Set to a bit mask as follows: * %000 00 1 00 (after drawing the left verge) * %000 10 1 00 (after drawing the right verge) Only bits 2-4 of this are used Returns: Y The track line just above the pitch angle of the segment at vergeDepthOfField (i.e. the furthest segment that might contain a verge)
.SetVergeBackground STX GG \ Set GG to the value passed in X STA U \ Set U to the index into the verge buffer DEC vergeBufferEnd \ Decrement the index of the last entry in the track \ verge buffer for the verge we are drawing LDA playerSideways \ If playerSideways < 40, then the player's car is CMP #40 \ facing along the track rather than sideways, so jump BCC sver8 \ to sver8 to join the loop below BCS sver9 \ If we get here then the player's car is facing \ sideways relative to the track direction, so jump to \ sver9 to skip the loop (this BCS is effectively a JMP \ as we just passed through a BCC) \ The following loop uses X as a loop counter to work \ through through the verge buffer from entry U to the \ end, setting the background colour if the verge goes \ off the left of the screen .sver1 LDY yVergeRight,X \ If the pitch angle (i.e. the track line) of the X-th CPY #80 \ entry in the verge buffer is 80 or more, then it is BCS sver7 \ not on-screen, so jump to sver7 to move on to the next \ entry in the verge buffer LDA vergeDataRight,X \ If bit 7 of the X-th entry's vergeDataRight is set, BMI sver7 \ then this segment is hidden behind a hill, so jump to \ sver7 to move on to the next entry in the verge buffer LDA GG \ If GG = %00010100, which is the value we passed after CMP #%00010100 \ drawing the right verge, then jump to sver2 BEQ sver2 LDA xVergeRightHi+16,X \ Set W to the high byte of the yaw angle of the verge's STA W \ outer edge LDA xVergeRightHi,X \ Set A to the high byte of the yaw angle of the verge's \ inner edge JMP sver3 \ Jump to sver3 to keep going .sver2 LDA xVergeRightHi,X \ Set W to the high byte of the yaw angle of the verge's STA W \ inner edge LDA xVergeRightHi+16,X \ Set A to the high byte of the yaw angle of the verge's \ outer edge .sver3 \ At this point, W is the yaw angle of the leftmost \ verge edge, and A is the yaw angle of the rightmost \ verge edge, for the verge that we are drawing CLC \ Set A = A + 20 ADC #20 BMI sver7 \ If A is negative, jump to sver7 to move on to the next \ entry in the verge buffer LDA W \ Set A = W + 20 CLC ADC #20 BPL sver7 \ If A is positive, jump to sver7 to move on to the next \ entry in the verge buffer \ If we get to this point, then: \ \ * A + 20 is positive \ \ * W + 20 is negative \ \ Adding 20 degrees to the yaw angles will move them to \ the right by half the screen width, so this is the \ same as moving the angles from the left edge of the \ screen to the middle \ \ We then check whether moving the angles to the centre \ pushes the rightmost verge edge in A past the centre \ (i.e. positive), while still leaving the leftmost edge \ in the left half (i.e. negative) \ \ If so, then this means the verge is straddling the \ left edge of the screen, so we need to consider \ setting the background colour for this track line to \ the verge colour \ First, though, we need to skip forward through the \ verge buffer until we get to the next entry that is \ followed by a non-hidden entry (so this skips over \ any entries that are hidden behind hills) \ \ This ensures that we get to an entry in the verge \ buffer that has a verge colour, as hidden entries do \ not have a colour associated with them .sver4 LDA vergeDataRight+1,X \ If the next entry in the verge buffer is not hidden BPL sver5 \ behind a hill, jump to sver5 INX \ Increment X to point to the next entry in the verge \ buffer INC U \ Increment U to point to the next entry in the verge \ buffer CPX vergeBufferEnd \ If X < vergeBufferEnd then we haven't reached the end BCC sver4 \ of the verge buffer, so loop back to check the next \ entry .sver5 LDA backgroundColour,Y \ Set T to the current entry in the background colour STA T \ table for track line Y LDA backgroundColour,Y \ Set A to the current entry in the background colour \ table for track line Y BEQ sver6 \ If A = 0, then the background colour is currently \ set to black, so jump to sver6 to set the background \ colour to the verge colour AND #%00011100 \ Extract bits 2-4 of A, which contain the verge type in \ bits 3-4, and details in bit 2 of whether the colour \ was set by the SetVergeBackground routine CMP GG \ If A = GG, then bits 2-4 match the mask we passed to BEQ sver6 \ the routine, so jump to sver6 to set the background \ colour to the verge colour \ \ The possible values of GG are: \ \ * %000 00 1 00 (after drawing the left verge) \ \ * %000 10 1 00 (after drawing the right verge) \ \ So this jumps if we already set the background colour \ entry in this routine, and the verge type was \ leftVergeStart after drawing the left verge, or \ rightVergeStart after drawing the right verge \ If we get here then bits 2-4 of the current background \ colour do not match the mask in GG, so we have not \ already set this colour on this routine for the \ leftVergeStart or rightVergeStart verges ROR A \ Rotate the C flag into bit 7 of A, where: \ \ * C flag is clear if A < GG \ \ * C flag is set if A > GG EOR T \ If T and A have different values of bit 7, jump to BMI sver7 \ sver7 to move on to the next entry in the verge buffer \ Otherwise we fall through into sver6 to set the \ background colour to the verge colour .sver6 LDA vergeDataRight,X \ Set A to the verge data for the entry in the verge \ buffer that is crossing the left edge of the screen AND #%00000011 \ Extract the colour of the verge, which is in bits 0-1, \ so this is the colour that we want to store in the \ background colour table (as this is the colour of the \ verge mark that's at the left edge of the screen) ORA GG \ OR the verge colour with the mask that we passed to \ the routine, to give: \ \ * %000001xx (after drawing the left verge) \ \ * %000101xx (after drawing the right verge) \ \ where %xx is the colour of the verge STA backgroundColour,Y \ Store the result as the background colour for track \ line Y .sver7 INC U \ Increment the verge buffer index in U to move on to \ the next entry in the verge buffer .sver8 \ This is where we join the loop LDX U \ Set X to the loop counter in U, which contains the \ index of the entry to process in the verge buffer CPX vergeBufferEnd \ If X < vergeBufferEnd, jump back to sver1 to process BCC sver1 \ this entry .sver9 LDX vergeDepthOfField \ Set Y to the pitch angle (i.e. the track line) of the LDY yVergeRight,X \ entry in the verge buffer at the depth of field INY \ Increment the track line in Y RTS \ Return from the subroutine
Name: DrawCornerMarkers [Show more] Type: Subroutine Category: Drawing objects Summary: Draw any visible corner markers Deep dive: Corner markers
Context: See this subroutine on its own page References: This subroutine is called as follows: * MainDrivingLoop (Part 2 of 5) calls DrawCornerMarkers

Arguments: markersToDraw The number of corner markers to draw - 1
.DrawCornerMarkers LDY #0 \ We work our way through the markers we need to draw, \ using Y as the marker number, counting up from 0 to \ markersToDraw - 1 .corn1 CPY markersToDraw \ If Y = markersToDraw, then we have drawn all the BEQ corn7 \ markers, so jump to corn7 to reset markersToDraw to \ zero and return from the subroutine LDX markerListIndex,Y \ Set X = markerListIndex for marker Y, so X contains \ the index of the corresponding entry in the track \ segment list for this corner markers STY markerNumber \ Store the marker number in markerNumber, so we can \ retrieve it at the end of the loop LDA markerData,Y \ If bit 5 of markerData for marker Y is clear, then the AND #%00100000 \ marker is white, so jump to corn2 to skip the BEQ corn2 \ following two instructions LDA #%00001111 \ Map logical colour 2 in the colour palette to physical STA colourPalette+2 \ colour 1 (red in the track view), so the corner marker \ is drawn in red rather than white .corn2 LDA xMarkerHi,Y \ Set (U A) = (xMarkerHi xMarkerLo) for marker Y STA U \ LDA xMarkerLo,Y \ So (U A) contains the x-axis distance between the \ track edge and the marker ASL A \ Set (U A) = (U A) << 1 ROL U \ = xMarker * 2 STA T \ Set (U T) = (U A) \ = xMarker * 2 CLC \ Set (A V) = (U A) + X-th value from xVergeRight ADC xVergeRightLo,X \ STA V \ starting with the low bytes LDA xVergeRightHi,X \ And then the high bytes ADC U \ \ So (A V) contains the verge's x-coordinate plus the \ distance between the track edge and the marker \ (doubled), to give the x-coordinate of the marker CMP #24 \ If A < 24, jump to corn3 BCC corn3 CMP #232 \ If A < 232, i.e. 24 <= A < 232, jump to corn6 to move BCC corn6 \ on to the next loop .corn3 \ If we get here then A < 24 or A >= -24, so the marker \ is on-screen ASL V \ Set (A V) = (A V) << 2 ROL A \ ASL V \ so A < 96 or A >= -96 ROL A CLC \ Set xPixelCoord = A + 80 ADC #80 \ STA xPixelCoord \ where 80 is the x-coordinate of the middle of the \ screen (as the screen is 160 pixels wide) LDA yVergeRight,X \ Set yPixelCoord = X-th value from yVergeRight STA yPixelCoord \ \ Which is the pitch angle (i.e. y-coordinate) of \ the verge that has the corner markes LDY #2 \ Set Y = 2 so the following loop shifts (U T) left by \ two places .corn4 ASL T \ Set (U T) = (U T) << 1 ROL U DEY \ Decrement the shift counter BNE corn4 \ Loop back until we have left-shifted by Y places LDA U \ Set A = U, so we now have: \ \ (A T) = (U T) << 2 \ = xMarker * 2 * 2 BPL corn5 \ If A is positive, jump to corn5 to skip the following EOR #&FF \ A is negative, so negate A using two's complement, so CLC \ A now contains |A| ADC #1 .corn5 STA scaleUp \ Set scaleUp = |A| \ \ So the size of the corner marker is based on the \ x-axis distance between the track edge and the marker LDA #6 \ Set objectType = 6, the object type for a corner STA objectType \ marker JSR DrawObject \ Draw the corner marker .corn6 LDA #%11110000 \ Map logical colour 2 in the colour palette to physical STA colourPalette+2 \ colour 1 (white in the track view), which sets it back \ to the default value LDY markerNumber \ Set Y to the marker number that we stored in \ markerNumber at the start of the loop INY \ Increment the marker number to draw the next loop JMP corn1 \ Loop back to corn1 .corn7 LDA #0 \ Reset markersToDraw to zero as we have drawn all the STA markersToDraw \ corner markers RTS \ Return from the subroutine
Name: UpdatePositionInfo [Show more] Type: Subroutine Category: Text Summary: Apply any position changes and update the position information at the top of the screen
Context: See this subroutine on its own page References: This subroutine is called as follows: * UpdateLapTimers calls UpdatePositionInfo
.UpdatePositionInfo LDA positionChangeBCD \ Set A = positionChangeBCD BEQ posi1 \ If A = 0 then the race position has not changed, so \ jump to posi1 to skip updating the position number \ Otherwise we need to add the position change to the \ current position number, so we can update the number \ at the top of the screen SED \ Set the D flag to switch arithmetic to Binary Coded \ Decimal (BCD) CLC \ Set A = currentPositionBCD + A ADC currentPositionBCD \ = currentPositionBCD + positionChangeBCD STA currentPositionBCD \ Set currentPositionBCD = A CLD \ Clear the D flag to switch arithmetic to normal BEQ posi1 \ If A = 0, jump to posi1 CMP #&21 \ If A >= &21, jump to posi1 BCS posi1 LDX #0 \ Set positionChangeBCD = 0, as we have now applied the STX positionChangeBCD \ change of position to currentPositionBCD STX G \ Set G = 0 so the call to Print2DigitBCD below will \ print the second digit and will not print leading \ zeroes when printing the position number LDX #10 \ Print the position number in A at column 10, pixel LDY #24 \ row 24, on the first text line at the top of the JSR Print2DigitBCD-6 \ screen .posi1 BIT updateDriverInfo \ If bit 7 of updateDriverInfo is clear, jump to posi2 BPL posi2 \ to skip printing the driver names at the top of the \ screen LDY positionAhead \ Set Y to the position of the driver in front of us LDA #24 \ Print the name of driver Y in the "In front:" part of JSR PrintNearestDriver \ the header LDY positionBehind \ Set Y to the position of the driver behind us LDA #33 \ Print the name of driver Y in the "Behind:" part of JSR PrintNearestDriver \ the header .posi2 LSR updateDriverInfo \ Clear bit 7 of updateDriverInfo so we don't update the \ driver names until the value of updateDriverInfo \ changes RTS \ Return from the subroutine
Name: ProcessContact [Show more] Type: Subroutine Category: Car geometry Summary: Process collosions between the player and the other cars
Context: See this subroutine on its own page References: This subroutine is called as follows: * MainDrivingLoop (Part 2 of 5) calls ProcessContact

Returns: V Bit 7 is set if there is a collision
.ProcessContact LDA processContact \ If processContact is zero then there no other cars BEQ DrawObjectEdge-1 \ close enough to the player's car for there to be any \ contact, so return from the subroutine (as \ DrawObjectEdge-1 contains an RTS) LDA #0 \ Set processContact = 0 to reset the flag, so we only STA processContact \ check for contact when a car is flagged as being close SEC \ Set bit 7 of V ROR V LDA #37 \ Set A = 37 - objectDistanceLo SEC \ SBC objectDistanceLo \ The value of (objectDistanceHi objectDistanceLo) is \ left over from the last call to CheckForContact, which \ was last called for the nearest car in front of us \ when building object for the five cars in front of us, \ from furthest to nearest, as part of this chain of \ routines: \ \ MoveAndDrawCars > BuildVisibleCar > BuildCarObjects \ > GetObjectAngles > CheckForContact \ \ Note that BuildVisibleCar does get called once more at \ the end of MoveAndDrawCars, for the car behind us, but \ as that car is not visible, it doesn't set the object \ distance \ \ So, in short, objectDistanceLo is the low byte of the \ distance between the player and the nearest car, which \ is the car we want to process for a collision \ \ This means that a higher value of A means a closer \ collision, so this is effectively a measure of how \ dangerous this collision is, in the range 0 to 37 BCS cont1 \ If the above subtraction didn't underflow then the \ other car is at a distance of 37 or less, so jump to \ cont1 to skip the following instruction LDA #5 \ The subtraction underflowed, so the other car is a bit \ of a distance away, so set A = 5 so have a minor \ collision .cont1 ASL A \ Set A = A * 2 \ \ so the damage measure is now in the range 0 to 74 STA U \ Set U = A, so U now contains the damage measure in the \ range 0 to 74 LDX collisionDriver \ Set X to the driver number of the car being hit (the \ "other car") LDY currentPlayer \ Set Y to the driver number of the current player CMP #40 \ If A < 40, then the collision is not bad enough to BCC cont2 \ push the other car off the track, so jump to cont2 LDA raceStarted \ If bit 7 of raceStarted is clear then this is either BPL cont2 \ a practice or qualifying lap, so jump to cont2 to skip \ the following instruction JSR PushCarOffTrack \ If we get here then the collision is close enough to \ push the other car off the track, and this is a race, \ so we push the other car off the track and out of the \ race .cont2 LDA objYawAngleHi,X \ Set A to the yaw angle for the other car minus the yaw SEC \ angle for the player, which we will call dYawAngle SBC playerYawAngleHi ASL A \ Set A = A * 4 ASL A \ = dYawAngle * 4 PHP \ Push the N flag onto the stack, which contains the \ sign of dYawAngle LDA carSpeedHi,Y \ Set A to the high byte of the player's speed CPX #20 \ If the driver number of the other car is >= 20, then BCS cont4 \ jump to cont4 as this is not a computer-controlled \ driver, so we do not adjust its speed CMP carSpeedHi,X \ If the high byte of the player's speed is >= the high BCS cont3 \ byte of the other driver's speed, jump to cont3 with \ A containing the higher of the two speeds, so the \ other car gets bumped to a slightly higher speed than \ the faster car (by adding 12, as the C flag is set) LDA carSpeedHi,X \ Set A to the high byte of the other driver's speed, so \ A now contains the higher of the two speeds BNE cont4 \ Jump to cont4 to leave the speed of the other car \ alone, as it is going faster than the player (this BNE \ is efftively a JMP, as we know from the above that \ carSpeedHi,Y < carSpeedHi,X, which implies that the \ value of carSpeedHi,X must be non-zero) .cont3 \ If we get here, it's because the player is going \ faster than the other car, and we jumped here via a \ BCS, so the C flag is set, which means the following \ adds 12 to the other car's speed ADC #11 \ Increase the high byte of the other driver's speed by STA carSpeedHi,X \ 12, to speed it up after being hit by the faster \ player's car .cont4 \ By this point, A contains the speed of the faster car \ following the collision, as the high byte of the speed JSR Multiply8x8 \ Set (A T) = A * U \ = carSpeedHi * damage measurement (0 to 74) \ \ So (A T) is higher with closer and faster collisions CMP #16 \ If A < 16, jump to cont5 to skip the following BCC cont5 LDA #16 \ A >= 16, so set A to 16 as the maximum value of A .cont5 \ By this point, (A T) is a measurement of how dangerous \ the collision was, on a scale of 0 to 16, so now we \ convert that into the amount of spin to apply to our \ car PLP \ Restore the sign of dYawAngle, which we stored on the \ stack above, so the N flag is positive if the other \ car's yaw angle is larger (i.e. the other car is to \ the right of the player's car), or negative if the \ other car's yaw angle is smaller (i.e. the other car \ is to the left of the player's car) JSR Absolute16Bit \ Set the sign of (A T) to match the result of the \ subtraction above, so A is now in the range -16 to \ +16, with the sign reflecting the position of the \ other car: \ \ * -16 to 0 if the other car is to the left \ \ * 0 to +16 if the other car is to the right \ \ So we can use this to apply spin to our car, so we \ spin in the correct direction and with an amount \ that's proportional to the severity of the collision \ Fall through into SquealTyres to set spinYawAngleTop \ to the amount of yaw spin in A and make the sound of \ squealing tyres
Name: SquealTyres [Show more] Type: Subroutine Category: Driving model Summary: Make the tyres squeal
Context: See this subroutine on its own page References: This subroutine is called as follows: * CheckForCrash calls SquealTyres

Arguments: A The new value for spinYawAngleTop
.SquealTyres STA spinYawAngleTop \ Set spinYawAngleTop = A LDA #%10000000 \ Set bit 7 in tyreSqueal for both tyres, so they squeal STA tyreSqueal STA tyreSqueal+1 LDA #4 \ Make sound #4 (crash/contact) at the current volume JSR MakeSound-3 \ level RTS \ Return from the subroutine
Name: DrawObjectEdge (Part 1 of 5) [Show more] Type: Subroutine Category: Drawing objects Summary: Draw the specified edge of an object part Deep dive: Creating objects from edges
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawObjectEdges calls DrawObjectEdge * ProcessContact calls entry point DrawObjectEdge-1

This part of the routine calculates the pixel x-coordinate of the edge, plus the block number and pixel x-coordinate of the next edge (if there is one), so we can use them in the next call to DrawObjectEdge. Arguments: topTrackLine Top track line of the edge (higher value, 0 to 79) bottomTrackLine Bottom track line of the edge (lower value, 0 to 79) thisEdge This edge (as a scaled scaffold measurement) nextEdge The next edge (as a scaled scaffold measurement) xPixelCoord The pixel x-coordinate of the centre of the object colourData Colour data: * Bits 0-1 = logical fill colour * Bits 2-3 = logical edge colour * Bit 4 = if set and this is a left or right edge, then use the fill colour instead of the edge colour if this is an outside edge (i.e. a left edge in the left half of the screen, or a right edge in the right half of the screen), so the edge is effectively hidden A The fill colour to the right of the edge to draw: * For left edges: bits 0-1 contain the fill colour from bits 0-1 of the colour data * For right edges: contains 0 * For extra edges: bits 0-1 contain the fill colour from bits 0-1 of the colour data Y Edge type: * 0 = second or third edge in a four-edge object part (an "extra edge") * 1 = left edge * 2 = right edge rightOfEdge The fill byte to the right of the previous edge (or, if this is the first edge, the background colour to the left of the first edge) If this is a second call to DrawObjectEdge and we need to draw this edge in the same pixel byte as the previous edge, this contains the pixel byte from the previous call with the first edge already drawn blockNumber For extra or right edges only: the dash data block number of the previous edge drawn by DrawObjectEdge nextEdgeCoord For extra or right edges only: the pixel x-coordinate of the edge to draw (as returned by the previous call to DrawObjectEdge) nextBlockNumber For extra or right edges only: the data block number of the edge to draw (returned by the previous call to DrawObjectEdge) prevEdgeInByte Determines whether we have already inserted the previous edge into the pixel byte we are building * 0 = there is no other edge in the pixel byte we are building * Non-zero = the previous edge is already in the pixel byte we are building, and the current edge needs to go in the same byte edgePixelMask For extra or right edges only: * 0 = there is no other edge in the pixel byte we are building * Non-zero = the pixel mask of the edge that was drawn into rightOfEdge in the previous call Returns: blockNumber The dash data block number that was drawn into rightOfEdge The fill byte to the right of the edge we just drew: * If we just successfully drew an edge and drew the result on-screen, this contains the fill colour of the object * If we created a pixel byte but the next edge needs to be drawn in the same byte, this contains the pixel byte from this call edgePixelMask The pixel mask of the edge that was drawn, which will: * Be empty if the previous edge was already drawn and we do not need to share this pixel byte with the previous edge * Contain set pixels for the previous edge if both this edge and the previous edge need to share the same pixel byte nextEdgeCoord The pixel x-coordinate for the next edge nextBlockNumber The dash data block number for the next edge prevEdgeInByte The correct setting for the next edge: * 0 if the next edge is not in the same byte as the one we just drew * Non-zero (bit 7 is set) if the next edge is in the same byte as the one we just drew (in which case the screen has not been updated, and the next call to DrawObjectEdge needs to insert the next edge into the pixel byte in rightOfEdge, using the pixel mask in edgePixelMask) Other entry points: DrawObjectEdge-1 Contains an RTS
.DrawObjectEdge STY J \ Set J to the edge type in Y, so we can fetch it later LDX rightOfEdge \ Set leftOfEdge = rightOfEdge, so we effectively step STX leftOfEdge \ along the line, from the previous edge to this edge \ (so the fill byte to the right of the previous edge \ becomes the fill byte to the left of this edge) AND #3 \ Set X to bits 0-1 of A, which contains the logical TAX \ colour of the object's fill colour LDA objectPalette,X \ Set rightOfEdge to logical colour X from the object STA rightOfEdge \ palette, so we fill the object to the right of this \ edge with the colour in A LDA blockNumber \ Set prevBlockNumber to the dash data block number that STA prevBlockNumber \ was left over from the previous call to DrawObjectEdge \ so we can use it if this isn't a left edge LDA colourData \ Set X to bits 2-3 of colourData, which contains the AND #%00001100 \ logical colour of the edge we want to draw LSR A LSR A TAX LDA objectPalette,X \ Set edgePixel to logical colour X from the object STA edgePixel \ palette, which we will mask later to contain just the \ single pixel required to draw the edge LDA #0 \ Set P = 0, for use as the low byte of (Q P), in which STA P \ we are going to build the address we need to draw into \ in the dash data \ We now set thisEdge and blockNumber according to the \ edge type: \ \ * Left edge, set: \ \ thisEdge = xPixelCoord + thisEdge / 2 \ blockNumber = thisEdge / 4 \ \ * Right or extra edge, set: \ \ thisEdge = nextEdgeCoord \ blockNumber = blockNumberForNext CPY #1 \ If Y <> 1, jump to draw3 BNE draw3 \ If we get here then Y = 1, so we are drawing the left \ edge LDA thisEdge \ Set A to the scaled scaffold measurement for the left \ edge, which was passed to the routine in thisEdge \ We now set A = A / 2, retaining the sign in A and \ rounding towards zero BPL draw1 \ If A is positive, jump to draw1 SEC \ Set A = A / 2, inserting a set bit into bit 7 to ROR A \ retain the sign of A, and rounding the division up ADC #0 \ towards zero by adding bit 0 of A to the result JMP draw2 \ Jump to draw2 to skip the following instruction .draw1 LSR A \ Set A = A / 2, which will retain the sign of A as we \ know A is positive, rounding the result down towards \ zero .draw2 CLC \ Set thisEdge = A + xPixelCoord ADC xPixelCoord \ = thisEdge / 2 + xPixelCoord STA thisEdge LSR A \ Set blockNumber = A / 4 LSR A \ = thisEdge / 4 STA blockNumber JMP draw4 \ Jump to draw4 .draw3 \ We jump here if Y <> 1, i.e. Y = 0 or 2, so we are \ either drawing an extra edge or the right edge LDA nextBlockNumber \ Set blockNumber = nextBlockNumber STA blockNumber LDA nextEdgeCoord \ Set thisEdge = nextEdgeCoord STA thisEdge CPY #0 \ If Y <> 0, jump to draw7 BNE draw7 .draw4 \ We have now set thisEdge and blockNumber according to \ the edge type, so now we set nextEdgeCoord and \ nextBlockNumber as follows: \ \ nextEdgeCoord = xPixelCoord + nextEdge / 2 \ \ nextBlockNumber = nextEdgeCoord / 4 LDA nextEdge \ Set A to the scaled scaffold measurement for the next \ edge \ We now set A = A / 2, retaining the sign in A and \ rounding towards zero BPL draw5 \ If A is positive, jump to draw5 SEC \ Set A = A / 2, inserting a set bit into bit 7 to ROR A \ retain the sign of A, and rounding the division up ADC #0 \ towards zero by adding bit 0 of A to the result JMP draw6 \ Jump to draw6 to skip the following instruction .draw5 LSR A \ Set A = A / 2, which will retain the sign of A as we \ know A is positive, rounding the result down towards \ zero .draw6 CLC \ Set nextEdgeCoord = A + xPixelCoord ADC xPixelCoord \ = nextEdge / 2 + xPixelCoord STA nextEdgeCoord LSR A \ Set nextBlockNumber = A / 4 LSR A \ = nextEdgeCoord / 4 STA nextBlockNumber \ By this point we have: \ \ * Left edge: \ \ thisEdge = xPixelCoord + thisEdge / 2 \ blockNumber = thisEdge / 4 \ \ * Right or extra edge, set: \ \ thisEdge = nextEdgeCoord \ blockNumber = blockNumberForNext \ \ and we also have the following: \ \ nextEdgeCoord = xPixelCoord + nextEdge / 2 \ nextBlockNumber = nextEdgeCoord / 4 \ \ So we have: \ \ * leftOfEdge contains the fill colour of the object, \ or if the previous edge is within the same pixel \ byte and we are now drawing the next edge, it \ contains the pixel byte from the previous call, \ which contains the previous edge \ \ * rightOfEdge contains the fill colour, or (if we \ need to draw this edge in the same pixel byte as \ the previous edge), it contains the pixel byte \ from the previous call \ \ * edgePixel contains a four-pixel byte in the edge \ colour passed in bits 2-3 of colourData, which we \ will mask later to a single pixel \ \ * thisEdge contains the pixel x-coordinate of the \ edge to draw \ \ * blockNumber contains the dash data block number \ for the edge to draw (as each dash data block is \ four pixels wide) \ \ * nextEdgeCoord and nextBlockNumber contain the \ pixel x-coordinate and dash data block number of \ the next edge, ready to be used in the next call \ to DrawObjectEdge
Name: DrawObjectEdge (Part 2 of 5) [Show more] Type: Subroutine Category: Drawing objects Summary: Calculate the screen address for the edge we want to draw Deep dive: Creating objects from edges
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.draw7 LDA blockNumber \ Set A to the dash data block number for the edge to \ draw, which we stored in blockNumber in part 1 CMP #20 \ If blockNumber >= 20, set bit 0 of T, otherwise clear ROL T \ it, so bit 0 of T is clear if the edge is in the left \ half of the screen, and set if the edge is in the \ right half LSR A \ Set (A P) = (A P) >> 1 ROR P \ = (blockNumber 0) >> 1 \ = blockNumber * 128 \ = blockNumber * &80 CLC \ Set (Q P) = (A P) + dashData ADC #HI(dashData) \ STA Q \ This addition works because the low byte of dashData \ is zero \ So we now have: \ \ (Q P) = dashData + blockNumber * &80 \ \ which is the start address of the dash data block, \ because the dash data blocks occur every &80 bytes \ from dashData IF _ACORNSOFT OR _4TRACKS STA draw27+2 \ Modify the following instruction at draw27: LDA P \ STA draw27+1 \ LDX &3000,Y -> LDX #(Q P),Y \ \ This is pseudo-code, but it means we have modified the \ instruction to load the Y-th byte from the dash data \ block address we just calculated, i.e. load the Y-th \ byte of the dash data block for the edge to draw ENDIF LDX blockNumber \ Set X to the dash data block number for the edge to \ draw CPX #40 \ If blockNumber < 40 then blockNumber is a valid dash BCC draw8 \ data block number in the range 0 to 39, so jump to \ draw8 to keep going JMP draw32 \ Otherwise blockNumber is not a valid dash data block \ number and is off the right edge of the screen, so \ jump to draw32 to work out whether we need to fill \ the object all the way to the right edge of the screen .draw8 LDA bottomTrackLine \ Set A = bottomTrackLine, which is the number of the \ track line at the bottom of the object, and which is \ the same as the offset into the dash data block for \ the bottom edge CMP dashDataOffset,X \ If A >= the dash data offset for our dash data block, BCS draw9 \ then A is pointing to dash data, so jump to draw9 to \ skip the following instruction LDA dashDataOffset,X \ Set A to the dash data offset for our dash data block, \ so it points to the first byte of the block's dash \ data (i.e. the lowest byte of the dash data block \ on-screen) .draw9 STA blockOffset \ Set blockOffset = A, so blockOffset contains the dash \ data block offset for the bottom track line of the \ edge we want to draw CMP topTrackLine \ If A < topTrackLine, then the track lines are the BCC draw11 \ right way around, so jump to draw11 to keep going in \ part 3 CPY #1 \ If Y <> 1, then we are drawing either a right edge or BNE draw10 \ an extra edge, so jump to draw29 via draw10 to check \ whether we need to fill to the left of this edge, and \ then move on to the next edge \ If we get here then A >= topTrackLine and Y = 1, so \ we are drawing the left edge and the bottom track line \ is higher than the top track line, which means there \ is nothing to draw, so we return from the subroutine RTS \ Return from the subroutine .draw10 JMP draw29 \ Jump to draw29 to check whether we need to fill to the \ left of this edge, and then move on to the next edge
Name: DrawObjectEdge (Part 3 of 5) [Show more] Type: Subroutine Category: Drawing objects Summary: Construct a pixel byte for the edge we want to draw Deep dive: Creating objects from edges
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.draw11 LDA colourData \ If bit 4 of colourData is clear, jump to draw12 to AND #%00010000 \ keep the edge colour we set at the start of part 1 BEQ draw12 TYA \ If Y = 0, then we are drawing an extra edge, so jump BEQ draw12 \ to draw12 to keep the edge colour we set at the start \ of part 1 \ Otherwise bit 4 of colourData is set and this is a \ left or right edge, which means we use the fill colour \ instead of the edge colour, but only if this is an \ outside edge (i.e. a left edge in the left half of the \ screen, or a right edge in the right half of the \ screen), so the edge is effectively hidden by merging \ it into the object's fill EOR T \ Set A = bit 0 of Y EOR bit 0 of T AND #1 BEQ draw12 \ If A = 0, then one of the following is true: \ \ * Y = 2 (%10) and bit 0 of T = 0, in which case we \ are drawing a right edge in the left half of the \ screen \ \ * Y = 1 (%01) and bit 0 of T = 1, in which case we \ are drawing a left edge in the right half of the \ screen \ \ In either case, jump to draw12 to keep the colour we \ set in edgePixel at the start of part 1, as this is \ not an outside edge \ If we get here then bit 4 of colourData is set, and we \ are drawing a left or right edge as an outside edge, \ so we set the edge colour to the logical colour in \ bits 0-1 of colourData, i.e. the fill colour LDA colourData \ Set X to bits 0-1 of colourData, which contains the AND #%00000011 \ fill colour of the object part we are drawing TAX LDA objectPalette,X \ Set edgePixel to the fill colour, so the edge merges STA edgePixel \ into the object's background .draw12 LDA thisEdge \ Set A to thisEdge, which we set in part 1 to the \ pixel x-coordinate of the edge to draw AND #3 \ Set X = A mod 4, which is the number of the pixel of TAX \ the edge we want to draw within the four-pixel byte \ (i.e. 0 to 3, left to right) LDA yLookupLo+8,X \ Set A to the X-th pixel mask from yLookupLo+8, which \ is a pixel byte with the X-th pixel clear EOR #&FF \ Invert A so it contains a pixel byte with only the \ X-th pixel set AND edgePixel \ Apply the pixel mask to the edge colour in edgePixel, STA edgePixel \ so edgePixel now contains a pixel byte with only the \ X-th pixel set, and that pixel is set to the edge \ colour CPY #1 \ If Y >= 1, then we are drawing a left or right edge, BCS draw16 \ so jump to draw16 \ If we get here then Y = 0, so we are drawing one of \ the extra edges as part of a four-edge object part LDA pixelsToLeft,X \ Set A to the X-th pixel mask from pixelsToLeft, which \ is a pixel byte with all the pixels set to the left of \ the X-th pixel AND leftOfEdge \ Set these pixels to leftOfEdge, which is either the STA T \ fill colour of the object (if there is just this edge \ in the pixel byte), or it's the pixel byte with the \ previous edge already drawn (if we have both edges in \ the same byte) \ \ In either case, this fills the pixel byte with the \ correct contents to the left of this edge and stores \ them in T LDA rightOfEdge \ Set A to a pixel byte with the pixels to the right of AND pixelsEdgeRight,X \ the X-th pixel set to rightOfEdge, so this is the same \ thing but with the bytes to the right of the edge ORA T \ OR the two together so we have a pixel byte with the \ correct bytes to the left and right of this edge AND yLookupLo+8,X \ Apply the X-th pixel mask from yLookupLo+8, so this \ clears the X-th pixel in the pixel byte ORA edgePixel \ We set up edgePixel above to contain a single pixel \ for the edge in position X, set to the edge colour, \ so this sets the X-th pixel in A to the edge colour, \ so the pixel byte in A now contains the edge itself STA I \ A now contains a pixel byte with the correct bytes set \ to the left and right of the edge, plus the correct \ colour set for the edge pixel, so store this pixel \ byte in I LDA prevEdgeInByte \ If prevEdgeInByte = 0 then this edge is the first one BEQ draw13 \ in this byte, so jump to draw13 to potentially draw \ this edge using a quick-draw routine that doesn't \ worry about any existing background content (as this \ is always overwritten by the extra parts in the middle \ of the object part) \ If we get here then prevEdgeInByte is non-zero, so \ this pixel byte contains both the previous edge and \ this edge LDA #0 \ Set prevEdgeInByte = 0, to reset it for the next call STA prevEdgeInByte \ to DrawObjectEdge, as we have moved on to the next \ edge since it was made non-zero LDA edgePixelMask \ Set L to the pixel mask for the previous edge, which STA L \ we set in the previous call to DrawObjectEdge to have \ bits set for the pixels to the right of the previous \ edge, so once the extra edges are done we can use it \ when filling the last part of the extra section EOR #&FF \ Set A to the inverse, so it has bits set for the \ pixels of the previous edge and everything to its left AND I \ Insert the pixels from I so the left part of our pixel \ byte is given the correct edge and everything to the \ left JMP draw17 \ Jump to draw17 to draw the edge using this pixel byte .draw13 \ If we get here then we are not sharing this pixel byte \ with the previous edge, so we can use a quick-draw \ routine that doesn't worry about any existing \ background content (as this is an extra edge) LDX blockNumber \ If blockNumber <> nextBlockNumber, then the next edge CPX nextBlockNumber \ isn't in the same dash data block (i.e. in the same BNE draw14 \ column), so jump to draw14 to draw this edge \ Otherwise the next edge is in the same byte as this \ one, so we need to return from the subroutine via \ draw29 (i.e. first check whether we need to fill to \ the left of this edge, and then return) LDA I \ Store the pixel byte in rightOfEdge so the next call STA rightOfEdge \ to DrawObjectEdge can add the next edge to it JMP draw29 \ Jump to draw29 to check whether we need to fill to the \ left of this edge, and then move on to the next edge .draw14 \ We now draw this edge using a quick-draw routine that \ doesn't worry about any existing background content LDA I \ Set A to the pixel byte for the edge we want to draw BNE draw15 \ If the pixel byte is non-zero, jump to draw15 to skip \ the following instruction LDA #&55 \ Set A = &55 to use as the value of WW below (&55 in \ the screen buffer represents colour 0, or black) .draw15 LDY topTrackLine \ Set Y = topTrackLine, which is the number of the \ track line at the top of the object, which is the \ same as the offset into the dash data block for the \ top edge JMP DrawEdge \ Jump to DrawEdge to draw the edge from topTrackLine \ down to blockOffset, which we set in part 2 to the \ bottom track line of the edge we want to draw, and \ rejoin the routine at draw29 for the next edge .draw16 \ If we get here then we are drawing either a left or \ right edge, and Y is 1 or 2 respectively BNE draw18 \ We did a CPY #1 before jumping here, so this jumps to \ draw18 if Y <> 1, i.e. Y = 2, so we jump to draw18 if \ we are drawing a right edge \ If we get here then we are drawing a left edge in \ pixel X within the pixel byte, so we now calculate the \ pixel byte for the left edge LDA pixelsToLeft,X \ Set A to the X-th pixel mask from pixelsToLeft, which \ is a pixel byte with all the pixels set to the left of \ the X-th pixel - in other words, with all the pixels \ outside of the object (to the left of the left edge) \ set STA L \ Store this pixel mask in L, so L contains a pixel mask \ containing the pixels to the left of the left edge EOR #&FF \ Invert A so it contains a pixel byte with the X-th \ pixel set, plus all the pixels to the right - in other \ words, with the edge and the inside of the object set AND rightOfEdge \ Set the object pixels to rightOfEdge, which contains \ the fill colour for the object (as at this point we \ won't be sharing a pixel byte with the previous edge, \ as there is no previous edge) AND yLookupLo+8,X \ Apply the X-th pixel mask from yLookupLo+8, so this \ clears the X-th pixel in the pixel byte ORA edgePixel \ We set up edgePixel above to contain a single pixel \ for the edge in position X, set to the edge colour, \ so this sets the X-th pixel in A to the edge colour, \ so the pixel byte in A now contains the edge itself .draw17 \ If we get here then we are either drawing a left edge, \ or this is a four-edge object part and we are sharing \ this pixel byte with the previous edge LDX blockNumber \ If blockNumber <> nextBlockNumber, then the next edge CPX nextBlockNumber \ isn't in the same dash data block (i.e. in the same BNE draw19 \ column) as this one, so jump to draw19 to draw this \ edge \ If we get here then: \ \ * The next edge is in the same pixel byte as the \ edge we are currently drawing, so we don't draw \ this byte yet, but instead return from the routine \ so the next call to DrawObjectEdge can pick up the \ baton and insert the next edge into the byte we \ just created \ \ * The C flag is set, as the above comparison was \ blockNumber = nextBlockNumber STA rightOfEdge \ Set rightOfEdge to the pixel byte in A, ready for \ the next call to DrawObjectEdge to pick it up LDA L \ Set edgePixelMask to the pixel mask in L, which STA edgePixelMask \ contains the pixels to the left of the left edge, to \ pass to the next call to DrawObjectEdge ROR prevEdgeInByte \ Set bit 7 of prevEdgeInByte, so it is non-zero for the \ next call to DrawObjectEdge, to indicate that the next \ edge needs to be inserted into the pixel byte that we \ just built, alongside the current edge RTS \ Return from the subroutine .draw18 \ If we get here then we are drawing a right edge in \ pixel X within the pixel byte, so we now calculate the \ pixel byte for the right edge \ \ This routine draws the right edge in the pixel byte, \ making sure we keep any previous edge that's already \ drawn in the same pixel byte LDA edgePixelMask \ Set A to the edge pixel mask of the previous edge, \ which will: \ \ * Be empty if the previous edge was already drawn \ and we do not need to share this pixel byte with \ the previous edge \ \ * Contain set pixels for the previous edge if both \ this edge and the previous edge need to share the \ same pixel byte ORA pixelsToRight,X \ The pixelsToRight table contains pixel bytes with all \ the pixels set to the right of the X-th pixel, so this \ sets all the pixels to the right of the X-th pixel \ A now contains a pixel byte with pixels set to the \ right of this edge, and if the previous edge is in the \ same byte, pixels are also set to the left of that \ edge STA L \ Store this pixel mask in L so we can use it in part 4, \ so L contains a pixel mask containing the pixels to \ the right of the right edge (and the left of the \ previous edge, if it's in the same byte) EOR #&FF \ Invert A so it contains the pixels of this edge, plus \ the previous edge if this was drawn in the same byte AND leftOfEdge \ Set these pixels to leftOfEdge, which is either the \ fill colour of the object (if there is just this edge \ in the pixel byte), or it's the pixel byte of the \ previous edge (if we have both edges in the same byte) \ \ In either case, this fills the pixel byte with the \ correct contents AND yLookupLo+8,X \ Apply the X-th pixel mask from yLookupLo+8, so this \ clears the X-th pixel in the pixel byte ORA edgePixel \ We set up edgePixel above to contain a single pixel \ for the edge in position X, set to the edge colour, \ so this sets the X-th pixel in A to the edge colour, \ so the pixel byte in A now contains the edge itself \ So we now have a pixel byte in A that contains this \ edge in the correct colour, and if this pixel byte \ also contains the previous edge, that's in there too
Name: DrawObjectEdge (Part 4 of 5) [Show more] Type: Subroutine Category: Drawing objects Summary: Draw the edge into the screen buffer, merging with any content already in the buffer Deep dive: Creating objects from edges
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.draw19 \ By this point, we have the following: \ \ * A contains the pixel byte we need to use to draw \ this edge (incorporating the previous edge if it's \ close enough to to be in the same pixel byte) \ \ * L contains a pixel mask containing set pixels for \ those pixels outside of the object and its fill, \ so to the left of the left edge, or to the right \ of the right edge, or to the left and right \ of pixel byte that contains two edges \ \ * (Q P) contains the address of the dash data block \ containing the edge we want to draw \ \ * blockOffset contains the dash data block offset \ for the bottom track line of the edge we want to \ draw \ \ * topTrackLine contains the number of the track line \ at the top of the edge we want to draw \ \ So now we actually draw the edge STA I \ Set I to the pixel byte for the edge we want to draw IF _ACORNSOFT OR _4TRACKS BNE draw20 \ If the pixel byte is non-zero, jump to draw20 to skip \ the following instruction LDA #&55 \ Set A = &55 to use as the value of WW below (&55 in \ the screen buffer represents colour 0, or black) .draw20 STA WW \ Set WW to the pixel byte (or &55 if the pixel byte is \ zero) LDA #0 \ Set edgePixelMask = 0, so the next call to STA edgePixelMask \ DrawObjectEdge ignores any edges from this call (as \ we are about to draw them on-screen) LDY blockOffset \ Set W to the current screen buffer byte at the bottom LDA (P),Y \ of the edge that we want to draw STA W LDA #&AA \ Replace the byte at the bottom of the edge with &AA, STA (P),Y \ to use as a marker LDY topTrackLine \ Set Y = topTrackLine, which is the number of the \ track line at the top of the object, which is the \ same as the offset into the dash data block for the \ top edge JMP draw24 \ Jump into the following loop at the entry point draw24 \ to draw the edge from the top byte to the bottom byte \ \ We jump into the loop with the following set: \ \ * Y contains the offset of the top of the edge we \ want to draw, which we now use as a loop counter \ to work our way from the top of the edge to the \ bottom (i.e. Y gets decremented as we draw each \ pixel byte) \ \ * The bottom track line of the edge contains the \ marker byte &AA \ \ * W contains the original byte that was in the \ marker's location .draw21 \ We loop back here if we fetch the next byte down from \ the screen buffer into A and find that it is non-zero CMP #&55 \ If the current byte is &55, then this represents black BNE draw22 \ (colour 0), so set A = 0 so it's the correct pixel LDA #0 \ byte for the current buffer contents .draw22 AND L \ The bit mask in L contains set pixels for those \ outside of the edge and its fill, and clear pixels for \ the edge and object fill, so this clears the pixels \ where we want to draw the edge and object fill ORA I \ Replace those cleared bits with the edge and fill that \ we want to draw BNE draw23 \ If the result is non-zero, then jump to draw23 to skip \ the following instruction LDA #&55 \ The result is zero, i.e. colour 0 (black), so set \ A = &55, which is the value we use to represent \ colour 0 (black) in the screen buffer .draw23 STA (P),Y \ Draw the resulting pixel byte into the screen buffer \ by writing it to the Y-th byte of the relevant dash \ data block DEY \ Decrement the track line counter in Y to move down to \ the next pixel line on-screen .draw24 \ This is the entry point for the loop, which is between \ draw21 and the loop's end logic at draw28 LDA (P),Y \ If the current byte in the screen buffer is non-zero, BNE draw28 \ then it is not empty, so jump to draw28 to merge the \ pixel byte with the non-empty byte that's already in \ the screen buffer .draw25 JSR GetColour \ The current byte in the screen buffer is zero, which \ means it should inherit the colour of the byte to the \ left, so call GetColour to work out what this byte's \ colour would be on-screen, and put it into A AND L \ The bit mask in L contains set pixels for those \ outside of the edge and its fill, and clear pixels for \ the edge and object fill, so this clears the pixels \ where we want to draw the edge and object fill ORA I \ Replace those cleared bits with the edge and fill that \ we want to draw BNE draw26 \ If the result is non-zero, then jump to draw23 to skip \ the following instruction LDA #&55 \ The result is zero, i.e. colour 0 (black), so set \ A = &55, which is the value we use to represent \ colour 0 (black) in the screen buffer .draw26 STA (P),Y \ Draw the resulting pixel byte into the screen buffer \ by writing it to the Y-th byte of the relevant dash \ data block DEY \ Decrement the track line counter in Y to move down to \ the next pixel line on-screen .draw27 LDX &3000,Y \ Set X to the Y-th byte in the screen buffer, which is \ the next byte down the screen after the one we just \ draw \ \ Gets modified at the start of part 2 as follows: \ \ LDX &3000,Y -> LDX #(Q P),Y \ \ In other words, we have modified the instruction to \ load the Y-th byte from the dash data block address \ for the edge we are drawing, i.e. load the Y-th byte \ of dash data block blockNumber BEQ draw26 \ If the next byte down the screen is zero, then loop \ back to draw26 to draw the edge in this byte as well, \ so we keep drawing the edge downwards while the \ existing contents of the screen buffer are empty TXA \ The next byte down the screen is non-zero, so copy the \ value into A .draw28 CMP #&AA \ If this is not the marker for the bottom of the edge, BNE draw21 \ loop back to draw21 to draw the next pixel byte over \ the top of this non-empty byte in the screen buffer \ We just reached a value that matches the marker at the \ bottom of the edge, which is either our marker or a \ valid entry in the screen buffer that happens to have \ this value LDA #0 \ Overwrite the &AA value with 0, though this appears to STA (P),Y \ have no effect, as if we loop back to draw25 in the \ following conditional, this value will be overwritten, \ and if we fall through to the LDA W below, it will \ be overwritten there CPY blockOffset \ If Y <> blockOffset then this can't be our marker, as BNE draw25 \ the marker is on line blockOffset, so jump back to \ draw25 to merge this byte with the correct background \ colour \ If we get here then we have reached our marker at the \ bottom of the edge LDA W \ Restore the entry in the dash data block that we STA (P),Y \ overwrote with the marker, whose original contents we \ stored in W LDX J \ If the edge type in J = 1, then we have just drawn the CPX #1 \ left edge, so jump to draw31 to return from the BEQ draw31 \ subroutine as we are done drawing \ If we get here then we have just drawn an extra or \ right edge, so we need to fill the next column to the \ right of the edge we just drew (i.e. in the next block \ along), so that the background colour is restored \ after the object INC blockNumber \ Fill the column to the right of the edge we just drew, JSR FillAfterObject \ so the correct background colour is shown to the right DEC blockNumber \ of the object part ELIF _SUPERIOR OR _REVSPLUS LDA #0 \ Set edgePixelMask = 0, so the next call to STA edgePixelMask \ DrawObjectEdge ignores any edges from this call (as \ we are about to draw them on-screen) LDY topTrackLine \ Set Y = topTrackLine, which is the number of the \ track line at the top of the object, which is the \ same as the offset into the dash data block for the \ top edge JMP sraw6 \ Jump into the following loop at the entry point sraw6 \ to draw the edge from the top byte to the bottom byte \ \ We jump into the loop with the following set: \ \ * Y contains the offset of the top of the edge we \ want to draw, which we now use as a loop counter \ to work our way from the top of the edge to the \ bottom (i.e. Y gets decremented as we draw each \ pixel byte) \ \ * The bottom track line of the edge contains the \ marker byte &AA \ \ * W contains the original byte that was in the \ marker's location .sraw1 LDA (P),Y \ If the current byte in the screen buffer is zero, then BEQ sraw2 \ jump to sraw2 to work out what colour it would be \ on-screen CMP #&55 \ If the current byte is not &55, then the current byte BNE sraw3 \ in the scren buffer contains something, so jump to \ sraw3 with the contents in A LDA I \ The current byte in the screen buffer is &55, or \ black, so set A to the pixel byte we want to draw BNE sraw5 \ If we want to draw something that isn't black (i.e. \ which is non-zero), jump to sraw5 to store it in the \ screen buffer BEQ sraw4 \ If we get here then we want to draw a black pixel \ byte, so jump to sraw4 to store &55 in the screen \ buffer (which is the screen buffer value for black) .sraw2 JSR GetColourSup \ The current byte in the screen buffer is zero, which \ means it should inherit the colour of the byte to the \ left, so call GetColourSup to work out what this \ byte's colour would be on-screen, and put it into A .sraw3 \ At this point A contains the byte in the screen \ buffer, converted into the byte it represents when \ the buffer is copied to the screen AND L \ The bit mask in L contains set pixels for those \ outside of the edge and its fill, and clear pixels for \ the edge and object fill, so this clears the pixels \ where we want to draw the edge and object fill ORA I \ Replace those cleared bits with the edge and fill that \ we want to draw BNE sraw5 \ If the result is non-zero, then jump to sraw5 to skip \ the following instruction .sraw4 LDA #&55 \ Set A = &55, which is the value we use to represent \ colour 0 (black) in the screen buffer .sraw5 STA (P),Y \ Draw the resulting pixel byte into the screen buffer \ by writing it to the Y-th byte of the relevant dash \ data block DEY \ Decrement the track line counter in Y to move down to \ the next pixel line on-screen .sraw6 \ This is the entry point for the loop, which is between \ sraw1 and the loop's end logic in the next instruction CPY blockOffset \ If Y <> blockOffset then this can't be our marker, as BNE sraw1 \ the marker is on line blockOffset, so jump back to \ sraw1 to merge this byte with the correct background \ colour LDX J \ If the edge type in J = 1, then we have just drawn the CPX #1 \ left edge, so jump to draw31 to return from the BEQ draw31 \ subroutine as we are done drawing \ If we get here then we have just drawn an extra or \ right edge, so we need to fill the next column to the \ right of the edge we just drew (i.e. in the next block \ along), so that the background colour is restored \ after the object INC blockNumber \ Fill the column to the right of the edge we just drew, JSR FillAfterObjectSup \ so the correct background colour is shown to the right DEC blockNumber \ of the object part ENDIF
Name: DrawObjectEdge (Part 5 of 5) [Show more] Type: Subroutine Category: Drawing objects Summary: Fill the object if required and loop back for the next edge Deep dive: Creating objects from edges
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.draw29 \ We have finished drawing the edge, so now we need to \ fill the inside of the object part, from the previous \ block to the current one LDA prevBlockNumber \ If prevBlockNumber < 40, then it is a valid block CMP #40 \ number in the range 0 to 39, so jump to draw30 to skip BCC draw30 \ the following instruction and fill the object from \ this block onwards \ If we get here then prevBlockNumber is off-screen, \ which means it must be off the left edge of the screen \ (as we are working from left to right), so we now need \ to fill from the left edge of the screen to the edge \ we just drew LDA #&FF \ Set prevBlockNumber = -1, so the following subtraction STA prevBlockNumber \ sets: \ \ A = blockNumber - prevBlockNumber - (1 - C) \ = blockNumber - -1 - 1 \ = blockNumber \ \ so the following fills this many blocks, which means \ it fills all the blocks from the left edge of the \ screen to the edge we just drew .draw30 LDA blockNumber \ Set A = blockNumber - prevBlockNumber - (1 - C) CLC \ = blockNumber - prevBlockNumber - 1 SBC prevBlockNumber BEQ draw31 \ If A <= 0, then: BMI draw31 \ \ blockNumber - prevBlockNumber - 1 <= 0 \ \ blockNumber - prevBlockNumber <= 1 \ \ so the current block and the previous block are either \ the same block or neighbours, in which case there is \ no gap to fill between the edges, so jump to draw31 to \ return from the subroutine as we are done drawing \ If we get here then A > 0, so from the above: \ \ blockNumber - prevBlockNumber > 1 \ \ so there is at least one full block between the \ current block and the previous block \ \ We therefore need to fill this gap with the relevant \ fill colour, with the number of blocks between the \ two edges given in A (without including the edges \ themselves) TAX \ Set X = A, so X contains the number of blocks we need \ to fill to the left of the edge we just drew JSR FillInsideObject \ Fill the inside of the object (i.e. all the blocks \ between the previous edge and the edge we just drew) .draw31 RTS \ Return from the subroutine .draw32 \ We jump here if the block number in blockNumber \ is >= 40, which means we the edge we are trying to \ draw is off the right of the screen, so now we need \ to work out whether we need to fill the object up to \ the edge of the screen LDY J \ If the edge type in J = 1, then we are drawing the CPY #1 \ left edge, so jump to draw31 to return from the BEQ draw31 \ subroutine as the whole object part is off-screen LDA prevBlockNumber \ If prevBlockNumber >= 40, then the dash data block CMP #40 \ number from the previous call to DrawObjectEdge is BCS draw31 \ also past the right edge of the screen, so jump to \ draw31 to return from the subroutine as the whole \ part between this edge and the previous edge is \ off-screen \ Otherwise we are drawing a right edge or an extra edge \ and the previous call to DrawObjectEdge was on-screen, \ so we need to fill between the previous edge and the \ right edge of the screen LDA #40 \ Set blockNumber = 40 to represent the block beyond the STA blockNumber \ right edge of the screen in the calculation at draw30, \ which works out whether to call FillInsideObject to \ fill from the previous edge to this block number BNE draw30 \ Jump to draw30 (this BNE is effectively a JMP as A is \ never zero)
Name: GetTyreDashEdge [Show more] Type: Subroutine Category: Dashboard Summary: Copy the pixel bytes along the tyre and dashboard edges so they can be feathered
Context: See this subroutine on its own page References: This subroutine is called as follows: * GetTyreDashEdges calls GetTyreDashEdge

Modify the FillAfterObject routine before calling it. Arguments: X Sets the destination address for the copy Y Alters the routine flow to either fill or copy A The value to use when copying black pixel bytes
IF _ACORNSOFT OR _4TRACKS .GetTyreDashEdge STX edge7+1 \ Modify the following instruction at edge7: \ \ STA (P),Y -> STA (R),Y when X = LO(R) \ \ STA (P),Y -> STA (P),Y when X = LO(P) STY edge11+1 \ Modify the following instruction at edge11: \ \ BNE edge3 -> BNE edge1 when Y = &DF \ \ BNE edge3 -> BNE edge3 when Y = &E7 STA edge6+1 \ Modify the following instruction at edge6: \ \ LDA #&55 -> LDA #0 when A = 0 \ \ LDA #&55 -> LDA #&55 when A = &55 \ Fall through into FillAfterObject to copy the edge \ data to the location specified in (S R) ENDIF
Name: FillAfterObject [Show more] Type: Subroutine Category: Drawing objects Summary: Fill the block to the right of an object Deep dive: Creating objects from edges
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawObjectEdge (Part 4 of 5) calls FillAfterObject

Arguments: blockNumber The dash data block number to draw in (i.e. the block to the right of the object) blockOffset The dash data offset for the bottom of the edge to draw topTrackLine Top track line number, i.e. the number of the start byte in the dash data block
IF _ACORNSOFT OR _4TRACKS .FillAfterObject LDA blockNumber \ Set A to the dash data block number in blockNumber CMP #40 \ If A >= 40 then this is not a valid dash data block BCS edge12 \ number, so jump to edge12 to return from the \ subroutine \ We now calculate the start address of dash data block \ A, which will be at dashData + &80 * A (because the \ dash data blocks occur every &80 bytes from dashData) \ \ We do this using the following simplification: \ \ dashData + &80 * A \ = dashData + 256 / 2 * A \ = HI(dashData) << 8 + LO(dashData) + A << 7 \ \ LO(dashData) happens to be zero (as dashData = &3000), \ so we can keep going: \ \ = HI(dashData) << 8 + A << 7 \ = (HI(dashData) << 1 + A) << 7 \ = ((HI(dashData) << 1 + A) << 8) >> 1 \ \ In other words, if we build a 16-bit number with the \ high byte set to HI(dashData) << 1 + A, and then shift \ the whole thing right by one place, we have our result \ \ We do this below, storing the 16-bit number in (Q P) CLC \ Set A = A + HI(dashData) << 1 ADC #HI(dashData)<<1 \ \ so our 16-bit number is (A 0), and we want to shift \ right by one place LSR A \ Shift (A 0) right by 1, shifting bit 0 of A into the \ C flag STA Q \ Set Q = A, to store the high byte of the result in Q STA edge9+2 \ Modify the high byte of the address in the instruction \ at edge9 to Q LDA #0 \ Shift the C flag into bit 7 of A, so A now contains ROR A \ the low byte of our result STA P \ Set P = A, to store the low byte of the result in P, \ giving the result we wanted in (Q P) STA edge9+1 \ Modify the low byte of the address in the instruction \ at edge9 to P, so if we have the following: \ \ LDX &3000,Y -> LDX #(Q P),Y \ \ This is pseudo-code, but it means we have modified the \ instruction to load the Y-th byte from the dash data \ block address we just calculated, i.e. load the Y-th \ byte of dash data block A (i.e. dash data block \ blockNumber) LDY blockOffset \ Set Y to the dash data offset for the edge to draw LDA (P),Y \ Set W to the byte in the dash data offset from this STA W \ block, which is the byte before the actual dash data LDA #&AA \ Store &AA in this byte, so it can act as a marker for STA (P),Y \ when we work our way through the data below LDY topTrackLine \ Set Y to the number of the top track line, so we work \ down from this byte within the data block, moving down \ in memory until we reach the marker \ \ So we are working down the screen, going backwards in \ memory from byte topTrackLine to the marker that we \ just placed at the start of the dash data JMP edge4 \ Jump into the following loop at the entry point edge4 \ to draw the fill from the top byte to the bottom byte .edge1 \ This part of the loop, between edge1 and edge5, is \ only used by the GetTyreDashEdge routine, which \ modifies the loop to copy pixels instead of filling \ them CMP #&55 \ If the current byte is &55, then this represents black BNE edge2 \ (colour 0), so set A = 0 so it's the correct pixel LDA #0 \ byte for the current buffer contents .edge2 STA (R),Y \ Store the pixel byte we fetched from the screen buffer \ in the Y-th byte of (S R), which copies the byte from \ the screen buffer into the address set up in the \ CopyTyreDashEdges routine (i.e. this copies the edges \ into tyreRightEdge or dashRightEdge) .edge3 DEY \ Decrement the byte counter to move down the screen \ within the dash data block .edge4 LDA (P),Y \ Fetch the Y-th byte from the dash data block BNE edge10 \ If the current byte in the screen buffer is non-zero, \ then it is not empty, so jump to edge10 to move on to \ the next byte, as we only need to fill empty bytes .edge5 JSR GetColour \ The current byte in the screen buffer is zero, which \ means it should inherit the colour of the byte to the \ left, so call GetColour to work out what this byte's \ colour would be on-screen, and put it into A BNE edge7 \ If the colour byte is non-zero, skip the following \ instruction .edge6 LDA #&55 \ Set A = &55, which is the value we use to represent \ colour 0 (black) in the screen buffer \ \ Gets modified by the GetTyreDashEdge routine: \ \ * LDA #0 when GetTyreDashEdge is called with \ A = 0 \ \ * LDA #&55 when GetTyreDashEdge is called with \ A = &55 .edge7 STA (P),Y \ Draw the resulting pixel byte into the screen buffer \ by writing it to the Y-th byte of the relevant dash \ data block \ \ Gets modified by the GetTyreDashEdge routine: \ \ * STA (R),Y when GetTyreDashEdge is called with \ X = LO(R) \ \ * STA (P),Y when GetTyreDashEdge is called with \ X = LO(P) .edge8 DEY \ Decrement the byte counter to move down the screen \ within the dash data block .edge9 LDX &3000,Y \ Set X to the Y-th byte in the screen buffer, which is \ the next byte down the screen after the one we just \ draw \ \ Gets modified at the start of part 2 as follows: \ \ LDX &3000,Y -> LDX #(Q P),Y \ \ In other words, we have modified the instruction to \ load the Y-th byte from the dash data block address \ for the edge we are drawing, i.e. load the Y-th byte \ of dash data block blockNumber BEQ edge7 \ If the next byte down the screen is zero, then loop \ back to edge7 to draw the edge in this byte as well, \ so we keep drawing the edge downwards while the \ existing contents of the screen buffer are empty TXA \ The next byte down the screen is non-zero, so copy the \ value into A .edge10 CMP #&AA \ Check to see if the next byte is the marker for the \ bottom of the edge .edge11 BNE edge3 \ If this is not the marker for the bottom of the edge, \ loop back to edge3 to draw the next pixel byte over \ the top of this non-empty byte in the screen buffer \ \ Gets modified by the GetTyreDashEdge routine: \ \ * BNE edge1 when GetTyreDashEdge is called with \ Y = &DF \ \ * BNE edge3 when GetTyreDashEdge is called with \ Y = &E7 LDA #0 \ Overwrite the &AA value with 0 to remove the marker STA (P),Y CPY blockOffset \ If Y <> blockOffset then this can't be our marker, as BNE edge5 \ the marker is on line blockOffset, so jump back to \ edge5 to merge this byte with the correct background \ colour \ If we get here then we have reached our marker at the \ bottom of the edge LDA W \ Restore the entry in the dash data block that we STA (P),Y \ overwrote with the marker, whose original contents we \ stored in W .edge12 RTS \ Return from the subroutine ENDIF
Name: GetTyreDashEdgeSup [Show more] Type: Subroutine Category: Dashboard Summary: Copy the pixel bytes along the tyre and dashboard edges so they can be feathered
Context: See this subroutine on its own page References: This subroutine is called as follows: * GetTyreDashEdges calls GetTyreDashEdgeSup

Modify the FillAfterObject routine before calling it. Arguments: X Sets the destination address for the copy Y Alters the routine flow to either fill or copy A The value to use when copying black pixel bytes
IF _SUPERIOR OR _REVSPLUS .GetTyreDashEdgeSup STX sedg7+1 \ Modify the following instruction at sedg7: \ \ STA (P),Y -> STA (R),Y when X = LO(R) \ \ STA (P),Y -> STA (P),Y when X = LO(P) STY sedg5+1 \ Modify the following instruction at sedg5: \ \ BNE sedg8 -> BNE sedg1 when Y = &EF \ \ BNE sedg8 -> BNE sedg8 when Y = &09 STA sedg6+1 \ Modify the following instruction at sedg6: \ \ LDA #&55 -> LDA #0 when A = 0 \ \ LDA #&55 -> LDA #&55 when A = &55 \ Fall through into FillAfterObjectSup to copy the edge \ data to the location specified in (S R) ENDIF
Name: FillAfterObjectSup [Show more] Type: Subroutine Category: Drawing objects Summary: Fill the block to the right of an object Deep dive: Creating objects from edges
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawObjectEdge (Part 4 of 5) calls FillAfterObjectSup

Arguments: blockNumber The dash data block number to draw in (i.e. the block to the right of the object) blockOffset The dash data offset for the bottom of the edge to draw topTrackLine Top track line number, i.e. the number of the start byte in the dash data block
IF _SUPERIOR OR _REVSPLUS .FillAfterObjectSup LDA blockNumber \ Set A to the dash data block number in blockNumber CMP #40 \ If A >= 40 then this is not a valid dash data block BCS sedg10 \ number, so jump to sedg10 to return from the \ subroutine \ We now calculate the start address of dash data block \ A, which will be at dashData + &80 * A (because the \ dash data blocks occur every &80 bytes from dashData) \ \ We do this using the following simplification: \ \ dashData + &80 * A \ = dashData + 256 / 2 * A \ = HI(dashData) << 8 + LO(dashData) + A << 7 \ \ LO(dashData) happens to be zero (as dashData = &3000), \ so we can keep going: \ \ = HI(dashData) << 8 + A << 7 \ = (HI(dashData) << 1 + A) << 7 \ = ((HI(dashData) << 1 + A) << 8) >> 1 \ \ In other words, if we build a 16-bit number with the \ high byte set to HI(dashData) << 1 + A, and then shift \ the whole thing right by one place, we have our result \ \ We do this below, storing the 16-bit number in (Q P) CLC \ Set A = A + HI(dashData) << 1 ADC #HI(dashData)<<1 \ \ so our 16-bit number is (A 0), and we want to shift \ right by one place LSR A \ Shift (A 0) right by 1, shifting bit 0 of A into the \ C flag STA Q \ Set Q = A, to store the high byte of the result in Q LDA #0 \ Shift the C flag into bit 7 of A, so A now contains ROR A \ the low byte of our result STA P \ Set P = A, to store the low byte of the result in P, \ giving the result we wanted in (Q P) LDY topTrackLine \ Set Y to the number of the top track line, so we work \ down from this byte within the data block, moving down \ in memory until we reach the marker \ \ So we are working down the screen, going backwards in \ memory from byte topTrackLine to the marker that we \ just placed at the start of the dash data JMP sedg9 \ Jump into the following loop at the entry point sedg9 \ to draw the fill from the top byte to the bottom byte .sedg1 \ This part of the loop, between segd1 and segd5, is \ only used by the GetTyreDashEdgeSup routine, which \ modifies the loop to copy pixels instead of filling \ them CMP #&55 \ If the current byte is &55, then this represents black BNE sedg2 \ (colour 0), so set A = 0 so it's the correct pixel LDA #0 \ byte for the current buffer contents .sedg2 STA (R),Y \ Store the pixel byte we fetched from the screen buffer \ in the Y-th byte of (S R), which copies the byte from \ the screen buffer into the address set up in the \ CopyTyreDashEdges routine (i.e. this copies the edges \ into tyreRightEdge or dashRightEdge) .sedg3 DEY \ Decrement the track line counter in Y to move down to \ the next pixel line on-screen CPY blockOffset \ If Y = blockOffset then we have reached the bottom of BEQ sedg10 \ the objec, so jump back to sedg10 to return from the \ subroutine .sedg4 LDA (P),Y \ Fetch the current byte from the screen buffer .sedg5 BNE sedg8 \ If the current byte in the screen buffer is non-zero, \ then it is not empty, so jump to sedg8 to move on to \ the next byte, as we only need to fill empty bytes \ \ Gets modified by the GetTyreDashEdge routine: \ \ * BNE sedg1 when GetTyreDashEdge is called with \ Y = &EF \ \ * BNE sedg8 when GetTyreDashEdge is called with \ Y = &09 JSR GetColourSup \ The current byte in the screen buffer is zero, which \ means it should inherit the colour of the byte to the \ left, so call GetColourSup to work out what this \ byte's colour would be on-screen, and put it into A BNE sedg7 \ If the result is non-zero, then jump to sedg7 to skip \ the following instruction .sedg6 LDA #&55 \ Set A = &55, which is the value we use to represent \ colour 0 (black) in the screen buffer \ \ Gets modified by the GetTyreDashEdge routine: \ \ * LDA #0 when GetTyreDashEdge is called with \ A = 0 \ \ * LDA #&55 when GetTyreDashEdge is called with \ A = &55 .sedg7 STA (P),Y \ Draw the resulting pixel byte into the screen buffer \ by writing it to the Y-th byte of the relevant dash \ data block \ \ Gets modified by the GetTyreDashEdge routine: \ \ * STA (R),Y when GetTyreDashEdge is called with \ X = LO(R) \ \ * STA (P),Y when GetTyreDashEdge is called with \ X = LO(P) .sedg8 DEY \ Decrement the track line counter in Y to move down to \ the next pixel line on-screen .sedg9 \ This is the entry point for the loop, which is between \ sedg4 and the loop's end logic in the next instruction CPY blockOffset \ If Y <> blockOffset then we haven't reached the bottom BNE sedg4 \ of the object yet, so jump back to sedg4 to fill the \ next byte down .sedg10 RTS \ Return from the subroutine ENDIF
Name: DrawEdge [Show more] Type: Subroutine Category: Drawing objects Summary: Draw an edge, overwriting whatever is already on-screen
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawObjectEdge (Part 3 of 5) calls DrawEdge

This routine rejoins the DrawObjectEdge routine to move on to the next edge in the current object part. Arguments: (Q P) The address of the dash data block to draw in Y The top track line blockOffset The bottom track line A The pixel byte for the edge
.dred1 STA (P),Y \ Draw the pixel byte into the screen buffer by writing \ it to the Y-th byte of the relevant dash data block DEY \ Decrement the track line counter in Y to move down to \ the next pixel line on-screen .DrawEdge \ This is the entry point for the routine CPY blockOffset \ If Y <> blockOffset then loop back to draw the next BNE dred1 \ byte, as we haven't reached the bottom track line JMP draw29 \ Jump to draw29
Name: GetTyreDashEdges [Show more] Type: Subroutine Category: Dashboard Summary: Fetch the pixel bytes from along the edge of the dashboard or tyre and fill the block to the right of the edge appropriately
Context: See this subroutine on its own page References: This subroutine is called as follows: * CopyTyreDashEdges calls GetTyreDashEdges

Arguments: X The number of the leftmost dash data block to copy A The number of the dash data block after the last block to copy (so the last block to draw is A - 1) Y Start at this byte in the dash data, so we work down the screen from track line Y (S R) The address of the table into which we copy the pixel bytes from the specified edge
.GetTyreDashEdges STA blockCounter \ Set a loop counter in blockCounter, so the following, \ X loops from blockNumber to blockCounter - 1 \ (i.e. A - 1) .gedg1 STX blockNumber \ Store the loop counter in blockNumber STY topTrackLine \ Set topTrackLine to the offset of the start byte LDA dashDataOffset,X \ Set blockOffset = the dash data offset for block X STA blockOffset LDX #LO(R) \ Set X so the call to GetTyreDashEdge modifies the \ FillAfterObject routine to draw to (S R) instead of \ (Q P) IF _ACORNSOFT OR _4TRACKS LDY #&DF \ Set Y = &DF so the call to GetTyreDashEdge modifies \ the FillAfterObject routine at edge11 to BNE edge1, \ so the routine copies into (S R) instead of filling \ the screen buffer LDA #0 \ Set A = 0, so the call to GetTyreDashEdge modifies the \ FillAfterObject routine to store 0 as the value for \ colour 0 (instead of the &55 that the screen buffer \ uses to represent black) JSR GetTyreDashEdge \ Modify the FillAfterObject routine and run it to copy \ the edge bytes into the table at (S R) LDX #LO(P) \ Set X so the call to GetTyreDashEdge modifies the \ FillAfterObject routine back to drawing to (Q P) LDY #&E7 \ Set Y = &E7 so the call to GetTyreDashEdge modifies \ the FillAfterObject routine at edge11 back to BNE \ edge3 LDA #&55 \ Set A = &55, so the call to GetTyreDashEdge modifies \ the FillAfterObject routine back to storing &55 as the \ value for colour 0 INC blockNumber \ Increment the block number JSR GetTyreDashEdge \ Modify the FillAfterObject routine back to its default \ code and run it, which fills the block to the right of \ the dashboard or tyre edge with the appropriate \ content ELIF _SUPERIOR OR _REVSPLUS LDY #&EF \ Set Y = &DF so the call to GetTyreDashEdgeSup modifies \ the FillAfterObjectSup routine at sedg5 to BNE sedg1, \ so the routine copies into (S R) instead of filling \ the screen buffer LDA #0 \ Set A = 0, so the call to GetTyreDashEdgeSup modifies \ theFillObject routine to store 0 as the value for \ colour 0 (instead of the &55 that the screen buffer \ uses to represent black) JSR GetTyreDashEdgeSup \ Modify the FillAfterObjectSup routine and run it to \ copy the edge bytes into the table at (S R) LDX #LO(P) \ Set X so the call to GetTyreDashEdgeSup modifies the \ FillAfterObjectSup routine at back to drawing to (Q P) LDY #&09 \ Set Y = &09 so the call to GetTyreDashEdgeSup modifies \ the FillAfterObjectSup routine at sedg5 back to BNE \ sedg8 LDA #&55 \ Set A = &55, so the call to GetTyreDashEdgeSup \ modifies the FillAfterObjectSup routine back to \ storing &55 as the value for colour 0 INC blockNumber \ Increment the block number JSR GetTyreDashEdgeSup \ Modify the FillAfterObjectSup routine back to its \ default code and run it, which fills the block to the \ right of the dashboard or tyre edge with the \ appropriate content ENDIF LDX blockNumber \ Fetch the loop counter from blockNumber into X CPX blockCounter \ If X <> blockCounter, loop back until we have copied BNE gedg1 \ from block blockNumber to block blockCounter - 1 RTS \ Return from the subroutine
Name: CopyTyreDashEdges [Show more] Type: Subroutine Category: Dashboard Summary: Fetch the pixel bytes from the right edge of the left tyre and the right edge of the dashboard, and fill to the right of the edge
Context: See this subroutine on its own page References: This subroutine is called as follows: * MainDrivingLoop (Part 2 of 5) calls CopyTyreDashEdges

This routine populates the tyreRightEdge and dashRightEdge tables with the pixel bytes along the right edge of the left tyre and the right edge of the dashboard respectively. It also fills the block to the right of the edge with the appropiate content, so the feathered edges don't fill to the right.
.CopyTyreDashEdges LDA #HI(tyreRightEdge) \ Set (S R) = tyreRightEdge STA S \ LDA #LO(tyreRightEdge) \ so the call to GetTyreDashEdges copies the pixel data STA R \ from the tyre edge into the tyreRightEdge table LDY #27 \ Start at byte 27 in the dash data, so we work down the \ screen from track line 27 LDX #3 \ Loop through dash data blocks 3 to 5 LDA #6 JSR GetTyreDashEdges \ Fetch the pixel bytes from along the right edge of the \ left tyre and fill the block to the right of the edge \ with the appropriate content LDA #HI(dashRightEdge) \ Set (S R) = dashRightEdge STA S \ LDA #LO(dashRightEdge) \ so the call to GetTyreDashEdges copies the pixel data STA R \ from the dashboard edge into the dashRightEdge table LDY #43 \ Start at byte 43 in the dash data, so we work down the \ screen from track line 43 LDX #26 \ Loop through dash data blocks 26 to 33 LDA #34 JSR GetTyreDashEdges \ Fetch the pixel bytes from along the right edge of the \ dashboard and fill the block to the right of the edge \ with the appropriate content RTS \ Return from the subroutine
Name: FillInsideObject [Show more] Type: Subroutine Category: Drawing objects Summary: Fill the object part from the previous edge to the current edge Deep dive: Creating objects from edges
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawObjectEdge (Part 5 of 5) calls FillInsideObject

Arguments: X The number of bash data blocks inside the object that we need to fill blockNumber The block number of the edge to fill up to (i.e. we fill to the left of this block number) leftOfEdge The pixel byte to fill the object with topTrackLine Top track line of the object (higher value, 0 to 79) bottomTrackLine Bottom track line of the object (lower value, 0 to 79)
.FillInsideObject LDA leftOfEdge \ Set A to the byte we want to fill the object with BNE fill1 \ If it is non-zero, jump to fill1 to skip the following \ instruction LDA #&55 \ The fill byte is zero, i.e. colour 0 (black), so set \ A = &55, which is the value we use to represent \ colour 0 (black) in the screen buffer .fill1 STA V \ Store A in V, so V contains the correct fill byte for \ the screen buffer LDA #&7F \ Set T = &7F - topTrackLine SEC \ SBC topTrackLine \ We subtract this value from the start addresses for STA T \ the two dash data blocks that we are going to fill \ concurrently, and add the same value to the offset \ for the bottom line in VV (see the next instruction) \ \ There must be a reason for all this shenanigans, but \ it's currently eluding me ADC bottomTrackLine \ Set VV = T + bottomTrackLine STA VV \ We now calculate the start address of dash data block \ blockNumber - 1, which will be at: \ \ dashData + &80 * (blockNumber - 1) \ \ because the dash data blocks occur every &80 bytes \ from dashData \ \ We do this using the following simplification, where \ A = (blockNumber - 1) \ \ dashData + &80 * A \ = dashData + 256 / 2 * A \ = HI(dashData) << 8 + LO(dashData) + A << 7 \ \ LO(dashData) happens to be zero (as dashData = &3000), \ so we can keep going: \ \ = HI(dashData) << 8 + A << 7 \ = (HI(dashData) << 1 + A) << 7 \ = ((HI(dashData) << 1 + A) << 8) >> 1 \ \ In other words, if we build a 16-bit number with the \ high byte set to HI(dashData) << 1 + A, and then shift \ the whole thing right by one place, we have our result \ \ So this is the same as: \ \ ((HI(dashData) << 1 + blockNumber - 1) << 8) >> 1 \ \ We do this below, storing the 16-bit number in (Q A) LDA blockNumber \ Set A = blockNumber STA U \ Set U = blockNumber CLC \ Set A = A - 1 + HI(dashData) << 1 ADC #HI(dashData)<<1-1 \ = blockNumber - 1 + HI(dashData) << 1 \ \ so our 16-bit number is (A 0), and we want to shift \ right by one place LSR A \ Shift (A 0) right by 1, shifting bit 0 of A into the \ C flag STA Q \ Set Q = A, to store the high byte of the result in Q STA S \ Set S = A, to store the high byte of the result in S LDA #0 \ Shift the C flag into bit 7 of A, so A now contains ROR A \ the low byte of our result \ We now have our result in (Q A), which contains the \ start address of dash data block blockNumber - 1 \ We now subtract T, though as noted above, I'm unclear \ on the reason for this (but the maths all balances out \ in the end, so let's go with it) SEC \ Set (Q P) = (Q A) - T SBC T \ STA P \ We also set the C flag depending on the subtraction EOR #&80 \ Set (S R) = (Q P) - &80 STA R \ \ starting with the low bytes \ \ We can do the subtraction more efficiently by using \ EOR to flip between &xx00 and &xx80, as the dash data \ blocks always start at these addresses BPL fill2 \ We then decrement the high byte, but only if the EOR DEC S \ set the low byte to &80 rather than &00 (if we just \ set it to the latter, the BPL will skip the DEC) .fill2 BCS fill4 \ If the subtraction above didn't underflow, jump to \ fill4 to skip the next two instructions .fill3 DEC Q \ Otherwise decrement the high bytes in (Q P) and (S R) DEC S \ as the low byte subtraction underflowed .fill4 \ By this point, we have: \ \ * (Q P) points to the start address of dash data \ block blockNumber - 1, minus T \ \ * (S R) points to the start address of dash data \ block blockNumber - 2, minus T \ \ So if we fill block (Q P), we will be filling the \ block to the left of the edge, and if we fill block \ (S R), we will be filling the block further to the \ left \ We now enter a loop to fill the object, filling either \ one or both of these blocks at a time LDY U \ Set Y to the block number in U, which starts out as \ blockNumber and goes down by 2 on each loop iteration LDA fillDataOffset-1,Y \ Set A to entry Y - 1 from fillDataOffset, which gives \ us the offset of the bottom line of block Y - 1, \ i.e. blockNumber - 1, adjusted to ensure that filling \ to the left works properly DEY \ Set U = Y - 2 DEY \ = U - 2 STY U CMP bottomTrackLine \ If A < bottomTrackLine, then the bottom of the object BCC fill5 \ is below the bottom of the block, so jump to fill5 to \ do the fill from bottomTrackLine and up ADC T \ Set Y = A + T + C TAY \ = bottom line offset + T + 1 BPL fill6 \ If Y < &80, jump to fill6 to do the fill from track \ line Y and up, so the fill will start from address: \ \ (Q P) + Y = (Q P) + A + T + 1 \ = start address of (blockNumber - 1) - T \ + A + T + 1 \ = start address of (blockNumber - 1) \ + A + 1 \ \ i.e. from track line A in blockNumber - 1 \ If we get here then Y >= &80, which means: \ \ A + T + C >= &80 \ \ A + &7F - topTrackLine + 1 >= &80 \ \ A - topTrackLine >= 0 \ \ A >= topTrackLine \ \ so the bottom line offset is above the top track line, \ which is why we don't do the fill for these two blocks CPX #2 \ If X >= 2, jump to fill8 to move on to the next two BCS fill8 \ blocks to the left RTS \ Return from the subroutine .fill5 LDY VV \ Set Y = VV \ = T + bottomTrackLine \ \ so the fill will start from address: \ \ (Q P) + Y = (Q P) + T + bottomTrackLine \ = start address of (blockNumber - 1) - T \ + T + bottomTrackLine \ = start address of (blockNumber - 1) \ + bottomTrackLine \ \ i.e. from bottomTrackLine in blockNumber - 1 .fill6 \ We now do the fill, filling the relevant blocks from \ offset Y up to offset &7F, so that's: \ \ * From (Q P) + Y to (Q P) + &7F \ \ * And from (S R) + Y to (S R) + &7F if X >= 2 \ \ In the latter case we then reduce X by 2 and loop back \ to do the next two blocks LDA V \ Set A to the byte we want to fill with CPX #2 \ If X < 2, jump to fill9 to fill just one block and BCC fill9 \ return from the subroutine .fill7 STA (P),Y \ Fill the Y-th byte of (Q P) and (S R) with A STA (R),Y INY \ Increment Y to point to the next byte down the screen BPL fill7 \ Loop back until we have filled down to the &7F-th byte .fill8 DEX \ Set X = X - 2 DEX \ \ to move left by two blocks BNE fill3 \ If we haven't filled all the blocks, jump to fill3 to \ fill the next two blocks to the left RTS \ Return from the subroutine .fill9 STA (P),Y \ Fill the Y-th byte of (Q P) with A INY \ Increment Y to point to the next byte down the screen BPL fill9 \ Loop back until we have filled down to the &7F-th byte RTS \ Return from the subroutine