Friday, May 13, 2016

Optimizing space, adding a switch.

Thanks to the nice folks at AtariAge, I got some feedback, mostly about various bugs, which have been cleaned up.  Now I'm trying to power through and add the rest of the content. Which is tricky, because I just don't have that much space left.

The biggest area of concern right now is enemies.  My space left in the bank where I store enemy data is small. Just a couple hundred bytes.  And I needed to add definitions for at LEAST 3 more enemies (bosses for the next 3 dungeons). And hopefully, another 3 or so more general enemies, so the 2nd half of the game isn't completely boring.

The first order of business was to read through the existing enemy definition code, and start optimizing. The first few enemy functions were unnecessarily huge. The amount of code for the slime and toady were just silly. I managed to compact it all a little bit, and squeezed in the 3 boss monsters.

Without much space left, the next 2 enemies were just palette/graphic swaps with more HP/Damage of previous enemies.

But one thing I wanted to add in dungeon 3 was a switch that controlled doors. How could I manage that with only about 10 bytes left?  I'm handling it like an enemy (it's a non-player object, and enemies are the only things I have in that category). The first hurdle was that my index that points to the enemy definition overflowed.

I have my enemies laid out in a big block of data, one after another:
    .byte #<SlimeFrame0
    .byte #>SlimeFrame0             
    .byte #<SlimeFrame1
    .byte #>SlimeFrame1             
    .byte #8                        ;sprite height
    .byte #$86                      ;color
    .byte #<EnemySlimeUpdate
    .byte #>EnemySlimeUpdate
    .byte #1                        ;hp
    .byte #1                        ;damage

    .byte #&lt;SlimeFrame0
    .byte #>SlimeFrame0             
    .byte #&lt;SlimeFrame1
    .byte #>SlimeFrame1             
    .byte #8                        ;sprite height
    .byte #$D8                      ;color
    .byte #<EnemyGreenSlimeUpdate
    .byte #>EnemyGreenSlimeUpdate
    .byte #1                        ;hp
    .byte #1                        ;damage

Actually, on the 6502, this is a pretty inefficient way of laying out data, which I'll talk about another data. But anyway, for each room, I store an index of which enemy type we're dealing with (ie #0 for slime, 1 for Green Slime). Then I multiply that number by 11 (the number of bytes per enemy), add it to the address of EnemyDefSlime, and we have the address of our enemy definition.

But the switch was my 24th enemy. 24 times 11 is 264. (do you see where I'm going with this?) The 8 bit number I used to track the index overflowed.  

Well, I really didn't want to switch to a 16 bit number for this (math beyond 8 bits is a hassle on the 6502), so I ended doing a one-off hack, and hardcoding a special case for #24 to just go to the right definition.

The update code for the switch was tiny, and I managed to squeeze it in by doing another optimization pass at my other enemies.  But now I needed to add code in the door handler to open/close the doors based on my switch.  But my bank that handled that logic was also full. So I ended up moving a bunch of code to my kernel/graphics bank to free up space in my main bank.

Long story short (too late), I got it working. I have exactly 0 bytes of space remaining in my allocated enemy area, and 1 byte left in my main bank.  I still have 200 bytes of space in the graphic bank and the room definitions bank, so there's a TINY bit of wiggle room left.

But not much.  Let's hope I don't find any other bugs, or come up with any other crazy things that I feel I need to add.

Edit: Oh no, I just realized that I forgot to actually implement the power increase that happens when you find the power ring item. That would only be about 5 bytes, but it needs to be in the main bank. Sigh.


sverx said...

Are you sure you can't cram all the enemy info into 10 bytes instead of 11? So you'll have room for 25 enemies. Some of your info in those structs look like they can actually occupy just few bits, or can be turned into an enum to save even more...

Nathan Tolbert said...

Great question. I totally could, although it means sacrifices other places. Really, I should have done it completely differently (parallel arrays), but now that I'm this far along, it comes down to optimizations within the enemy definitions vs the amount of code in other places to support it.

For example, the last byte (enemy size and missile size) is currently in the right format to copy directly to a sprite hardware register. I could probably overload it with more information, but I'm not sure I'd save more than the 24 bytes in the long run once I wrote the code to break the data out and get it in the right format. It was just easier at this point to do my cheesy hack instead. But I like your line of thinking...I might do a blog post just detailing some of the options I could have done to improve it.