SCHG How-to

Fix screen boundary spindash bug

From Sonic Retro

(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. This can also be seen when fighting the Aquatic Ruin Zone boss. Spin dash into the boss from the top of the totem pole as he comes toward you, and you will be rebounded into the screen boundary, and will then encounter the bug if you attempt another spin dash.

Fixing the bug

Locate the word_1AD0C label. In this routine, locate the following lines:

	bra.s	Obj01_Spindash_ResetScr
; ===========================================================================
; word_1AD0C:

And add code just before it so that it looks like this:

	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:

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:

; loc_1A630:
	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

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:

; loc_1AC8E:
	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

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:

; loc_1A974:
	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:
	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

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:

	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


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.

SCHG How-To Guide: Sonic the Hedgehog 2 (16-bit)
Fixing Bugs
Fix Demo Playback | Fix a Race Condition with Pattern Load Cues | Fix Super Sonic Bugs | Use Correct Height When Roll Jumping | Fix Jump Height Bug When Exiting Water | Fix Spin Dash Code and Add Spin Dash Speeds | Fix Screen Boundary Spin Dash Bug | Correct Drowning Bugs | Fix Camera Y Position for Tails | Fix Tails Subanimation Error | Fix Tails' Respawn Speeds | Fix Accidental Deletion of Scattered Rings | Fix Ring Timers | Fix Rexon Crash | Fix Monitor Collision Bug | Fix EHZ Deformation Bug | Correct CPZ Boss Attack Behavior | Fix Bug in ARZ Boss Arrow's Platform Behavior | Fix ARZ Boss Walking on Air Glitch | Fix ARZ Boss Sprite Behavior | Fix Multiple CNZ Boss Bugs | Fix HTZ Background Scrolling Mountains | Fix OOZ Launcher Speed Up Glitch | Fix DEZ Giant Mech Collision Glitch | Fix Boss Deconstruction Behavior | Fix Speed Bugs
Design Choices
Remove the Air Speed Cap | Disable Floor Collision While Dying | Modify Super Sonic Transformation Methods & Behavior | Enable/Disable Tails in Certain Levels | Collide with Water After Being Hurt | Retain Rings When Returning at a Star Post | Improve the Fade In\Fade Out Progression Routines | Fix Scattered Rings' Underwater Physics | Insert LZ Water Ripple Effect | Restore Lost CPZ Boss Feature | Prevent SCZ Tornado Spin Dash Death | Improve ObjectMove Subroutines | Port S3K Rings Manager | Port S3K Object Manager | Port S3K Priority Manager | Edit Level Order with ASM‎ | Alter Ring Requirements in Special Stages | Make Special Stage Characters Use Normal DPLCs | Speed Up Ring Loss Process
Adding Features
Create Insta-kill and High Jump Monitors | Create Clone and Special Stage Monitors | Port Knuckles
Sound Features
Port Sonic 1 Sound Driver | Port Sonic 2 Clone Driver | Port Sonic 3 Sound Driver | Expand the Music Index to Start at $00 (Sonic 2 Clone Driver Version)
Extending the Game
Extend the Level Index Past $10 | Extend the Level Select | Extend Water Tables | Add Extra Characters | Free Up 2 Universal SSTs