Actions

SCHG How-to

Difference between revisions of "Fix screen boundary spindash bug"

From Sonic Retro

m (The bug explained)
(Fixed bug description and fix)
Line 4: Line 4:
  
 
==Fixing the bug==
 
==Fixing the bug==
Locate the '''Sonic_CheckSpindash''' routine. In this routine, locate the two pairs of lines that call the '''Sonic_LevelBound''' and '''AnglePos''' routines. The first pair is just before the '''return_1AC8C''' label, while the second pair is just after the '''loc_1AD8C''' label. In both cases, you should delete or comment out the following lines:
+
Locate the '''word_1AD0C''' label. In this routine, locate the following lines:
<asm> bsr.w Sonic_LevelBound
+
<asm> bra.s Obj01_Spindash_ResetScr
bsr.w AnglePos
+
; ===========================================================================
 +
; word_1AD0C:
 +
</asm>
 +
And add code just before it so that it looks like this:
 +
<asm> move.b angle(a0),d0
 +
jsr (CalcSine).l
 +
muls.w inertia(a0),d1
 +
asr.l #8,d1
 +
move.w d1,x_vel(a0)
 +
muls.w inertia(a0),d0
 +
asr.l #8,d0
 +
move.w d0,y_vel(a0)
 +
 
 +
bra.s Obj01_Spindash_ResetScr
 +
; ===========================================================================
 +
; word_1AD0C:
 
</asm>
 
</asm>
After you have done that, the bug will be fixed.
+
After you have done that, the bug will be fixed. As a bonus, this will also fix a related bug that causes Sonic to jump straight up (losing all speed that would be gained from the spindash) if you jump one frame after releasing the spindash.
  
 
==Things to keep in mind==
 
==Things to keep in mind==
Line 28: Line 43:
 
move.w d0,y_vel(a0) ; <= (1) this line
 
move.w d0,y_vel(a0) ; <= (1) this line
 
</asm>
 
