ruffle-rs / ruffle

A Flash Player emulator written in Rust
https://ruffle.rs
Other
15.66k stars 813 forks source link

Clicking on stacked buttons in Super Mario 63 #12335

Open Vexxter9 opened 1 year ago

Vexxter9 commented 1 year ago

Describe the bug

In Super Mario 63, there is a glitch known as Level Designer Preserve (LDP for short). With this glitch you can bring the menu used for the Level Designer into other parts of the game. What you can also do in this state, is something called LDP cloning. Using some complicated movement and tricks, you can spawn another Level Designer menu on top of the previous one.

This has been known for a while now, but has only recently been found to actually be useful for a new TAS only any% route that me and 3 other people have come up with.

While this glitch does somehow work the same way it does in Flash Player, there is one small issue: When you click on one of these menus, it clicks on all of them at the same time. This is a problem, because the only thing that makes this route useful is that you can store multiple of these menus simultaneously and use them to skip rooms back to back.

Because the setup required to do this glitch is very convoluted and difficult, you can instead use this custom version of the game that can achieve this LDP state by pressing "/", then typing "ldp" and pressing Enter. This will spawn a Level Designer menu in the top left of the screen. https://cdn.discordapp.com/attachments/860251444774305792/1133514294055686296/MushROM.swf

To be more specific, the code that the game runs after entering this command is: _root.attachMovie("LDMenu","M",_root.getNextHighestDepth());

Expected behavior

What should happen, is that after activating the LDP command multiple times, you can still click on File -> Test course multiple times instead of clicking on all of them at the same time.

Affected platform

Desktop app

Operating system

Linux

Browser

No response

Additional information

While this is a very small change that will probably only affect this game, it would be very greatly appreciated since this is basically our only way of TASing this route and it would be sad to see such a huge timesave just unable to be achieved.

GTcreyon commented 9 months ago

Made some headway. The specific issue is that only the lowest-depth (first-spawned?) button is clickable. Ones above it do not work. This applies to hovering, too, with only the lowest layer processing properly. You can see the hover effect peek out from underneath when hovering over Help, due to the slightly extended bevel caused by the P's descending stroke: image

It does seem to be an issue with hitTest. Replacing the three relevant occurrences of this.hitTest(_root._xmouse,_root._ymouse,false) with true in FFDec results in behavior on Flash that is equivalent to behavior on Ruffle. However, I still don't understand why it behaves this way on Flash. Perhaps a bug?

It's possible that it's related to the fact that these layers all have the same instance name. I've read a few threads online that make note of that.

https://stackoverflow.com/questions/42610148/flash-as2-hittest-not-working-properly

"I tried giving every piece of the wall the same instance name so that whenever the character collided with that instance, the character would stop. But unfortunatelly it only detects collision on the last created object :(" http://www.trainingtutorials101.com/2011/02/simple-as3-collision-detection-using.html

GTcreyon commented 9 months ago

Did some digging with @Zeekza. The issue seems to be related to the fact that the level designer layers are not named uniquely (although not quite in the way I had envisioned when researching in those threads). Giving them unique names when adding them via the /ldp command (naming them getTimer() instead of M) made Flash behave the same as Ruffle, with all layers being triggered at once.

Moreover, calling getRect() on the button elements (character 7682) of each Level Designer layer (character 9079) shows that only the button in the first-spawned (bottom) layer properly returns a bounding box - the others return undefined. Once the first one is removed by clicking through to Test Course, the next one in the queue will gain a proper bounding box, etc. This behavior does not occur on Ruffle; all buttons have a valid bounding box.

This lack of bounding box seems to be causing the hitTest to return false on those upper layers, since hitTest relies on a bounding box for its checks.

Our running hypothesis is that because we're creating multiple Level Designer layers with the same name, "M", and AS2 treats children as properties, they're all being assigned to _root.M. This means that they all share a reference. Something about this shared reference is causing the children of M (the level designer layer(s)) to have an invalid bounding box. The way I imagine this is that:

I am reasonably confident that this is undefined behavior in Flash, rather than a bug in Ruffle, but replicating that behavior would resolve this issue.