Guardian Force De-Lag

[Download 2018/04/08]
(Updated: 09 April 2018 03:30:45 UTC)


Success's hidden gem Guardian Force is a truly amazing game, but has one very unfortunate flaw: the program is extremely poorly optimized in terms of input latency. Even on the ST-V arcade hardware, the game has somewhere on the order of 6 to 7 frames of input lag. This is beyond the point of uncomfortable, and makes the game almost unbearably hard to control.

So, I had a feeling there would be some low-hanging fruit here. You don't get 6 to 7 frames of input lag if you're even remotely paying attention to how your inputs are handled. So, time to pop open IDA and MAME and get cracking. Turns out, yes there are at least 2 frames of easy-to-fix lag in software.


Burn the attached binary file to any 8-bit ROM with a similar pinout to the 27C010. The file size is very small, and this is the only ROM size required. So you could use a 27C020, 040, 080, etc. You don't need to even repeat the file for those ROMs, it's very forgiving. :)

Once your ROM is burned, open up the cart. You will see an empty EPROM socket on the cart's board, as shown below.

Simply put the ROM you've burned in that socket, and close it back up. That's it!

Version CRC SHA1
2018/04/08 c770aad3 e80847ae3f0241b5f50d8356c520e81622b15f31

Developer's Notes

So with this, I had to figure out how the patch EPROM worked on the ST-V. It turns out it's really simple, all I really had to do was take the beginning of the regular ROM data and work some patching code into it. From there, I needed to find the sources of input lag and devise how to remove them. So, it's watchpoint time.

In the end, I traced the input data coming from the hardware through 2 interstitial buffers which were implemented by Success. We'll name the points here as: hw_inp, the input data exposed directly from the hardware; smpc_buffer, the input data which would normally come from Saturn pads; smpc_parsed, the input data which is derived from smpc_buffer.

Now, the critical thing to know about these buffers is that they're not all updated in one place, or at one time. Normally the smpc_buffer needs to be updated by the Saturn's input reading microcontroller, but the ST-V doesn't use this for inputs. Success clearly wrote their code for Saturn first, as the code which would normally read the data back from the microcontroller actually patches in data from hw_inp into the smpc_buffer.

We can look at these buffers in two ways: who fills them, and what order are they filled? If the buffers are used optimally (in a way which does not increase latency), these should essentially flow the exact same way. In Guardian Force's case, they literally flow the opposite.

Who fills who?
Hardware -fills-> hw_inp -fills-> smpc_buffer -fills-> smpc_parsed -fills-> Player's Input Code
What order are they filled?
Frame Start -> hw_inp -> Player's Input Code -> smpc_parsed -> smpc_buffer -> Frame End

For those paying attention, each one of these fills is introducing another frame of lag. The player's input code reads from smpc_parsed which isn't updated until after, meaning the data it is using is from the previous frame. The smpc_parsed buffer reads from smpc_buffer, which also isn't updated until after, meaning that too is reading the previous frame's buffer. smpc_buffer is the only one that isn't itself lagged, since hw_inp is always the current frame's data.

The obvious fix here is to invert the order of fills. In this way, each buffer reads from the buffer that was just filled, meaning it's still along the same frame's data. Thus, we remove 2 frames of input lag.

I'm sure there are more dumb sources of lag (it's still somewhere around 4 frames on hardware!), but this is such a massive improvement already that I figured it was worth posting.

~ trap15