FF6 Hacking
The Fly-off bug, and its fix - Printable Version

+- FF6 Hacking (https://www.ff6hacking.com/forums)
+-- Forum: Discussion Forums (https://www.ff6hacking.com/forums/forum-5.html)
+--- Forum: Magitek Research Facility (https://www.ff6hacking.com/forums/forum-9.html)
+--- Thread: The Fly-off bug, and its fix (/thread-4266.html)

The Fly-off bug, and its fix - C-Dude - 01-17-2023

[All credit to Osteoclave for the thorough investigation of this bug]

There's an event bug in Final Fantasy 6 that pollutes character acceleration.  When encountered, the bug sets a RAM timebomb which will lead to a soft-lock in certain situations.  You may have seen videos of this in the randomizer communities: here's an example (watch the upper right screen)


To summarize, if you hold a moveable direction as you exit character selection, in a party formation that is out of ID order (for instance, Locke before Terra) and then you later switch lead party members, their acceleration gets borked and they will fly off-screen (only on certain maps).

This bug occurs because the subroutine that initiates the party selection allows you to move before the event has finished completing.  Normally this would be fine, but when the party order is changed there's an overlap frame where the Actor in party slot 1 is the lowest ID member in the party.  As such, allowing the player to move when the party order is unstable is a big no-no.

Thankfully, the fix is a single byte adjustment in the event script:
CACBAD: {3A}                  Enable player to move while event commands execute
Changing this to {FD}, the wait-a-frame event command (event script equivalent of NOP) fixes the issue completely, as it prohibits allowing the player to move until all the event code has run its course AND it ensures that the game waits a tic so it can process the new party leader.  It's amazing when a single byte can make all the difference.

Osteoclave (of Worlds Collide acclaim) both discovered, explained, and fixed the bug in Discord.  This thread is for posterity, so that this information does not get lost along the way.
Alright, time for an update on the Phunbaba hang / party-member-flies-offscreen bug
The first thing I noticed from the .oops file was that Terra's X and Y coordinates were way out of whack
Expected: (0x00B000, 0x018000) Actual: (0x1B7E20, 0x159A18) and rising rapidly
The second thing was that Terra had nonzero horizontal and vertical movement values, as also seen in the party-member-flies-offscreen bug
H/V values: (0x4D0, 0x39C)
The third was Terra's object script pointer: CC/4C77, which is the wait-until-complete action queue that makes the party leader kneel before the screen fades back in
So my earlier guess about there being a problem inside CA/5EA9 somewhere was incorrect
A difference between the Phunbaba case and the other flies-offscreen cases: the Phunbaba case doesn't look like it will stop after a while
In the other cases, you start aligned with the tile grid (X/Y coordinates divisible by 4096 = 0x1000), and when you become realigned with the tile grid, the H/V values become zero and the flight stops
But in the Phunbaba case, it looks like you start misaligned
0x4D0 and 0x39C are both divisible by 4, so realignment should occur after at most 1024 frames (~17 seconds), but we can see in Lenophis' clip that this does not happen
Curiously, if you manually set the H/V values to (0x1, 0x1) with memory edits before fighting Phunbaba, the hang does not occur
But if you set them to (0x4D0, 0x39C) the same way, the hang happens reliably
The practical upshot here is that the Phunbaba hang is another instance of the party-member-flies-offscreen bug, though it behaves a little differently (which confused me for a while)
So, moving on. Those H/V values: (0x4D0, 0x39C) - how do they get written?
Turns out, the same way H/V values are normally written: the C0/7E77 function
C0/7E77 is called with Y = 0x29*n, to select an entry in the $0867 object table
The function reads $0875,Y (object speed) and $087E,Y (moving direction), and uses those to calculate and set the H value ($0871,Y) and V value ($0873,Y)
Normally "moving direction" is between 0x01-0x10; subtract one, shift left (= multiply by 2), use as an index into tables at C07ED4, C07EF4, C07F14, C07F34
But in this case, it seems "moving direction" was 0x00
Subtract one (0xFF), shift left (0xFE), use as index... and we're reading off the end of the table
C07ED4 + 00FE = C07FD2, which contains 0x00 (X delta)
Xdelta * 2^objectspeed = 0 here, independent of objectspeed
(Tedious explanation detected. Summarizing...)
If "moving direction" is 0x00, the reads go off the end of the tables and you get H = 0x4D0 (independent of object speed) and V = a value dependent on object speed
Now to the fun bit: how to make a write of these bad H/V values happen
- Get on the WoB airship
- Talk to the party-changer NPC
- Choose a party. The only restriction is that the party member that is earliest in the game's internal character order cannot be the leader. (Corollary: the party must have at least two members.) I used Locke/Terra in that order for most of my testing. Do not exit the party selection screen yet.
- Press and hold a direction on the D-Pad. The direction must be one that you could move toward from where you were standing before entering the party selection screen. So if you're standing to the left of the party-changer NPC, you could hold Up, Left, or Down. Holding Right would not work because the party-changer NPC itself blocks movement that way.
- While still holding the direction, exit the party selection screen
- When the screen starts to fade in, you can release the direction. The bad H/V values are now present on the party member that's earliest in the internal character order.
This setup also works if you access the party-selection screen through an available character aboard the airship, though you have to be more mindful of the direction you choose to press
You can test that the setup worked by going up to the deck of the airship, opening the X-button menu, and moving the character that's earliest in the internal character order into the leader position. Upon exiting the X-button menu, you will fly offscreen. (WARNING: This causes a softlock!)
(This does not work in every room - if you try it inside the airship, the H/V values get zeroed and everything is fine. Don't know why it works differently on different maps.)
This setup does not work on the WoR airship
However, it DOES work in vanilla FF6 (!)
So now we know how to make it happen. Let's prevent it from happening.
Since the setup doesn't work for the WoR airship, let's borrow some event scripting from that case...
CA/F5B2: Replace with six [FD] (no-op)
CA/F5B8: Call subroutine $CACB9F, no change from vanilla / existing FF6WC
CA/F5BC: Replace with [3B] and five [FD] (no-op)
CA/F5C2: Replace with four [FD] (no-op)
This clobbers the event flag check for the "mandatory Locke/Celes" case, but I think that's inaccessible in FF6WC anyway so it's probably fine
With those changes, the setup no longer works and hopefully the Phunbaba hang / party-member-flies-offscreen bug can no longer happen
Additional stuff: I tried the setup when recruiting characters from the Whelk check and Celes' prison under South Figaro, but it didn't write the bad H/V values in either case, so party selection from character recruitment is probably fine as-is
[*]Also, a cosmetic fix that will mitigate the party-member-flies-offscreen effect if it's possible to write bad H/V values in some other way...
The "Daryl's Tomb entrance" and "Locked Narshe door" scripts both call CA/C6AC, which makes all the party members visible and turns the party member that's earliest in the internal character order into the leader for the purposes of the event
If you replace the call to CA/C6AC with a call to CA/C766 (the subroutine that CA/C6AC calls to make all of the party members visible), it will retain the existing party order
So the current party leader will be the one unlocking doors
Since the leader doesn't change, and the leader's movement queue is the only one of the four that's wait-until-complete, this prevents the game from hanging
If bad H/V values are present, one of the other characters that just appeared might fly offscreen, but the game can continue
The $CAC6AC calls that should be updated to $CAC766:

CA/3F98: B2    Call subroutine $CAC6AC <-- Daryl's Tomb entrance
CC/0A22: B2    Call subroutine $CAC6AC <-- Locked Narshe doors
This concludes tonight's wall of text. [bows]
Did some poking around, and I've found a smaller set of changes to fix the "party member flies offscreen" bug:
CA/CBAD: 3A    Replace with [FD] (no-op, forces a one-frame wait)
(That's the entire fix) Advantages: - Literally a one-byte fix - Can be used to fix the bug in vanilla FF6, since it doesn't clobber the conditional branching for the Locke-and-Celes-mandatory case - Makes calling CA/CB9F a safe operation (so the bug won't recur if new code calls CA/CB9F and then returns control to the player without waiting a frame in between)