May 29, 2023

R.C. Pro-Am II (NES) Analysis

 

 


There are some TAS assessments of this game out there already, which I may reference here and there, but for the most part this is just my own notes on the game. 

Easter Egg

When the game first boots up - when the RAM is clear, one of the programmers named "SIMON" (also one of the CPU racers) uses his signature at bytes $01D4-$01D8 to let the program know the boot-up subroutines have been run. 

Audio

Byte $03CB holds the background music (BGM) track. When the program loads up, it gets set to track #12 for the first "trademark" splash screen, then to track #16. Both of these values point to an empty track. Clearly, at some point in development this splash screen was supposed to have a little jingle play. Perhaps it was cut due to memory constraints.

Each audio channel has its own track. Square/Pulse channel 1 typically holds the BGM track. The other channels have their own track pointers.

The BGM tracks are as follows:

01    Main Title
02    Name Entry
03    On Your Mark!
04    Pause
05    Model Shop
06    Race Finished
07    Continue Screen
08    Track Preview 
09    Racer Standings
0A    And They're Off!

The pulse channel SFX tracks are as follows:

02    Skidding 
12    Invalid Shop Selection
14    Last Lap
1A    Bumpy Car
20    Airplane Gun
22    Exit Parts Menu / Buckshot Drop
24    <Cut Content Item>
2C    Drag Race Checkpoint
36    Out of Ammo
38    Point Bonus / Valuable Pickup
3C    Ammo Pickup
3E    Ammo Crate Pickup
40    Roll Cage Pickup
42    Mini-Game Bonus
44    Generic Money Pickup 
4C    Freezer Shot
50    Shield Use 

The triangle channel SFX are as follows:

2C    Part Purchased

The noise channel SFX are as follows:

08    Projectile Shot
0E    Weapon Explosion
10    Car Explosion
16    Splash 
2A    Aerial Bomb Run
52    Nitro Used

Unused SFX indexes are as follows, with best guess as to what might be:

06    Pulse error beep / Noise splash
0A    Pulse Item Pickup?
0C    Pulse Chevron Boost?
18    Noise Crash
1C    Pulse Red Light
1E    Pulse Green Light
26    Pulse Out Of Time
28    Pulse thud / Noise selector
2E    <NO SOUND>
30    <NO SOUND>
32    Same as 0C
34    Same as 0A
3A    Pulse upgrade / Noise explosion
46    Noise puddle splash
48    Pulse extra refill
4A    Pulse menu select / Noise splish
4E    Pulse plummet / Noise water drop
 

The Shop

The game only enters the shop if any player has cash. Cash (and many other variables) is reset only when the title screen is first loaded and upon game over. This means you can give any player cash during the title screen and the game will go to the shop screen after the first track's preview. 

A player can have #FFFF cash at any time, but anything above #270F will glitch text. 

The developers originally intended all extras to only be purchasable once per shop visit, much like ammo and continues. It did this the same way weapons were free after initial purchase, by flagging the range of $077A-$0784 with player-specific bitmasks. Ammo and continues have additional variables per player to keep track of how many times the price of each will get marked up. There is no mark-up for shields, slicks and nitros, and a player can resupply all three between races if desired, so this feature was likely removed to accommodate that mechanic. If the selected extra is equipped and not at full supply, the full purchase price is charged and the extra is refilled. If the extra is not equipped, but the player still has a remaining supply, the extra is equipped without being refilled. This gives the player the option of swapping extras without having to pay again.

Oil Slick Cheat-Glitch

When you buy a weapon or extra from the shop,  or when you pick one up during a race, whichever one you currently have gets reset to #FF. If you cheat and give yourself both a weapon and an extra, however, the program will use both at the same time. Weapons and oil slicks share some of the same RAM, though - including speed variables. Since extras get processed before weapons, the weapon's speed will override the slick's, turning your oil slicks into projectiles as well. The weapon will retain its attributes, then place the oil slick on the ground when it finishes moving. For example, if a freezer slick hits a car, it will freeze the car for one second, but if it misses, it will leave an oil slick at the end of its path. Slicks can wind up off the track if the projectile leaves the track. 

Both ammo and supplies will get used up. If either runs out, it will become a normal weapon or slick until more ammo is picked up.

Race Number Errata

