Adequate title, since the game's painful to play. Half of it's the lack of radar. Half of it's the ridiculously awful and loose controls. I imagine it could be made perfect and damageless in a TAS, but... when you get randomly shot from someone and you don't know where that someone is, that's some annoying crap right there.
Anyone anymore familiar with this game than I am? ><
Newcomer here, just started on trying to TAS this game. Here are some aspects of the game:
The main thing is shootdodge, AKA bullet time. If you shootdodge you can avoid enemy shots while shooting back, but at the cost of slowing down. I believe to TAS this game effectively requires minimal use of it. TASing the first level has me using it just once.
Max slows down as you run and shoot, as just opposed to just running.
Painkillers are used to heal over time. Though, in critical health, Max can auto-heal 1/4 of the maximum damage.
In the early parts of the game, shotgun is preferred for one-hit kills, whereas the M9 can kill with 3 shots. Pump-action is slower than sawed-off, and getting Dual Ingram is the fastest way for max damage. I haven't tested the Jackhammer though...
There's at least two instances where entertainment might be sacrificed for speed, quoted from the Faq here: http://www.gamefaqs.com/gba/919070-max-payne/faqs/29071
In Empire of Evil, there's a room with a drumset which Max can use.
In Angel of Death, one of the mansion rooms has a piano that Max can
use. Try playing the Max Payne theme song! :)
And finally, there is a Hard Mode, Dead on Arrival, though I'm confused as to how to start a new game on Hard Mode without getting all the weapons transferred from the old save.
I'll try to fill in more info soon and get a WIP for the first act soon.
https://www.youtube.com/watch?v=UPPGxsbyNEM
Audio appears to desync. Well you get the idea
Quick run of level 1. It is mostly optimized until the room with 4 enemies.
I think there are 12 levels. So the game should be under 20 minutes long if level length stays similar. What do you think about a full run?
Nice WIP! I don't think it will be so entertaining to watch full movie, but it's a nice game choice! I had an idea to test this game in 2015, but you decreased my wishlist :-)
Read also this: http://www.gamefaqs.com/gba/919070-max-payne/cheats
TASing is like making a film: only the best takes are shown in the final movie.
Nice WIP! I don't think it will be so entertaining to watch full movie, but it's a nice game choice! I had an idea to test this game in 2015, but you decreased my wishlist :-)
Read also this: http://www.gamefaqs.com/gba/919070-max-payne/cheats
Yeah I tested level 2 a bit. Enemies have more health. Shotgun still ends them in 1 shot. Boss takes 2 shots. And then sawed off in Level 3. Sawed off fires 10 frames faster per shot. 31 vs 41.
Since my first submission to the site a few weeks ago I also thought about making a TAS of Max Payne GBA; this game choice is due to the fact that other than Crash/Spyro Fusion, the only other GBA game I ever speedran was, well, Max Payne GBA. To be fair, there is some other underlying motivation, but more on this in a bit. The purpose of this post is to share some important insights/questions for TASing this game.
Game version
The first question is which game version to use. There is the US version (released 2003/12, build from 2003/10) and the EU version (released 2004/03, build from 2003/12), and they differ to a degree where one version is clearly to be preferred for RTA runs over the other. The full list can be found over at speedrun.com (source), but the differences most relevant to speedrunning are:
Buffering inputs through screen transitions works only in the EU version (admittedly, for a TAS this should be of no consequence)
Enemies unfairly shooting Max over objects has been fixed in the EU version
There is a PAL-exclusive glitch called "ammo underflow", where you can shoot a weapon without ammo (cancel the weapon animation by pressing L, and then you can shoot any weapon you have) and get a negative ammo count. Upon starting a new level this number is adjusted (only the final two bits are kept, so -1 = FFFFFFFF in HEX becomes 000000FF = 255)
And lastly: after level 5 you lose all your weapons for story reasons. However, only in the EU version do you get them back after level 6 (although without ammo)
There are some more minor differences in favor of the EU version, but combining points 3 and 4 (i.e., the ability to get max ammo on basically any weapon in the second half of the game) are the biggest reasons why I strongly believe that, even in TAS, the EU version should be a lot faster.
Turning off blood
Next, one thing figured out half a year after the RTA speedrunning community for this game formed is that turning off blood ("gore") in the options reduces lag by a lot. We never timed it because the difference in gameplay was so glaringly obvious, but with TAS tools it's easy to put some numbers to that. For this I converted exileut's old level 1 input file (still with VBA-next core) to modern Bizhawk as accurately as I could from just the file and the video. Next, I played through level 1 after turning off gore in the options before starting the game (input file) and I found that, while this extra menuing loses 111 frames, just on level 1 I was 186 frames faster (full room-by-room comparison spreadsheet).
For a super rough full-run estimate, level 1 takes up ~7% of the full run so scaling this timesave up results in ~2600 frames / ~43s, although the margin of error on this number likely is considerable. Still, this number is roughly in line with the difference between the last RTA WR with blood on and the first RTA WR with blood off (disregarding a big mistake in the last level this difference was ~47s, although it's hard to factor out differences in execution. But this is only meant as a ballpark estimate anyway). In any case, this extra menuing at the start of the run is more than worth it.
The mysterious door clip
Now before starting an actual TAS there is one unsolved mystery which definitely has to be understood---at least partially---given how impactful it is. Many years ago, I "randomly" clipped through a door in level 2 (potential timesave: 40-45s). This has been reproduced three more times by various people, but nobody had ever figured out anything about it... until recently. This was my other motivation for looking into this game with TAS tools: to figure out what the hell is going on here. Looking at all the videos again I had the idea that, maybe, breaking boxes has something to do with this?
And indeed, I found that whenever a box is broken in level 2, some bit in the memory region 0x00512D - 0x00522E (Combined WRAM) is flipped. This is interesting because the byte 0x00512F decides whether or not that locked door has already been unlocked (blasted away with dynamite). So what can---and sometimes does---happen is that shooting a box in the room with the locked door flips 0x00512F, thus making the door traversable. In fact, I even managed to create an input file with the door clip! This was essential for further testing; I figured out that:
it does not matter a box is shot, once you're in the room with the locked door, each box seems to be tied to a specific bit.
breaking boxes in previous rooms changes which bits boxes flip in subsequent rooms. This is also why nobody figured this trick out at the time, because the testing was most likely limited to the room itself.
the frame on which these previous boxes are broken influences which bits boxes in subsequent rooms are tied to (as explained in the notes of the previously linked input file). In fact, this behavior seems to change every single frame
While all of this is super interesting and super helpful, this does still beg the question:
What is going on here, game-internally? How does the frame a box is shot on determine the bit that is flipped when shooting boxes in subsequent rooms?
While one could probably make a TAS just by playing around (e.g., with LUA scripting) and getting a decently fast door clip, understanding the mechanics behind breaking boxes would be highly beneficial for a couple of reasons. First of all, obviously, this would enable optimizing the door clip route for level 2. But even beyond that, this may be the foundation of door clips in other levels. More generally, boxes seem to behave weirdly in this game in general, or so I think. Over the course of testing all of this, at one point I suddenly had all weapons unlocked at the start of level 3 (the corresponding cheat was off and there was no saved game, as seen at the start of the video). Admittedly, there is no evidence which suggests that whatever happened in that video is tied to boxes, all I'm basing this on is Occam's razor. Still, in my opinion this makes a strong case for trying to better understand boxes in this game.
This where I'm stuck at the moment, but if I find out anything else on this topic I'll share it here :)
I was looking forward to seeing any progress on this game!
toca wrote:
While all of this is super interesting and super helpful, this does still beg the question:
What is going on here, game-internally? How does the frame a box is shot on determine the bit that is flipped when shooting boxes in subsequent rooms?
We can only speculate on this one. Judging by the fact of fitting so much content, including Max's voicelines (albeit compressed), I may give an assumption that some RAM was reused for different logic purposes.
The game is most likely programmed in C (or even Assembler) which lets you tweak low-level memory and retrieve the data by referencing its absolute address, not only via pointers to variables. So, there is probably some spots with slightly wrong hex address references and the "winning" bit is involved in it indirectly.
What, if you do the opposite: collect the detonators, return to the room with the door and destroy a box? Will it produce zero at 512F on rare occasions?
TASing is like making a film: only the best takes are shown in the final movie.
The game is most likely programmed in C (or even Assembler) which lets you tweak low-level memory and retrieve the data by referencing its absolute address, not only via pointers to variables. So, there is probably some spots with slightly wrong hex address references and the "winning" bit is involved in it indirectly.
Hey, great to see that one of the "OGs" is still interested in the game! I like that theory about overlapping memory regions. The absolute-address part is backed by the fact that boxes throughout the whole game flip bits in 0x00512D-0x00522E. Also, the 4-byte address at 0x00512C seems to always correspond to level-specific info (e.g., which room-number you're in, whether a door is open, ...) so there's, in theory, more potential for opening doors by shooting crates. When going through the all collectibles guide, the only rooms with 1. boxes in the room, 2. boxes in an adjacent room (edit: while boxes more than one room before can influence things, cf. this video, I haven't been able to make such a manip work myself) and 3. a locked door are:
level 2, room M4 (the one this whole discussion is about)
level 8, room M19 (the byte corresponding to the locked door is 0x005130, would save 5-10s depending on the ammo route)
level 8, room M20 (the byte corresponding to the locked door is 0x00512F, would save ~5s)
While I did use LUA scripting for doors 2 and 3 on that list I, unfortunately, didn't get access to the correct byte in thousands of attempted frames. The problem is that for whatever reason, boxes seem to be written only to specific bytes; so far I've never seen a box that changed more than 10 different bytes in that whole region. So unless we learn something new about this whole mechanism and what the game really does here, I'll say that the level 2 door clip is the only one that's really possible.
Dimon12321 wrote:
What, if you do the opposite: collect the detonators, return to the room with the door and destroy a box? Will it produce zero at 512F on rare occasions?
Great question! When setting all the bytes in the region to 1, shooting boxes did not set the byte back to zero. When setting all the bytes in the region to 2, shooting boxes did set certain bytes back to 1. So the logic indeed seems to be "set value at byte to 1" (and not the inversion/flip of a bool or similar). This is another puzzle piece, although I still can't think of what this value could possibly represent. It's not the box breaking animation, the box state, or the box content (those are all stored elsewhere); yet, I don't have any idea what else the game could be tracking with these.
the frame on which these previous boxes are broken influences which bits boxes in subsequent rooms are tied to (as explained in the notes of the previously linked input file. In fact, this behavior seems to change every single frame
Because the whole box behavior seems to depend on the frame I checked for memory addresses which change with the flipped bytes of the boxes. In doing so I stumbled across two interesting addresses:
0x042B18 (Combined WRAM). At first glance this looks like a frame counter but there has to be more to it. Freezing this address freezes the game (except for the music) and this can be fixed by unfreezing the variable. So somehow this variable is what "makes the game move forward"(?). Actually, there is a related address: 0x004074; this seems to also be a frame counter, but only within a level (after each level it's reset to zero) & freezing it freezes the game in the same way. In any case, neither of them seemed to influence on the whole box breaking question (but they are very interesting in other regards, more on this below)
0x0181A4 (Combined WRAM). This 4-byte address seems to be in one-to-one correspondence with what bytes are flipped by boxes. However, changing it (either before or after the room transition) does not influence the flipped bits, so this is not cause-and-effect; rather, it seems that 0x0181A4 is indicative of how breaking a box in the next room will behave(?) In any case the 0x0181xx memory region seems to have something to do with all of this, but that's all I got from staring at the hex editor. Actually, I have a feeling I can't find out much more about this by just staring at memory addresses so I think I'll just move on and try to find the optimal level 2 box route through scripting...
Now the other thing I wanted to mention about 0x042B18 is that it seems to be the game's way to introduce "randomness". This is based on three observations:
I did not find anything that looks like a "classic" RNG value (which changes wildly because it's updated according to some "newRNG=(oldRNG*key+offset)mod(232)" rule)
When running scripts, sometimes the movement just ... "broke". What I mean by this is that the same movement inputs sometimes did not get me, say, through a door anymore (and the only difference was that all of this happened at a later frame, but there were no enemies or other relevant objects in the room with me). I haven't done a side-by-side comparison of two such attempts yet, but there has to be a small variation in movement distance or something??
I loaded up my level 2 door clip proof-of-concept file and the first time the value at 0x042B18 is increased to 1, I set it back to 0 in the hex editor. This led to the first kill in level 1 not working because one of the shots did not land, although all the same inputs happened on the same frame as before when everything worked. Other times, changing the value in question made me run into a pillar (where in the original route I squeezed just past it)
This will make TASing this game terrible because the only way to re-optimize earlier levels is to essentially re-do the entire TAS. For now I guess I'll just continue TASing to get a better feeling for everything (e.g., I haven't thought about the ammo route with ammo underflow because that's not done in RTA runs), but I think I'll need at least two, maybe even three rounds of TASing to get to a reasonable end result.
P.S. I also played around with resetting the console during saving, but I didn't find anything interesting there either.
After quite some time and extensive LUA scripting search I found a level 2 configuration which skips the detonators (via 0x00512F) without being too slow:
Link to video and bk2 file
Fun fact: level 1 is 6s faster than my gold here, and level 2 is a whopping 56 (!) seconds faster than my gold (so my estimate of 40-45s was too low by quite a bit). Also here is a spreadsheet which compares all bk2 files of this game that are out there room-by-room (this WIP here is in the "room 5" column and it is the fastest in all rooms unless there's an explanation as to why that's not possible, e.g., different route, RNG).
Anyway while it's hard to give an estimate for a potential final time, sub 20 is already guaranteed with this; the question is just how far below that I'll end up. Sub 19? Sub 18? We'll see. I'll revisit this skip on the second run-through and maybe find an even better configuration, but for now I'll move on to level 3 to finally get an idea of the weapon & ammo route etc.
It could be that the game tracks which boxes have been broken, because the actual box is an entity that gets unloaded when it's not on screen, and the game doesn't want to make boxes magically re-appear when you look at them again?
It could be that the game tracks which boxes have been broken, because the actual box is an entity that gets unloaded when it's not on screen, and the game doesn't want to make boxes magically re-appear when you look at them again?
Not a bad theory! Unfortunately, nothing changed when I set these particular 1s back to 0 right after shooting the box, even after exiting and re-entering the room. Another reason this can't be what's going on is that. sometimes, two boxes in a room set the same byte in that memory region to 1, while other (rare) times no byte in that region gets changed.
Yeah that's what I'm landing on as well, either unimplemented feature or bug are the most likely options given what we currently know. Which is bad because it turns optimizing box manips into quite extensive LUA script searches, but what can you do...
Level 3 out of 12: the first time bullet time manip becomes important. For example, the second enemy in the level I bullet time for a few frames so I shoot him when he's right in front of me so I don't have to walk as far to get the shotgun. Also happens at 0:54 video time so I don't get shot but I save the ammo (as well as 1 frame).
Another thing that becomes relevant from here on out are enemies who are invincible until a certain animation has played out, e.g., the mini boss at 1:15 video time, which only can be shot once he walked back for ~2 seconds. That's also why I was waiting there for a few frames, although it may be possible to run in right away and survive by precise movement manip; but that's for the second TAS round (as I said earlier I need something to compare and optimize against to get the best possible final result)
The final thing to note is that the ammo route works perfectly so I underflow the ammo on the very last enemy, meaning I start the next level with 255 shotgun shells so I don't have to worry about ammo anymore (until level 6 where I lose all my weapons for story reasons).
Link to video and bk2 file
All in all, this is roughly 20s (!) faster than my gold here; as usual, feedback and ideas are welcome :)
Edit: I also started collecting all enemy data in a spreadsheet which should make weapon routing much easier (for weapon info such as damage, shot frequency, etc. see the previously mentioned notes doc)
Link to video and bk2 file
In a bout of hyperfixation I got a first version of levels 4, 5, and 6 done since my last post. Obviously there will be timesave here and there, but as I said previously I need to get an idea of the weapon/ammo/painkiller route before it makes sense to really lock in levels. Also in the process I got some ideas of how LUA scripting can make optimizing some rooms less tedious; for rooms where you just go straight from door to door that's easy to do by hand, but the more turns happen the more possibilities have to be checked. And that's not even factoring in enemy manipulation: basically every room with enemies is a trade-off between lag (the more enemies, the slower the game), ammo, and time lost by shooting. What I mean by this is that it very much depends on the room whether it's faster to let all enemies live and just walk by, or whether killing 1, 2, ..., all enemies is faster (because the time lost to kill enemies is gained back by less lag). Take the room at 2:09 video time as an example for this: the route looks vastly suboptimal but if I turned towards the exit any earlier, then the enemy is on-screen earlier, thus causing more lag than I'd save by walking less (hence losing time overall); and killing the guy would loses 7 frames. Another example is at 4:00 video time where the fastest route I found was to kill two enemies but leave the third one behind. In any case, these large-search-space problems and trade-off questions where I except scripting to be immensely helpful the second time through.
For the levels themselves: while I don't think there's a point in going into too much detail at such a preliminary stage (cf. column Q in this spreadsheet for more), one thing worth pointing out is that---in addition to the previously employed method of dodging enemy shots via just a few frames of bullet time (happens again at 0:41 or 2:46 video time)---another manip of this kind is to shoot at specific times (e.g., at 3:00 video time). This may look pointless at first, but this short stutter in movement makes the enemy miss his shots so Max can leave the room alive. In that particular room I found these shots to be faster and more efficient than bullet time manipulation, and I didn't find a path where I'd survive by just walking.
Next, the game is throwing out some bigger boss fights: at the end of level 5 and at the end of level 6. Both will be entirely non-trivial to optimize properly because of the interplay between shooting, movement, bullet time, movement during bullet time, etc.. I'm not entirely sure yet whether there'll be a way around optimizing these by hand. I have considered using BasicBot for these things, but the problem is that shooting only happens if A is pressed at least 2 consecutive frames (sometimes even more) so this bot will just not find any solutions, or if it does then these solutions don't even come close to what I can find by hand. Indeed, I tried using the bot on the way simpler task of optimizing a door entrance (which is easy by hand) and already there the aforementioned problem occurred all the time.
Finally, level 6 (3:26 video time) is where animation cancelling (the basis of attack storage) becomes useful because I can stop the baseball bat attack as soon as the enemy is dead. What is more, in RTA runs we use the M9 from the first enemy as an intermediate between the baseball bat and the M4 we get later; however, in TAS I can manipulate the third enemy in the level (3:35 video time) to stay in my way so I can kill them with the baseball bat and get the M4 at the same time, to which I immediately switch then.
Altogether, in terms of timesave over RTA, what you see in the video is 17s than my level 4 gold, 10s faster than my level 5 gold, and 10s faster than my level 6 gold. What this means is that if I just matched my golds from here on out, then the final time would be 18:45; so realistically we're looking at a 17:xx final (leaderboard) time I'd say
Link to video and bk2 file
Back with another WIP! This update comes a bit later than I had originally planned because I went back and catalogued every enemy encountered in the run in this spreadsheet. Combining this with all the damage and cooldown times of all weapons this complete documentation of how long killing any enemy takes with any weapon will allow to make more informed decisions about the final weapon routes.
Now for the levels 7, 8, and 9 from the above video. The main new speedtech introduced in level 8 is mine boosting: when Max bullet time jumps into the laser mines, the explosion gives him a significant speed boost. This even allows to skip some rooms entirely, because we can exit them before the obstacle appears on which we'd have to wait. Other than that, what you can really see in this WIP is how much time "not planning ahead" can lose (which is okay because the point of this first pass is to gather all relevant information). Here is what I learned & what I will need to keep in mind for the final TAS:
I have to pick up one, or better two ingrams before level 6. This way I have a dual ingram for level 5 (which will save time there, and will also let me skip the ingram pickup in levels 7 and 8. The ingram in level 8 in particular loses 3s to get). Most importantly, this would let me underflow the ingram ammo in level 7 already. Compared to this WIP that will cut the shotgun ammo underflow at the end of level 7 (1:00 video time) which I only did because I didn't know yet when I'd get & best switch to the dual ingram
There is a triple mine boost in level 8 which skips a lot of waiting for fire cycles, but I need to enter that level with at least 4 painkillers. To be fair, I had 5 painkillers after level 6, but I used 2 of them on level 7 because I wasn't careful and wanted to progress quickly; hence I picked up a slow painkiller in level 8 (1:57 video time) which I will be able to cut in the future now that I know the exact requirements for the optimal version of this boost.
Other than that I don't think there's too much to say for the moment; the final thing I'll point out is, as usual, the timesave: already this unoptimized version saves 15s on my level 7 gold, 18s on my level 8 gold, and another 15s on my level 9 gold. As predicted in my last post, already this first pass is very much on track for a 17:xx (17:55 just by matching my golds from here on out). I'm curious to see how close the final TAS will get to 16:xx, although I currently doubt that it'll cross that line
Incredible investigation! Frankly speaking, I wouldn't bother going so deep into spreadsheeting all this, especially with enemies standing next to one another (hence, neglecting any time spent for extra movement).
I'm surprised that Sawed-Off gun deals more damage than Pump Action gun. And judging by how realistic the game tries to be, I though the mechanic of Ingram damage dealing would be more dynamic in terms or damage range. Sometimes I love when devs don't make things complicated
TASing is like making a film: only the best takes are shown in the final movie.
Incredible investigation! Frankly speaking, I wouldn't bother going so deep into spreadsheeting all this, especially with enemies standing next to one another (hence, neglecting any time spent for extra movement).
Thank you! I agree that parts of the spreadsheet are superfluous, and that some of the frame estimates of killing enemies are probably more of a ballpark number than frame-accurate (but it should still precise enough to draw conclusions from). However, I figured that if I'm doing this I may as well do it as thoroughly as possible -- you never know what kind of information will come in handy later
Link to video and bk2 file
Finished v1! For the last three levels (starting from 13:09 video time) I didn't think super hard about routes or optimizations—especially in level 11—and yet, the final time of this first iteration is 16:53.285 (RTA) / 17:23.173 (TAS timing). The main reason the 17-minute RTA-barrier is already broken is level 11: clean-cut fighting is what saves most time over RTA runs, which is why level 11 in the above video is 43 (!!) seconds faster than my best ever RTA level 11 (3:17, so the TAS is 22% faster). And that's not even the best that level can be: with a better ammo route I can probably save another few seconds there.
The next step is to theorycraft the optimal weapon/ammo route. I'll report back once that's done & I started working on the second (and hopefully final) iteration of this TAS :)
7:25 After you collect the baseball bat, would it be faster to switch to M9 and shoot the second guy? Or run past first two guys and just kill the one with M4?
Also, you may waste some Dual Ingram ammo in one of those lift section and do the ammo underflow trick to have enough ammo till the end of the game instead of switching to Sawed Off gun as the next best weapon
TASing is like making a film: only the best takes are shown in the final movie.
The last week I've been busy with routing and all kinds of testing & figuring out how the game works, so I'd like to report on what I consider to be the biggest recent development: I figured out level 2 door clip! As in: I figured out the inner workings of, as well as the purpose behind the function that sometimes mysteriously opens doors by shooting boxes. The insights from this aren't as useful as it may sound at first, but—if nothing else—the knowledge I gained here should speed up routing this clip into v2 of the TAS by a bit. Before I expand on this, however, let me first respond to the latest questions:
Dimon12321 wrote:
7:25 After you collect the baseball bat, would it be faster to switch to M9 and shoot the second guy? Or run past first two guys and just kill the one with M4?
About the elevator enemy in level 6, switching to the M9 is actually the RTA strat because it's basically impossible to kill moving enemies with a melee weapon. As for the timing: you need 4 M9 shots to kill him which takes 28 frames (compared to if that enemy didn't exist), in addition to the 21 frames the extra weapon switch loses. On the other hand, using the baseball bat + animation cancelling the long swing animation is ~30 frames—so using the bat should be around half a second faster than switching to M9 because you don't have to switch weapons. And running past them isn't feasible because the extra damage loses time later on. I'll of course test all options in v2 again, but at least this was my reasoning for sticking to the bat.
Dimon12321 wrote:
Also, you may waste some Dual Ingram ammo in one of those lift section and do the ammo underflow trick to have enough ammo till the end of the game instead of switching to Sawed Off gun as the next best weapon
I've also considered that, and it's certainly a good idea to underflow Ingram ammo most efficiently. There is a chance I won't even have to do that though because it looks like I can make the route such that the Ingram ammo runs out more or less naturally at the end of all levels where I have to underflow. But that I can only say with certainty once the route is final. In any case: good call, and of course thank you for all the points you raised, it's much appreciated :)
Now onto the big news:
Everything about level 2 door clip
Everything written below I have figured out by debugging and by spending way too much time staring at assembly instructions, register values, and trace logs. The upshot is that there are actually two functions being executed right after one another: The first one is the "faulty" one that sets the door byte to 1 sometimes, and the second function is the one that actually & correctly logs that the just-shot crate is broken, by setting the correct byte in RAM to 1. And it's looking highly likely that the first function is an earlier implementation of this "keep track of which boxes have been destroyed already" that the devs forgot to remove. (Of course, I'll give more detail on all of that & how I got to those conclusions below)
Function 2: "the intended one"
When I first looked at the instructions that sometimes flip the door byte I noticed that the function immediately after it looks very similar to these "glitched" instructions. It turns out that this second function does in fact log that the box you just shot is now permanently broken; its instructions in plain English are:
"Go to the memory region where info about events in the level, e.g., crates, cutscenes, etc. is stored (=*(02005120)), add the box ID (*("r6" + 0x72)) and set that byte to 1 to store the information that this box has been broken"
The exact code here reads:
Function 2: Set *(**(0x08026C40) + *(0x08026C48)) + *("r6" + 0x72)(1-byte) to 1, where * means dereferencing the address
To connect this code to the instructions I wrote out: the part inside the first star [i.e., **(0x08026C40) + *(0x08026C48)] for level 2 equals 0x02005120. This 4-byte address is part of a static block in RAM which stores a lot of vital info, such as the room number, whether bullet time is active, etc. The specific address 0x02005120 is a pointer to a different block in RAM where information about "events" in the level are stored, such as information about what cutscenes/dialogues have played already, or what boxes have been broken. So *(**(0x08026C40) + *(0x08026C48)) = *(02005120) says "go to the block in RAM where the box infos are stored" and the *("r6" + 0x72) offset says "within that block we're now in, go to the byte that corresponds to the box that was just shot and set it to 1". I was able to confirm this by setting such a byte to 1 manually before entering a room for the first time, and the box in there was in fact broken already.
You may have noticed that I have ignored what exactly r6 is here, and I will delay it until that information becomes relevant.
Function 1: "oops forgot to delete this"
It turns out that understanding how the box-tracking function actually works adds a lot of context to the first block of instructions (the one we actually want to understand), because with that context it's clear that this is an earlier attempt at this feature that no longer matches the data/RAM structure of the game the devs eventually decided on—with interesting consequences. Unlike with function 2 let me first post the instructions and then explain:
Function 1: Set address **(0x08026C40) + *(0x08026C44) + 2 + *("r6" + 0x74)(1-byte) to 1
If we compare this to the previous code, we see a lot of the same snippets: **(0x08026C40) is still there, then *(0x08026C44) is almost the previous *(0x08026C48), and the box ID *("r6" + 0x72) from earlier is now *("r6" + 0x74). Indeed, **(0x08026C40) is still the start of the static block with vital information, and the rest just adds some offset within that block; notably, this time around they don't grab the pointer in 0x02005120 and go to the other memory region, but instead this function writes to the memory block with vital information directly. Indeed, if we dereference this for level 2, the instruction simplifies to "Set address 0x0200512E + *("r6" + 0x74) to 1". I assume that *("r6" + 0x74) was the box ID at some earlier point in development (again, more on this r6 thing in a bit) and they first intended to store event & box information in this static block with vital information directly, but for some reason outsourced that into another memory region later on. This theory is further substantiated by the fact that there still is a 256-byte region shortly after 0x02005120 where the values are always 0, that is, this is probably the region they intended to track boxes and maybe other events, and it is still initialized that way:
Why does the door clip work?[Disclaimer: What I write from here on out is only about level 2; I haven't yet tested what all these pointers dereference to in other levels and whether the following is true everywhere]
If function 1 would only write to that memory region outlined in blue then nothing of note would ever happen. However, the region that function 1 can write to does not fully overlap with this all-zero region. Either due to an oversight or due to a shift of data in that memory region at some point in development, the 6 bytes before the blue region can also be set to 1 by our glitched function. And the second of these six is the byte that controls whether the door in the fourth room in level 2 is open:
When does the door clip work? Or: "register 6"
With this structure of the RAM in mind, let's go back to what the function does: "Set address 0x0200512E + *("r6" + 0x74) to 1". Because our door-byte is 0x0200512F, the door clip works if and only if the 1-byte value at *("r6" + 0x74) is 1. Here, r6 is short for register 6, one of the 16 registers of the GBA's CPU, and I've already said that in practice, r6 is just a pointer to the per-box struct for the box you just shot. But in order to really understand & have a chance at manipulating this glitch I had to figure out two things:
How is r6 built before the instructions of function 1
If *("r6" + 0x72) is the actual box ID, then what is *("r6" + 0x74)?
If I want to have even a chance of manipulating this magical value at address "r6" + 0x74, these two questions are crucial to answer. For question 1, after a while I indeed managed to figure out a stable way of building the address in register 6:
r6 = *(*(0x0200C904) + box_offset)
Here, 0x0200C904 is part of a block in RAM with information specific to the level, such as Max's coordinates & speed, what weapons and ammo you have, etc. (strictly speaking, 0x0200C904 is not stable but changes with each level and should probably be replaced with another pointer that is actually stable throughout the whole game). Now for level 2, the specific address 0x0200C904 is a pointer to the region where information about objects and actors (like boxes) are stored. More precisely, that region doesn't store box information directly, but it rather stores yet another pointer to where the box information actually is, and that pointer is of course different for each box (this is what the box_offset is for; as an example, for level 2, room 4 the box_offsets are 0x24 and 0x28).
With question 1 settled we come to question 2: what is this value in "r6" + 0x74 that's taken as an offset in function 1? For better or worse the answer is: junk data. When the box info region starting at r6 is initialized upon entering a room, for example r6+0x72 gets initialized at the box ID, but r6+0x74 is left untouched—so whatever was stored in that byte before the region is filled with new information is also what the byte will be like afterwards. This is another reason why the door clip works: if they had properly initialized r6+0x74 to be, say, 0, then this glitch would never happen because then function 1 would always target 0x0200512E/could never target the door-byte 0x0200512F. But because r6+0x74 is just a "random" byte, (if not manipulated) there's essentially a 1/256 chance that the door unlocks upon shooting a crate in that room. This 1/256 chance also explains why this glitch was so difficult to replicate (on top of the fact that when this was first found we didn't know that shooting boxes was what opened the door).
Does all this info help?
Yes and no. Yes, knowing all of this will make routing this skip into v2 easier because I now know whether it will work already when I enter the room. Previously my script had to actually shoot the boxes and look at the door-byte, but this time around I can just look up the byte in r6+0x74 for both boxes in the room, and if either of them is 1 I know that the skip will work. So if nothing else this will save some time when executing my script. Also I turned this into a Lua script which displays information about the boxes and whether *(r6+0x74)=1 on-screen:
The reason this isn't as useful as it seems at first (at least as of now) is that r6 (i.e., the memory region where the box info is stored upon entering the room) jumps around wildly, even when shifting the room entry by a single frame. So before I actually enter the room with the locked door it's basically impossible to tell where in memory r6+0x74 will be; hence I currently don't see a way how a targeted manipulation could ever work.
Questions & to do'sACE potential?
This is the big one; after all we're dealing with a function that changes bytes that weren't meant to be changed in that way. My intuition here, however, is that I don't think this will lead to ACE; at least for level 2 the bytes you can write to with this function that have an actual effect are very limited & they are only ever in the same place in RAM, and I think that the base (**(0x08026C40) + *(0x08026C44) + 2) for where function 1 writes is stable so you only ever have 6 bytes to target? But this is my first to do: test whether this is actually the case & see where this function writes/what this memory region looks like for other levels. Or maybe this formula I found is only valid for level 2, and in other parts of the game it writes to entirely different regions in memory? Only one way to find out.
Other crates?
If the box in level 2, room 4 can open doors, what can boxes in other rooms do? Again, the range of bytes targeted by function 1 is limited, but when I first made the connection between boxes and the locked door I already looked at all other crates in the game to see 1. which ones are closed to other locked doors, and 2. what bytes get flipped when shooting them at different times/under different circumstances. While I didn't find anything useful back then, now that I understand function 1 in all detail I could go back and make list of crates & all the addresses that can be accessed by each crate. I'll put this on the back burner for now because this seems like a lot of work for something that will likely lead nowhere, and I'd like to make some actual progress on the TAS.
tl;dr:
The glitch is caused by two back-to-back routines that run when you shoot a crate: a leftover, now buggy one (which they forgot to remove) writes into the global state block using the uninitialized byte at r6+0x74, which sometimes lands on the door flag (0x0200512F), while the next routine correctly marks the crate as broken in the event table. The clip triggers only when (r6+0x74) == 1 for a box in that room—so you can predict it by checking that byte (even though the box struct’s address moves between loads).
edit: because of all the pointers I found I was also able to write a Lua script that reliably displays coordinates/speed/HP/... on screen for all levels. Looks like this: