Actions

SCHG How-to

Difference between revisions of "Fix screen boundary spindash bug"

From Sonic Retro

m (The bug explained)
Line 53: Line 53:
 
bra.s Obj01_Spindash_ResetScr ; <= (2) this line
 
bra.s Obj01_Spindash_ResetScr ; <= (2) this line
 
</asm>
 
</asm>
The spindash code unleashes the spindash by setting Sonic's ''inertia'', with the intent that '''Obj01_UpdateSpeedOnGround''' will correctly set its X and Y components. 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 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:
 
The third important part is in the '''Sonic_LevelBound''' routine:
Line 102: Line 102:
 
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. 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.
  
 
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) before the horizontal speed is set from the new inertia (lines (1), above). The result is that you get stuck at the screen boundary.

Revision as of 22:58, 20 May 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 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: <asm> bsr.w Sonic_LevelBound bsr.w AnglePos </asm> After you have done that, the bug will be fixed.

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.

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. 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.

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.