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:
The pulse channel SFX tracks are as follows:
The triangle channel SFX are as follows:
The noise channel SFX are as follows:
Unused SFX indexes are as follows, with best guess as to what might be:
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:
PHA save R[1]
PLA
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.
corner: $835D/$83CD
corner: $8373/$83E3
camera: $81CC
behavior: $0000 (FATAL ERROR)
position: $90A2/$90D6
corner: $83B7/$840F
position: $90B9/$90D9
position: $9092/$90DF
corner: $8335/$8349
position: $90CC/$9130
corner: $81A6/$8234
corner: $8264/$0000 (FATAL ERROR)
position: $9092/$0000 (FATAL ERROR)
behavior: $85EC
camera: $810F (horizontal lock)
behavior: $849C (no collisions)
corner: $81AC/$81BF
position: $90CC/$9130 (invert)
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.
11 Tug-o-War Line
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.
29 Mud 4 Small Vertical Exit High (#8A,#A2)
4C Logs Short Horizontal Entry (#00-#40)
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