After the racer's standings have been calculated on the victory screen, the program first checks if the race number ($0750) was #00. If it was, then it goes on to the next race. Otherwise, if only one player is playing or the race number was less than #27, it checks if a player lost and gives him a chance to continue. If any human players remain, if the race number was #1B or greater and there are no more human contenders, nothing special happens. Similarly, if the race number was #27 or higher, nothing special happens, yet. We can tell there was cut or restructured code here because of an RTS-chain - when two or more RTS calls appear one after the other. In the absence of either scenario, the race number is increased. Now, immediately after this it checks if the race number is #00, #28 or greater. My hunch is they realized they had logically redundant code, so they moved it to here, but forgot to remove all the old code. 

You can actually set the race number to #28, which the program will interpret as a Tug-o-Truck mini-game. However, none of the trucks will move and the mini-game will end with the same results every time. The only way to get the race number to be #00 after a completing a race is by cheating. Either you would have to lock the race number at #00 (which would be a waste of a cheat), or set the race number directly to #FF on the race standings screen by editing the byte in RAM directly (no cheat codes). This leads me to believe they intended to have a "demo race", but scrapped that idea for whatever reason.

Randomizer

The program uses a 32-bit randomizer located at $0756-$0759. If we represent the dataset as R[0...3], where R[0] corresponds to $0656, then the randomization routine is as follows:

LDA R[1]
PHA save R[1]
ASL
ROL R[2]
ROL R[2]
XOR R[3]
STA R[1]
PLA
STA R[3]
XOR R[2]
PHA
LDA R[0]
STA R[2]
PLA
ADC FRAME_COUNTER
ROL
STA R[0] 

Thus we see that $0756 holds the working randomized value. However, the seed itself is never (or very rarely) accessed outside the routine, meaning for all intents and purposes, the results are pretty random. The player would have to not only control which frame the routine gets called in, but also how many times the routine gets called, which can vary as the program goes on. Arguably, it is doable.

CPU Behavior

Each of the CPU cars have their own levels, even if they buy upgrades. The yellow car has weaker tires when starting out, but if it gets to upgrade its tires, it will eventually surpass every other racer, including the player. Until that time, the blue car has the best tires. The green car starts off with better tires than the player, but all of the player's tire upgrades are better. 

Each CPU opponent is assigned a "shopping level". This determines the order in which a CPU opponent will buy upgrades. At a glance, it doesn't cheat prices, so it may be prudent to prevent CPU opponents from collecting any cash during races. Since each race will grant a CPU opponent some amount of cash during the standings screen, each opponent will inevitably purchase upgrades, however.

If a CPU opponent gets hit by a weapon other than buckshot, an explosion timer is set to prevent the car from doing anything, but the car will either accelerate up to a speed of #10 or maintain its current speed. However, the 8th, 12th, or 15th attack has a 75% chance to count as a critical hit and slow the car down completely if it is not currently at max speed.

In addition to a base max speed ($0613,X) based on car model and motor level, CPU racers have two speed limits imposed upon them - a cruising speed limit ($0617,X) and a safe speed limit ($061B,X). The base speed limit is the highest limit, followed by crusing speed, then the safe speed. The program decides which speed limit to use based on the CPU's proximity to the lead player:

  • If the car is onscreen behind the lead player, the car's acceleration is increased by double the distance to the player and then it accelerates up to the base speed limit. 
  • If the car is offscreen behind the lead player, the base speed limit is increased by a whopping #32 and the car accelerates by 4 up to it.
  • If the car is onscreen and ahead of the lead player, the crusing speed limit is used. 
  • If the car is offscreen and ahead of the lead player by only one checkpoint, the cruising speed limit is used.
  • If the car is offscreen and ahead of the lead player by at least two checkpoints, the safe speed limit is used.
  • If the car is turning, each angle away from the direction of travel reduces the applied base speed limit by 2. If the car is moving faster than this revised speed limit, it decelerates at a speed of 3/4 each frame.

Track Design

Please note: For the sake of this article, "vertical" refers to the northeast and southwest directions, whereas "horizontal" refers to the northwest and southeast directions, which is how each section correlates to the racetrack display at the top of the screen. 

There are 48 races, with each race using one of 27 track templates, arranged in order at 0x000972 - 0x0009A1 in the ROM. Track #18 is tug-of-war and track #19 is drag racing, but track #1A was removed, so there are really only 24 available track templates. If I was to venture a guess on what track #1A was, a stray byte of data shows it mapped to the tug-of-war game; the other two events used tilesets matching the previous races; and track #1A was set to tileset 0, but this may have just been the result of deleted data. Trying to play track #1A crashes the game.

