Jan 15, 2024

CV3's Password Entry Code Wasted Memory

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. 

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.

Original tile set

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.

Icon (selected)   04 04
                  86 A4 A4 87
                  96 00 00 97
                  96 00 00 97
                  A6 A5 A5 A7
Icon (regular)    04 04
                  80 81 81 82
                  90 00 00 92
                  90 00 00 92
                  A0 A1 A1 A2
Pass (selected)   03 03
                  86 A4 87
                  96 00 97
                  A6 A5 A7
Pass (regular)    03 03
                  80 81 82
                  90 00 92
                  A0 A1 A2

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 04 and 03. We don't even need those in an array, because the program knows which size box it's going to use anyway.

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:

Optimized tile set

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). 

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.

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 is logical, it can be removed completely if we change the BNE op at $0077BB to BEQ. 

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.

No comments:

Post a Comment

©TheouAegis Productions™. Powered by Blogger.