The standard way to watch a tool assisted speed-run is via a pre-recorded video. This sucks, not only are you having to download a very large video file, but the quality of said file, even when bit-rates are high will still be very substandard. The other option for watching is to download the specific game rom, emulator, and button press file. This is just inconvenient, as you have to trawl all over the internet to locate required pieces.
As the options discussed previously were unacceptable, a new solution was needed. The solution that was found was to bake the button press data into a modified version of the rom, the tool assisted speed run would then run like a built-in demo. This procedure is very game specific and may not even be possible for all games depending on how complex their lag behavior is, the game I have used is the MegaDrive game Sonic 1.
De-synchronization and lag frames
The main issue facing tool assisted speed-runs is desync due to the difference in timing between various emulators and the real hardware. The main cause of this desync (in the case of Gens-rerecording) is the fact that the emulator records controller input synchronous to the display frames, this means a single mismatched lag frame will cause all subsequent controller input to be desyned.
The other and much more insidious cause is the fact that (in the case of Sonic1) during the execution of the game engine the interrupts are left enabled, this is such that if a lag frame occurs, the music (sonic 1 uses main cpu for music) and palette (LZ water), will continue to update. There is another unfortunate side effect however, the frame counter is incremented in the vertical interrupt handler. This counter is the timing source for many in-game effects some of which effect the game-play such that desync can occur.
Solutions to desync
The main cause of desync is simple to correct, instead of recording the controller state on each display frame one shall instead record the actual controller state read by the game code. With this method of recording, lag shall no-longer cause controller desync.
The solution for the secondary cause is not so simple. The value of the frame timer is critical, even being off by 1 can significantly change the behavior of the game. There is no way to model the value of this variable as it is linked to the lag frames and thus the specific timing of the recording emulator. The only solution is to record its exact value for each frame.
Implementation – Controller Data
The controller data consists of two bytes for each event, the first being the timer increment and the second being the controller state. To perform the conversion of controller data it was necessary to modify the gens-rerecording emulator, specifically controller reads and frame timer reads were hooked, each controller read is considered the start/end of an event, reads to the frame timer are noted and used to determine the timer increment for each event.
Implementation – Sonic 1
The Sonic 1 game was modified to playback the converted controller data, the controller read function was modified to read from the controller data. The increment of the frame timer was removed from the vertical interrupt handler, instead the frame timer is updated explicitly in the controller read routine, its value increased by the amount specified in the timer increment byte of the controller event.
Sonic1 speed-runs exploit glitches in the game to achieve insane speeds and often skip entire levels all together. The in-game camera however is limited to just 16 pixels per frame scrolling. With this limitation much of the TAS video is that of just scrolling. To correct this I removed the scroll speed limit from the game, the camera shall now stay centered on sonic no matter how fast he moves. Specifically I increased the scrolling speed to 32px left and 64px right. If sonic moves faster than this such as wrapping to the end of the level then the entire screen is reloaded.
One would think such drastic changes to the game would cause desync, but having corrected the lag frame issues I can now make major changes without causing a desync. Any reader who understands the Sonic1 engine would wonder how removing the camera position limit did not result in desync as the game objects are effected by screen position. Well I simply duplicated the screen position variables, the original 16 pixel limit is still used internally to keep the engine happy.
One might also think that all these changes would cause massive slowdowns, what with the checking of two screen positions, particularly with the sprites, which have to be drawn with oneĀ screen position but have their onscreen bit set using another. And the doubled, or quadrupled scroll speed. But actually the slowdown is about the same of maybe even a bit less! This is because some of the existing code in sonic1 is slow as fuck, I made many optimizations both major and minor, particularly the horizontal scroll, I can update 64 pixels about the same speed as the game used to mange 16.
Converted runs
Sonic The Hedgehog (W,) (REV 01) [!]Aglar.zip
Sonic The Hedgehog (W,) (REV 01) [!]no_zips_Aglar.zip
source code – github
I would really like to do other sonic games as well but this really took to long, and also the newer sonic games being much more complicated will likely have more serious issues that may or may not be solvable.