</asm>
This block sets Sonic's X and Y velocities accordingly to the ground angle and his inertia. '''Obj01_UpdateSpeedOnGround''' is called by the '''Sonic_Move''' routine; this information will become important shortly.
+
This block sets Sonic's X and Y velocities accordingly to the ground angle and his inertia. '''Obj01_UpdateSpeedOnGround''' is called by the '''Sonic_Move''' routine; this information will become important shortly. If you paid attention, you will notice that this is the same block of code I used above to fix the bug.
  
 
The bug then begins its journey at the '''Sonic_UpdateSpindash''' routine:
 
The bug then begins its journey at the '''Sonic_UpdateSpindash''' routine:
Line 102: Line 117:
 
rts
 
rts
 
</asm>
 
</asm>
This is called when Sonic is on the ground and not rolling. It is the only routine during which you can charge or release a spindash. In fact, the first thing it does is check if the spindash should be/is being charged or is being released. In all of these cases, the '''Sonic_CheckSpindash''' routine eventually calls the '''Sonic_LevelBound''' and '''AnglePos''' routines. In any of these cases, doing this is not only redundant (as '''Obj01_MdNormal''' will call them anyway), but also harmful in the case of '''Sonic_LevelBound''': if you are at the edge of the screen when you unleash the spindash, you will get to '''Sonic_LevelBound''' ''before'' you get to '''Sonic_Move'''; hence, your X speed will never be set from your inertia (lines marked (1) above), and in '''Sonic_LevelBound''', the lines marked (3) will kill your speed and inertia because you are at the screen boundary.
+
This is called when Sonic is on the ground and not rolling. It is the only routine during which you can charge or release a spindash. In fact, the first thing it does is check if the spindash should be/is being charged or is being released. Moreover, there is code inside '''Sonic_CheckSpindash''' that will cause it to '''not''' to return to this function (thus ''skipping'' all following function calls) if a spindash is being charged or released.
 +
 
 +
In any case, if you are charging or releasing a spindash, the '''Sonic_CheckSpindash''' routine eventually calls the '''Sonic_LevelBound''' and '''AnglePos''' routines. These are necessary when charging the spindash because these functions would be skipped otherwise, but the call to '''Sonic_LevelBound''' is harmful when ''releasing'' the spindash because Sonic's X speed will never be set from his inertia (which would normally happen in the lines marked (1)), as in '''Sonic_LevelBound''' the lines marked (3) will kill Sonic's speed and inertia if he are at the screen boundary. In this case, the important block of code from the call to '''Sonic_Move''' is the one I highlighted above, and the one I used to fix the bug. Note that actually calling '''Sonic_Move''' will cause a running frame to be displayed for 1 frame when releasing the spindash, among probably many other similar side-effects.
  
So the sequence is: spindash being unleashed sets your inertia (lines (2), above); '''Sonic_LevelBound''', called from '''Sonic_CheckSpindash''', kills speed and inertia if you are at the screen boundary (lines (3), above) before the horizontal speed is set from the new inertia (lines (1), above). The result is that you get stuck at the screen boundary.
+
So the sequence is: spindash being unleashed sets your inertia (lines (2), above); '''Sonic_LevelBound''', called from '''Sonic_CheckSpindash''', kills speed and inertia if you are at the screen boundary (lines (3), above) since the horizontal speed is never set from the new inertia (lines (1), above). The result is that you get stuck at the screen boundary.

Revision as of 15:48, 17 July 2011

(Original guide by Flamewing)

The spindash, as seen in Sonic 2, Sonic 3 and Sonic & Knuckles, comes with an annoying bug: if you spindash when you are at the boundaries of the screen, the spindash will be canceled and you will remain at the edge of the screen. This is easiest to see in Sky Chase Zone at the part where you encounter the Wing Fortress: if you stay at the right edge of the screen, you will be unable to spindash left unless you walk a bit left first.

Fixing the bug

Locate the word_1AD0C label. In this routine, locate the following lines: <asm> bra.s Obj01_Spindash_ResetScr

===========================================================================
word_1AD0C

</asm> And add code just before it so that it looks like this: <asm> move.b angle(a0),d0 jsr (CalcSine).l muls.w inertia(a0),d1 asr.l #8,d1 move.w d1,x_vel(a0) muls.w inertia(a0),d0 asr.l #8,d0 move.w d0,y_vel(a0)

bra.s Obj01_Spindash_ResetScr

===========================================================================
word_1AD0C

</asm> After you have done that, the bug will be fixed. As a bonus, this will also fix a related bug that causes Sonic to jump straight up (losing all speed that would be gained from the spindash) if you jump one frame after releasing the spindash.

Things to keep in mind

This bug will be present in any other player objects as well, that means it needs to be fixed for Tails and Knuckles if they exist.

The bug explained

The bug is quite involved in its inner workings. To understand it, you must understand that, while on ground, Sonic's speed is completely determined by his inertia, as can be seen on routine Obj01_UpdateSpeedOnGround, on label Obj01_Traction: <asm>Obj01_UpdateSpeedOnGround: [...]

loc_1A630

Obj01_Traction: move.b angle(a0),d0 jsr (CalcSine).l muls.w inertia(a0),d1 ; <= (1) this line asr.l #8,d1 move.w d1,x_vel(a0) ; <= (1) this line muls.w inertia(a0),d0 ; <= (1) this line asr.l #8,d0 move.w d0,y_vel(a0) ; <= (1) this line </asm> This block sets Sonic's X and Y velocities accordingly to the ground angle and his inertia. Obj01_UpdateSpeedOnGround is called by the Sonic_Move routine; this information will become important shortly. If you paid attention, you will notice that this is the same block of code I used above to fix the bug.

The bug then begins its journey at the Sonic_UpdateSpindash routine: <asm>; loc_1AC8E: Sonic_UpdateSpindash: [...] moveq #0,d0 move.b spindash_counter(a0),d0 add.w d0,d0 move.w SpindashSpeeds(pc,d0.w),inertia(a0) ; <= (2) this line tst.b (Super_Sonic_flag).w beq.s + move.w SpindashSpeedsSuper(pc,d0.w),inertia(a0) ; <= (2) this line + [...] btst #0,status(a0) beq.s + neg.w inertia(a0) ; <= (2) this line + bset #2,status(a0) move.b #0,(Sonic_Dust+anim).w move.w #SndID_SpindashRelease,d0 ; spindash zoom sound jsr (PlaySound).l bra.s Obj01_Spindash_ResetScr ; <= (2) this line </asm> The spindash code unleashes the spindash by setting Sonic's inertia, with the intent that Obj01_UpdateSpeedOnGround will correctly set the X and Y components of Sonic's velocity. At the end of the block, the jump to Obj01_Spindash_ResetScr will eventually reach label loc_1AD8C; this information is also going to become important shortly.

The third important part is in the Sonic_LevelBound routine: <asm>; loc_1A974: Sonic_LevelBound: move.l x_pos(a0),d1 move.w x_vel(a0),d0 ; <= (3) this line ext.l d0 ; <= (3) this line asl.l #8,d0 ; <= (3) this line add.l d0,d1 ; <= (3) this line swap d1 move.w (Camera_Min_X_pos).w,d0 addi.w #$10,d0 cmp.w d1,d0 ; has Sonic touched the left boundary? bhi.s Sonic_Boundary_Sides ; if yes, branch move.w (Camera_Max_X_pos).w,d0 addi.w #$128,d0 tst.b (Current_Boss_ID).w bne.s + addi.w #$40,d0 + cmp.w d1,d0 ; has Sonic touched the right boundary? bls.s Sonic_Boundary_Sides ; if yes, branch [...]

loc_1A9BA

Sonic_Boundary_Sides: move.w d0,x_pos(a0) move.w #0,2+x_pos(a0) ; subpixel x move.w #0,x_vel(a0) ; <= (3) this line move.w #0,inertia(a0) ; <= (3) this line bra.s Sonic_Boundary_CheckBottom </asm> In the marked lines, the X velocity is added to the X coordinate. This is later used to see if the screen boundary will be reached this frame and, if so, kill the horizontal speed.

Now go to the Obj01_MdNormal routine: <asm>Obj01_MdNormal: bsr.w Sonic_CheckSpindash ; <= (2) (3) this line bsr.w Sonic_Jump bsr.w Sonic_SlopeResist bsr.w Sonic_Move ; <= (1) this line bsr.w Sonic_Roll bsr.w Sonic_LevelBound jsr (ObjectMove).l bsr.w AnglePos bsr.w Sonic_SlopeRepel

return_1A2DE: rts </asm> This is called when Sonic is on the ground and not rolling. It is the only routine during which you can charge or release a spindash. In fact, the first thing it does is check if the spindash should be/is being charged or is being released. Moreover, there is code inside Sonic_CheckSpindash that will cause it to not to return to this function (thus skipping all following function calls) if a spindash is being charged or released.

In any case, if you are charging or releasing a spindash, the Sonic_CheckSpindash routine eventually calls the Sonic_LevelBound and AnglePos routines. These are necessary when charging the spindash because these functions would be skipped otherwise, but the call to Sonic_LevelBound is harmful when releasing the spindash because Sonic's X speed will never be set from his inertia (which would normally happen in the lines marked (1)), as in Sonic_LevelBound the lines marked (3) will kill Sonic's speed and inertia if he are at the screen boundary. In this case, the important block of code from the call to Sonic_Move is the one I highlighted above, and the one I used to fix the bug. Note that actually calling Sonic_Move will cause a running frame to be displayed for 1 frame when releasing the spindash, among probably many other similar side-effects.

So the sequence is: spindash being unleashed sets your inertia (lines (2), above); Sonic_LevelBound, called from Sonic_CheckSpindash, kills speed and inertia if you are at the screen boundary (lines (3), above) since the horizontal speed is never set from the new inertia (lines (1), above). The result is that you get stuck at the screen boundary.