Wednesday 8 February 2023

The Bacura Hit-Box Mystery!

Since I've been inundated* with questions about the Bacura hit-box fix, I thought I'd write up a blog entry on it...

Xevious maintains a table of object variables including X & Y "world" coordinates, stored as 16-bit values. These values are scaled up from their sprite coordinates for various reasons I won't go into here.

Once a frame, the SUB CPU program iterates through the object table and updates a shadow copy of the hardware sprite registers in RAM. These shadow registers can then be efficiently block-copied to the sprite hardware registers during VBLANK. It goes without saying that the format of the these registers are the same as the hardware registers; in Xevious, the X register is 9 bits, and the Y register 8 bits.

During the course of the game, Xevious must calculate collisions between objects - bullets, bombs, enemies and the Solvalou - to determine which objects need to be destroyed. For efficiency reasons, (ie. to eliminate scaling and allow the use of 8 bit calculations), Xevious uses the sprite shadow registers in the hit-box calculations. In fact it shifts down the X coordinate to 8 bits in each calculation routine.

The problem with the Neo Geo transcode is that the sprite hardware X & Y registers are themselves shifted (up), not conducive to efficient calculations. Additionally, the X coordinate is adjusted for 2x2-tile sprites due to the way the sprites are implemented on the Neo Geo, which breaks the calculations (I found that out the hard way!)

As a result, the 68K transcode actually maintains 2 copies of the "shadow" registers (at least for X & Y); the 2nd being the same (actually, similar) format to the original registers. To make the hit-box calculations a little more efficient, I pre-shift the X coordinate down into 8 bits so it need not be done in every hit-box routine. Thus the hit-box calculation algorithms - magic numbers and all - are preserved in the transcode.

After checking the routine for the Bacura more than a few times, I was stumped as to where things were going wrong!?!

My intention was to compare values side-by-side between the original Xevious and the transcode. Patching the code to start on Area 3 and starting a game before the attract mode ran meant that each Bacura would appear in exactly the same spot at exactly the same time on each version.

First up - the original Xevious. With 4 Bacura on the screen I halted execution and looked at the Y coordinates for objects #16-19 in the object table. Since the screen is rotated, and Bacura move straight down the screen, the Y coordinate for each never changes, which makes things easy! I took note of those and then set a breakpoint in the routine that checked if the Solvalou has been hit by a Bacura.

Somewhat surprisingly, the value in the sprite shadow Y registers did not correspond to the Y coordinates in the object table (even taking the scaling into consideration)! And this was actually my first clue. But it was late, so I went to bed with a theory, but still guessing.

Next morning I had time to have a quick look at it again. Initially I was confused, as the update sprite shadow register routine appeared to just store the scaled value directly to shadow RAM. But then I noticed it was calling a routine to handle the screen flipping for a cocktail cabinet. The transcode does not support flipping - for obvious reasons - but I took another look at the Z80 routine. I did annotate that some time ago, but forgot that it adjusts both X & Y values even in the case that the screen ISN'T flipped for cocktail mode! And although the X value is simply incremented by 4 - which won't affect hit-box calculations - the Y value is actually SUBTRACTED from $EF, which certainly WILL affect the calculations!!! Eureka!

So I updated the transcode to apply both X & Y adjustments (for the sake of completeness) to the 2nd copy of the X & Y registers and now the hit-box for the Bacura is correct - yay!

Note that for 1x1-tile sprites, the calculations were still technically incorrect, but the difference was too subtle to notice during gameplay. I DID think that they were perhaps a pixel off as it's possible to bomb two adjacent objects at once if you aim just right, but then thought my memory was playing tricks. But with the 2x1-tile Bacura sprites, the difference was an entire sprite tile width - 16 pixels - impossible to miss!

Today with the 2nd of 8 known bugs fixed I forged ahead with the audit - mainly more micro-optimisations. FTR these micro-optimisations are things like using MOVEQ/ADDQ/SUBQ where possible, LEA instead of MOVEA.L, and small (byte) branches etc etc. I'm now exactly halfway through the 2nd ROM device (of four).

* this may not be the same meaning of the word you're used to!

No comments:

Post a Comment