Once we reach the red faces that blow bubbles, we arrive at the biggest timesave of the entire TAS! This timesave occurs by reducing the amount of lag frames that the red faces generate. The way I discovered that this was possible was when I was trying to resync an old TAS for my 2P warps movie, and noticed that a different number of lag frames occured in the sections with the red faces than the original TAS had. From there, I soon discovered that the direction that the bubbles were blown was based on RNG, and that certain directions could reduce the number of lag frames.
At the time, I didn't know what specific direction reduced lag the most or how manipulatable it was, but I did know that I could alter RNG by pressing select on player 1's controller a random number of times before the red face appeared. From here, I was able to write a simple script that would press select a random number of times for 100 trials and find the combination that best minimized lag. Using this, I was able to make each red face go from generating about 100 frames of lag to about 43 frames of lag, which was a nice improvement! The red faces each blow bubbles for a total of 256 non-lag frames, so any frames of lag that you save compared to an older TAS represent an equal number of frames saved in the new TAS.
I could have just stopped there, and decided that I'd done a good enough job with my lag reduction techniques. BUT, I wanted to completely finish off my Battletoads 2P TASing, and to do so, I had to be sure that there wasn't an easy way to save more time compared to what I'd already done. If I didn't, then someone could just run a similar script for lag reduction for more trials than I did and get, say, 42 frames of lag, and obsolete the movie. While a movie can be easily obsoleted, it's hard to really think of the category as being "done," so I got to work looking for how RNG directly influences the pattern of the bubbles in the level.
Before explaining how I figured this out, it's worth explaining in more detail how RNG in Battletoads works, which was previously figured out by earlier Battletoads TASers. Essentially, there are 4 memory addresses which each store 1 byte, which act as RNG addresses. These addresses are 0X25, 0X26, 0X27, and 0X28, which can be thoughout of as RNG_1, RNG_2, RNG_3, and RNG_4 respectively. On every frame, the values in these addresses are combined and XORed with each other a number of times which changes on each frame. Additionally, the inputs from player 1's controller on the last frame are used to change RNG on each frame (a single number can represent what buttons player 1 pressed by representing each button as 1 bit of a 1 byte number which is 1 when pressed and 0 when not pressed, since there are 8 bits in a byte and 8 buttons on an NES controller). Because of this, you can effectively directly control the RNG values using player 1's inputs. However, because there are 32 bits worth of RNG values, there are effectively 4 billion possible RNG states, which means that simulating through every possible combination of RNG values is not practical (which could be done if there was only 1 RNG address, since there would then be only 256 possible RNG values).
Keeping all of this in mind, I decided to write a script that would freeze the value of RNG_1 and play through a section of the red faces blowing bubbles, and report how much lag was generated. The script would initially set RNG_1 to 0 and play through a section. It would then repeat this process with an RNG_1 value of 1, and then 2, etc, and report which state caused the least lag.
After all of this was done, I repeated this process on RNG_2 as part of my efforts to figure out which of the 4 RNG addresses were controlling how many lag frames occurred. While changing the values of RNG_1 didn't have much of an impact on how many lag frames occurred, changing the value of RNG_2 had a MASSIVE impact on the number of lag frames that occurred, with a very low number of lag frames generally occurring about once every 8 frames, and a very high number of lag frames occurring on the frame after a very low number of lag frames occurred. On the frames with a low number of lag frames, the bubbles also generally moved up much faster than they did on other frames, and on frames with high numbers of lag frames the bubbles generally blew straight across the screen from one end to the other without moving up or down. There was definitely a pattern going on, but due to random variability in how much lag each possible simulation generated, it wasn't immediately obvious to me what this pattern was. I was curious if RNG_2 needed to be a specific value on all frames or only on the frame when a new bubble spawned in order to reduce lag, so I wrote a new script that only changed RNG_2's value on frames where a new bubble spawned, and found that it produced an equally low number of lag frames, so I knew that RNG_2 was setting some initial property of the bubble when it spawned in to make it generate a low number of lag frames.
In order to figure out how RNG_2 influenced the bubbles' properties to reduce lag, I set a breakpoint to occur whenever a new object spawned, and set the code to output to a trace log. I then advanced to a frame where a bubble spawned, scrolled to the breakpoint listed in the trace log, and then looked for where the value of RNG_2 was used. From all of this, I discovered the following 5 lines of code, which explained exactly what was going on:
LDA RandomValue_2
AND #$07
SEC
SBC #$03
STA Objects_Z_speed,X
In other words, on the frame when a bubble spawned, the value of RNG_2 was bitwise ANDed with 7. Then, 3 was subtracted from the resulting number, and this value was stored as the bubble's Z-speed, which controls how quickly the object moves up or down. Thus, all bubbles have a Z-Speed value ranging from -3 to 4. A Z-speed of 0 caused the bubbles to go all the way across the screen, which caused the most lag, while a Z-speed of 4 made the bubbles move offscreen the fastest, which reduced the lag the most. As such, by manipulating RNG_2 on the frame when a bubble loads to have a value that, when ANDed with 7 equaled 7, I could minimize the number of lag frames that occurred, and effectively make the bubbles fly up really quickly offscreen. It's worth noting that each bubble has a random chance of spinning straight up or far forwards from its original path on a given frame (which causes more lag to happen), so even if every bubble spawned with a Z-Speed of 4, it would still require a lot of testing to find the pattern that best reduced lag.
After this, I got to work writing a script to brute force an optimal bubble pattern for me. The basic logic for my script was that whenever a new object was created, if the value of RNG_2 & 7 wasn't equal to 7, then the last-created save state would be loaded, and random inputs would be supplied on player 1's controller to try again. This process would repeat until RNG_2 had a correct value, at which point a new save state would be made, and execution would continue from there. This process would continue for 400 frames, at which point the trial would end, and a new attempt would begin starting from before the first bubble spawned. The button presses from a given trial and the number of lag frames that happened were all kept track of, and when a new best for number of lag frames occurred, the button pressed needed to make this happen were output to a text file in a format that would let them be copy and pasted directly into the "Input Log.txt" file of a bk2 file, which could let them be copied directly into my tasproj file.
This all did a really good job at reducing lag, but I decided that I could go further. Eventually, I wrote an altered version of this script which would only run until 10 objects spawned, at which point the current trial would end. This would effectively break each red face section up into 3 smaller sections (about 30 bubbles are produced by each red face), which made better outcomes become more likely (for example, if each bubble had a 1/2 chance of not causing a lot of lag, then this would make it take about 1028 trials to find the ideal lag pattern, as opposed to taking 4 billion tries to find the ideal lag pattern). Additionally, I redesigned my script so that it would end a trial immediately if the number of lag frames that had happened was more than the current best, which also sped things up a bit. Lastly, I let my script run for hours at a time on each section, and I often broke each red face up into more than 3 pieces to reduce lag as much as possible. Using all of this, I was able to make the 4 red face sections take up 8, 5, 9, and 7 lag frames respectively!
To really see how much time this all saves, I have created a comparison encode below, which shows the red faces from my new TAS on the left, and the red face sections from feos and MESSHUGAH's old 2P Warps TAS on the right (which didn't try to reduce lag on the red faces). This video also shows player 1's inputs on the bottom of the screen, with non-lag inputs appearing in green boxes, and the input display turning red on lag frames. The current total number of lag frames for the current red face is also displayed above each input display as well. Note that the downswing by my toads to get off the poles also generates 2 frames of lag on most faces, so the total number of lag frames that occurs in most red face sections is 2 frames higher than the number generated as a result of lag frames from the bubbles blowing. This video is a great example of how a TAS can take a disordered and chaotic section of a game and convert it into something improbably well-ordered and fast!
I have also included links to my scripts for making these lag and input displays in both BizHawk and FCEUX down below. Note that two small changes to the code (which are described in a comment at the start of the scripts) could be made to make these into general purpose scripts that could be used for any NES game.
1. BizHawk Script:
https://github.com/Lobsterzelda/TAS/blob/master/Battletoads/Scripts_Folder/Display_And_Lag_BizHawk.lua
2. FCEUX Script:
https://github.com/Lobsterzelda/TAS/blob/master/Battletoads/Scripts_Folder/Display_And_Lag_FCEUX.lua
And now for the comparison encode!
Any improvements to the red face sections will probably require some sort of script similar to the one that I wrote. To help out whatever TASers want to try their hand at Battletoads next, I have included a link to the script I used to reduce lag as much as possible on the red faces. I hope that this helps!
https://github.com/Lobsterzelda/TAS/blob/master/Battletoads/Scripts_Folder/Red_Faces_Lag_Reduction_Small_Chunks.lua
At the end of this level, because of my faster spring jumps, I make it to the last red face a lot sooner than I did in my old TAS. I also hold L+R when near each of the red faces to make the camera scroll up so that the red faces spawn a few frames earlier (your y-position gets reset when you grab the pole, so your sprite goes back to normal once you grab the pole). As a result of all of this, I can just barely make it onto the spring that goes around the tower a cycle earlier than I could in my original TAS (if you don’t land on the spring, then you will void out and die when you hit the bottom of the screen). The spring takes 128 frames to go in a complete circle around the tower, so this in turn lets me save a little more than 2 seconds over my original TAS here.
After this, we enter the final climb up the tower. This is one of the most annoying sections to TAS in the entire game, since there's an enemy blowing bubbles which generates a lot of lag while you're climbing. This enemy can also randomly decide to perform a move that pushes you off the platforms every time, which you have to avoid with RNG manipulation. The lag generated by this enemy seems to not be very controllable with RNG manipulation, so I mainly just controlled the frame that I spawned the enemy on to reduce lag, since that does seem to slightly effect lag. I also landed on each of the springs a little faster than I did in my old TAS by using faster jumps and jumping through the sides of the springs, which pushes you up on to the top of the springs a little sooner.
Lastly, I hold L+R at the same time at the end of the level to force the camera to scroll up high enough that it triggers the level-end object to spawn (which you normally only trigger when you reach the top platform of the level, as opposed to just shifting your sprite up to the top of the level), which ends the level and takes us to the final showdown with the nefarious dark queen!
In order to see a full comparison of level 12 in my new TAS and in the old TAS, please see the video below, which shows level 12 of my new TAS on the left, and level 12 of the old TAS on the right:
Frames Saved: 495