Terrain collisions are handled by collision sections, whose mappings are referenced at $(00AA,00AB). Each section has an entrance and an exit. A section can be reversed by setting bit 7 of its ID, which forces the entrance to function as the exit, and vice-versa. On-ramps will also become off-ramps, right will become left, etc., etc.
DO NOT SET BIT 6 OR IT WILL CRASH THE GAME!
Some sections can occupy the same position along the track as other positions, differentiated by whichever checkpoint they occupy. Intersections, for example, will use one section when crossing horizontally and a different section when crossing vertically, with the racer using whichever section corresponds to his current checkpoint. There are 59 sections with defined collision behaviors.

Track behavior relies on a "track position" variable for each car. The correlation of this value to the track varies depending on the orientation of the track, thus prior to handling behaviors, each section has to re-orient the car's track position to work within the track behavior. For most track sections, this is either a direct copy (negated if the section is reversed) or a "90-degree rotation" in a sense. The bends, however, have a more complex code which sets the orientation based on the car's position within the section. I won't go get into the intricacies of these four routines, but interestingly, apparently the "elbow" turns were originally intended to have different behavior, but the developer(s) scrapped that and gave them the same behaviors as the normal bends. Whether this was just refactored code or there were too many bugs in the original code, I can't say. Peculiarly, this is only evidenced by the reversed bends -- the default bend code points appears to have been completely edited out. There are 59 sections referenced in this look-up table.

Seemingly in relation to this duality of positions relative to the track, each section affects a variable which I haven't made sense of just yet. In my research, I have simply labeled it as "cornering", since that is where it seems to actually matter. The look-up table which handles setting this variable when entering a section references 61 sections.

Yet another set of routines seem to handle camera control on each track section, for some reason. To further make me think there were two more track sections removed during production, this look-up table also references 61 sections

The track preview screen has data for 61 sections. So are there 59 sections or are there 61 sections? The game doesn't even use that many anyway, but there is strong evidence they intended to use 61?

