Hello again everyone!
I am writing this post to recruit for help with a new Battletoads TAS that I'm working on.
Right now, I'm trying to create a new movie to obsolete the current 2P Warps TAS. In order to do this, I would like to use the jet-glitch in the turbo tunnel to spawn the warp portal to level 5, which would allow me to completely skip level 4 (saving over a minute!). Doing this, however, is no easy feat. I'll describe what I've learned from doing my research so far:
Objects in Battletoads:
In Battletoads, all properties of objects are stored in a block of memory in RAM which starts at address $3C1. There are a maximum of 15 objects that can exist at a given point in time in the game. These attributes can best be visualized in a table, where the rows of the table represent specific attributes, and the columns of the table represent specific objects. For example, ID is the first attribute, with values from $3C1 to $3CF representing the ID values of each of the 15 object slots (ex. the ID of object 2 is in $3C2, and the ID of object 4 is in $3C4, etc.).
The next row stores an attribute named animation_1, which starts at $3D0, and goes up to $3DF. This pattern repeats for all attributes, and there are a total of 35 attributes for each object. However, for the purposes of the glitch I am trying to do, only a few attributes actually matter, which are ID, X-Pos_High, X-Pos_Low, Y-Pos_High, Y-Pos_Low, Z-Pos_High, Z-Pos_Low, and animation_2. To see an example of what this table looks like in memory, click the link below to a picture of this table, where the top-left-most cell represents $3C1 in memory, and the bottom-right most cell represents $5CD:
https://github.com/Lobsterzelda/TAS/blob/master/Battletoads/Debug_Info/Battletoads_Objects.jpg
Of particular interest in this picture is the column highlighted in blue, which represents the warp portal object. From performing some debugging, I figured out that an object MUST have two attributes equal to specific values in order for the object to act as a warp portal. First, the ID value of the object must be equal to $22. Second, the value of the animation_2 attribute must be equal to $55. If one of these values is altered using a hex editor, then the warp portal disappears, which doesn't happen when editing the other attributes of the portal.
With all of that background info covered, let's get into the checkpoint glitch!
The Glitch:
During normal gameplay, $B7-$B8 stores a 16 bit number which represents the address where the game should look to start spawning new objects from whenever you restart a level, hit a checkpoint, or walk past the trigger for a new object to load (this is called the "config pointer"). Ordinarily, this value is something very high like $E5D3, which is high enough up in memory that it is in the game's ROM.
When one toad dies on the turbo tunnel jet before the first checkpoint has been activated, the game becomes confused. Normally, if you die before activating the 1st checkpoint, you go back to the start of the level, and if you die after hitting the 1st checkpoint, you respawn at the last checkpoint you activated. As a result of these actions, when you respawn, the game tries to update the config pointer using uninitialized data, which causes 0 to be written to $B7-$B8. Since $00 is part of RAM, the game now starts loading objects based on the values stored at the start of RAM!
Whenever the config pointer tries to load an object, it looks at the 11 byte sequence that starts at the address referenced by the config pointer's value, and it uses that to decide what values the new object should have for each of its attributes. The values for an object's ID, High-X_Position, Low-X_Position, High-Y_Position, Low-Y_Position, High-Z_Position, Low-Z_Position, and the object's animation_2 attribute are all determined directly by looking at nearby addresses.
To give an example of how this actually works, suppose that $B7/$B8 = $0016, and the following table represents the current values stored in RAM when the new object tries to load:
https://github.com/Lobsterzelda/TAS/blob/master/Battletoads/Debug_Info/Sample_Data.jpg
In this case, the new object would have an ID value of $22, a High_X_Position byte of $05, a Low_X_Position byte of $5C, a High_Y_Position byte of $3B, a Low_Y_Position byte of $28, a High_Z_Position byte of $30, a Low_Z_Position byte of $12, and an animation_2 value of $48. The reason for this is that ID is calculated as the value stored at the address referenced by the config pointer, High_X_Position is copied from the location of the config pointer plus an offset of 3, Low_X_Position has an offset of 4, High_Y_Position has an offset of 5, Low_Y_Position has an offset of 6, High_Z_Position has an offset of 7, Low_Z_Position has an offset of 8, and animation_2 has an offset of 9. Since 11 total bytes make up the object's descriptor, after every time a new object loads, the config pointer has its value increased by 11.
The Problem:
In order for an object with the right ID and animation_2 value to spawn, the addresses near the spot where the config pointer is looking at have to have the correct values. Unfortunately, there isn't really enough manipulable addresses in a row for most of the game's memory to make this work. However, there is a strong candidate address with the potential to work: $3F4. In order for the object that spawns when the config pointer reaches $3F4 to be a viable warp portal, the High-X-Position byte of the 7th object loaded into memory must be $22, the Low-X_Position byte of player 1 must be $55, and the values of the High-X-Position byte of the 8th-15th objects loaded into memory must be values that will translate to coordinates for the warp portal that will put it somewhere reachable to the toads.
The high-x_position byte of the objects that spawn into memory can be manipulated by making sure that the third address after the value referenced by the config pointer is equal to favorable values at the point when new objects are loaded. However, this is all very difficult to manipulate: Because there's only 15 object slots and the config pointer needs to increase 92 times to reach $3F4, the game runs out of space to load new objects well before reaching this point (the config pointer generally increases by 11 once every frame until there's no room for more objects). As such, pre-existing objects need to unload several times in order to create enough space for the config pointer to keep increasing. If all object slots are filled and the 5th object (for example) unloads, then on the next frame, a new object will spawn in the 5th object slot.
The question now becomes, how do we make the config pointer read addresses to spawn objects at points which are favorable to us, and how do we force the config pointer to get this high? Moving to the right sometimes unloads certain objects, but other times, this has no real effect on the allocated objects.
The Solution:
Since two minds are greater than one, I am posting here to see if anybody can help me come up with ideas to tackle this problem. Note that you DON'T need to have any experience TASing Battletoads or NES games in order to work on solving this problem. Preferably, having experience with ACE-like TASes or TASes that spawn corrupted objects (such as Mega Man 1) would be useful for figuring this out.
If someone can help me figure out a way to get this to work, then I will make them a coauthor for the new TAS. The new 2P warps will be about the same level of entertainment as the currently published movies, which means it will probably be accepted for stars. As such, if you are interested in submitting a movie that will be accepted for stars, now's your chance!
I have attached below a link to a lua script made by Feos which allows for objects and their properties to be visualized in FCEUX. I modified the script slightly to make it easier to display the table.
https://github.com/Lobsterzelda/TAS/blob/master/Battletoads/Debug_Info/Object_Map.lua
Additionally, I have attached below a link to my new TAS of the game so far, which plays up until the jet glitch is activated in level 3. As such, if you want to do some testing playing around with the glitch, you can do so using this movie file.
http://tasvideos.org/userfiles/info/64374095014736966
Feel free to post a reply in this thread or send me a personal message if you have any questions about how this glitch works, or if you have any ideas for things you think I could try. Any suggestions are welcome, so don't worry about whether or not your suggestion makes sense (after all, this glitch is so complicated that almost nobody fully understands it, and I certainly don't!).
I look forward to seeing what ideas the TASVideos community can come up with :)
Important Update:
It turns out that if an object is created with an ID value of 255, then no new object is added to the object map. Additionally, the value of the config pointer will be reset such that the addresses 1 and 2 addresses above the current config address value become the next value of the config pointer.
For example, if the config address was $16 (which stores Player 2's inputs), $17 stored the value $03 and $18 stored the value $FE, and $16 held $FF, then after the next frame loaded, the config pointer would be set to $03FE, which is the address of player 2's low x-position!
$17 and $18 seem to be basically random and are used to hold temporary values, so it should be theoretically possible to force the config pointer to go wherever we want now! Additionally, if the config address is $2C (which also stores player 2's inputs), then the config address will always be reset to 0 on the next frame, since $2D and $2E are addresses which always hold 0 and are unused!
What this means is, there's a way to keep resetting the config pointer every other frame, there's a way to make the config pointer make one arbitrary jump, and it's possible to control the ID value of every other object in the object map table!
From here, I can see a good plan for how to actually activate this warp: Keep using the above process to set the ID values of several objects to have the desired values (including waiting for objects to disappear from memory so that they can be re-allocated) so that every object can have it's id value set. Then, set the config pointer to jump to an address in the ID table, and use those values to spawn the warp portal and execute a warp!
Let me know what you guys think about this idea.
Second Important Update:
As it turns out, whenever an object spawns with an ID value of $55, the game force-ably unloads it on the next frame (I'm not sure if it ever actually appears in the first place). Because of this, you can never set an ID value to $55, which means the above idea won't work.
Is all lost then? Is there no way to pull this glitch off? Maybe not...
Another possibility is to manipulate the low-x_Position byte of the objects that spawn in to match the values of the warp portal object. If you wait until the config pointer hits $21, then the low-x-position of the object that spawns in will be based on the value stored in $25. $25 stores the RNG for the game, which is calculated every frame, and is altered every time you press a button, meaning you have direct control over it!
The issue? $21 is a temporary value which is almost always set to 0. $21 is used for the ID of the object to be spawned, and whenever an object has an ID value of 0, the game doesn't load it in...
Besides all of this, every time you reset the config pointer, another random object gets loaded in with an ID based on the value in $0B, which is a counter that goes up by 1 every frame (and is therefore almost never 0).
To make matters worse, $17 and $18 are calculated roughly based on player 2's x-position and animation respectively (with occasional rare jumps to far-away values). However, in order to move right enough to get $17 equal to $CE, you have to stop punching repeatedly. However, if player 2 doesn't punch every 4 frames, he can't reset the config pointer (since this requires pressing every button to do it). Thus, P2 is trapped in place. The only way around this is to prevent Player 1 from voiding out when you respawn, so that he can pick up player 2 and throw him somewhere else.
The grind for uncovering all of these secrets in this game just never ends...