tag:blogger.com,1999:blog-52136063209864621482024-03-05T05:42:38.793-08:00GMvania Developer's Blog<br><br><b>Notes and updates on Theou Aegis' Castlevania engine</b>Theou Aegishttp://www.blogger.com/profile/15226096714222858333noreply@blogger.comBlogger134125tag:blogger.com,1999:blog-5213606320986462148.post-62852338158385237462024-01-15T13:54:00.000-08:002024-01-15T13:57:20.035-08:00CV3's Password Entry Code Wasted Memory<p>Both the name entry and password entry screens fall under the same finite state machine in CV3. They use the same palette and a lot of the same code, so it's only logical to treat them as two parts of the same system. However, that's not to say the programmer couldn't have shaved off quite a few bytes of RAM by cleaning up the code a bit. Whether this would amount to micro-optimizing or not would require analyzing the entire PRAM bank, but considering clusters of unused bytes were prime real estate on NES cartridges, I think it's safe to say these may have been useful optimizations. </p><p>The biggest offender is the selection box on the password entry screen. Selection boxes are just tiles. The password screen is comprised of a series of tiles that form boxes. The normal box is the darker one on the left, while the selected box is the lighter one to the right. Notice the health bars wedged between them. This forced the lighter box's tiles to be disjointed to fit them in the same CHR. </p><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><img alt="" height="200" src="data:image/png;base64,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" style="margin-left: auto; margin-right: auto;" width="200" /></td></tr><tr><td class="tr-caption" style="text-align: center;"><i>Original tile set</i><br /></td></tr></tbody></table><p style="text-align: center;"></p><p>One consequence of this setup is the programmer was forced to write a conversion table for the tiles. Instead, he just mapped all the tiles out. The program takes the previous position of the cursor, performs a tile
write for the regular box, then takes the current position of the cursor
and performs a tile write for the selected box. </p><p style="margin-left: 40px; text-align: left;"><span style="font-family: courier;"><b>Icon (selected)<span> </span>04 04 </b><br /><span> </span><span> </span><span> </span><span> </span><span> </span>86 A4 A4 87 <br /><span> </span><span> </span><span> </span><span> </span> 96 00 00 97 <br /><span> </span><span> </span><span> </span><span> </span> 96 00 00 97 <br /> <span> </span><span> </span> A6 A5 A5 A7 <br /><b>Icon (regular)<span> </span>04 04</b> <br /><span> </span><span> </span><span> </span><span> </span> 80 81 81 82 <br /><span> </span><span> </span><span> </span><span> </span> 90 00 00 92 <br /><span> </span><span> </span><span> </span><span> </span><span> </span>90 00 00 92 <br /><span> </span><span> </span><span> </span><span> </span> A0 A1 A1 A2 <br /><b>Pass (selected) 03 03 </b><br /><span> </span><span> </span><span> </span><span> </span><span> </span>86 A4 87 <br /><span> </span><span> </span><span> </span><span> </span> 96 00 97 <br /><span> </span><span> </span><span> </span><span> </span> A6 A5 A7 <br /><b>Pass (regular) 03 03 </b><br /><span> </span><span> </span><span> </span><span> </span> 80 81 82 <br /><span> </span><span> </span><span> </span><span> </span> 90 00 92 <br /><span> </span><span> </span><span> </span><span> </span> A0 A1 A2</span></p><p style="text-align: left;">The first two values in each set are the height and width of the box. Everything after that is the tile layout in order. That is 58 bytes of data just right there! If we delete the tile mappings and keep just the sizes, we're down to 8 bytes. The regular boxes are the same size as their selected counterparts, so we can cut that number in half. That's 4 bytes, which would be perfectly acceptable if you want to leave things open for the possibility of irregularly shaped selection boxes. Since the final project only uses uniform dimensions, though, we can cut that down to just <b>04</b> and <b>03</b>. We don't even need those in an array, because the program knows which size box it's going to use anyway. <br /></p><p style="text-align: left;">The next step would be to rearrange the CHR. This is no small feat, though. Editing the CHR itself is simple. It could be made to look like this:</p><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><img alt="" height="200" src="data:image/png;base64,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" style="margin-left: auto; margin-right: auto;" width="200" /></td></tr><tr><td class="tr-caption" style="text-align: center;"><i>Optimized tile set</i><br /></td></tr></tbody></table><p style="text-align: left;"> In this way, the code for the selection boxes could be simplified. For the previous selection, you could either just rewrite the original tile data, or take the current tile and subtract 3. For the new selection, you could take either the original tile data and add 3, or take the current tile and add 3. As an added bonus, this may even free up additional bytes (I haven't looked into it). </p><p style="text-align: left;">Unfortunately, that CHR is shared elsewhere. The sound test screen uses it. It's used for the status bar also. You would need to go through the ROM and revise all the tile data that uses the light box and update the healthbar render code. Doable, but tedious.</p><p style="text-align: left;"><br /></p><p style="text-align: left;">We can free up 13 more bytes by removing redundancies in the code that sets the selected icon in the pass grid. At $0077A4 in CV3j, those 9 bytes are fundamentally identical to the 9 bytes immediately prior. The only difference is one used ADC while the other used ORA, but the resulting value will always be the same, so it's redundant. And one saves the value to Y register, the other pushes the value to the stack. Since the Y register doesn't get modified anytime soon, the nearby PLA op could be changed to a TYA op instead. Shortly thereafter at $0077BD, the program loads #00 and jumps a line. The issue here is the value in A register is already #00, making the LDA op pointless. Furthermore, while the subsequent BEQ op<i> is logical</i>, it can be removed completely if we change the BNE op at $0077BB to BEQ. </p><p style="text-align: left;"><br /></p><p style="text-align: left;">I know there is a lot more redundancy in the game's code and plenty of bytes to free up, but these 71 bytes were the most egregious to me because they were all in the same section of the program.<br /></p><p>
</p><p></p>Theou Aegishttp://www.blogger.com/profile/15226096714222858333noreply@blogger.com0tag:blogger.com,1999:blog-5213606320986462148.post-56024715701290489542024-01-10T10:42:00.000-08:002024-01-10T12:00:53.603-08:00Opening Up Breakable Walls With Subweapons In CV3<p>CV3 has very strict rules for breakable walls. One of those strict rules is that only Trevor's whip, Sypha's staff, Grant's dagger, and Alucard's fireballs can break walls. But what if you want to make walls breakable by other means? Well, I'm here to help you with that. It is a very simple feat that you can accomplish by changing <i><b>just one byte</b></i> in the ROM!</p><p> </p><h3 style="text-align: left;"> Removing The Whip Limitation<br /></h3><p>Breakable walls have a finite state machine at their core. While in state 0, they will check for collisions with weapons. If the PC is on the last frame of a normal attack (when the weapon's <b>damage</b> variable is set), then the wall will check for collisions with the primary weapon <i>only</i>. The only time subweapons will be checked is if the PC is on any other frame of the attack or not attacking at all. </p><p>You may be wondering why the program even checks for subweapon collisions against breakable walls if only primary weapons affect the walls. Grant's dagger and Alucard's fireballs do not occupy the primary weapon's data structure, so they are for all intents and purposes counted as subweapons, even though they do not require holding UP on the D-pad. <br /></p><p>Resolving this minor issue is fairly simple. The entire instruction can be found at $00B149 in the ROM for CV3j or $00B18D for CV3u. <br /></p><p style="margin-left: 80px; text-align: left;"><span style="font-family: courier;"><u>AD 30 06</u> <u>D0 03</u> <u>4C 3E B2</u> <u>4C 8F B1</u> CV3u<br /><u>AD 30 06</u> <u>D0 03</u> <u>4C F8 B1</u> <u>4C 4B B1</u> CV3j<br /></span><span style="font-family: courier;"> | | | |<br />if damage | | |<br /> == 0 | |<br /> check subweapons |<br /> else check weapon</span></p><p style="text-align: left;">Instruction <b>AD</b> tells the program to check a variable, in this case the damage variable $0630. Instruction <b>D0</b> then tells the program to jump 3 bytes if the damage variable is anything other than 0, which would be the case when the PC is on the last frame of a normal attack. Instruction <b>4C</b> tells it to jump to another subroutine, then exit all subroutines after it runs. Address $B1F8 is the subroutine for subweapon checks, while $B14B is the primary weapon check. Both subroutines exit upon completion. </p><p style="text-align: left;">If we wanted to check for <i>both</i> weapon and subweapon collisions at the same time, we would need to change 4C4BB1 to <b>204BB1</b>. Instruction <b>20</b> tells the program to jump to another subroutine, then resume this current subroutine after running the other. Unfortunately, if we leave it at that, it would cause an error, because the next line of code is unrelated to this. We would need to also move our new instruction back 3 bytes. </p><p style="text-align: left;">Now that <b>D0 </b>instruction is going to swap the behaviors of the weapon and subweapon collisions. We don't want that. Instead, we will want to change it to instruction <b>F0</b>, which will instead check if the damage variable is #00. In other words, only check the primary weapon if it is on the last frame of animation. The code will be slightly different in CV3u due to different addresses, but the instruction changes are the same. So now our final line of code should be</p><p style="margin-left: 80px; text-align: left;"><span style="font-family: courier;"><u>AD 30 06</u> <u>F0 03</u></span><span style="font-family: courier;"> <u>20 8F B1</u></span><span style="font-family: courier;"> <u>4C 3E B2</u> CV3u<br /></span><span style="font-family: courier;"><u>AD 30 06</u> <u>F0 03</u> <u>20 4B B1</u> </span><u><span style="font-family: courier;">4C F8 B1</span></u><span style="font-family: courier;"> CV3j<br /></span><span style="font-family: courier;"> | | | |<br />if damage | | |<br /> >0 | |<br /> check weapon |<br /> then check subweapons</span></p><p>Of course, all of this is optional and unlikely to actually affect gameplay significantly. If players are throwing subweapons while attacking with the primary weapon, they will probably be too focused on other things to notice the subweapons not breaking walls all the time.</p><p> </p><h3 style="text-align: left;">Enabling Other Subweapons</h3><p style="text-align: left;">If we know that Grant's dagger and Alucard's fireball are treated as subweapons, and that they are the only two subweapons allowed to break walls, there obviously is code checking what type of subweapon hit the breakable wall. Fortunately for us, that conditional is just complex enough that it offers up quite a bit of flexibility, but is simple enough that we can utilize it even with just 1 byte change. You can find the conditional in the ROM at address $00B214 in CV3j. </p><p style="margin-left: 80px; text-align: left;"><span style="font-family: courier;"><u>BD 4E 05</u> <u>C9 0A</u> <u>F0 13</u> <u>C9 08</u> <u>F0 0F<br /></u> | | | | |<br />switch object | | |<br /> case Fire: | | |<br /> check collision | |<br /> case Dagger: |<br /> check collision<br /></span></p><p style="text-align: left;">This time the code is essentially a switch statement called within a with() loop. Let's suppose you are running off CV3u and didn't need Grant's dagger. Perhaps you want to make the Holy Water break walls instead. We only need to alter one of the cases. In this case, we could just change <b>08</b> to <b>04</b>, which is the ID of the Holy Water subweapon (not the drop). This would retain Alucard's fireballs. </p><p style="text-align: left;">What if you instead wanted to allow <i>all</i> subweapons to damage breakable walls? You would need to turn this into an if-else chain, and fortunately you only need to change one or two bytes here to make that happen. If you change either <b>F0</b> into <b>D0</b>, you will enable all subweapons except for whichever subweapon was specified. If you alter the first one, you prevent Alucard's fireball from breaking walls. If you alter the second one, you prevent Grant's dagger from breaking walls. If you change both to <b>D0</b>, you just succeeded in enabling <i>all</i> subweapons to break walls. If you wanted all subweapons except, say, the cross, you would change <b>0A F0</b> to <b>02 D0</b>, which would essentially make the code "if Subweapon[X].Object != obj_Cross_Weapon then check_collisions()". You wouldn't need to make any further changes, because the check against Grant's dagger becomes redundant.</p><p style="text-align: left;"><br /></p><h3 style="text-align: left;">Important: Disclaimer</h3><p style="text-align: left;">If ou use reVamp to edit the ROM and make your own levels, it is very difficult to add breakable walls to levels. The editor was designed to pretty much only allow you to edit pre-existing terrain objects. If a room didn't have any terrain objects, you were out of luck. If you hack the ROM yourself, you can of course easily add breakable walls into rooms that do not have any terrain objects. However, the game itself has a hard limit on how many breakable walls you can have and where you can place them.</p><p style="text-align: left;">As reVamp's little disclaimer tells you, there is a hard limit of two breakable walls per room. It erroneously states the walls must be at least one screen apart, but the first breakable block you encounter in the game disproves that. I don't know where the developer even got that idea.<br /></p><p style="text-align: left;">What reVamp does not tell you is that there appears to be a hard limit of four walls per block. I say "appears" because two bytes of zero page RAM I have not yet identified but act as "destroy flags" would fall within this permissible range. Unfortunately, using reVamp, you cannot reach this limit. Each block has an ID assigned to it. The game does not assign that ID automatically, so you must define it yourself. The breakable wall editor in reVamp does not give you that ability. If you go into the actual ROM and give a breakable block an ID greater than #03, the wall may not spawn when entering the room. An ID of #05 should make it spawn all the time, since it would point to gamepad 2. <br /></p>Theou Aegishttp://www.blogger.com/profile/15226096714222858333noreply@blogger.com0tag:blogger.com,1999:blog-5213606320986462148.post-81144482896816780172024-01-09T14:44:00.000-08:002024-01-10T10:43:10.585-08:00Castlevania III Myth: The Meat Has The Power To Heal<p>I've gone through my whole life thus far believing that the mystery meat is what heals Trevor & friends. It turns out all this time I have been mistaken. The meat does nothing! It's the wall that heals you.</p><div class="separator" style="clear: both; text-align: center;"><a href="https://www.ifcj.org/wp-content/uploads/Yael-at-Western-Wall_2021-04-12_2645_14683445-750x375-2.jpg" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="375" data-original-width="750" height="320" src="https://www.ifcj.org/wp-content/uploads/Yael-at-Western-Wall_2021-04-12_2645_14683445-750x375-2.jpg" width="640" /></a></div><br /><p></p><p>There is an actual Mystery Meat object in CV3. It's object #90. If the player tries to pick it up, it simply disappears. There is no code associated with it other than, quite literally, "do nothing." </p><p>When you bust down a wall and see the meat drop, or any other item, what you are really seeing is the wall itself, just with a different sprite. If you see a multiplier drop from the wall, it's not really a multiplier, it's still just the wall. If you see a big heart drop from the wall, it's not really a heart, it's still the wall. </p><p>We've been lied to this entire time!<br /></p>Theou Aegishttp://www.blogger.com/profile/15226096714222858333noreply@blogger.com0tag:blogger.com,1999:blog-5213606320986462148.post-33859821476763629022024-01-03T17:06:00.000-08:002024-01-03T17:08:07.258-08:00The Case of the Missing Headless Pirate Sprite<p>Enemies with melee attacks all used the same batch of sprites when dealing damage, at least in CV3J -- specifically the damage-dealing frame. This includes the Whip Skeleton, Sword Skeleton, Headless Pirate, </p><div style="text-align: center;"><a href="https://media1.tenor.com/m/QSF5gnCFjfMAAAAC/girls-costume-warehouse-and-frog.gif"><img alt="and frog." border="0" data-original-height="373" data-original-width="498" src="https://media1.tenor.com/m/QSF5gnCFjfMAAAAC/girls-costume-warehouse-and-frog.gif" /></a></div><p>This actually made it pretty easy to narrow down which sprite goes to the Whip Skeleton, which goes to the Sword Skeleton, which ones go to the Headless Pirate, and which ones go to the Frog. The thing is, the Headless Pirate's animation has 4 sprites -- $08F2, $08F4, $08F6, and $08F8 -- but it only uses three of them. The animation skips over sprite $08F6. </p><p>Sprite $08F6 looks like the inbred lovechild of $08F4 and $08F8. It has the body and hand positioning of $08F4 and the sword of $08F8, but the sword is off by 8 pixels (I'm estimating). If we rewrite the Headless Pirate's sprite animation to incorporate $08F6, we could come up with what might pass as a sword flourish similar to one of Charlotte's attacks in Samurai Showdown.<br /></p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://www.culturaneogeo.com/personajes2/charlotte/char-ss4special.gif" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="204" data-original-width="304" height="134" src="https://www.culturaneogeo.com/personajes2/charlotte/char-ss4special.gif" width="200" /></a></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhSUDaAj8ynFtrNPwY43tdN4PGUddEF-TgO8IKjp4M_u4w-lbmq1ysryW1PPoSDj-rwZEid8XZEvH-nksgeGTrYXAAxPWR66_uUlsVyiIvaquLWulr_gdgd0-T4IDG9UlnXivqIKTDvDt27qolL3j3Vum11RAArdShfalTUefGdikBsQliCpopowJ4M43aW/s256/headlesspirate_0.gif" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="224" data-original-width="256" height="224" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhSUDaAj8ynFtrNPwY43tdN4PGUddEF-TgO8IKjp4M_u4w-lbmq1ysryW1PPoSDj-rwZEid8XZEvH-nksgeGTrYXAAxPWR66_uUlsVyiIvaquLWulr_gdgd0-T4IDG9UlnXivqIKTDvDt27qolL3j3Vum11RAArdShfalTUefGdikBsQliCpopowJ4M43aW/s1600/headlesspirate_0.gif" width="256" /></a></div><p>I didn't like how the sword had the same shape in $08F6 and $08F8, so I tried flipping the sprite vertically to see how it would look. I never messed with sprites in CV3, so I didn't know what to expect. I looked up the various bit uses in the OAM on NESDev, found the byte which affected the sword's palette, then set what I assumed was the flip bit. Not only did it flip the sword, it made the sword line up with the hand! I reworked the animation to make it look more like a door stopper, or like the Headless Pirate flourished the sword on thrust.</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi56oL7zf3xCKFR1ubzk2a7azVIX1oVnqd7SjjCUjLTEfvz6hEDH4TIEDqlUEHc_Ux-aZLw_1yTe8SBBgQTb69komd3KDFc1aOnPFbVAFPkHUXR31BAQeOO56fLoL_D6pB6fMbElmVE-M7Sd7MlQYP-LmRkb11rjqQ87I9M6gWnmA50W4M8klJq4Ixlx4UJ/s256/headlesspirate_2.gif" style="margin-left: 1em; margin-right: 1em;"></a><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi56oL7zf3xCKFR1ubzk2a7azVIX1oVnqd7SjjCUjLTEfvz6hEDH4TIEDqlUEHc_Ux-aZLw_1yTe8SBBgQTb69komd3KDFc1aOnPFbVAFPkHUXR31BAQeOO56fLoL_D6pB6fMbElmVE-M7Sd7MlQYP-LmRkb11rjqQ87I9M6gWnmA50W4M8klJq4Ixlx4UJ/s256/headlesspirate_2.gif" style="margin-left: 1em; margin-right: 1em;"></a><a href="http://i.imgur.com/Dy7wFSZ.gif" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="266" data-original-width="475" height="179" src="http://i.imgur.com/Dy7wFSZ.gif" width="320" /></a></div><img border="0" data-original-height="224" data-original-width="256" height="224" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi56oL7zf3xCKFR1ubzk2a7azVIX1oVnqd7SjjCUjLTEfvz6hEDH4TIEDqlUEHc_Ux-aZLw_1yTe8SBBgQTb69komd3KDFc1aOnPFbVAFPkHUXR31BAQeOO56fLoL_D6pB6fMbElmVE-M7Sd7MlQYP-LmRkb11rjqQ87I9M6gWnmA50W4M8klJq4Ixlx4UJ/s1600/headlesspirate_2.gif" width="256" /></div><p>That was how it looked going from $08F8 to $08F6 then $08F4. I wondered what it would look like just adding $08F6 to the end of the Headless Pirate's official attack animation. The result was more like a full sword swipe. It looks a bit weird if the Headless Pirate holds the sword out for too long, but it would probably look nice as a quick slash.<br /></p><p></p><p></p><div class="separator" style="clear: both; text-align: center;"><div class="separator" style="clear: both; text-align: center;"></div><div class="separator" style="clear: both; text-align: center;"></div><div class="separator" style="clear: both; text-align: center;"></div><div class="separator" style="clear: both; text-align: center;"></div><div class="separator" style="clear: both; text-align: center;"></div><div class="separator" style="clear: both; text-align: center;"></div><div class="separator" style="clear: both; text-align: center;"></div><div class="separator" style="clear: both; text-align: center;"></div></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg5KP3zSoQ_zeKWuss5YNjAN6SY1NDsFyQC4sSc06d0LJ3ocxlD5CcZ-uURNTFcv90bKh0D25ZGPI_nVeoQWTWpIizm1yP_EuquRIK55kl4_CaezPTL0136FZFrj77FeXZq8DSgpW2g9scjj5WVi9sQucp_Hk78iRW9Qf__0A_pw2Ehe03d8Qdi75KH3bV_/s256/headlesspirate_3.gif" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="224" data-original-width="256" height="224" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg5KP3zSoQ_zeKWuss5YNjAN6SY1NDsFyQC4sSc06d0LJ3ocxlD5CcZ-uURNTFcv90bKh0D25ZGPI_nVeoQWTWpIizm1yP_EuquRIK55kl4_CaezPTL0136FZFrj77FeXZq8DSgpW2g9scjj5WVi9sQucp_Hk78iRW9Qf__0A_pw2Ehe03d8Qdi75KH3bV_/s1600/headlesspirate_3.gif" width="256" /></a></div><p>The Headless Pirate's attack animation is handled by its state machine, so each frame is deliberate. Giving it an extra frame of animation would have only taken up 4 bytes of data in its state machine. I find it hard to believe that they were pruning code, looking for 4 bytes to free up, and this guy's sprite animation came to mind. </p><p>The simplest, plausible explanation is that the programmer accidentally specified $08F2 as either the first or last frame, which itself only lasts for a split second, rather than $08F6. However, the sprite still looks broken, even in that split second. With the sprite unaltered, starting the animation with $08F6 would make it look like the Headless Pirate is stabbing down from high, similar to how Charlotte starts up her attack above. If the flip bit is set, it would look like the Headless Pirate did a quick slash down and then straightened the sword -- a very nice animation in slow-motion. If the animation ends with $08F6 instead, the original sprite looks atrociously broken. However, if the flip bit is set, then the result would be nearly identical to the animation above and look really good as well.</p><p>So here's what I think really happened: The sprite artist drew the Headless Pirate to have an attack animation comprised of four unique frames. The programmer wrote the Headless Pirate's state machine to include 4 frames of animation for the attack. For some reason, their proprietary software failed to set the flip bit when exporting the sprite to code. At some point in development, they noticed the sprite was broken. Rather than try to fix the sprite, which would have required a costly and time-consuming software update, they simply changed it from $08F6 to $08F2.</p><p>We will probably never know the truth.<br /></p>Theou Aegishttp://www.blogger.com/profile/15226096714222858333noreply@blogger.com0tag:blogger.com,1999:blog-5213606320986462148.post-60105625451140523002023-11-23T11:04:00.000-08:002023-11-23T11:04:18.616-08:00Bit by bit, I'm going insane<p>I think I finally figured out what that high bit was in the "behavior class" variable for bosses and enemies. I searched the PROM for when the byte was loaded. Most of the references just ANDed by #7f, which tells me nothing. I narrowed the search down to BMI (branch if minus) or BPL (branch if plus) checks. Nothing. Then I tried narrowing the search down to AND #80 checks. I got one hit and it seemed to be the only one I would find referencing that high bit. </p><p>Any object less than #10 wouldn't run that line of code. That is, the bosses skipped that code. ...So why do the bosses have that bit set? Guess I need to search a little more for that answer, hopefully. All other objects set that bit when they run their "Create Event". I use quotes because all instances essentially run a creation event in the spawn routine. All the variables are cleared to 0, then the object and behaviors are set. Well, it turns out there is a separate routine which runs every step or so checking if that high bit is cleared. If so, it sets the sprite for the instance and then sets the bit to let the game know that instance has already had its sprite set. </p><p>First issue: Why run that code every step? Why not set the sprite when the object is created? This is just a waste of CPU space. <br /></p><p>Second issue: The game already sets the sprites for instances <i>inside their behavior classes</i> or inside the instance which spawned them. Or did I take that for granted and overlooked something somewhere? </p><p>Perhaps more updates will follow. In the meantime, #wtfkonami.<br /></p>Theou Aegishttp://www.blogger.com/profile/15226096714222858333noreply@blogger.com0tag:blogger.com,1999:blog-5213606320986462148.post-55195056408078507712023-10-16T14:15:00.002-07:002023-10-16T14:15:19.641-07:00I found how to allow Sypha to freeze ALL bosses for up to FOUR seconds!<p>When I mapped the RAM addresses for CV3, I forgot a couple of the identifiers I used were simply meant to be notes, because I didn't know what exactly the bytes were used for. Two of those bytes were $00C1 and $00C2 (CV3u: $00C4 and $00C5). I noticed the bosses kept reading one of those two bytes. My notary identifier simply said "Ally Disabled", because you could not change allies while either of those bytes were set. </p><p>I played through the game countless times, fighting every enemy and every boss, waiting for my breakpoint on $00C1 and $00C2 to trigger. It finally occurred to me to search the ROM for any writes to $00C1,X. I knew where the read point was, so manually set $00C1 to trigger the read, then searched for #99C100, which would be the write operation. I looked at the code surrounding the first hit. That was when I spotted something I hadn't seen in while -- a check on the boss's "Hit Effect" variable for a value of #02. Somehow, the significance of that value was still fresh in the recesses of my mind. </p><p>That was the damage modifier for Sypha's Ice spell!<br /></p><p>It totally slipped my mind that ally swapping is also disabled when Sypha freezes anything. I had studied Sypha's code, as well as the ally swap code, never giving much thought as to why $00C1 and $00C2 were checked separately from the enemies' status (if an enemy's status is set to "frozen", you can't swap allies). It hadn't occurred to me that since bosses were programmed distinctly from regular enemies, any special considerations for the PC might also need some considerations. </p><p>While inspecting the two bytes, I learned that the same timer that was used to stun the boss was also used to handle freezing. How did the game know what value to set the timer to? I followed the code and found the database of frozen times. Most bosses had freeze times, most projectiles didn't. One notable boss without a freeze time is The Creature. I wanted to verify this, so I warped to it and started spamming Ice. Sypha could freeze the rocks, but not The Creature. I changed the freeze time in the ROM to #FF, the max duration. And that's all she wrote. <br /></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5XD4fivOR1gzy302IwgvpB_-vUemmalBpDmKQMBfgPRX8uhBPFjCjMSN9Aylr-916-HiM1omeTQnnr2_fKBH1vX7b4UdeQOWx_gm4d7Ob4BBpbR361d8UZzUZEgZSH6BMzFQNAaV2ndxeVsogaLorYs8aZtRCVBQ8Y26_FXcMwlPKXMbloVo5zQAPB1IU/s256/Akumajou%20Densetsu%20(Japan)-0.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="224" data-original-width="256" height="350" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5XD4fivOR1gzy302IwgvpB_-vUemmalBpDmKQMBfgPRX8uhBPFjCjMSN9Aylr-916-HiM1omeTQnnr2_fKBH1vX7b4UdeQOWx_gm4d7Ob4BBpbR361d8UZzUZEgZSH6BMzFQNAaV2ndxeVsogaLorYs8aZtRCVBQ8Y26_FXcMwlPKXMbloVo5zQAPB1IU/w400-h350/Akumajou%20Densetsu%20(Japan)-0.png" width="400" /></a></div><br /><p>You can find the boss freeze duration database in the ROM at $24330 (CV3j) or $24328 (CV3u). The data set is 47 bytes long (48 if you count the #00 immediately before it).<br /></p>Theou Aegishttp://www.blogger.com/profile/15226096714222858333noreply@blogger.com0tag:blogger.com,1999:blog-5213606320986462148.post-3692500044975711632023-10-13T16:25:00.000-07:002023-10-13T16:25:13.976-07:00Did Dracula have a Genie?<p>We can argue all day and night about whether the final bosses in CV3 were actually Dracula himself or ritual summons, but that's not the point. What is known without debate is that Dracula did indeed summon demons in CV3, such as Baphomet ("Leviathan"). </p><p><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://themongomania.files.wordpress.com/2008/07/castlevania3_hard.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="224" data-original-width="256" height="224" src="https://themongomania.files.wordpress.com/2008/07/castlevania3_hard.jpg" width="256" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;"><i>Castlevania III final boss</i><br /></td></tr></tbody></table></p><p>I still have no idea what the floating mass of vile-spewing heads could have been, but I had previously considered the final boss to be Pazuzu. It's a demon from a classic horror film (<i>The Exorcist</i>), so it would make sense for Pazuzu, or an entity inspired by Pazuzu, to be used in a Castlevania game. However, now I'm not so sure. <br /></p><p><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://static.wikia.nocookie.net/megamitensei/images/c/c1/Pazuzu_(P_O.A.).png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="800" data-original-width="555" height="296" src="https://static.wikia.nocookie.net/megamitensei/images/c/c1/Pazuzu_(P_O.A.).png" width="206" /> </a></td><td style="text-align: center;"> </td></tr><tr><td class="tr-caption" style="text-align: center;"><i>Pazuzu, Lord of Winds</i></td><td class="tr-caption" style="text-align: center;"><i> </i></td><td class="tr-caption" style="text-align: center;"><br /></td></tr></tbody></table></p><p>Pazuzu had the head of a lion or dog. He also had a snake dick or tail.
We don't see either of those features in the final boss. We do see
talons on a human body with Akkadian wing, along with a crown or something on the head. I'm going to
overlook the posture of the hands, which I will explain why shortly.</p><p><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://collectionapi.metmuseum.org/api/collection/v1/iiif/322614/1568267/main-image" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="800" data-original-width="570" height="406" src="https://collectionapi.metmuseum.org/api/collection/v1/iiif/322614/1568267/main-image" width="290" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;"><i>Genius of Nimrud<br /></i></td></tr></tbody></table><br /></p><p>The beak finally nagged me enough, so I googled demons and Sumerian entities with bird heads. That's when I stumbled upon an image of a relief featuring a Sumerian humanoid with the head and wings of a bird. It even has a similar posture to Pazuzu, but it is lacking talons. Furthermore, instead of a single horn or crown on its head, it has a row of feathers. </p><p>This entity, now referred to as a griffin-demon by some archaeologists, was at one time erroneously identified as the demon Nisroc. So even if you don't like the idea of Dracula summoning genies, it could still have been a "documented" demon.<br /></p><p>The raised hand could be a sign of blessing. When Trevor defeated Dracula, the genie would no longer bless him, so the arms were lowered. This could apply to either Pazuzu or the geni.</p><div class="separator" style="clear: both; text-align: center;"></div><div class="separator" style="clear: both; text-align: center;"></div><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="http://www.hardcoregaming101.net/cvbackup/drac3rd.gif" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="350" data-original-width="521" height="313" src="http://www.hardcoregaming101.net/cvbackup/drac3rd.gif" width="466" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;"><i>What a relief - it's just a statue!</i><br /></td></tr></tbody></table><div class="separator" style="clear: both; text-align: center;"> </div><p>It's not necessarily canon, but Harmony Of Dissonance depicts the same demon as what is clearly a statue, since one of the arms is busted off. It's not a relief, which is how Nisroc was depicted. Could this mean the final boss really was Pazuzu, but with the design features of the Genie of Nimrud blended with that of Pazuzu in order to avoid copyright infringements?</p><p>Or maybe Dracula's powers were granted to him by a genie after all, not literal demon. After all, we see mummies in Dracula's castle throughout the series. Yes, this is a reference to <i>The Mummy</i> and all the other mummy horror films, but it could also suggest that Dracula was dabbling in Middle-Eastern religions. The final boss was Dracula's genius - a guardian spirit, that's why he had such a large statue erected.<br /></p>Theou Aegishttp://www.blogger.com/profile/15226096714222858333noreply@blogger.com0tag:blogger.com,1999:blog-5213606320986462148.post-38347445885728714312023-10-02T13:51:00.002-07:002023-10-02T13:51:27.402-07:00Who The Hell Coded CV3's Bosses? Like, WTF?<p>When I tried mapping out all the "object" indices -- that is, the value which defines the general behavior of every enemy and item in CV3 -- I was left with a series of "Unknown" objects. Many of these don't have any significant behaviors, making them nearly impossible to figure out what they could have been. I didn't think much of it, as I had mapped everything out that was in-game, as far as I knew.</p><p>While analyzing the bosses -- again, for the umpteenth time -- I realized they have their own "behavior class". That's a variable that pretty much dictates for every enemy and item how it should behave inside its finite state machine. Cool. Behavior classes are pretty pointless in bosses -- they're just one extra thing the game has to account for that could have been handled in the main code (like CV1's beautiful boss code). So I started mapping out all the classes for bosses. When I was done, I was left with a few gaps.</p><p>"No biggy," I thought. "I'll just fight all the bosses again and set breakpoints for these specific behaviors. The ones I don't find, I'll dig deeper into the code to see if they were deleted or cut content."</p><p>Starting from the bottom, my first missing boss class was #48. I searched the PROM for all references to LDA #48. Nothing useful. Then I searched for LDA #C8, which I still don't know what the point of that is, but that high bit means something for bosses. I got a hit, so I read the code that was supposed to set the class to #C8. It belonged to one of the Unknown boss objects. Needless to say, I got really excited in anticipation that I was about to discover lost content.</p><p>...It was the Giant Bat.</p><p>Hear me out! It wasn't like I hadn't mapped out the Giant Bat already. It's object #04, uses classes #18, #19, #1A, #1B, and #46. I was looking at the code in question and it dealt with the boss thingy (it could have been a projectile, for all I knew) flipping its sprite when damaged by the player. I thought, "What the heck would flip its sprite when damaged?" The only thing I could think of at the time was the Giant Bat. So, I went to the swamp and faced the boss, set a breakpoint for the start of the code in question, and started the fight. Nothing at first, so the next thing to try was attacking the bat.</p><p>BINGO! Except not really.</p><p>I ran the game. The debugger stopped again. I looked at the object index for the Giant Bat.</p><p>#04.</p><p>Why the hell was it running the Unknown object's code for the Giant Bat, instead of the Giant Bat's code? It was time for the trace logger. I ran the game and looked at what code it processed before the breakpoint. It deliberately skipped the Giant Bat object's code if the boss was active. That's when it hit me. </p><p>Four of the Unknown objects are the Skeleton, Cyclops, Giant Bat, and Grant bosses.There are a few other Unknown boss objects for me to look into, but I suspect now they are similar to these four. Apparently, whoever programmed the bosses used one object for when a boss waits for the player to get close enough and a different object for movement. However, at some point he decided to just point the waiting object to the movement code instead of changing the object. </p><p>Yet another reason for me to despise whoever coded the bosses...</p><p>Oh yeah, and if I hadn't brought it up already: Death and the Doppelganger were apparently coded by a different programmer, because they are the only enemies (including items) to not use behavior classes. Oh, and I can't forget the occasional boss that sets the class to #80, which is just #00 with the high bit set, and then never changes the class ever again. Like, why even bother at all?</p><p>*cries in a corner*<br /></p>Theou Aegishttp://www.blogger.com/profile/15226096714222858333noreply@blogger.com0tag:blogger.com,1999:blog-5213606320986462148.post-13559894178682092052023-06-05T16:43:00.000-07:002023-06-05T16:43:11.913-07:00Top Players Tennis<p style="text-align: left;"><span style="font-weight: normal;">This game doesn't get much love, and rightly so, but I grew up with it. I could have sworn back then that stats like Spin made a huge difference in how the ball traveled. Yet, playing it again and looking at the code for this game, Spin is more or less negligible. I don't think my version was any different from the version of the ROM I have. The nice thing about playing on a ROM was I could finally figure out what each stat actually did! </span></p><p style="text-align: left;"><span style="font-weight: normal;">I waded into this code in an attempt to help someone make an old-school tennis game. This was the only tennis game I was very familiar with. These are some of my takeaways from this recent expedition.<br /></span></p><h4 style="text-align: left;"><span style="font-weight: normal;"> </span></h4><h2 style="text-align: left;">Ball Physics </h2><h3 style="text-align: left;"><span style="font-weight: normal;"><u>BALL MOTION</u></span></h3><p>On the surface, this game appears to be semi-realistic. It incorporates aerodynamics and such. However, the vertical axis is treated separately from the horizontal axis. This may be simply to make the logic simpler for the designers, but it does make the game feel like it originated as a 2.5D side-view tennis game. The artificial z axis is for gravity. The vertical axis is for impulse motion. The horizontal axis is for angular returns and ball spin.<br /><br /><span style="font-weight: normal;"><u>COURT SURFACES</u></span><br /></p><p style="text-align: left;">Each
court has a restitution value that affects how high a ball bounces, and
a friction value that affects the vertical speed after bouncing. The
higher the value, the less significant the changes in velocity. The
French Open's clay court has a friction value of 68% and a restitution
value of 62%, meaning with each bounce the vertical speed will decrease
by 32% and the bounce height will decrease by 38%. In contrast,
Wimbledon's grass court has a friction value of 81% and restitution
value of 59%, meaning with each bounce the vertical speed will decrease
by only 19% and the bounce height will decrease by 41%. The differences
between restitution values might not seem like much, but after factoring
in gravity, a 3% difference is significant. </p><p style="text-align: left;">For
the sake of comparison, the back walls have restitution and friction
values of 37% each. The net has a restitution of 65% and a friction
value of 22% affecting both vertical and horizontal speeds.<br /></p><h3 style="text-align: left;"><span style="font-weight: normal;"><u>Swing Types<br /></u></span><span style="font-weight: normal;"></span></h3><p style="text-align: left;">There
are 6 types of swings. Each swing type scales the normal vector - in
other words, the rising speed - of the ball by a percentage as the ball
leaves the racquet. </p><ol style="text-align: left;"><li>The standard
swing, as well as a standard serve, using A has no effect on the ball
when hit in the lower court, and scales the rising speed of the ball by
112% in the upper court. </li><li>A diving swing using A+B away from the net scales the rising speed by 43% in the lower court, and by 50% in the upper court. </li><li>A play at the net (both standing and leaping), scales the rising
speed by 12% in the lower court, and by 48% in the upper court. </li><li>A lob swing using B increases the rising speed of the ball by 150% in the lower court, and by 154% in the upper court. </li><li>A jump serve scales the rising speed by 62% in the lower court, and by 59% in the upper court.</li><li>A soft serve scales the rising speed by 96% in the lower court, and by 78% in the upper court.</li></ol><p>The
differences between lower court and upper court are to account for the
game's perspective. In a game without such 2D perspective, this would be
detrimental for players in the upper court.</p><h3 style="text-align: left;"><span style="font-weight: normal;"><u>Ball Gravity</u></span></h3><p><span style="font-weight: normal;">Gravity
is unique for the ball and can be thought of as the net vector of
physical gravity, aerodynamic lift, and aiming. A default gravity is
calculated based on what is essentially a random value updated after the
ball is hit. For the player, the height of the ball when serving
determines the racquet angle, with the majority of angles setting
gravity to 1/2pps². For the CPU, the value is randomly chosen. Then, if
the ball is being served, an additional 1/32pps² is added make the ball
drop faster. If the player is near the net, 1/64pps² is added to
gravity.</span></p><p><span style="font-weight: normal;"> </span></p><h2 style="text-align: left;"><span style="font-weight: normal;"><b>PLAYER STATS</b> <br /></span></h2><h4 style="text-align: left;"><span style="font-weight: normal;"><u>STRENGTH</u></span></h4><p>Affects vertical speed of the ball upon impact with racquet. Each level adds 5/64pps to the ball's vertical speed. Players should try to max this out before playing the Wimbledon Open.<br /></p><p><b>Note </b>that higher ball speed increases the likelihood of out-of-bounds or fault shots, but is valuable for scoring aces. On grass courts, with their low friction coefficient, higher ball speed is preferred.<br /></p><h4 style="text-align: left;"><span style="font-weight: normal;"><u>SPEED</u></span></h4><p>Affects maximum running speed. Default is 117/32 pixels per step. The first 2 levels add 16/32pps to your speed, then only 8/32pps per level after that, until level 10, which again increases it by 16/32pps. Players should therefore only put up to 2 points in SPEED when starting out.</p><p><b>Note </b>that higher running speeds result in longer stopping delays. Higher AGILITY does help mitigate this, but not by much.<br /></p><h4 style="text-align: left;"><span style="font-weight: normal;"><u>AGILITY</u></span></h4><p>Affects acceleration and deceleration rates of the player. Default acceleration rate is 15/64pps². Default deceleration rate is 25/64pps². The first 2 levels increase acceleration by 1/32pps² and deceleration by 1/64pps². Each level after that only increases acceleration by 1/128pps² and deceleration by 1/256pps², except that last 2 levels. Level 9 increases acceleration by 1/32pps² and deceleration by 1/64pps², whereas level 10 increases both acceleration by 1/64pps². Players should therefore only put 1 or 2 points in AGILITY when starting out.</p><p><b>Note </b>that deceleration rates do not improve parallel to maximum speeds, increasing momentum adversely. Players should maximize AGILITY before raising SPEED past level 3.<br /></p><h4 style="text-align: left;"><span style="font-weight: normal;"><u>STAMINA</u></span></h4><p>Affects reduction of STRENGTH, SPEED and AGILITY levels. This is an arguably useless stat, as even at level 0 it will still take 5 minutes and 40 seconds before the first stat drop. On top of that, it has laughable diminishing returns until level 8. Players should therefore only put 2 points in STAMINA and then ignore it until the three affected stats have been maxed.<br /></p><h4 style="text-align: left;"><span style="font-weight: normal;"><u>SKILL</u></span></h4><p>Affects horizontal speed of the ball upon impact with racquet. Roughly speaking, it increases the angles the ball can travel. There are essentially 8 positions on the racquet the ball can hit, with 8 possible directions the ball can travel (16 after final calculations), for a total of 64 horizontal speed values per level, making it difficult to actually quantify this stat. Basically, each level uniformly increases horizontal speed by some factor determined by the angle the ball is hit, which results in the ball traveling at wider angles. Beginner players may want to max this out first, even though it does potentially result in more returns going out-of-bounds.<br /></p><p><b>Note </b>that at higher levels, this makes serves veer away from the center of the court. This is critical for scoring aces in the early stages of the game. Against faster and smarter opponents, this is less valuable.<br /></p><h4 style="text-align: left;"><span style="font-weight: normal;"><u>BALLSPIN</u></span></h4><p>Affects acceleration of the ball by 1/256pps² per level. Not entirely useless. Left and right spin affect horizontal acceleration explicitly. Top-spin and slices affect gravity explicitly. A slice (back-spin) effectively reduces gravity on the ball, whereas top-spin increases it. I don't think vertical acceleration is affected by Spin at all.<br /></p><h4 style="text-align: left;"><span style="font-weight: normal;"><u>FOCUS</u></span></h4><p>Affects reduction of SKILL and SPIN levels. This is an
arguably useless stat, as even at level 0 it will still take 5 minutes
and 40 seconds before the first stat drop. On top of that, it has
laughable diminishing returns until level 8. Players should therefore
only put 2 points in FOCUS and then ignore it until the two affected
stats have been maxed.</p><h4 style="text-align: left;"><span style="font-weight: normal;"><u>MIRACLE</u></span></h4><p>Affects which miracle shots you can perform. Once a miracle shot is unlocked, each level after that improves the shot up to four times. No miracle shots are available at level 0.<br /></p><ol style="text-align: left;"><li>Miracle Speed, unlocked at level 1. </li><ul><li>Level 1: increases ball gravity by 3/256pps² and speed by 6/4pps</li><li>Level 2: increases ball gravity by 13/256pps² and speed by 3/4pps</li><li>Level 3: increases ball gravity by 23/256pps² and speed by 4/4pps</li><li>Level 4: increases ball gravity by 33/256pps² and speed by 5/4pps</li><li>Level 5: increases ball gravity by 43/256pps² and speed by 6/4pps<br /><b>Note </b>that there may be a typo with Miracle Speed's boost to vertical
movement. With the negligible gravity boost at its initial level, this
makes Miracle Speed practically suicidal at the first level. Players
therefore should put 2 points in MIRACLE together if intending to use
this game feature at all. </li></ul><li>Miracle Lob, unlocked at level 2. </li><ul><li>Level 2: sets top-spin to 31/32pps² and drop height to 112<br /></li><li>Level 3: sets top-spin to 28/32pps² and drop height to 128</li><li>Level 4: sets top-spin to 27/32pps² and drop height to 144</li><li>Level 5: sets top-spin to 26/32pps² and drop height to 160</li><li>Level 6: sets top-spin to 25/32pps² and drop height to 176<br /><b>Note</b> that the ball will always attempt to land in the same positions, roughly just behind the service lane to the middle of either service box. It will never go past that position. The service box chosen depends on the horizontal speed of the ball (see "SKILL" summary). Once the drop height is reached, the ball will fall wherever it currently is over the court. Because of this behavior, the skill is best used near the sidelines. <br /></li></ul><li>Miracle Spiral, unlocked at level 3. </li><ul><li>Level 3: radius of 10px</li><li>Level 4: radius of 16px</li><li>Level 5: radius of 24px</li><li>Level 6: radius of 36px</li><li>Level 7: radius of 49px<br /><b>Note</b> that only its horizontal position changes. The vertical offset is merely a
draw effect, making a Miracle Spiral return tricky for human opponents to follow. When used as a serve, CPU opponents will get confused, frequently hitting the ball before it has bounced, resulting in a point for the player.<br /></li></ul><li>Miracle Return, unlocked at level 4.</li><ul><li>Level 4: no effect</li><li>Level 5: accelerate vertical speed by 1/32pps² based on volley count</li><li>Level 6: accelerate vertical speed by 2/32pps² based on volley count</li><li>Level 7: accelerate vertical speed by 3/32pps² based on volley count</li><li>Level 8: accelerate vertical speed by 4/32pps² based on volley count</li><li>Level 9: accelerate vertical speed by 5/32pps² based on volley count<br /><b>Note</b> that this ability is <i>extremely difficult</i> to use. And glitchy... so glitchy. Volley count is set to 0 when served from the lower court, or set to 1 when served from the upper court, and increased whenever the ball is hit. That means typically this ability merely slows down the ball, making it relatively useless. The "return" referred to by the manual is actually in reference to the volley counter. If the ability is timed properly, the volley counter will not increase when the player hits the ball. This means if the ball hits the net or goes out of bounds before bouncing, it will count against the opponent. Sometimes it doesn't register and the hit is counted anyway, for some reason, in which case the player gets the miss instead of the opponent. Another use for it, which again requires very tight timing, is to use it as soon as the ball is in play on the opponent's serve, resulting in a let or fault. <br /></li></ul><li>Miracle Split, unlocked at level 5. Essentially the same as Miracle Spiral, but without the height illusion.<br /></li></ol><p><b>Note</b> that pulling off miracle shots can be understandably tricky. The game will check for a miracle shot every time the player's height is greater than 28px off the ground. Since this occurs many times during a jump, the player <i>must</i> continue to hold the D-pad in the direction corresponding to the desired miracle shot, otherwise the game will cancel the miracle shot completely.<br /></p><p><br /></p>Theou Aegishttp://www.blogger.com/profile/15226096714222858333noreply@blogger.com0tag:blogger.com,1999:blog-5213606320986462148.post-44625663906855616132023-05-29T09:22:00.002-07:002023-05-29T16:05:43.045-07:00R.C. Pro-Am II (NES) Analysis<p> </p><p style="text-align: center;"> </p><div class="separator" style="clear: both; text-align: center;"><a href="https://upload.wikimedia.org/wikipedia/en/9/9c/RC_Pro_Am_2_cover.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="352" data-original-width="256" height="352" src="https://upload.wikimedia.org/wikipedia/en/9/9c/RC_Pro_Am_2_cover.jpg" width="256" /></a></div><br /><p></p><p>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. </p><h3 style="text-align: left;">Easter Egg</h3><p style="text-align: left;">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. </p><h3 style="text-align: left;">Audio</h3><p style="text-align: left;">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.</p><p style="text-align: left;">Each audio channel has its own track. Square/Pulse channel 1 typically holds the BGM track. The other channels have their own track pointers. <br /></p><p style="text-align: left;">The BGM tracks are as follows:</p><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">01<span> </span>Main Title</span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">02<span> </span>Name Entry <br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">03<span> On Your Mark!</span></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span>04 Pause <br /></span></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span>05 Model Shop <br /></span></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span>06 Race Finished</span></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span>07<span> </span>Continue Screen<br /></span></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">08<span> </span>Track Preview </span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">09<span> Racer</span> Standings <br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">0A<span> And They're Off!</span></span></div><p style="text-align: left;">The pulse channel SFX tracks are as follows:<br /></p><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">02<span> </span>Skidding <br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">12<span> </span>Invalid Shop Selection <br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">14 Last Lap<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">1A<span> </span>Bumpy Car</span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">20<span> </span>Airplane Gun<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">22<span> </span>Exit Parts Menu / Buckshot Drop</span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">24<span> </span><Cut Content Item><br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">2C Drag Race Checkpoint<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">36<span> </span>Out of Ammo<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">38 Point Bonus / Valuable Pickup</span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">3C<span> </span>Ammo Pickup</span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">3E<span> </span>Ammo Crate Pickup<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">40<span> </span>Roll Cage Pickup</span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">42<span> </span>Mini-Game Bonus<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">44<span> </span>Generic Money Pickup </span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">4C Freezer Shot <br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">50<span> </span>Shield Use </span></div><div style="text-align: left;"><p style="text-align: left;">The triangle channel SFX are as follows:<span style="font-family: Source Code Pro; font-size: x-small;"><br /></span></p></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">2C<span> </span>Part Purchased </span><br /></div><p style="text-align: left;">The noise channel SFX are as follows:<span style="font-family: Source Code Pro; font-size: x-small;"> <br /></span></p><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">08<span> </span>Projectile Shot</span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">0E<span> </span>Weapon Explosion <br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">10<span> Car</span> Explosion<u><i><br /></i></u></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">16<span> </span>Splash </span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">2A<span> </span>Aerial Bomb Run <br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">52<span> </span>Nitro Used</span></div><div style="text-align: left;"><p style="text-align: left;">Unused SFX indexes are as follows, with best guess as to what might be<span style="font-family: Source Code Pro; font-size: x-small;">:</span></p><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">06<span> </span>Pulse error beep / Noise splash</span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">0A<span> </span>Pulse Item Pickup?</span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">0C<span> </span>Pulse Chevron Boost?</span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">18<span> </span>Noise Crash</span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">1C<span> </span>Pulse Red Light<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">1E<span> Pulse Green Light</span></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span>26<span> Pulse Out Of Time<br /></span></span></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span><span>28<span> Pulse thud / Noise selector</span> <br /></span></span></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span>2E<span> <NO SOUND></span></span></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span><span>30<span> </span><NO SOUND><br /></span></span></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span><span>32<span> </span>Same as 0C</span></span></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span><span>34<span> </span>Same as 0A</span></span></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span><span>3A<span> Pulse upgrade / Noise explosion</span></span></span></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span><span><span>46<span> Noise puddle splash</span></span></span></span></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span><span><span><span>48<span> </span>Pulse extra refill</span></span></span></span></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span><span><span><span>4A <span> Pulse menu select / Noise splish</span></span></span></span></span></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span><span><span><span><span>4E<span> </span>Pulse plummet / Noise water drop</span></span></span></span></span></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span><span> </span> </span><br /></span></div></div><h3 style="text-align: left;">The Shop</h3><p style="text-align: left;">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. </p><p style="text-align: left;">A player can have #FFFF cash at any time, but anything above #270F will glitch text. </p><p style="text-align: left;">The developers originally intended <i>all</i> 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. <br /></p><h3 style="text-align: left;">Oil Slick Cheat-Glitch</h3><p style="text-align: left;">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. </p><p style="text-align: left;">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.</p><h3 style="text-align: left;">Race Number Errata<br /></h3><p style="text-align: left;">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. </p><p style="text-align: left;">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.<br /></p><h3 style="text-align: left;">Randomizer</h3><p style="text-align: left;">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:</p><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">LDA R[1]<br />PHA <i>save R[1]</i></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">ASL<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">ROL R[2]</span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">ROL R[2]</span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">XOR R[3]</span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">STA R[1]</span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">PLA</span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">STA R[3]</span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">XOR R[2]</span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">PHA</span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">LDA R[0]</span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">STA R[2]<br />PLA<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">ADC FRAME_COUNTER</span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">ROL</span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">STA R[0] </span></div><br />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. <br /><h3 style="text-align: left;">CPU Behavior </h3><p>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. </p><p>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.</p><p>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.</p><p>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:<br /></p><ul style="text-align: left;"><li>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. </li><li>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.<br /></li><li>If the car is onscreen and ahead of the lead player, the crusing speed limit is used. </li><li>If the car is offscreen and ahead of the lead player by only one checkpoint, the cruising speed limit is used.</li><li>If the car is offscreen and ahead of the lead player by at least two checkpoints, the safe speed limit is used.<br /></li><li>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. </li></ul><h3 style="text-align: left;">Track Design</h3><p style="text-align: left;">Please note: For the sake of this article, "vertical" refers to the <i>northeast</i> and <i>southwest</i> directions, whereas "horizontal" refers to the <i>northwest</i> and <i>southeast</i> directions, which is how each section correlates to the racetrack display at the top of the screen. </p><p style="text-align: left;">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.<br /></p><p style="text-align: left;">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.<br /><b>DO NOT SET BIT 6 OR IT WILL CRASH THE GAME!<br /></b>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 <b>59 sections</b> with defined collision behaviors.<br /></p><p style="text-align: left;">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 <b>59 sections</b> referenced in this look-up table. <br /></p><p style="text-align: left;">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 <b>61 sections</b>.</p><p>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 <b>61 sections</b>. </p><p>The track preview screen has data for <b>61 sections</b>. 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?<br /></p><p style="text-align: left;">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 <i><UNUSED></i> is missing critical data and may crash the program. Sections labeled <i><DEFINED></i> 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.<br /></p><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">00<span> </span><UNUSED></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">01<span> </span>SW Northern Bend [0,1,2]<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">02<span> </span>NW Eastern Bend<span> </span><span>[0,1,2]</span><br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">03<span> </span>SE Western Bend<span> </span><span>[0,1,2]</span><br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">04<span><span> NE Southern Bend [0,1,2]</span><br /></span></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">05<span> </span>NW Horizontal Stretch [0,1,2]<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">06<span> </span>SW Vertical Stretch [0,1,2]<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">07<span> NW Stripe Lane [0,1,2]<br /></span></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span>08<span> NW Big Jump [2]<br /></span></span></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span>09<span> NW Left Shoulder [0,1]<br /></span></span></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">0A SW Right Elbow [0,1,2]<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">0B NW Right Elbow [0,1,2]<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">0C SE Right Elbow [0,1,2]<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">0D NE Right Elbow [0,1,2]<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">0E NW Right Shoulder [1]<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">0F (nw very wide horizontal stretch) [1]</span></div><div style="margin-left: 40px; text-align: left;"><div style="text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>camera: $8109 <br /></span></div><span style="font-family: Source Code Pro; font-size: x-small;"><b><span></span></b></span><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>behavior: $C446 <br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>corner: $830D/$8321 <br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>position: $90C8/$912A<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">10 NW Merge Center [1,2]<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">11 <UNUSED> (horizontal stretch) </span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>camera: $8109 <br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><b><span> </span></b><b><span> </span>behavior: $8400 (AUDIO-VISUAL ERROR)<br /></b></span></div><div style="margin-left: 40px; text-align: left;"><div style="text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>corner: $830D/$8321 <br /></span></div><span style="font-family: Source Code Pro; font-size: x-small;"><span></span></span><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>position: $90C8/$912A<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">12 NW Intersection [0,1,2]<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">13 SW Intersection [0,1,2]<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">14 <UNUSED> Diagonal south-east [1,2]</span></div><div style="margin-left: 40px; text-align: left;"><div style="text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>camera: $8115 <br /></span></div><span style="font-family: Source Code Pro; font-size: x-small;"><b><span></span></b></span><span style="font-family: Source Code Pro; font-size: x-small;"><b><span> </span><span> </span>behavior: $0000 (FATAL ERROR)<br /></b></span><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>corner: $835D/$83CD <br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>position: $9083/$90D0<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">15 <UNUSED> Diagonal west-south [1,2]</span></div><div style="margin-left: 40px; text-align: left;"><div style="text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>camera: $817F <br /></span></div><span style="font-family: Source Code Pro; font-size: x-small;"><b><span></span></b></span><span style="font-family: Source Code Pro; font-size: x-small;"><b><span> </span><span> </span>behavior: $0000 (FATAL ERROR)<br /></b></span><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>corner: $8373/$83E3<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>position: $9092/$90D3<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">16 <UNUSED> Diagonal north-east [1,2]<br /><span> </span><span> camera: $81CC</span><br /><span> </span><span> </span><b>behavior: $0000 (FATAL ERROR)<br /></b></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>corner: $8387/$83F7<br /><span> </span><span> </span>position: $90A2/$90D6<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">17 <UNUSED> Diagonal west-north [1,2]</span></div><div style="margin-left: 40px; text-align: left;"><div style="text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>camera: $8215 <br /></span></div><span style="font-family: Source Code Pro; font-size: x-small;"><b><span></span></b></span><span style="font-family: Source Code Pro; font-size: x-small;"><b><span> </span><span> </span>behavior: $0000 (FATAL ERROR)<br /></b><span> </span><span> </span>corner: $83B7/$840F<br /><span> </span><span> </span>position: $90B9/$90D9<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">18 (nw wide horizontal stretch) [1]<br /><span></span></span><div style="text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>camera: $8109 <br /></span></div><span style="font-family: Source Code Pro; font-size: x-small;"><b><span></span></b></span><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>behavior: $C29F</span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>corner: $81A6/$8234<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>position: $90C8/$912A<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">19 (nw wide horizontal stretch) [1]</span></div><div style="margin-left: 40px; text-align: left;"><div style="text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>camera: $8109 <br /></span></div><span style="font-family: Source Code Pro; font-size: x-small;"><b><span></span></b></span><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>behavior: $C2F1 <br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>corner: $81A6/$8234 <br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>position: $90C8/$912A<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">1A <UNUSED>NW Right Elbow (same as 0B) [1,2]</span></div><div style="margin-left: 40px; text-align: left;"><div style="text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>camera: $80A7 <br /></span></div><span style="font-family: Source Code Pro; font-size: x-small;"><b><span></span></b></span><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>behavior: $C289 <br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>corner: $8264/$82D0<br /><span> </span><span> </span>position: $9092/$90DF<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">1B <UNUSED> (vertical stretch)</span></div><div style="margin-left: 40px; text-align: left;"><div style="text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>camera: $810F <br /></span></div><span style="font-family: Source Code Pro; font-size: x-small;"><b><span></span></b></span><span style="font-family: Source Code Pro; font-size: x-small;"><b><span> </span><span> </span>behavior: $0000 (FATAL ERROR)<br /></b></span><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>corner: $8335/$8349<br /><span> </span><span> </span>position: $90CC/$9130<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">1C <UNUSED> (horizontal stretch) [1]</span></div><div style="margin-left: 40px; text-align: left;"><div style="text-align: left;"><div style="text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>camera: $8109 <br /></span></div><span style="font-family: Source Code Pro; font-size: x-small;"><b><span></span></b></span><span style="font-family: Source Code Pro; font-size: x-small;"><b><span> </span><span> </span>behavior: $0000 (FATAL ERROR)<br /></b></span><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>corner: $81A6/$8234<br /></span></div><span style="font-family: Source Code Pro; font-size: x-small;"><span></span></span><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>position: $90C8/$912A<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">1D <UNUSED> (eastern bend,wide?)</span></div><div style="margin-left: 40px; text-align: left;"><div style="text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>camera: $80A7 <br /></span></div><span style="font-family: Source Code Pro; font-size: x-small;"><b><span></span></b></span><span style="font-family: Source Code Pro; font-size: x-small;"><b><span> </span><span> </span>behavior: $0000 (FATAL ERROR)<br /></b></span><span style="font-family: Source Code Pro; font-size: x-small;"><b><span><span> </span><span> </span>corner: $8264/$0000 (FATAL ERROR)<br /></span></b><span><span> </span><span> </span><b>position: </b>$9092<b>/$0000 (FATAL ERROR)<br /></b></span></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">1E NW Overpass [0,1]<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">1F<span> </span>NW Off-Ramp [0,1]<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">20 NW On-Ramp [0,1]<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">21 SW Underpass [1]<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">22 SW Merge Left [1,2]<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">23 SW Merge Right [1,2]<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">24 NW Valley [0,1]</span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">25 NW Right Hill [1]<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">26 NW Raised Track [2]<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">27 NW Lowered Track [2]<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">28 (</span><span style="font-family: Source Code Pro; font-size: x-small;">sw</span><span style="font-family: Source Code Pro; font-size: x-small;"> vertical stretch right lane) [1]</span></div><div style="margin-left: 40px; text-align: left;"><div style="text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>camera: $810F <br /></span></div><span style="font-family: Source Code Pro; font-size: x-small;"><b><span></span></b></span><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>behavior: $CAF3 <br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>corner: $81CC/$8247 <br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>position: $90CC/$9130<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">29 (</span><span style="font-family: Source Code Pro; font-size: x-small;">sw </span><span style="font-family: Source Code Pro; font-size: x-small;">vertical stretch right lane) [1]</span></div><div style="margin-left: 40px; text-align: left;"><div style="text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>camera: $810F <br /></span></div><span style="font-family: Source Code Pro; font-size: x-small;"><b><span></span></b></span><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>behavior: $CAFD <br /></span></div><div style="margin-left: 40px; text-align: left;"><div style="text-align: left;"><div style="text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>corner: $81CC/$8247 <br /></span></div><span style="font-family: Source Code Pro; font-size: x-small;"><span></span></span><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>position: $90CC/$9130<br /></span></div><span style="font-family: Source Code Pro; font-size: x-small;"></span><span style="font-family: Source Code Pro; font-size: x-small;">2A <DEFINED> NW Elevated Stretch [2]</span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>camera: $8109 (vertical lock)<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>behavior: $CAD5<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> corner: $81A6/$8234</span> <br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> position: $90C8/$912A (rotate)</span><br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">2B (northern bend) [1,2]</span></div><div style="margin-left: 40px; text-align: left;"><div style="text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>camera: $809A <br /></span></div><span style="font-family: Source Code Pro; font-size: x-small;"><b><span></span></b></span><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>behavior: $84D6 <br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>corner: $824D/$82A4 <br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>position: $9083/$90E8<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">2C (nw eastern bend. delayed turn) [1,2]</span></div><div style="margin-left: 40px; text-align: left;"><div style="text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>camera: $80A7 <br /></span></div><span style="font-family: Source Code Pro; font-size: x-small;"><b><span></span></b></span><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>behavior: $8537 <br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>corner: $8264/$82D0 <br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>position: $9092/$90FA<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">2D (southern bend wide right angle) [1,2]</span></div><div style="margin-left: 40px; text-align: left;"><div style="text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>camera: $80C5 <br /></span></div><span style="font-family: Source Code Pro; font-size: x-small;"><b><span></span></b></span><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>behavior: $858E <br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>corner: $8290/$82F9 <br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>position: $90B9/$911B<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">2E <DEFINED> SE Right Elbow (same as 0C) [1,2]</span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>camera: $80B2<br /><span> </span><span> </span>behavior: $85EC<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>corner: $8276/$82E2 <br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>position: $90A2/$9107<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">2F SW Stripe Lane [2]<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">30 NW Short South Median [2]<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">31 NW Off-ramp Intersection [0,2]</span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">32 NW Short North Median [2]<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">33 <UNUSED> SW Vertical Stretch {collision} [2]<br /><span> </span><span> </span>camera: $810F (horizontal lock)<br /><span> </span><span> </span><b>behavior: $849C (no collisions)</b><br /><span></span></span><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>corner: $81AC/$81BF <br /><span></span></span><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>position: $90CC/$9130 (invert)<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">34 <UNUSED> (horizontal stretch) [2]</span></div><div style="margin-left: 40px; text-align: left;"><div style="text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>camera: $8109 <br /></span></div><span style="font-family: Source Code Pro; font-size: x-small;"><b><span></span></b></span><span style="font-family: Source Code Pro; font-size: x-small;"><b><span> </span></b><b><span> </span>behavior: $849C (no collisions) <br /></b></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span><b>corner: $0000</b>/$823A <b>(FATAL ERROR) <br /></b></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>position: $90C8/$912A<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">35 NW Icy Stretch [1,2]<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">36 NW Icy Stretch [1,2]<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">37 <DEFINED>NW Long Meridian [2]</span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>camera: $8109 (vertical lock)<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>behavior: $8D74 <br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>corner: $81A6/$8234 <br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>position: $90C8/$912A (rotate)<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">38 NW Icy Stretch [1]</span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">39 <UNUSED>(sw icy patch vertical stretch) [1]</span></div><div style="margin-left: 40px; text-align: left;"><div style="text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>camera: $810F <br /></span></div><span style="font-family: Source Code Pro; font-size: x-small;"><b><span></span></b></span><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>behavior: $8E5D <br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>corner: $81CC/$8247 <br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>position: $90CC/$9130<br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">3A (horizontal stretch) [1]</span></div><div style="margin-left: 40px; text-align: left;"><div style="text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>camera: $8109 <br /></span></div><span style="font-family: Source Code Pro; font-size: x-small;"><b><span></span></b></span><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>behavior: $8E52 <br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>corner: $81A6/$8234 <br /></span></div><div style="margin-left: 40px; text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span> </span><span> </span>position: $90C8/$912A</span></div><div style="text-align: left;"><p style="text-align: left;">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.</p><p style="text-align: left;">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). <br /></p><p style="text-align: left;">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. </p><p style="text-align: left;">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. <br /></p><h3 style="text-align: left;">Terrain Elements<br /></h3><p style="text-align: left;">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.<br /></p><p style="text-align: left;">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.</p><p style="text-align: left;">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.</p><div style="text-align: left;"><span style="font-family: Source Code Pro;">00<span> </span>None</span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;">01<span> Oil Slick</span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>02<span> </span>Water Puddle</span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>03<span> </span>Horizontal Dirt Jump</span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>04<span> Horizontal Flooded Track</span></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>05<span> </span><Unused></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>06 <span> Chevron to SE</span></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span><span>07<span> </span>Chevron to NW</span></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span><span>08<span> Chevron to NE<br /></span></span></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span><span><span>09 Chevron to SW</span></span></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span><span><span>0A<span> </span><Unused></span></span></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span><span><span>0B<span> </span><Unused></span></span></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span><span><span>0C<span> </span><Unused></span></span></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span><span><span>0D<span> Vertical Dirt Jump</span><br /></span></span></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span><span><span>0E<span> </span><Unused></span></span></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span><span><span>0F<span> </span>same as 07 <br /></span></span></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span><span><span>10<span> </span><Unused><br />11<span> </span>Tug-o-War Line</span></span></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span><span><span>12<span> </span>Mud Shape 1</span></span></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span><span><span>13<span> </span>Mud Shape 2</span></span></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span><span><span>14<span> </span>Mud Shape 3<br /></span></span></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span><span><span>15<span> </span>Mud Shape 4<br /></span></span></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span><span><span>16<span> </span>Mud Shape 5<br /></span></span></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span><span><span>17<span> </span>Mud Shape 6<br /></span></span></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span><span><span>18<span> </span>Horizontal Logs<br /></span></span></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span><span><span>19<span> </span>SHORT Chevron to NE </span> </span></span></span><br /></div><p style="text-align: left;">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.<br /></p><div style="text-align: left;"><span style="font-family: Source Code Pro;">00<span> None<br /></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>01 Chevron Horizontal Entry High to NW (#30,#8C)</span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>02 Chevron Horizontal Entry Middle to NW (#30,#70)<br /></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>03<span> </span>Chevron Horizontal Entry Low to NW (#30,#54)<br /></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>04<span> </span>Chevron Horizontal Exit High to NW (#B0,#8C)</span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>05<span> </span>Chevron Horizontal Exit Middle to NW (#B0,#70)<br /></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>06<span> </span>Chevron Horizontal Exit Low to NW </span></span><span style="font-family: Source Code Pro;"><span> (#B0,#54)</span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>07 Chevron Horizontal Entry High to SE (#30,#8C)</span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>08<span> </span>Chevron Horizontal Entry Middle to SE (#30,#70)<br /></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>09<span> </span>Chevron Horizontal Entry Low to SE (#30,#54)</span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>0A<span> </span>Chevron Horizontal Exit High to SE (#B0,#8C)</span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>0B<span> </span>Chevron Horizontal Exit Middle to SE (#B0,#6C) <br /></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>0C<span> </span>Chevron Horizontal Exit Low to SE (#B0,#54)</span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>0D<span> </span>Water Puddle Horizontal Entry Low (#20,#74)</span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>0E<span> </span>Water Puddle Horizontal Center High (#54,#97)</span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>0F<span> </span>Water Puddle Horizontal Exit Middle (#CB,#80)</span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>10<span> </span>Oil Slick Vertical Exit Middle (#8B,#D0)</span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>11<span> </span>Oil Slick Vertical Center Low (#3E,#74)</span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>12<span> </span>Oil Slick Vertical Entry High (#9A,#00)</span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>13 Chevron Vertical Entry High to NE (#7F,#24)</span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>14<span> </span>Tug-o-War Contested Zone (#30-#D0)</span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>15<span> Chevron Vertical Exit Middle to NE (#6C,#B0)</span><br /></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>16 Chevron Vertical Exit Middle to SW (#6C,#B0)</span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>17<span> </span>Oil Slick Horizontal Entry Low (#04,#68)</span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>18 Oil Slick Horizontal Exit Middle (#AB,#8D)<br /></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>19 Chevron Horizontal Entry Middle to SE (#40,#6C)<br /></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>1A<span> </span>Water Puddle Horizontal Entry Middle (#2E,#80)<br /></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>1B Water Puddle Vertical Entry Middle (#6E,#33)<br /></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>1C Oil Slick Vertical Exit Middle (#80,#C4)<br /></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>1D Oil Slick Horizontal Center Low (#84,#68)<br /></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>1E Water Puddle Horizontal Late Entry Middle (#40,#80)<br /></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>1F Oil Slick Horizontal Entry High (#00,#94)<br /></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>20 Oil Slick Horizontal Center High (#80,#94)<br /></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>21<span> Water Puddle Vertical Late Center Middle (#80,#A2)<br /></span></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span><span>22 Water Puddle Vertical Exit Middle (#87,#EA)<br /></span></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span><span>23 Water Puddle Horizontal Exit Middle (#E0,#80)<br /></span></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span><span>24<span> </span>Oil Spill Horizontal Exit Low (#A7,#6C)<br /></span></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span><span>25<span> </span>Chevron Vertical Entry Middle to NE (#6C,#30)<br /></span></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span><span>26 <span> Water Puddle Vertical Center High? (#96,#7C)</span></span><br /></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>27 Mud 1 Small Horizontal Exit Low (#D0,#70) <br /></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>28 Mud 2 Small Vertical Exit Low (#7C,#A0) </span><br />29<span> Mud 4 Small Vertical Exit High (#8A,#A2)<br /></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>2A Mud 5 Small Vertical Entry Low (#68,#34)</span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>2B<span> </span>Mud 2 Small Vertical Entry Middle (#7C,#20)<br /></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>2C Mud 4 Small Vertical Entry High (#8A,#22)<br /></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>2D Mud 6 Long Horizontal (#28-#DD)</span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>2E<span> Chevron Vertical Entry Middle to SW (#68,#18)</span></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>2F<span> </span>Chevron Vertical Exit High to NE (#7F,#A4)<br /></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>30 Chevron Vertical Entry Low to NE (#56,#24)<br /></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>31 Chevron Vertical Exit Low to NE (#56,#A4)</span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>32<span> </span>Chevron Vertical Center Middle to SW (#69,#98)<br /></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>33 <span> Chevron Horizontal Center Middle to SE (#88,#6C)</span><br /></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>34<span> Chevron Vertical Entry Low to SW (#51,#20)</span></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>35<span> Chevron Vertical Exit Low to SW </span>(#51,#A0)<br /></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>36<span> </span>Dirt Jump Horizontal Center High (#6C,#8D)<br /></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>37 Logs Short Horizontal Exit (#AA-#EA)</span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>38 <span> Chevron Horizontal Entry Middle to NW (#00,#70)</span><br /></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>39<span> </span>Logs Short Horizontal Late Entry (#2A-#6A)</span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>3A<span> </span>Logs Full Horizontal (#00,#FF)<br /></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>3B<span> Mud 3 Small Horizontal Exit Low (#D0,#6C)</span><br /></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>3C Dirt Jump Horizontal Entry Low (#60,#4E)<br /></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>3D Logs Long Horizontal (#28-#F8)<br /></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>3E<span> </span>Dirt Jump Horizontal Exit Low (#B7,#4E)<br /></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>3F<span> Logs Short Horizontal Center (#7B,BB)</span></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span><span>40<span> Chevron SHORT Vertical Entry Middle to NE (#6C,#20)</span></span></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span><span>41<span> Chevron SHORT Vertical Exit Middle to NE (#6C,#A0)</span></span></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span><span>42<span> </span>Oil Slick Vertical Exit High (#93,#C7)<br /></span></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span><span>43<span> Oil Slick Vertical Center Middle (#51,#83)</span></span></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span><span>44<span> Oil Slick Vertical Entry Low (#2B,#91)</span></span></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span><span>45<span> Chevron Vertical Exit Middle to SW (#8B,#A0)</span></span></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span><span>46<span> </span>Oil Slick Vertical Center Middle (#8B,#4F)<br /></span></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span><span>47<span> </span>Flooded Track Middle (#64-#A4)<br /></span></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span><span>48<span> </span>Oil Slick Vertical Late Center High (#98,#80)</span></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span><span>49<span> </span>Water Puddle Horizontal Entry High? (#16,#7C)<br /></span></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span><span>4A<span> </span>Oil Slick Vertical Early Center Middle (#80,#42)<span> </span><br /></span></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span><span>4B <span> Mud Patch Small Horizontal Center Low (#50,#70)</span></span><br />4C<span> </span>Logs Short Horizontal Entry (#00-#40)<br /></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>4D<span> </span>Dirt Jump Horizontal Entry Low (#40,#4E)<br /></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span>4E Chevron Horizontal Center Middle to SE (#84,#6C)</span><br /></span></div><h3 style="text-align: left;">Pick-Ups & Bombs </h3><p style="text-align: left;">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.</p><p style="text-align: left;">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. </p><p style="text-align: left;">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.</p><h3 style="text-align: left;">Water</h3><div style="text-align: left;"><p>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. </p><h3 style="text-align: left;">Item Pickups</h3><p style="text-align: left;">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. </p><p style="text-align: left;">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". <br /></p></div><h3 style="text-align: left;">Multiplayer</h3><p style="text-align: left;">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. </p><p style="text-align: left;">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. </p><p style="text-align: left;">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. <br /></p><h3 style="text-align: left;">Mini-Games<br /></h3><p style="text-align: left;">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 <i>must be</i> 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. <span style="font-family: inherit;">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). </span><br /></p></div>Theou Aegishttp://www.blogger.com/profile/15226096714222858333noreply@blogger.com0tag:blogger.com,1999:blog-5213606320986462148.post-3772019761606347892023-03-04T18:20:00.037-08:002023-03-20T14:45:14.857-07:00Super Sprint (NES) Analysis<p> </p><div class="separator" style="clear: both; text-align: center;"><a href="https://tcrf.net/images/d/d1/Super_Sprint_-_NES_-_Title_Screen.png" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="240" data-original-width="256" height="240" src="https://tcrf.net/images/d/d1/Super_Sprint_-_NES_-_Title_Screen.png" width="256" /></a></div><br /><p></p><p>I've spent the last few days mapping out a lot of the RAM and a handful of code for Tengen's port of the arcade classic Super Sprint.</p><p>Let's talk about the car mechanics. What is it about upgrades in games being practically worthless? Nearly every upgrade in this game is not worth the effort to get it. Tire traction simply affects how quickly you can turn while moving. Each level of traction control improves turning by 1/32 of a step. In other words, after 32 steps, you will have turned 5 degrees more than with the next level of traction down. You actually turn slower the higher your engine's RPM, typically 1/16 of a step slower. You turn the fastest when at a complete stop, regardless of tire traction. </p><p>Tire traction is a worthless upgrade on any tracks with embankments. On the arcade, these were sections of slabbed track typically. In the NES port, they are typically sections of track with large dirt patches along the outside edge, probably to resemble mounds of dirt under the track. When a car reaches one of these embankments, the tire traction is immediately maximized. It never resets - they forgot to put in code to reset it once you leave the embankment! </p><p>Acceleration level is a bit difficult to rate. Each level of acceleration improves the rate at which your car's RPM will increase. RPM increases pretty quickly as it is, so this is arguably a trivial stat. However, RPM determine speed, so the faster you can get those RPM up, the faster you will go.<br /></p><p>Top speed has a bit of a misnomer. It's just your speed. There is no gradual gain. Each level affects how fast your car is going relative to the RPM. At level 1, a car will top out at 3.875pps. This goes up to roughly 4.36pps at level 2. At max level, a car's top speed is only 4.844pps. The diminishing returns in this upgrade are laughable, however the mid-range speeds do improve somewhat more steadily. As I said when discussing acceleration, you may be spending considerable time in the mid range at later stages.</p><p>Some guides for Super Sprint will warn against upgrading Top Speed,
stating the higher it is, the more likely you are to crash and explode.
The simple fact of the matter is, the higher your speed, the less time
you have to react to oncoming collisions. A car's speed has no bearing
on whether it explodes or not. Explosive crashes are determined by how
many RPM the car is running and if the car hit the wall at the exact
angle opposite the normal (i.e., 180 degrees opposite the normal). One
might therefore conclude it would be best to max out Top Speed first
rather than acceleration, but the terminal point is #0F00 RPM.
Considering max RPM is #1F00, even with level 1 Acceleration it doesn't
take much to crash into a wall. </p><p>Tile layouts for each screen are pretty straightforward. Each sequence of titles starts with a "length" byte. If the value is less than #80, the program will place the tile denoted by the next byte that many times. For example, #2000 would place tile #00 32 times (1 row). If the value is greater than #80, the value is ANDed by #7f and the program will place that many tiles based on the next series of bytes. For example, #83001415 would place tile #00, then tile #14, then tile #15. The data tends to limit repeating tiles to 32 times, but that shouldn't matter. For example, many tracks start with #20002000, but I don't see any reason not to use #4000 to save two bytes -- probably personal taste. Note that the winner screen tiles are referenced four times, since the pointer is also used to keep track of which racer won. The same goes for the upgrades screen (but for only player 1 and player 2).<br /></p><p>Each track is actually mapped out for the AI. You may have noticed the AI typically stays within the middle of the track, unless it gets knocked out of position. This is because each track is mapped out for the AI, like so:<br /></p><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEioBMUSwjrCmxWyQg1k8_WovxevLCEV29z4sylmHMPBZ4fYHsvqVUFypKrEfLZcldx3uAMtqzovs3swCn_1x33zjMb9wpBC4ktBWclNZrXo-hFjv1rEQwtja9gdyiJ-Kmtg1Di4jrUMahtucqixfGMRdEKmXlJxPzcYeILnw6Js4EDMHXK2mLk9pBeE4Q/s768/track_mapping.png" style="margin-left: auto; margin-right: auto;"><img alt="Track 6 AI map" border="0" data-original-height="623" data-original-width="768" height="519" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEioBMUSwjrCmxWyQg1k8_WovxevLCEV29z4sylmHMPBZ4fYHsvqVUFypKrEfLZcldx3uAMtqzovs3swCn_1x33zjMb9wpBC4ktBWclNZrXo-hFjv1rEQwtja9gdyiJ-Kmtg1Di4jrUMahtucqixfGMRdEKmXlJxPzcYeILnw6Js4EDMHXK2mLk9pBeE4Q/w640-h519/track_mapping.png" title="Track 6 AI Map" width="640" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;"><i><span style="font-size: x-small;">Rendering of AI map for track 6</span></i><br /></td></tr></tbody></table><p>Each arrow corresponds to the direction the AI car should turn toward when on that particular tile. The numbers above each arrow correspond to one of eight degrees of speed the AI should try to reach for that position on the track. There are 15 target speeds, but only targets 4, 6, 8, 10, 12, 13, 14, and 15 are used. It's all very roundabout, but this way they could fit a majority of speeds in three bits.<br /></p><p>The AI has a racing level and a speed level. The racing level affects what the target RPM is for the AI. This increases by up to three levels between races. When it reaches level 16, it resets and increases the speed level. This affects how fast the AI cars move as their RPM increase. There are only 4 speed levels, but the speed increases are significant.</p><p>The white car is your rival. The red car is expected to
lose. The blue car may occasionally win when controlled by the CPU. The
white car is the only CPU racer "following" the race. It considers the
red car the weakest racer and will increase its own racing level if it
falls more at least 4 checkpoints behind. If it is ahead of the red car,
it will then do the same with the blue car. Once the white car has
pulled ahead of the red and blue car, it will then increase its racing
level if the yellow car - player 1 - is at least four positions ahead.
These are just temporary increases, so it will maintain the standard
racing level as long as it is three or fewer positions behind any car.<br /></p><p>In addition to the AI map, there is also a terrain map, which maps the behavior of each tile to a specific function in the program, as well as the direction of the normal vector of the wall. For example, a tile mapped to terrain #20 corresponds to a vertical wall on the left, which has a normal vector angle of 0 degrees, whereas a tile mapped to terrain #14 corresponds to the interior of the outside wall of a left turn and will has a normal vector of 315 degrees. Unrestricted terrains, such as #00, have a normal of 355 degrees, but they wouldn't map to a function that relies on the normal. Many tracks just boil down to vertical wall, horizontal wall, or diagonal wall. Diagonal wall tracks typically just compare (x mod 8) to (y mod 8) with some slight adjustments to either x or y in order to determine if the car is on the track. As an example, here is the terrain mapping for the first track:</p><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjIkSoPWVEvwOVdVI9j21V4JxDMR-DlOahF-1UGoT9Og34rTekXnppAl8UDBn-nkDwQaHl7kxlRnhwYgUXXHlo76boJnXRI7Tfr8CB1YwZJ1R-G5QHprjsE69MeHJm5rky4qmVfmNvCxu9umpY5kUDstVj7_8oVK1egoCcJoi5q5vPjYa9k6dWX3uQ-1A/s770/track_terrain.png" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="599" data-original-width="770" height="498" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjIkSoPWVEvwOVdVI9j21V4JxDMR-DlOahF-1UGoT9Og34rTekXnppAl8UDBn-nkDwQaHl7kxlRnhwYgUXXHlo76boJnXRI7Tfr8CB1YwZJ1R-G5QHprjsE69MeHJm5rky4qmVfmNvCxu9umpY5kUDstVj7_8oVK1egoCcJoi5q5vPjYa9k6dWX3uQ-1A/w640-h498/track_terrain.png" width="640" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;"><i><span style="font-size: x-small;">Rendering of tile terrain behavior map for track 1</span></i><br /></td></tr></tbody></table><p>As you can see on the terrain mapping, some tiles map to the range of #40 to #4E, each of which is conspicuously arranged in a line (more or less). These are the checkpoints which determine if the player is actually following the track. Each checkpoint has a predetermined direction (±90 degrees) a car must be moving when crossing it in order for the racer's position to advance. This is a very simple method of updating track position on winding tracks which makes it difficult to more accurately track who is in first, who's in second, and so on. The developers would have to alter the code slightly to allow for more than 15 checkpoints. In some other racing games, the program has an array of checkpoints and calculates the car's distance from the next checkpoint to determine where it is, which would be easier to modify than the method seen here. However, Super Sprint's method does have its perks, as seen here:<br /></p><div class="separator" style="clear: both; text-align: center;"></div><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><img border="0" data-original-height="599" data-original-width="770" height="498" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg608fNNkhjEXvJ0d8xrbOftr1HXuonmbXMUbLJOtE7UTUv5yOCxqFftl3h2qqn82zF86rquG3RxyfJF4pmvehi_FTfCel4A6y-Utzn_XUWp2qD8r84x15fvl1wniICkChyo0Z5TDkNuQ43rI6znxAcPqFwddW0iAXy3MdaHJRAxxl53VJOTsbLbckh9w/w640-h498/track_terrain2.png" style="margin-left: auto; margin-right: auto;" width="640" /></td></tr><tr><td class="tr-caption" style="text-align: center;"><i><span style="font-size: x-small;">Example of checkpoint on shortcut<br /></span></i></td></tr></tbody></table><div class="separator" style="clear: both; text-align: left;"><p style="text-align: left;">As you can see in one of the tracks with a shortcut, checkpoint 5 not only cuts across the actual track, but it also cuts across the dirt shortcut. This would be much more difficult using a checkpoint array. <br /></p></div><div class="separator" style="clear: both; text-align: left;"><p style="text-align: left;">Super Sprint does keep track of which
car is in the lead, flashing "P1" if the yellow car is leading, or "P2"
if the blue car is leading and controlled by a player (or in attract
mode). The info is only updated when a car's position advances. Due to how spread out checkpoints can be, this means there can be a significant delay between when a car pulls ahead and when the leader is highlighted. </p></div><div class="separator" style="clear: both; text-align: left;"><p style="text-align: left;">Notice how the track typically has a solid shadows on the left and bottom, but overpasses cast dithered shadows. This was not due to the NES's limited palette, but was actually done to create the illusion of driving under the overpass.</p></div><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"><tbody><tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjY0Eg7c02_4kfMDCq4I8KfsrZr-ORySFZZIK-uNAGsx7y-jV3AV_VrB_Q9wPwhnmcnptNNfsT7v1VFEBMXgS5Jp0Y6jul_U7xkd8Ky5gSZHGuc4hr4RSQg4Je9KH1S5V9YcNID6E3Y3VuBIbiRVagUcklVE4Qi5kBBlLAqBSwa_auJncGr3w6ITTTEUw/s80/Super%20Sprint-3.png" style="margin-left: auto; margin-right: auto;"><img border="0" data-original-height="80" data-original-width="80" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjY0Eg7c02_4kfMDCq4I8KfsrZr-ORySFZZIK-uNAGsx7y-jV3AV_VrB_Q9wPwhnmcnptNNfsT7v1VFEBMXgS5Jp0Y6jul_U7xkd8Ky5gSZHGuc4hr4RSQg4Je9KH1S5V9YcNID6E3Y3VuBIbiRVagUcklVE4Qi5kBBlLAqBSwa_auJncGr3w6ITTTEUw/w400-h400/Super%20Sprint-3.png" width="400" /></a></td></tr><tr><td class="tr-caption" style="text-align: center;"><i><span style="font-size: x-small;">Palette attributes of overpass tiles</span></i><br /></td></tr></tbody></table><p>Most track tiles use either palette 0 or palette 1, but overpasses use palette 2, as seen in the image above. The difference between palette 0 and palette 2 is that black is swapped out with grey. In the other three palettes, there is no grey at all - that's the transparency. When a car is just about to pass through an overpass, it is switched to low priority. This means the black and yellow pixels (as well as green) will be rendered over it. Since the grey bits are typically the transparency, they won't cover the car. However, since the overpass explicitly uses <i>solid</i> <i>grey</i> instead of transparency, it will cover up the car entirely. The program uses specific terrain mappings for toggling sprite priority. There are also specific terrain mappings to tell the program where the hidden rails under the overpass are. </p><p><u><b>Terrain Mappings</b></u> <br />They were pretty lax on their terrain handling. For example, you may find #16 and #17 together, but you won't necessarily find #14 and #15 together. There's also the weird issues with some tiles clearly being 95% obstruction, but the code treating it as only 75%.<br /></p><div style="text-align: left;"><u><b> </b></u><span style="font-family: Source Code Pro; font-size: x-small;">00<span> </span>=<span> </span>$E86D<span> </span>Open road (RTS)<br /></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">01<span> </span>=<span> $F287<span> </span></span></span><span style="font-family: Source Code Pro; font-size: x-small;"><span><span><span>Protrusion in top-right └</span></span></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span><span><span>02<span> </span>=<span> </span>$F25F<span> Protrusion in top-left</span></span></span></span></span><span style="font-family: Source Code Pro; font-size: x-small;"><span><span> ┘</span></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span>03<span> </span>=<span> </span>$F273</span><span><span> </span>Protrusion in bottom-left ┐<br /></span>04<span> </span>=<span> </span>$F29B<span> Protrusion in bottom-right ┌</span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">05<span> </span>=<span> </span>$F2B2<span> Wall in top half</span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">06<span> </span>=<span> </span>$F2C4<span> Wall in left half</span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;">07<span> </span>=<span> </span>$F2D3<span> Wall in bottom half</span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span>08<span> </span>=<span> </span>$F2E5<span> </span>Wall in right half</span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span>09<span> </span>=<span> </span>$F305<span> Wall in bottom-left</span></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span><span>0A <span> =<span> </span>$F2F1<span> Wall in bottom-right<br />0B<span> </span>=<span> </span>$F319<span> Wall in top-right</span></span></span></span></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span><span><span><span><span>0C<span> </span>=<span> </span>$F32D<span> Wall in top-left<br />0D<span> </span>=<span> </span>$F350<span> Divider left<br />0E<span> </span>=<span> </span>$F341<span> Divider bottom<br />0F<span>-13 </span>= <span> </span><span> </span><span> </span>unused</span></span></span></span></span></span></span></span></span></div><span style="font-family: Source Code Pro; font-size: x-small;"><span><span><span><span><span><span><span><span><span>14<span> </span>=<span> </span>$F365<span> Wall diagonal top-left upper<br /></span></span></span></span></span></span></span></span></span></span></span><div style="text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span><span><span><span><span><span><span><span><span><span>15<span> </span>=<span> </span>$F35F<span> </span>Wall diagonal top-left lower<br /></span></span></span></span></span></span></span></span></span></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span><span><span><span><span><span><span><span><span><span>16<span> </span>=<span> </span>$<span>F383</span><span> </span>Wall diagonal bottom-right upper<br /></span></span></span></span></span></span></span></span></span></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span><span><span><span><span><span><span><span><span><span>17<span> </span>=<span> </span>$F37D<span> </span>Wall diagonal bottom-right lower<br /></span></span></span></span></span></span></span></span></span></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span><span><span><span><span><span><span><span><span><span>18<span> </span>=<span> </span>$F3A0<span> </span>Wall diagonal bottom-left upper</span></span></span></span></span></span></span></span></span></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span><span><span><span><span><span><span><span><span><span>19<span> </span>=<span> </span>$F39A<span> </span>Wall diagonal bottom-left lower<br /></span></span></span></span></span></span></span></span></span></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span>1A<span> </span>=<span> </span>$F3BB<span> </span>Wall diagonal top-right upper<br />1B<span> </span>=<span> </span>$F3B5<span> </span>Wall diagonal top-right lower</span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span>1C<span> </span>=<span> </span>$F2AF<span> Wall in top half (check tile below)<br />1D<span> </span>=<span> </span>$F2BE<span> </span>Wall in left half (check tile right)<br />1E<span> </span>=<span> </span>$F2D0<span> </span>Wall in bottom half (check tile above)</span></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span><span>1F<span> </span>=<span> </span>$F2DF<span> Wall in right half (check tile left)</span></span></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span><span><span>20-27<span> </span>=<span> </span>$E86E<span> </span>Grass <i>(</i><span><span><i>lower bits set normal vector)<br /></i></span></span></span></span></span></span><span style="font-family: Source Code Pro; font-size: x-small;"><span><span><span>28<span> </span>=<span> </span>$F413<span> </span>Embankment</span></span></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span><span><span>29<span> </span>=<span> </span>$F41A<span> </span>Overpass corner (bounces player)<br /></span></span></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span><span><span>2A<span> </span>=<span> </span>$F434<span> </span>Overpass left edge</span></span></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span><span><span>2B<span> </span>=<span> </span><span> <span> </span> unused <i>(Underpass left edge?)</i><br /></span></span></span></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span><span><span>2C<span> </span>=<span> </span>$F443<span> Overpass bottom Edge<br /></span></span></span></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span><span><span>2D<span> </span>=<span> </span><span> </span><span> </span><span> unused <i>(Underpass bottom edge?)</i></span><br />2E<span> </span>=<span> $F452</span><span> </span>Overpass right edge<br />2F<span> </span>=<span> </span><span> </span><span> </span> unused <i>(Underpass right edge?)</i><br /></span></span></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro; font-size: x-small;"><span><span><span>30<span> </span>=<span> </span>$F461<span> Overpass top Edge<br />31-3F =<span> </span><span> </span><span> </span> unused <i>(Underpass top edge?)</i><br />40-4D =<span> </span>$E9D4<span> </span>Checkpoint <i>(lower bits set order)</i> </span></span></span></span></span></div><div style="text-align: left;"><span style="font-family: Source Code Pro;"><span><span><span><span><span style="font-size: x-small;">4E<span> </span>=<span> </span>$EA06<span> </span>Finish line</span><br /></span></span></span></span></span></div><p>Each overpass must have a checkpoint on each side. The checkpoint
has additional information that tells the car which tiles bordering the
overpass it is allowed to pass. For example, a checkpoint might specify
tiles #2C and #30, meaning the car is allowed to go vertically <i>across</i> the overpass. Judging from the tile map above, it is likely the programmers intended separate tiles for overpasses and underpasses, but then decided to handle sprite priority separate from those specific tiles, likely to make track designing a tad simpler. <br /></p><p>There is a bug in the finish line terrain (#4E) code which allows the
player to cheat the lap counter by rapidly tapping the Start button
while over a finish line. If done right, the player can increment the
lap counter for any car by up to 4 laps. The cause of this seems to be
because Super Sprint runs parallel code. It's not uncommon for interrupt
requests to be used in order to pause one function, run some other
code, then go back to the interrupted function. The issue with this is
the programmer needs to be careful not to alter any RAM currently in use
at the time of the interrupt. This is what happened in Super Sprint.
Byte $001B temporarily holds the terrain value, but it is also used as a
pass-through variable when polling the gamepads. The gamepads are
polled during the interrupt, which is expedited when the Start button is
pressed. If no buttons are pressed on gamepad 2 when the game is paused
at just the right moment, the program will still be running the code
for terrain #4E, but with a value of #00 inside $001B instead of #4E.
Checkpoints verify the current checkpoint is the same as the target
checkpoint ($00B2). In the case of the finish line, when both values
match, it sets the target checkpoint to #00 and increases the lap
counter. When the glitch occurs, since the target checkpoint is #00 and
the temporary variable $001B is also #00, the lap count is increased
again. I "fixed" this by setting 0x<span style="color: #76a5af;">1</span>DBA9, 0x<span style="color: #76a5af;">1</span>DBB5, 0x<span style="color: #76a5af;">1</span>DBBF, and 0x<span style="color: #76a5af;">1</span>DBCB each to #13.<br /></p><p>Wall physics themselves are dubious. Many walls will ignore the normal vector if the car hitting it is controlled by the CPU. While this can occasionally be detrimental for the CPU, it effectively means the CPU will rarely carom wildly off walls. If a car caroms into a wall when its direction is within 90 degrees of the normal, it will effectively come to a stop unless it is practically finished caroming. Otherwise, the program reflects the angle of the car across the normal. If it is in line with the normal, the car explodes. If it is within 35 degrees of the normal, it is rotated to the normal, else if it is within 57 degrees of the normal, it is rotated to 56 degrees of from the normal. I don't know why it's so complicated, but I guess it had something to do with trying to make crashes slightly more realistic.</p>Bytes $009D-$00A0 are a weird bunch, used at crossroads and overpasses. Only two bits are used - bit 0 and 6. Bit 0 is set when disabling racecar and cyclone collision detection. Bit 6 is set when it wants to tell the program to render the car on the foreground, and also to give it greater height (earlier draw priority) than the other cars; the car is pushed to the background if bit 6 is not set at an intersection tile. The program tends to either set bit 0 alone, or both together. I don't think I have seen it set bit 6 alone. Cars are also prohibited from picking up the wrench or bonus points when bit 0 alone is set, in case they spawned on an overpass.<p>Another feature of setting bit 0 is it forces the CPU to maintain the
same direction while it is set. Even if it changes direction while
caroming off walls, it will steer itself back into whatever direction it
had when bit 0 was set. I experienced this "bug" one time during
regular gameplay, where a car that just approached an intersection got
hit by another car and sent flying into a wall, preventing the game from
clearing bit 0, which caused the car to ceaselessly drive head-on into a
wall until the race ended. </p><p>One other bug can happen with the inverse. Byte $035D is set to #00 when crossing an intersection checkpoint. Normally byte $035D is set to #80 when a car is driving the right direction through a checkpoint, but it gets set to #00 if the car is not going the right way. If a CPU-controlled car is at an intersection checkpoint and byte $35D is cleared, bit 0 will not get set and the CPU will steer through the intersection. The way intersections are designed in Super Sprint is the second pass is the main direction of the intersection. In other words, if a CPU exits the checkpoint before bit 0 is set, it will cut the intersection. Since this results in the CPU taking the checkpoints out of order, the next time it crosses the finish line will not count. <br /></p><p>The rate the car actually turns (its sprite) looks bugged due to
typos or logic errors to me. I've been trying to make sense of it, but I
can't. It has to be poor logic (because it happens twice). The rate at
which a player can turn is based on the direction the sprite is facing
minus the direction the track is going (from the AI's map). When trying
to turn left, it turns at full speed if the difference is greater than
180 degrees, or if the difference is greater than 1260 degrees. If it's
greater than 1260 degrees, it's naturally greater than 180 degrees as
well. Inversely, when trying to turn right, it turns at full speed if
the difference is less than 180 degrees or if the difference is less
than 1260 degrees. If it's greater than 180 but less than 1260, it's
still less than 1260, so why bother with the less-than-180 check, unless
it's a logic error. I thought they got the BCC and the BCS switched on
the 1260 (-180 degrees) check, which would be a quick fix setting 0x<span style="color: #76a5af;">1</span>DC53 to #B0 and 0x<span style="color: #76a5af;">1</span>DC6C
to #90 in the ROM, but after making those changes the game was
practically unplayable. It took drifting to a whole new level, but not
in a good way. It probably needs an abs() subroutine, but I am leaving
it as it is for now.</p><p>The player scores are literally just the text. For example, if the
HUD shows a score of 13750, the actual score is #00F6F8FCFAF5. When you
cross a checkpoint or pick up a point bonus, it adds 1 to the
next-to-last byte. If the value equals #FF, it subtracts 10 and adds 1
to the next byte, and so forth. If you somehow manage to exceed 999990
points, the game wraps back around to 000000 (with all zeroes visible).
There are no rewards for collecting points. </p><p>Wrenches spawn when P1 crosses the finish line on laps 1 and 4, if one is not already active. For each wrench, a flag is set so the program doesn't spawn more than one (the routine fires every frame the car is over the finish line). If the first wrench hasn't been picked up by the time the second wrench spawns, it disappears - because both wrenches share the same variables. <br /></p><p>I may update this post later as I delve deeper into the program. For now, I hope someone finds this sort of rubberducking of mine helpful.<br /></p><div class="separator" style="clear: both; text-align: left;"><br /></div>Theou Aegishttp://www.blogger.com/profile/15226096714222858333noreply@blogger.com0tag:blogger.com,1999:blog-5213606320986462148.post-8072064023432725242022-12-30T19:50:00.000-08:002022-12-30T19:50:20.793-08:00Broken Hit Detection: Grant vs. Dracula's Drool<p>We can see one of the shortcomings of the collision detection method in CV3 during the battle against Dracula's floating heads. The heads themselves function pretty normally. It's the drool that is bugged. Watching the RAM values, we can see when the drool splashes on the ground, it has a y-coordinate of #A0. Trevor, Sypha, and Alucard have a standing y-coordinate of #B0, but Grant has a y-coordinate of #B4. Thus, the drool sprite's origin is 16 pixels higher. </p><p>Collision detection in CV3 is basically <span style="font-family: verdana;"><br /><span style="font-size: x-small;">abs(PC.x - NPC.x) < (PC.width + NPC.width)/2</span></span><br />horizontally, and for the vertical check<br /><span style="font-size: x-small;"><span style="font-family: verdana;">abs(PC.y - NPC.y) < (PC.height + NPC.height)/2 </span></span></p><p>Given PC.y is #B0, NPC.y is #A0 (when splashing), NPC.height is 16, Trevor et al height is 24, and Grant height is 20 (while standing), we end up with the following requirements:<br /><span style="font-size: x-small;"><span style="font-family: verdana;">Trevor Hit = abs(#B0 - #A0) < (16+24)/2 = 16 < 20 = <b>true</b><br />Grant Hit = abs(#B4 - #A0) < (16+20)/2 = 20 < 18 = <b>false<br /></b></span></span>In other words, even if Grant stands in the exact same position as Trevor et al and there is drool at that position, Grant will <i>not</i> get hit!</p><p>There is a very small chance for Grant to get damaged by the drool splash on the ground. Just before the drool splashes, it performs one last collision check. At this time, there is a 25% chance the drool's y-coordinate will be #A3, depending on the y-coordinate the drool was spawned at. If we look back at the algorithm, when the drool is at #A0, it is 3 pixels away from hurting Grant. Thus, there is a 25% chance Grant will get hit by drool at the exact moment it hits the ground. </p><p>The drool gets snapped to #A0 when changing to the splash sprite, so Grant will <i>never</i> take damage from <i>walking</i> over a drool splash. Trevor et al, on the other hand, will. Grant's hitbox is technically 4 pixels below that of Trevor et al. In other words, Grant will actually take damage from drool if it's <i>below</i> him and he is in the air. Not only can jumping while standing in a drool splash hurt Grant, so can just simply being a few pixels above the drool as it's falling. Grant will seemingly take damage from nothing. </p><p>We could change Grant's height to alleviate this issue, but then that would actually remove any benefit he had elsewhere in the game. For example, a bone statue's fireball would be able to hit Grant now. Instead, the drool would need to be fixed. The sprite origin would need to be move down 16 pixels and the y-offset on impact would need to be adjusted to account for the change. <br /></p>Theou Aegishttp://www.blogger.com/profile/15226096714222858333noreply@blogger.com1tag:blogger.com,1999:blog-5213606320986462148.post-46825043063037134962022-12-29T00:58:00.001-08:002022-12-29T00:58:10.951-08:00That was an Unfortunate Glitch (Grant vs. Collapsing Walkway)<p>I don't really know what causes the glitch exactly until I look into the code, but Grant can actually outrun a collapsing walkway, resulting in multiple glitches. Someone surely already encountered it, probably during speedrunning. The issue stems from Grant having a faster walk speed. Trevor, Sypha, and Alucard all walk at 1 pixel per frame. Grant on the other hand walks at 1.25 pixels per frame. The collapsing bridge is programmed to keep pace with a character moving at 1 pixel per frame. </p><p>If you move fast and steadily enough, the rubble that appears over a block just before it automatically drops will wrap around the screen. If you keep moving forward too fast, the collision map pointer will wrap around, even though the tile map pointer will remain intact. This results in the rubble appearing over the wrong blocks without actually deleting them. In other words, you could potentially see one or two tiles still on the screen which are impossible to stand on or grab. If you are standing on or hanging from such tiles when the glitch occurs, you will fall to your demise. </p><p>If you inspect the collision map after walking over the bridge as Trevor, then compare it to walking over the bridge as Grant without hesitancy, you can actually verify the collision mask bug, denoted by bold text. The following is taken from Death's room.<br /></p><p style="margin-left: 40px; text-align: left;">00000000000000000000000000000000<br />000000000000000000000000000000<b>66</b><br />00000000000000000000000066666666<br />00000000000000000000660000000000<br />00060000000666000000000000000006<span> </span><i>Playing as Trevor/Sypha/Alucard</i><br />0000660000000000000000000000<b>00</b>00<br />00000000000000<b>00</b>0000000000000000<br />000000<b>00</b>0000000000000000000000<b>0</b>0<br />00000000000000000000000000000000<br /></p><p style="margin-left: 40px; text-align: left;">00000000000000000000000000000000<br />000000000000000000000000000000<b>00</b><br />00000000000000000000000066666666<br />00000000000000000000660000000000<br />00060000000666000000000000000006 <i>Playing as Grant</i><br />0000660000000000000000000000<b>66</b>00<br />00000000000000<b>66</b>0000000000000000<br />000000<b>66</b>0000000000000000000000<b>6</b>0<br />00000000000000000000000000000000<br /></p><p style="text-align: left;">The bolded 6's (solid) at the top of the TSA data are the final two blocks of the collapsing walkway which are normally intact at the end of the sequence. We can see that all the tiles that were intended to be part of the collapsing walkway had been cleared to 0. When we look at the G data, we see the 6's up top are now 0 (not solid), while much of the collapsed walkway at the bottom of the data is still set to 6. Even though two blocks were erroneously "removed", up to <i>seven</i> were erroneously ignored!</p><p style="text-align: left;">My theory as to why the collision map is affected and not the tile map/nametable is that the program directly targets the nametable addresses with a pointer, whereas the collision map is perhaps being calculated based on the position of the view in the room. But again, I would need to delve deeper into the code to figure this out.</p><p style="text-align: left;">In the meantime, you should probably avoid increasing player speed in CV3 hacks if collapsing walkways are going to be included in your game, as anything faster than 1 pixel per frame can easily cause some nasty bugs in CV3.<br /></p>Theou Aegishttp://www.blogger.com/profile/15226096714222858333noreply@blogger.com0tag:blogger.com,1999:blog-5213606320986462148.post-25695368303841645712022-12-16T10:47:00.003-08:002022-12-16T10:47:21.849-08:00Putting Candles in Stage 0E:01:01 in CV3<p>Did you ever notice how there are no candles in the final clock pendulum room just before Dracula's keep in CV3? There are bats in CV3u, but no candles. If you open up reVamp, you can put candles in that room just like any other room, but when you play your modified ROM, the room is still empty! </p><p>There's a very simple explanation for this: the game is hard-coded to not load candles in that room. It's an arguably pointless addition to the program. Sure, it [significantly] speeds up the initialization of that room, but it slightly slows down the initialization of all the other rooms in the game. And really, who cares about that? It's just the room initialization that's affected - ONE FRAME of execution per room!</p><p>In order to allow candles in stage 0E:01:01, you just need a very simple hack of the ROM file. You could probably even get by with a Game Genie code. In CV3u, simply jump to ROM address 0x028188 and change <b>208D</b> into <b>F004</b>. The Game Genie equivalent should be <b>EYNEAPEI GANEPOSA</b>. Be aware that the first code may adversely affect 0x03E188, which is PPU critical, although I did not notice anything on a quick playthrough. In CV3j, I advise against using Game Genie codes. Jump to ROM address 0x028057 and change <b>2091</b> into <b>F004</b>. </p><p>You may also need to make the same changes to 0x02808F in CV3j, which has to do with scrolling spawners and stuff. I didn't actually test this part out too thoroughly, but I didn't consider keeping an eye out for it either. In CV3u, you may need to change 0x028997, but I suspect that is only related to the bats.<br /></p>Theou Aegishttp://www.blogger.com/profile/15226096714222858333noreply@blogger.com0tag:blogger.com,1999:blog-5213606320986462148.post-34289725532434153682022-12-12T17:37:00.001-08:002022-12-12T17:37:13.259-08:00Was Grant's Axe an Upgrade?<p>The state machine for Grant's dagger is nearly identical as the state machine for Grant's axe. They both even check if Grant is attacking from a wall or from a ceiling. The problem with this is you can't attack with an axe while hanging from a wall or ceiling. It's hard-coded as such. Regardless of what buttons you hold, as soon as you press B, it attacks with a dagger. </p><p>Why does Grant's axe code check if you are attacking from a wall or ceiling? Well, what happens if you trick the game into thinking Grant is using an axe from the wall? Since Grant needs to attack behind him when on a wall, it will toss the axe as normal, but behind Grant. What if you trick it into thinking Grant is on the ceiling? He will literally just drop the axe, at which point gravity takes over.</p><p>All of this is handled by a database of offsets and speeds for both the axe and the dagger. In other words, the manually coded offsets and speeds for the axe being thrown from the wall or ceiling. Perhaps they scrapped the mechanic because you would be forced to throw an axe from the ceiling, or maybe they didn't like how it felt to throw an axe from the wall.</p><p>Or maybe - just a hunch - maybe the axe was meant to be a replacement for the dagger, rather than a supplemental subweapon. Picking up the axe would replace Grant's dagger completely. This idea could have been scrapped due to how awkward having only an arched attack may have been. <br /></p>Theou Aegishttp://www.blogger.com/profile/15226096714222858333noreply@blogger.com0tag:blogger.com,1999:blog-5213606320986462148.post-71220464605370307812022-12-12T11:52:00.001-08:002022-12-12T11:52:39.512-08:00The Case Of The Missing Audio in CV3<p>Both of Medusa's attacks and the teeter-totter obstacle have missing audio data. You can verify this by setting a breakpoint on $E249(j)/$E25F(u) when encountering either of those. The audio track data for each of them is partially set. The teeter-totter was intended to use a single square channel, while Medusa's attacks seemingly were intended to use a square channel and the noise channel. I say "seemingly" because the setup for Medusa's attacks is the same as the silencer for the water droplets.</p><p>So were the water droplets using removed audio? It's difficult to say. The silencer seems to do exactly what it's supposed to. In CV3u they remapped Medusa's attacks to the silencer, so it would seem that was intentional. Except... The water droplet sound effect uses two square channels, not the noise channel. So why would the silencer halt the noise channel? </p><p>There is some unused audio data (at least in CV3j) with an odd feature. The first byte was #ff (undefined). Ignoring that it was undefined, the obvious thing was that there was an unused audio data pair. I remapped Medusa's attacks to it and tested the results.</p><p>It sounded like the throwing axe sound. I copied the audio data and tried searching the ROM for other occurrences of it. Sure enough, it was the throwing axe sound. I was equally disappointed and thrilled. If I am correct, Medusa's arrow simply used the same sound effect as the throwing axe. It fit pretty well.</p><p>I tried remapping the teeter-totter to it, but the resulting square channel audio was clearly unintended. </p><p>If I remapped the water droplet silencer to the unused audio data, it seemed to work well initially. The whishy-whooshy sound played when the block dissolved. However, within seconds audio artifacts started appearing. This could have just been a bug in my emulator, but it was reminiscent of a an audio bug I used to hear in Top Players Tennis on my NES hardware. So my money's on Medusa's arrow was supposed to use a modified throwing axe sound.</p><p>Unfortunately, there was no other unused audio data to work with. <br /></p>Theou Aegishttp://www.blogger.com/profile/15226096714222858333noreply@blogger.com0tag:blogger.com,1999:blog-5213606320986462148.post-13532622278677269992022-12-12T09:32:00.003-08:002022-12-12T10:43:02.276-08:00The CV3j Audio Engine and the Woes of Bank Switching<p>With nearly 8.10% of CV3j's code to at least log (mapping it will much more gradually), I took a peek at the code pertaining to the audio. That's a huge swath of pure data, so any missing elements should be easy to spot. I used the music debugger (A+B+Start) and an $E249 breakpoint, I played every song, every sound effect, and every DCM that I'm aware of. I don't think I missed any parts of a song, other than "Pressure", which usually got cut short because I wasted no time running up to Dracula and forgot I was trying to log <i>all</i> data.</p><p>There are 125 tracks. All the sound effects and noise percussion are located in ROM bank #0C, while background music is spread across banks #04 and #05. The code for the audio engine itself is located in bank #0C. ...And banks #04 and #05. At first I naively thought each page may have had different parts of the engine, but when I noticed a significant amount of bank #0C was not logged, I searched the other two banks for that same data and found them already logged. It seems like a large chunk of the game's code is dedicated to game's audio engine, most of which isn't actually used by sound effects. However, after manually logging the bytes of each copy of the audio engine, it was roughly 5% of the game: #7AD (1,965) bytes long, duplicated on across three pages. <br /></p><p>All the audio envelopes are also stored in bank #0C. Of the 128 volume envelopes, 119 were defined, and of those, 60 were never utilized. Of the 20 pitch envelopes, 5 were never utilized. That comes out to roughly 350 bytes of ROM wasted just for the envelopes alone. Most of these envelopes are negligible and there's no real way of knowing for which channels each envelope was intended, but it may be fun for someone to play around with.</p><p>At one point, audio channel 9 was delegated to the DCM channel, but it appears to have been removed, possibly to save space in WRAM. Now instead of writing the track address for channel 9, which is always 4 bytes after the previous address, it just reads (in)directly from the DCM data itself. Thus, there's another <strike>60</strike> 90 bytes of ROM available to be recycled.</p>Theou Aegishttp://www.blogger.com/profile/15226096714222858333noreply@blogger.com0tag:blogger.com,1999:blog-5213606320986462148.post-67033275638666973442022-11-24T15:56:00.002-08:002022-11-24T15:56:59.927-08:00Was the Invincibility Potion a Buggy After-thought?<p>Byte $0088 in RAM (CV3j) is used to carry Trevor et al on moving platforms. It gets set when byte $007E has any of the lower four bits set. Byte $007E also stores the damage type that an enemy dealt to Trevor. The damage type sets the upper four bits of byte $007E. Unfortunately, enemy damage is saved directly, so in practically all cases it clears the lower four bits of $007E. </p><p>So what?</p><p>The platform collision happens before the enemy collision. However, the code to ride a platform happens after enemy collision. So if a platform is detected, but an enemy damages Trevor, Trevor is no longer detecting the platform. When it comes time to move with the platform, there is no platform registered anymore and Trevor falls through. </p><p>What if Trevor is invincible?</p><p>Here's where things get sketchy. There are two invincibility timers: an iFrames timer and a potion timer. Both timers prevent Trevor et al from losing any health. The invincibility potion does <i>not </i>prevent damage, it prevents <i>losing health from damage</i>. </p><p>In summary...</p><p>If Trevor et al has an invincibility potion active and is riding on a moving platform, colliding with any enemies, projectiles, or traps will cause Trevor to fall through the platform. There is only one invincibility potion in the entire game (and it's pretty useless too). Did they find the platform bug and decide to keep it in as a gimmick, but then later decided it made the game unfairly difficult, so they took out most of the invincibility potions rather than fixing the bug?<br /></p>Theou Aegishttp://www.blogger.com/profile/15226096714222858333noreply@blogger.com0tag:blogger.com,1999:blog-5213606320986462148.post-84752680051765144472022-11-23T22:25:00.002-08:002022-11-23T22:25:26.757-08:00One of CV3j's programmers was overly cautious ( >,>)..oO<p>In CV3j (and somewhere in CV3u, I presume), at address $01:8547 the program checks if the player has a partner; if not, the code advances the lowest substate in order to advance the sequence. This happens when Trevor agrees to take one of the other characters as a partner. There is just one slight issue here: the code will not run if Trevor is alone. In other words, if the code is running, it is guaranteed that Trevor has a partner and thus the substate-skipping code is never run. </p><p>When I see code like this, I can't help but think about Noboyuki Matsushima, one of Capcom's programmers who was overly cautious. I'm not suggesting CV3's code was as critically slow as Matsushima's - I don't know enough about coding to make such assumptions. I just mean there are pieces of code scattered throughout the program FCEUX has failed to map because they simply cannot be reached by normal means. As such, the conditionals which branch to said unreachables are frivolously (albeit negligibly) slow. <br /></p>Theou Aegishttp://www.blogger.com/profile/15226096714222858333noreply@blogger.com0tag:blogger.com,1999:blog-5213606320986462148.post-67373464072415530012022-11-23T21:28:00.001-08:002022-11-23T21:28:18.075-08:00When you spot unmapped code and are able to map it in under 10 minutes:<div class="separator" style="clear: both; text-align: center;"><a href="https://media.tenor.com/88DfzQS5tlQAAAAC/adventure-time-demon-cat.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="278" data-original-width="498" height="278" src="https://media.tenor.com/88DfzQS5tlQAAAAC/adventure-time-demon-cat.gif" width="498" /></a></div><br /><p><br /></p>Theou Aegishttp://www.blogger.com/profile/15226096714222858333noreply@blogger.com0tag:blogger.com,1999:blog-5213606320986462148.post-45665792362949896542022-11-21T17:54:00.003-08:002022-11-21T17:54:26.851-08:00Sypha Breaks Aquarius - Good Thing She's Restricted<p>If you use the debug feature (system state #07), you can play with Sypha in Aquarius (the sunken ruins). Just before the Bone Dragon King's first battle, one of the candles will give Sypha her Fire spell (assuming she could have been used in that stage to begin with). Her Fire spell literally <i>TWO-SHOT KILLS</i> the boss, completely breaking the flood sequence, forcing you to wait for time to run out and die!<br /></p>Theou Aegishttp://www.blogger.com/profile/15226096714222858333noreply@blogger.com0tag:blogger.com,1999:blog-5213606320986462148.post-46144864962964616342022-11-19T12:33:00.002-08:002022-11-19T12:33:20.242-08:00Super Phase Out!<p> Another archaeological finding.</p><p>At address $00141C in the ROM (CV3j) is a block of code which accelerates the ghosting speed of Trevor or his partner when swapping characters. This acceleration code is only run when the ghosting offset is 8 or higher. The partner swap effect does not last long enough for this to occur. However, if you set $0032 in the RAM to something big like #ff, the acceleration code will tun run. This results in the phantom images to spread out across the entire screen, then go back to the PC's position, then immediately spread out across the screen again (assuming you set $0032 high enough or froze it). </p><p><br /></p><p>I haven't seen this effect anywhere else in the game that I can recall. Perhaps it was lifted from another game? Maybe Dracula or one of the other bosses was supposed to use this effect? Maybe the game wasn't supposed to just cut directly from the score tally to the map screen? The world may never know.<br /></p>Theou Aegishttp://www.blogger.com/profile/15226096714222858333noreply@blogger.com0tag:blogger.com,1999:blog-5213606320986462148.post-83371367053782474502022-11-18T23:25:00.004-08:002022-11-18T23:50:17.179-08:00Findings from the Deep<p> Observations only a Famicom archaeologist could love!<br /></p><p>Even if you play a game straight through, you're going to miss out on a lot of code and data. The same is true when you skip around using the debugger cheats. Thus, once I got 87% of the ROM mapped, I went for a deep dive to try to find and address any unmapped code. In some cases, I just ignored the data when it was clearly a data set, but other times I went back and tried to coerce the game into reading (and mapping) said data. For the actual game code, however, I have tried to get it to run whenever possible. Knowing what each variable does naturally makes it easier to figure out what I need to do in the game in order to run said code.</p><p>Enter the character swapping states. Certain aspects of the game are broken up into two parts - a horizontal state and a vertical state. Character swapping is one such aspect. Game state #0b handles character swapping in a horizontal room, while game state #0c handles character swapping in a vertical room. </p><p>I noticed an unmapped block of code I eventually traced back to game state #0b. Part of it had been mapped, so in order to get to the unmapped parts, I had to meet some conditions. One of which was if the room was flooding. Another was if the character was farther up the screen than #4e pixels (or something like that), or farther down the screen than #dc pixels (or whatever). Testing the flood room was simple enough using the debugger, so off I went to the stage 08:04:00 with Grant as my partner. Once there, I swapped characters. I was unimpressed, but I checked and new code was indeed mapped. </p><p>I decided to test out game state #0c, so I jumped to stage 01:00:00. The code for #0b and #0c was nearly identical, so I looked to see if there was another branch that would point to the unmapped data. Once again, I saw a conditional for being between #4e pixels and #dc pixels (or whatever), but no flood requirement (because vertical rooms don't flood). Rather than crawling to the top of the room, I just set the y-coordinate to #30, which conveniently there was a already a block there. Since I didn't actually <i>move</i> up there, the screen did not scroll with me. Now that I was in a vertical room positioned above #4e (or whatever) pixels, it was time to map that code! I swapped characters once more, and... Nothing extraordinary happened.</p><p>At that point, I had an epiphany. The code which was already mapped had an IRQ control value. In other words, by default the game messes with scanline rendering when swapping characters. Specifically, the game creates this odd "horizontal banding" effect with the character sprites during a normal transition. The unmapped code skipped the IRQ control values. I slowed the game down, disabled the background, and swapped characters one more time....</p><p>It turns out the game will use the horizontal banding effect during character swaps only if no part of the character is off-screen or overlapping the status bar, or if the room isn't flooding. The rest of the effect still happens, just without the banding.</p><p>I warned you it'd probably be a boring observation. More such observations to possibly come later.</p><p> </p><p>Postscript: What exactly is background palette set #15 used for? It's the only background palette set not yet mapped for me. Every room's palette set is mapped, so I know I didn't miss a room. Sprite palletes #03, #12, #14, and #18 are also not mapped for me. I even checked the intro, the epilogue, the ending credits, the map screen, and the password screen. Nada!<br /></p>Theou Aegishttp://www.blogger.com/profile/15226096714222858333noreply@blogger.com0tag:blogger.com,1999:blog-5213606320986462148.post-1019459126178677822022-11-09T00:17:00.002-08:002022-11-09T00:17:50.815-08:00Cut Boss Content from CV3! Maybe it's nothing...<p>While trying to wrap up my daunting task of identifying the uses of most of the 0x800 bytes of RAM utilized in CV3, I came across a set of 3 variables pertaining to the bosses. One I had identified already, but misinterpreted its function. Let me just re-iterate that <i><b>I FUCKING HATE THE GUY WHO PROGRAMMED BOSSES IN CV3!!</b> </i>Or rather, I hate the coding style. Moving on....</p><p>Previously, I had just assumed byte $00B7(j)/$00BA(u) was just telling the game when the player was fighting a boss. I was sorely mistaken. It's actually a bitmasked value telling the game if the boss fight uses a secondary set of hitboxes. Not all bosses have just one hitbox, so this variable relays that to the game. Then byte $00B8/$00BB basically tells the game the boss has a melee attack, or something. I mean, that's essentially where I've seen it used. And then on top of that, byte $00B9/$00BC tells the game if the boss has different hitboxes for multiple phases (e.g., Dracula and his three forms). This third byte was pretty pointless for the Doppelganger, which used $00B7 and $00B9 for basically the same thing.</p><p>Anyhow, looking over the data each of these variables referenced, I noticed something odd (which was one reason it took me so long to come to grips with what I was looking at). Take $00B7 for starters. It points to a set of 8 values: [0D, 29, 0C, 08, 00, 00, 0F, 00]. Each of these is an object index: [Dracula, Dracula's Drool/Pazuzu's Platforms, Death's Skull, Moat Dragons, <i>empty, empty,</i> Doppelganger Attack, <i>empty</i>]. While there is nothing suspicious about the final empty object in the set, the two in the middle stick out. Perhaps these were redundant values for the Doppelganger, like how $B9 is redundant for the Doppelganger, but then why wouldn't they keep the first value instead of the third? </p><p>Looking at the hitbox data, it's clear some bosses have one hitbox (typically for invisible hitboxes like Death's skull) while others have two. Oddly, the empty objects also have hitboxes defined -- one even has THREE distinct hitboxes defined. That's not for the Doppelganger's whips, since those already each have their own redundant $00B7-$00B9 pairs. What's more intriguing is one of these hitboxes is relatively huge at 24x16 pixels. That's too short for Sypha's fire, but too big for any other character's. It's also pretty odd dimensions, being nearly square, but longer horizontally than vertically. The only extant boss that comes to mind is Medusa trying to slither. If neither of those, then I'm left to believe this was a boss left on the cutting room floor. </p><p> </p><p>And yes, while Dracula's drool is among the objects with distinctly defined hitboxes, I have not found any residual code that would suggest the unused melted weapon sprites were from throwing a dagger, axe, or cross at the acidic drool. Pazuzu's beams share the same object identifier as the drool, which is most likely why they are in this hitbox set. <br /></p>Theou Aegishttp://www.blogger.com/profile/15226096714222858333noreply@blogger.com0tag:blogger.com,1999:blog-5213606320986462148.post-76020291867810102632022-10-18T22:30:00.004-07:002022-10-21T11:21:19.353-07:00So many inconsistencies....<p>Not to flog a dead horse, but you can tell the bosses were programmed by different people just by looking at the $00BF to $00CA range of bytes. </p><p>You see $00BF used for bosses with more than one stage to the battle, such as Death. The problem is, only Death uses it. The Bone Dragon sets it before fleeing, but then clears it immediately - it wasn't even being read, from what I could tell. Dracula doesn't use the variable, nor does the wisp. </p><p>Bytes $00C9 and $00CA are bitmasks for the bosses with multiple parts getting hit and then momentarily stunned. You see it used in the Mummy boss. Oddly, the bitmask is 16 bits deep, but the Mummy boss only uses two bits, and both the Skull Knight and Baphomet use just one bit. The Bone Dragon doesn't even use the bitmask, nor do any of Dracula's forms; we could probably chalk this up to both being impossible to stun. To confuse matters, only Death's second form uses the bitmask, and I don't recall that being stunned. Then again, it could be like Baphomet where the stun duration is only 4 frames. The Bat boss is the one boss that you would be forgiven for assuming it used this feature....<br /></p><p>I still haven't figured out what $00C1 and $00C2 do, although I am guessing they are another 16-bit variable pertaining to one of the bosses. I don't know which. Two unknown variables possibly pertaining to a boss? Pazuzu's platforms? Nope. Moat Dragons? Nope. I still have yet to check The Creature, Doppelganger, Grant. What's so important about $00C1 and $00C2, you ask? Namely, you cannot swap characters when either byte is set. Medusa's petrify? Nope. I'm stumped.<br /></p>Theou Aegishttp://www.blogger.com/profile/15226096714222858333noreply@blogger.com0