Below is a list of each section with the direction of its entrance position (e.g., NW has the entrance in the northwest corner of the section). Many of them are unused according to this table (section #00 is unused in all tables). Unused sections which I cannot identify with 100% certainty will have descriptors in parentheses based on the content of each look-up table. A "wide" section is 32 pixels wider than a normal track, which is roughly 80 pixels wide, while a "very wide" section is 64 pixels wider. Anything beyond #00 explicitly labeled <UNUSED> is missing critical data and may crash the program. Sections labeled <DEFINED> aren't used, but shouldn't crash the game. Addresses listed below each section are their WRAM entries, not ROM. An address in bold will cause the specified unwanted behavior.

00    <UNUSED>
01    SW Northern Bend [0,1,2]
02    NW Eastern Bend [0,1,2]
03    SE Western Bend [0,1,2]
04    NE Southern Bend [0,1,2]
05    NW Horizontal Stretch [0,1,2]
06    SW Vertical Stretch [0,1,2]
07    NW Stripe Lane [0,1,2]
08    NW Big Jump [2]
09    NW Left Shoulder [0,1]
0A    SW Right Elbow [0,1,2]
0B    NW Right Elbow [0,1,2]
0C    SE Right Elbow [0,1,2]
0D    NE Right Elbow [0,1,2]
0E    NW Right Shoulder [1]
0F    (nw very wide horizontal stretch) [1]
        camera: $8109
        behavior: $C446
        corner: $830D/$8321
        position: $90C8/$912A
10    NW Merge Center [1,2]
11    <UNUSED> (horizontal stretch) 
        camera: $8109
        behavior: $8400 (AUDIO-VISUAL ERROR)
        corner: $830D/$8321
        position: $90C8/$912A
12    NW Intersection [0,1,2]
13    SW Intersection [0,1,2]
14    <UNUSED> Diagonal south-east [1,2]
        camera: $8115
        behavior: $0000 (FATAL ERROR)
        corner: $835D/$83CD
        position: $9083/$90D0
15    <UNUSED> Diagonal west-south [1,2]
        camera: $817F
        behavior: $0000 (FATAL ERROR)
        corner: $8373/$83E3
        position: $9092/$90D3
16    <UNUSED> Diagonal north-east [1,2]
        camera: $81CC
        behavior: $0000 (FATAL ERROR)
        corner: $8387/$83F7
        position: $90A2/$90D6
17    <UNUSED> Diagonal west-north [1,2]
        camera: $8215
        behavior: $0000 (FATAL ERROR)
        corner: $83B7/$840F
        position: $90B9/$90D9
18    (nw wide horizontal stretch) [1]
        camera: $8109
        behavior: $C29F
        corner: $81A6/$8234
        position: $90C8/$912A
19    (nw wide horizontal stretch) [1]
        camera: $8109
        behavior: $C2F1
        corner: $81A6/$8234
        position: $90C8/$912A
1A    <UNUSED>NW Right Elbow (same as 0B) [1,2]
        camera: $80A7
        behavior: $C289
        corner: $8264/$82D0
        position: $9092/$90DF
1B    <UNUSED> (vertical stretch)
        camera: $810F
        behavior: $0000 (FATAL ERROR)
        corner: $8335/$8349
        position: $90CC/$9130
1C    <UNUSED> (horizontal stretch) [1]
        camera: $8109
        behavior: $0000 (FATAL ERROR)
        corner: $81A6/$8234
        position: $90C8/$912A
1D    <UNUSED> (eastern bend,wide?)
        camera: $80A7
        behavior: $0000 (FATAL ERROR)
        corner: $8264/$0000 (FATAL ERROR)
        position: $9092/$0000 (FATAL ERROR)
1E    NW Overpass [0,1]
1F    NW Off-Ramp [0,1]
20    NW On-Ramp  [0,1]
21    SW Underpass [1]
22    SW Merge Left [1,2]
23    SW Merge Right [1,2]
24    NW Valley [0,1]
25    NW Right Hill [1]
26    NW Raised Track [2]
27    NW Lowered Track [2]
28    (sw vertical stretch right lane) [1]
        camera: $810F
        behavior: $CAF3
        corner: $81CC/$8247
        position: $90CC/$9130
29    (sw vertical stretch right lane) [1]
        camera: $810F
        behavior: $CAFD
        corner: $81CC/$8247
        position: $90CC/$9130
2A    <DEFINED> NW Elevated Stretch [2]
        camera: $8109 (vertical lock)
        behavior: $CAD5
        corner: $81A6/$8234
        position: $90C8/$912A (rotate)
2B    (northern bend) [1,2]
        camera: $809A
        behavior: $84D6
        corner: $824D/$82A4
        position: $9083/$90E8
2C    (nw eastern bend. delayed turn) [1,2]
        camera: $80A7
        behavior: $8537
        corner: $8264/$82D0
        position: $9092/$90FA
2D    (southern bend wide right angle) [1,2]
        camera: $80C5
        behavior: $858E
        corner: $8290/$82F9
        position: $90B9/$911B
2E    <DEFINED> SE Right Elbow (same as 0C) [1,2]
        camera: $80B2
        behavior: $85EC
        corner: $8276/$82E2
        position: $90A2/$9107
2F    SW Stripe Lane [2]
30    NW Short South Median [2]
31    NW Off-ramp Intersection [0,2]
32    NW Short North Median [2]
33    <UNUSED> SW Vertical Stretch {collision} [2]
        camera: $810F (horizontal lock)
        behavior: $849C (no collisions)
        corner: $81AC/$81BF
        position: $90CC/$9130 (invert)
34    <UNUSED> (horizontal stretch) [2]
        camera: $8109
        behavior: $849C (no collisions)
        corner: $0000/$823A (FATAL ERROR)
        position: $90C8/$912A
35    NW Icy Stretch [1,2]
36    NW Icy Stretch [1,2]
37    <DEFINED>NW Long Meridian [2]
        camera: $8109 (vertical lock)
        behavior: $8D74
        corner: $81A6/$8234
        position: $90C8/$912A (rotate)
38    NW Icy Stretch [1]
39    <UNUSED>(sw icy patch vertical stretch) [1]
        camera: $810F
        behavior: $8E5D
        corner: $81CC/$8247
        position: $90CC/$9130
3A    (horizontal stretch) [1]
        camera: $8109
        behavior: $8E52
        corner: $81A6/$8234
        position: $90C8/$912A

There are three tilesets, each with limited tiles. Consequently each tileset has a restriction on which sections you can even use. Most track sections are "available" only on the off-road track, although I can't help but feel this was just due to placeholder data they forgot to remove.

Many unused sections have similar effects to sections that were retained, which is a good sign they probably just tried to consolidate the track behaviors, but forgot to actually update the database. It is also possible chevrons, terrain, and/or hazards were part of the actual track sections in the planning stages. They are actually handled separately from the track sections themselves (although the track section itself does seem to affect which items and obstacles appear somehow). 

You can skip tracks by setting $0750 on the title screen or while points are being tallied. Once the track preview comes up, it is too late, since the track data has already been loaded into RAM. Track behavior is loaded from the PROM, so the sections being used for collision handling will not correspond to the visible track. 

The track preview, or map, is broken up into eight rows of 10 bytes each, although the first three bytes are reserved for the left border. The last three bytes per row are for the right border, but it may be possible to use them.

Terrain Elements

Terrain elements -- such as arrows, chevrons, jumps, and mud -- placed as normal tiles, but do nothing without element definitions. The game uses two tables for terrain elements. The first table at $(00B0,00B1) specifies which offset of the terrain element table to jump to. These values are local to each track and are typically in order of appearance. The second table at $(00AE,00AF) specifies which attributes those elements have. A value of #FF means there is no terrain data. The program cycles through all elements until it encounters an #FF. There is a hard limit of four terrain elements per section due to lazy coding, although more than that would be unnecessary.

If a track section with a terrain element is followed by one without any elements, the terminal #FF is typically referenced by the terrain offset. For example, if the offsets table had a straight-forward incremental progression [#00, #01, #02, #03, #04,... ], we know no two adjacent track sections both have terrain elements. If we had an offset table [#00, #01, #04, #05,... ], we know section 1 has three terrain elements, because section 2 must be pointing to #FF. On the other hand, if we had [#00, #01, #04, #06,... ], we know section 1 has two elements and section 2 has one element.

Each element is defined by x and y coordinates relative to the track section, followed by an "object" identifier. This is used to look up the address for the element's behavior. There are 26 element objects, but some were removed. I haven't done a code/data log, so I do not know if the elements have been removed in their entirety or if some residual code remains, but elements marked as unused have no address pointer and will crash the program. Element #0D is unused in the game, but the behavioral code was left in, making it identifiable. Below is a list of each element object.

00    None
01    Oil Slick
02    Water Puddle
03    Horizontal Dirt Jump
04    Horizontal Flooded Track
05    <Unused>
06    Chevron to SE
07    Chevron to NW
08    Chevron to NE
09    Chevron to SW
0A    <Unused>
0B    <Unused>
0C    <Unused>
0D    Vertical Dirt Jump
0E    <Unused>
0F    same as 07
10    <Unused>
11    Tug-o-War Line
12    Mud Shape 1
13    Mud Shape 2
14    Mud Shape 3
15    Mud Shape 4
16    Mud Shape 5
17    Mud Shape 6
18    Horizontal Logs
19    SHORT Chevron to NE 

Below is a list of each terrain element. Terrain elements may have multiple positions along the track section. For horizontal sections, "entry" is toward the northwest and "exit" is toward the southeast. For vertical sections, "entry" is toward the southwest" and "exit" is toward the northeast. "Center" is for any position in between. "Low" and "High" refer to relative coordinates on the track. For horizontal sections, "low" is toward the southwest and "high" is toward the northeast; for vertical sections, "low" is toward the northwest and "high" is toward the southeast. "Middle" is for any position in between. This can get confusing when the track or element runs in reverse, so I have also included the base coordinates checked for each element. The numbers for log bridges are the coordinates for the first and last logs. For chevrons, the direction of travel is also specified.

00    None
01    Chevron Horizontal Entry High to NW (#30,#8C)
02    Chevron Horizontal Entry Middle to NW (#30,#70)
03    Chevron Horizontal Entry Low to NW (#30,#54)
04    Chevron Horizontal Exit High to NW (#B0,#8C)
05    Chevron Horizontal Exit Middle to NW (#B0,#70)
06    Chevron Horizontal Exit Low to NW  (#B0,#54)
07    Chevron Horizontal Entry High to SE (#30,#8C)
08    Chevron Horizontal Entry Middle to SE (#30,#70)
09    Chevron Horizontal Entry Low to SE (#30,#54)
0A    Chevron Horizontal Exit High to SE (#B0,#8C)
0B    Chevron Horizontal Exit Middle to SE (#B0,#6C)
0C    Chevron Horizontal Exit Low to SE (#B0,#54)
0D    Water Puddle Horizontal Entry Low (#20,#74)
0E    Water Puddle Horizontal Center High (#54,#97)
0F    Water Puddle Horizontal Exit Middle (#CB,#80)
10    Oil Slick Vertical Exit Middle (#8B,#D0)
11    Oil Slick Vertical Center Low (#3E,#74)
12    Oil Slick Vertical Entry High (#9A,#00)
13    Chevron Vertical Entry High to NE (#7F,#24)
14    Tug-o-War Contested Zone (#30-#D0)
15    Chevron Vertical Exit Middle to NE (#6C,#B0)
16    Chevron Vertical Exit Middle to SW (#6C,#B0)
17    Oil Slick Horizontal Entry Low (#04,#68)
18    Oil Slick Horizontal Exit Middle (#AB,#8D)
19    Chevron Horizontal Entry Middle to SE (#40,#6C)
1A    Water Puddle Horizontal Entry Middle (#2E,#80)
1B    Water Puddle Vertical Entry Middle (#6E,#33)
1C    Oil Slick Vertical Exit Middle (#80,#C4)
1D    Oil Slick Horizontal Center Low (#84,#68)
1E    Water Puddle Horizontal Late Entry Middle (#40,#80)
1F    Oil Slick Horizontal Entry High (#00,#94)
20    Oil Slick Horizontal Center High (#80,#94)
21    Water Puddle Vertical Late Center Middle (#80,#A2)
22    Water Puddle Vertical Exit Middle (#87,#EA)
23    Water Puddle Horizontal Exit Middle (#E0,#80)
24    Oil Spill Horizontal Exit Low (#A7,#6C)
25    Chevron Vertical Entry Middle to NE (#6C,#30)
26    Water Puddle Vertical Center High? (#96,#7C)
27    Mud 1 Small Horizontal Exit Low (#D0,#70)
28    Mud 2 Small Vertical Exit Low (#7C,#A0)
29    Mud 4 Small Vertical Exit High (#8A,#A2)
2A    Mud 5 Small Vertical Entry Low (#68,#34)
2B    Mud 2 Small Vertical Entry Middle (#7C,#20)
2C    Mud 4 Small Vertical Entry High (#8A,#22)
2D    Mud 6 Long Horizontal (#28-#DD)
2E    Chevron Vertical Entry Middle to SW (#68,#18)
2F    Chevron Vertical Exit High to NE (#7F,#A4)
30    Chevron Vertical Entry Low to NE (#56,#24)
31    Chevron Vertical Exit Low to NE (#56,#A4)
32    Chevron Vertical Center Middle to SW (#69,#98)
33    Chevron Horizontal Center Middle to SE (#88,#6C)
34    Chevron Vertical Entry Low to SW (#51,#20)
35    Chevron Vertical Exit Low to SW (#51,#A0)
36    Dirt Jump Horizontal Center High (#6C,#8D)
37    Logs Short Horizontal Exit (#AA-#EA)
38    Chevron Horizontal Entry Middle to NW (#00,#70)
39    Logs Short Horizontal Late Entry (#2A-#6A)
3A    Logs Full Horizontal (#00,#FF)
3B    Mud 3 Small Horizontal Exit Low (#D0,#6C)
3C    Dirt Jump Horizontal Entry Low (#60,#4E)
3D    Logs Long Horizontal (#28-#F8)
3E    Dirt Jump Horizontal Exit Low (#B7,#4E)
3F    Logs Short Horizontal Center (#7B,BB)
40    Chevron SHORT Vertical Entry Middle to NE (#6C,#20)
41    Chevron SHORT Vertical Exit Middle to NE (#6C,#A0)
42    Oil Slick Vertical Exit High (#93,#C7)
43    Oil Slick Vertical Center Middle (#51,#83)
44    Oil Slick Vertical Entry Low (#2B,#91)
45    Chevron Vertical Exit Middle to SW (#8B,#A0)
46    Oil Slick Vertical Center Middle (#8B,#4F)
47    Flooded Track Middle (#64-#A4)
48    Oil Slick Vertical Late Center High (#98,#80)
49    Water Puddle Horizontal Entry High? (#16,#7C)
4A    Oil Slick Vertical Early Center Middle (#80,#42)
4B    Mud Patch Small Horizontal Center Low (#50,#70)
4C    Logs Short Horizontal Entry (#00-#40)
4D    Dirt Jump Horizontal Entry Low (#40,#4E)
4E    Chevron Horizontal Center Middle to SE (#84,#6C)

Pick-Ups & Bombs 

Items use data tables, but are added to the track at the start of the race, unlike terrain elements. The game uses two tables for item locations. The first table at $(00B2,00B3) specifies if a track section has any items on it and which ones. The second table at $(00B4,00B5) specifies where each of those items are on the track and which index in the status array $00BF,X the item corresponds to. A value of #FF means there is no further item data.

Item pick-ups and bombs share the same status flags, ranging from bytes $00BF to $00FA. If bit 2 is set, the pick-up or bomb can be triggered by any racer. Otherwise, only the racer specified by bits 0 and 1 can trigger it. For pick-ups, this affects the palette used to draw it. For bombs, this effectively disables the bomb for everyone except the specified racer. 

Items can only exist on ground-level track, specifically any height less than 8. If a car's height is greater than 8, it cannot pick up an item. Elevated track always sets a car's height above 8.

Water

Unlike many games which animate tiles by swapping out tilesets or even just a whole tile, water tiles are animated via direct PPU writes. There are 4 tile pattern writes across 32 frames, for a a total of 8 frames of animation. Pattern addresses are located at 0x018ADD (#1B10) to 0x018AE4 (#15A0), with wave patterns located in the addresses in the proceeding bytes from 0x018AE5 to 0x018B24. The 0x018B24 range are all #8B, so all addresses map to RAM #8BXX, where XX is defined at 0x018AE5. These are all lumped together in sets of 8 bytes, so the animation is simply just 0x018B25 to 0x018B54. 

Item Pickups

There are clearly some items missing, but their code remains. There is a list of each item batch at 0x007C28 in the ROM. There is no item #04 listed, but its code can be found at 0x006AA0 in the ROM, which I found while trying to look for SFX #24. The only time that audio track is played is within the code for item #04. 

The checkpoint map at $016E-$1BD is strictly used for items. It seems to be a 1:1 comparison to track sector layout. Either they intended originally to have item sections moveable, editable, or  possibly even exist "off-track". 

Multiplayer

There is no split screen in multiplayer, so the slower players get a temporary speed boost in order to keep them on screen. This is handled by setting byte $0602,X (where X is the player) to -1 as soon as a player goes offscreen. The camera will always follow whichever player is in the lead, so if the boosted player somehow overtakes the lead player while still boosted, the camera will follow the boosted car. During this time, the boosted player will be unable to steer, will not interact with any terrain, and will automatically follow the track. The boosted car will still be slowed from hitting the edge of the track around corners. The player will be boosted until he reaches the lead player, or up to 96 pixels along the track, whichever is less, at which point his speed will be reduced by #30 until reaching whatever speed the player had before the boost. 

Locking $0602,X to #FF for any player will make them go supersonic. This will also make the player take off immediately at the starting line. Locking it at anything else will prevent the boost from happening, forcing them to race blindly. 

The price of continues drops with more players. If there are 2 or 3 players, the price increases gradually, reaching $10,000 after five continues and capping out at $20,000 with the final (tenth) continue. If there are 4 players, the price of the first five continues is reduced drastically, then matches the pricing for 2 and 3 players beyond that.

Mini-Games

The same variable which tracks when a CPU racer is ahead of the player is also used to keep track of the next button required to be pressed by the player in mini-games - #40 for B Button and #80 for A Button. This variable is set to #80 on boot-up, so the first button a player presses must be the A Button, otherwise the CPU will get the advantage. With each subsequent race, the variable will retain whichever button was last required. For example, if you win the second Drag Race when pressing A, the next Tug-o-Truck will require you to press B first. Each successful button press accelerates by #150, otherwise the car decelerates #20 each frame. The CPU accelerates by #02 every frame. Every fourth frame the CPU gets a random speed boost. Upon reaching #1800 speed, the leading car's speed is reset to half the difference between both cars' speeds, with the losing car having its speed reset to 0 (fractional speeds are retained, though).

No comments:

Post a Comment

©TheouAegis Productions™. Powered by Blogger.