Skip to navigation

Revs on the BBC Micro

Revs D source

Name: ProcessOvertaking (Part 1 of 3) [Show more] Type: Subroutine Category: Tactics Summary: Process all cars for overtaking manoeuvres, checking first to see if the car has just finished overtaking the car in front Deep dive: Tactics of the non-player drivers
Context: See this subroutine on its own page References: This subroutine is called as follows: * FinishRace calls ProcessOvertaking * MoveAndDrawCars calls ProcessOvertaking

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

This routine checks whether an index in Y, which is relative to the start of a dash data block, is pointing to dash data within the block.
Arguments: UU Dash data block number (0 to 39) Y The index from the start of the dash data block
Returns: C flag The result, as follows: * Clear if offset Y does not point to dash data * Set if offset Y does point to dash data A A is unchanged X X is unchanged
.CheckDashData STA T \ Store A and X in T and U so we can retrieve them below STX U LDX UU \ Set X to the dash data block number TYA \ Set the C flag as follows: CMP dashDataOffset,X \ \ * C flag clear if Y < dashDataOffset,X \ \ * C flag set if Y >= dashDataOffset,X BNE cdas1 \ If Y <> the dashDataOffset of block X, skip the \ following instruction CLC \ If we get here then Y = the dashDataOffset of block X, \ so clear the C flag \ So by this point we have: \ \ * C flag clear if Y <= dashDataOffset,X \ \ * C flag set if Y > dashDataOffset,X \ \ As the dash data lives at the top of each dash data \ block, this gives us the result we want (as Y is \ pointing to data when Y > dashDataOffset,X) .cdas1 LDA T \ Restore the values of A and X that we stored above LDX U RTS \ Return from the subroutine