In order for this proof of concept movie to play back correctly, you must use the following hacks on the following frames:
1. On frame 3858, set $21 equal to $10, and then freeze its value.
2. On frame 3879, set $428 equal to $55, and then freeze its value.
3. On frame 3882, set $B7 equal to $1F, and set $B8 equal to $04. Then, freeze both values.
4. On Frame 3883, set $3FE equal to $69, and then freeze its value.
5. On Frame 3886, unfreeze all memory addresses.
The only hacks which currently simulate things that can't be done without hacks are freezing the value of $21 and setting the value of $B7 and $B8. The other hacks are just done for convenience to save me time TASing this, although with more manipulation, those values could have been set correctly without hacks.
The way this works is, the config pointer increases to $21. Since it's non-zero, it loads an object. The object's low X coordinate is equal to the value in $27. Conveniently, $27 stores RNG, which changes every time you press a different button, meaning we can directly control its values!
When the config pointer hits $2C, we hold down $FF to reset it back to 0. when the config pointer reaches $0B, it spawns an object with a low x-position equal to the value stored in $10, which is how many lives you have (which I set to 0 by killing myself a few times before this glitch). As such, every high coordinate byte is automatically set to 0, and we can initialize every low coordinate byte with the values we want. Also note that by holding inputs when the config pointer passes $16, you can spawn an extra object, if you want to make more space between the next slot.
When the config pointer is on $0B for the last time and spawns another object, the ID value of $0B is 14, which is conveniently an invalid ID value, causing the object to be deleted. This allows us to set the animation_2 attribute of the new object to the value we want based on RNG, which we then set to $55.
From there, we need the config pointer to jump to $41F to interpret this data as a new object. This is done with hacks here, although in actual gameplay, when the config pointer equals $16, if you hold down all buttons on player 2's controller, then the config pointer jumps to the address referenced by $17/$18. $17 is roughly based on player 2's x-coordinate, and $18 is roughly based on Player 2's animation. Since player 2 has to keep holding down every button and punching in order to keep resetting the config pointer to $0B, the only way to move him is for player 1 to pick him up and throw him.
After this, I hacked my x-coordinate to match where the portal spawns. This could have been avoided if I set the x-position of the portal to $69 instead of $40. However, redoing this would have altered the RNG and made me have to redo everything, so I just used hacks to change my low x-coordinate to save time.
After this, we hit the portal, and jump to level 5!
To summarize what's left to do to make this possible in a real TAS, we need to find a way to keep player 1 from dying after respawning from the jet glitch so he can pick up player 2, and we need a way to consistently get the value stored in $21 to be a number greater than 2. $21 is sometimes set to random values like $16, but most of the time its 0, and I haven't yet found a way to manipulate it. However, objects can't be spawned when $21 is 0, so a way around this must be found in order to make use of this setup.