Fix jump height bug when exiting water
From Sonic Retro
(Original guide by MoDule)
There is a bug in Sonic's water entry and exiting routines that is present in every main series 16-bit Sonic game. When Sonic is just above the point at which he would be considered submerged and jumps his jump height will be very low. This guide will show a possible way to fix this bug and explain its cause.
Fixing the bug
Locate the Obj01_InWater routine. It should look like this:
; loc_1A18E: Obj01_InWater: move.w (Water_Level_1).w,d0 cmp.w y_pos(a0),d0 ; is Sonic above the water? bge.s Obj01_OutWater ; if yes, branch ; <-- bset #6,status(a0) ; set underwater flag bne.s return_1A18C ; if already underwater, branch
At the indicated line, add this:
tst.w y_vel(a0) ; check if player is moving upward (i.e. from jumping) bmi.s return_1A18C ; if yes, skip routine
Why this fixes the bug will be explained further down.
Things to keep in mind
This is a general player object bug, so it will need to be fixed for Tails and Knuckles, too, if they are present.
The bug explained
This bug is the result of the in- and out water routines interacting in a way that isn't immediately obvious. On their own, they wouldn't cause this bug at all, since their conditions are mutually exclusive. The actual trigger lies elsewhere, in Sonic's jumping routine:
; loc_1AA38: Sonic_Jump: move.b (Ctrl_1_Press_Logical).w,d0 andi.b #button_B_mask|button_C_mask|button_A_mask,d0 ; is A, B or C pressed? beq.w return_1AAE6 ; if not, return [...] move.b #$E,y_radius(a0) move.b #7,x_radius(a0) move.b #2,anim(a0) ; use "jumping" animation bset #2,status(a0) addq.w #5,y_pos(a0) ; <-- return_1AAE6: rts
Right at the very end, before the routine returns, Sonic's position is increased slightly, moving him down. Now it's important to know, which order these routines run in. First, Sonic's movement code is run, where it checks if the player pushed a jump button. This is when Sonic is put in a jumping state, given some upward velocity and moved slightly down. Some time after that Sonic's water checks are run. If he was already very close to the water surface he might have been pushed under water by jumping. Now observe the code at Obj01_InWater:
; loc_1A18E: Obj01_InWater: move.w (Water_Level_1).w,d0 cmp.w y_pos(a0),d0 ; is Sonic above the water? bge.s Obj01_OutWater ; if yes, branch [...] asr.w x_vel(a0) asr.w y_vel(a0) ; <-- memory operands can only be shifted one bit at a time asr.w y_vel(a0) [...]
Since Sonic is under water this code is run. The game simulates the water's surface tension by halving Sonic's horizontal velocity and quartering his vertical velocity. Normally, this would just cause the water surface to break Sonic's fall, but since he's actually jumping, it reduces his jump strength considerably. During the next few frames Sonic slowly moves upward until he hits the water surface again. Now this code is run:
; loc_1A1FE: Obj01_OutWater: bclr #6,status(a0) ; unset underwater flag beq.s return_1A18C ; if already above water, branch [...] asl y_vel(a0) ; <-- [...]
The significant part is where Sonic's vertical speed is doubled. This simulates Sonic no longer being subject to the water's resistance. Keeping in mind that previously Sonic's speed was quartered and now only doubled; that leaves Sonic with only half of his intended jump height, at most. By only allowing the speed decrease when Sonic is not moving up, this bug is fixed.