IPB

Welcome Guest ( Log In | Register )


 

Recent Changes | Random | Help | Special Pages | Upload
home | info | forums | svn | irc | podcast | about

We've managed to restore 99% of our file loss earlier in the year, but it's now THE FINAL COUNTDOWN. Visit the wiki file request topic to help make Sonic Retro better than ever.

SCHG How-to:Fix Sonic 2's buggy spindash code and add spindash speeds

From Sonic Retro

In Sonic 2 and Sonic 3 & Knuckles, the spindash counter uses a word value for what's referenced in the spindash speed table as one byte and there's several instances where both word and byte values are used. Also, every time a button is pressed, the spindash counter value increases by 2 instead of 1 causing some spindash speeds to be unused. Due to the word and byte value confusion, the counter goes down rapidly to 0 making the top possible speed of the spindash unobtainable. This can cause some problems during game play, most notably how when charging a spindash with the buggy code on EHZ 2 at the start of the level and going as fast as possible with the code when discharged will cause Sonic to collide with the piranha badnik on the bridge near the start of the level and die. Fixing the code will increase the speed that Sonic can reach while spindashing and the revving down of the spindash will stop, allowing for less frantic button pressing to reach the top speed and the spindash will never have to be re-recharged while in the spindash position.

To fix the spindash code, find:

 
Sonic_CheckSpindash:
	tst.b	spindash_flag(a0)
	bne.s	Sonic_UpdateSpindash
	cmpi.b	#8,anim(a0)
	bne.s	return_1AC8C
	move.b	(Ctrl_1_Press_Logical).w,d0
	andi.b	#$70,d0
	beq.w	return_1AC8C
	move.b	#9,anim(a0)
	move.w	#$E0,d0
	jsr	(PlaySound).l
	addq.l	#4,sp
	move.b	#1,spindash_flag(a0)
	move.w	#0,spindash_counter(a0)
	cmpi.b	#$C,air_left(a0)	; if he's drowning, branch to not make dust
	bcs.s	+
	move.b	#2,(Sonic_Dust+anim).w
+
	bsr.w	Sonic_LevelBound
	bsr.w	AnglePos
 

and replace it with:

 
Sonic_CheckSpindash:
	tst.b	spindash_flag(a0)
	bne.s	Sonic_UpdateSpindash
	cmpi.b	#8,anim(a0)
	bne.s	return_1AC8C
	move.b	(Ctrl_1_Press_Logical).w,d0
	andi.b	#$70,d0
	beq.w	return_1AC8C
	move.b	#9,anim(a0)
	move.w	#$E0,d0
	jsr	(PlaySound).l
	addq.l	#4,sp
	move.b	#1,spindash_flag(a0)
	move.b	#0,spindash_counter(a0)
	cmpi.b	#$C,air_left(a0)	; if he's drowning, branch to not make dust
	bcs.s	+
	move.b	#2,(Sonic_Dust+anim).w
+
	bsr.w	Sonic_LevelBound
	bsr.w	AnglePos
 

now find:

 
Sonic_UpdateSpindash:
	move.b	(Ctrl_1_Held_Logical).w,d0
	btst	#1,d0
	bne.w	Sonic_ChargingSpindash
 
	; unleash the charged spindash and start rolling quickly:
	move.b	#$E,y_radius(a0)
	move.b	#7,x_radius(a0)
	move.b	#2,anim(a0)
	addq.w	#5,y_pos(a0)	; add the difference between Sonic's rolling and standing heights
	move.b	#0,spindash_flag(a0)
	moveq	#0,d0
	move.b	spindash_counter(a0),d0
	add.w	d0,d0
 

and replace it with:

 
Sonic_UpdateSpindash:
	move.b	(Ctrl_1_Held_Logical).w,d0
	btst	#1,d0
	bne.w	Sonic_ChargingSpindash
 
	; unleash the charged spindash and start rolling quickly:
	move.b	#$E,y_radius(a0)
	move.b	#7,x_radius(a0)
	move.b	#2,anim(a0)
	addq.w	#5,y_pos(a0)	; add the difference between Sonic's rolling and standing heights
	move.b	#0,spindash_flag(a0)
	moveq	#0,d0
	move.b	spindash_counter(a0),d0
	add.b	d0,d0
 

then find:

 
Sonic_ChargingSpindash:			; If still charging the dash...
	tst.w	spindash_counter(a0)
	beq.s	+
	move.w	spindash_counter(a0),d0
	lsr.w	#5,d0
	sub.w	d0,spindash_counter(a0)
	bcc.s	+
	move.w	#0,spindash_counter(a0)
+
	move.b	(Ctrl_1_Press_Logical).w,d0
	andi.b	#$70,d0
	beq.w	Obj01_Spindash_ResetScr
	move.w	#$900,anim(a0)
	move.w	#$E0,d0
	jsr	(PlaySound).l
	addi.w	#$200,spindash_counter(a0)
	cmpi.w	#$800,spindash_counter(a0)
	bcs.s	Obj01_Spindash_ResetScr
	move.w	#$800,spindash_counter(a0)
 

and replace it with:

 
Sonic_ChargingSpindash:			; If still charging the dash...
	tst.b	spindash_counter(a0)
	beq.s	+
	move.b	spindash_counter(a0),d0
	lsr.b	#5,d0
	sub.b	d0,spindash_counter(a0)
	bcc.s	+
	move.b	#0,spindash_counter(a0)
+
	move.b	(Ctrl_1_Press_Logical).w,d0
	andi.b	#$70,d0
	beq.w	Obj01_Spindash_ResetScr
	move.w	#$900,anim(a0)
	move.w	#$E0,d0
	jsr	(PlaySound).l
	addi.b	#$1,spindash_counter(a0)
	cmpi.b	#$8,spindash_counter(a0)
	bcs.s	Obj01_Spindash_ResetScr
	move.b	#$8,spindash_counter(a0)
	bra.s	Obj01_Spindash_ResetScr
	move.b	#$8,spindash_counter(a0)
 

next find:

 
CheckGameOver:
	move.b	#1,($FFFFEEBE).w
	move.b	#0,spindash_flag(a0)
	move.w	(Camera_Max_Y_pos_now).w,d0
	addi.w	#$100,d0
	cmp.w	y_pos(a0),d0
	bge.w	return_1B31A
	move.b	#8,routine(a0)	; => Obj01_Gone
	move.w	#$3C,spindash_counter(a0)
	addq.b	#1,(Update_HUD_lives).w	; update lives counter
	subq.b	#1,(Life_count).w	; subtract 1 from number of lives
	bne.s	Obj01_ResetLevel	; if it's not a game over, branch
	move.w	#0,spindash_counter(a0)
	move.b	#$39,(Object_RAM+$80).w ; load Obj39 (game over text)
	move.b	#$39,(Object_RAM+$C0).w ; load Obj39 (game over text)
	move.b	#1,(Object_RAM+$C0+mapping_frame).w
	move.w	a0,(Object_RAM+$80+parent).w
	clr.b	(Time_Over_flag).w
 

and change it to:

 
CheckGameOver:
	move.b	#1,($FFFFEEBE).w
	move.b	#0,spindash_flag(a0)
	move.w	(Camera_Max_Y_pos_now).w,d0
	addi.w	#$100,d0
	cmp.w	y_pos(a0),d0
	bge.w	return_1B31A
	move.b	#8,routine(a0)	; => Obj01_Gone
	move.b	#$3C,spindash_counter(a0)
	addq.b	#1,(Update_HUD_lives).w	; update lives counter
	subq.b	#1,(Life_count).w	; subtract 1 from number of lives
	bne.s	Obj01_ResetLevel	; if it's not a game over, branch
	move.b	#0,spindash_counter(a0)
	move.b	#$39,(Object_RAM+$80).w ; load Obj39 (game over text)
	move.b	#$39,(Object_RAM+$C0).w ; load Obj39 (game over text)
	move.b	#1,(Object_RAM+$C0+mapping_frame).w
	move.w	a0,(Object_RAM+$80+parent).w
	clr.b	(Time_Over_flag).w
 

now find:

 
 
Obj01_ResetLevel:
	tst.b	(Time_Over_flag).w
	beq.s	Obj01_ResetLevel_Part2
	move.w	#0,spindash_counter(a0)
	move.b	#$39,(Object_RAM+$80).w ; load Obj39
	move.b	#$39,(Object_RAM+$C0).w ; load Obj39
	move.b	#2,(Object_RAM+$80+mapping_frame).w
	move.b	#3,(Object_RAM+$C0+mapping_frame).w
	move.w	a0,(Object_RAM+$80+parent).w
	bra.s	Obj01_Finished
 

and replace it with:

 
Obj01_ResetLevel:
	tst.b	(Time_Over_flag).w
	beq.s	Obj01_ResetLevel_Part2
	move.b	#0,spindash_counter(a0)
	move.b	#$39,(Object_RAM+$80).w ; load Obj39
	move.b	#$39,(Object_RAM+$C0).w ; load Obj39
	move.b	#2,(Object_RAM+$80+mapping_frame).w
	move.b	#3,(Object_RAM+$C0+mapping_frame).w
	move.w	a0,(Object_RAM+$80+parent).w
	bra.s	Obj01_Finished
 

then find:

 
Obj01_ResetLevel_Part2:
	tst.w	(Two_player_mode).w
	beq.s	return_1B31A
	move.b	#0,($FFFFEEBE).w
	move.b	#$A,routine(a0)	; => Obj01_Respawning
	move.w	(Saved_x_pos).w,x_pos(a0)
	move.w	(Saved_y_pos).w,y_pos(a0)
	move.w	(Saved_art_tile).w,art_tile(a0)
	move.w	(Saved_layer).w,layer(a0)
	clr.w	(Ring_count).w
	clr.b	(Extra_life_flags).w
	move.b	#0,obj_control(a0)
	move.b	#5,anim(a0)
	move.w	#0,x_vel(a0)
	move.w	#0,y_vel(a0)
	move.w	#0,inertia(a0)
	move.b	#2,status(a0)
	move.w	#0,move_lock(a0)
	move.w	#0,spindash_counter(a0)
 

and replace it with:

 
Obj01_ResetLevel_Part2:
	tst.w	(Two_player_mode).w
	beq.s	return_1B31A
	move.b	#0,($FFFFEEBE).w
	move.b	#$A,routine(a0)	; => Obj01_Respawning
	move.w	(Saved_x_pos).w,x_pos(a0)
	move.w	(Saved_y_pos).w,y_pos(a0)
	move.w	(Saved_art_tile).w,art_tile(a0)
	move.w	(Saved_layer).w,layer(a0)
	clr.w	(Ring_count).w
	clr.b	(Extra_life_flags).w
	move.b	#0,obj_control(a0)
	move.b	#5,anim(a0)
	move.w	#0,x_vel(a0)
	move.w	#0,y_vel(a0)
	move.w	#0,inertia(a0)
	move.b	#2,status(a0)
	move.b	#0,move_lock(a0)
	move.b	#0,spindash_counter(a0)
 

now find:

 
Obj01_Gone:
	tst.w	spindash_counter(a0)
	beq.s	+
	subq.w	#1,spindash_counter(a0)
	bne.s	+
	move.w	#1,(Level_Inactive_flag).w
+
	rts
 

and then change it to:

 
Obj01_Gone:
	tst.b	spindash_counter(a0)
	beq.s	+
	subq.b	#1,spindash_counter(a0)
	bne.s	+
	move.w	#1,(Level_Inactive_flag).w
+
	rts
 

Go to:

 
TailsCPU_Respawn:
	move.w	#4,(Tails_CPU_routine).w	; => TailsCPU_Flying
	move.w	x_pos(a1),d0
	move.w	d0,x_pos(a0)
	move.w	d0,(Tails_CPU_target_x).w
	move.w	y_pos(a1),d0
	move.w	d0,(Tails_CPU_target_y).w
	subi.w	#$C0,d0
	move.w	d0,y_pos(a0)
	ori.w	#$8000,art_tile(a0)
	move.b	#0,spindash_flag(a0)
	move.w	#0,spindash_counter(a0)
 

and change it to:

 
TailsCPU_Respawn:
	move.w	#4,(Tails_CPU_routine).w	; => TailsCPU_Flying
	move.w	x_pos(a1),d0
	move.w	d0,x_pos(a0)
	move.w	d0,(Tails_CPU_target_x).w
	move.w	y_pos(a1),d0
	move.w	d0,(Tails_CPU_target_y).w
	subi.w	#$C0,d0
	move.w	d0,y_pos(a0)
	ori.w	#$8000,art_tile(a0)
	move.b	#0,spindash_flag(a0)
	move.b	#0,spindash_counter(a0)
 

Now find:

 
TailsCPU_Normal:
	cmpi.b	#6,(MainCharacter+routine).w	; is Sonic dead?
	bcs.s	TailsCPU_Normal_SonicOK		; if not, branch
	; Sonic's dead; fly down to his corpse
	move.w	#4,(Tails_CPU_routine).w	; => TailsCPU_Flying
	move.b	#0,spindash_flag(a0)
	move.w	#0,spindash_counter(a0)
	move.b	#$81,obj_control(a0)
	move.b	#2,status(a0)
	move.b	#$20,anim(a0)
	rts
 

then replace it with:

 
TailsCPU_Normal:
	cmpi.b	#6,(MainCharacter+routine).w	; is Sonic dead?
	bcs.s	TailsCPU_Normal_SonicOK		; if not, branch
	; Sonic's dead; fly down to his corpse
	move.w	#4,(Tails_CPU_routine).w	; => TailsCPU_Flying
	move.b	#0,spindash_flag(a0)
	move.b	#0,spindash_counter(a0)
	move.b	#$81,obj_control(a0)
	move.b	#2,status(a0)
	move.b	#$20,anim(a0)
	rts
 

now find:

 
Tails_CheckSpindash:
	tst.b	spindash_flag(a0)
	bne.s	Tails_UpdateSpindash
	cmpi.b	#8,anim(a0)
	bne.s	return_1C75C
	move.b	(Ctrl_2_Press_Logical).w,d0
	andi.b	#$70,d0
	beq.w	return_1C75C
	move.b	#9,anim(a0)
	move.w	#$E0,d0
	jsr	(PlaySound).l
	addq.l	#4,sp
	move.b	#1,spindash_flag(a0)
	move.w	#0,spindash_counter(a0)
	cmpi.b	#$C,air_left(a0)	; if he's drowning, branch to not make dust
	bcs.s	loc_1C754
	move.b	#2,(Tails_Dust+anim).w
 

then change it to:

 
Tails_CheckSpindash:
	tst.b	spindash_flag(a0)
	bne.s	Tails_UpdateSpindash
	cmpi.b	#8,anim(a0)
	bne.s	return_1C75C
	move.b	(Ctrl_2_Press_Logical).w,d0
	andi.b	#$70,d0
	beq.w	return_1C75C
	move.b	#9,anim(a0)
	move.w	#$E0,d0
	jsr	(PlaySound).l
	addq.l	#4,sp
	move.b	#1,spindash_flag(a0)
	move.b	#0,spindash_counter(a0)
	cmpi.b	#$C,air_left(a0)	; if he's drowning, branch to not make dust
	bcs.s	loc_1C754
	move.b	#2,(Tails_Dust+anim).w
 

then find:

 
Tails_UpdateSpindash:
	move.b	(Ctrl_2_Held_Logical).w,d0
	btst	#1,d0
	bne.s	Tails_ChargingSpindash
 
	; unleash the charged spindash and start rolling quickly:
	move.b	#$E,y_radius(a0)
	move.b	#7,x_radius(a0)
	move.b	#2,anim(a0)
	addq.w	#1,y_pos(a0)	; add the difference between Tails' rolling and standing heights
	move.b	#0,spindash_flag(a0)
	moveq	#0,d0
	move.b	spindash_counter(a0),d0
	add.w	d0,d0
 

and replace it with:

 
Tails_UpdateSpindash:
	move.b	(Ctrl_2_Held_Logical).w,d0
	btst	#1,d0
	bne.s	Tails_ChargingSpindash
 
	; unleash the charged spindash and start rolling quickly:
	move.b	#$E,y_radius(a0)
	move.b	#7,x_radius(a0)
	move.b	#2,anim(a0)
	addq.w	#1,y_pos(a0)	; add the difference between Tails' rolling and standing heights
	move.b	#0,spindash_flag(a0)
	moveq	#0,d0
	move.b	spindash_counter(a0),d0
	add.w	d0,d0
 

now find:

 
Tails_ChargingSpindash:			; If still charging the dash...
	tst.w	spindash_counter(a0)
	beq.s	loc_1C7F8
	move.w	spindash_counter(a0),d0
	lsr.w	#5,d0
	sub.w	d0,spindash_counter(a0)
	bcc.s	loc_1C7F8
	move.w	#0,spindash_counter(a0)
 

and replace it with:

 
Tails_ChargingSpindash:			; If still charging the dash...
	tst.b	spindash_counter(a0)
	beq.s	loc_1C7F8
	move.b	spindash_counter(a0),d0
	lsr.b	#5,d0
	sub.b	d0,spindash_counter(a0)
	bcc.s	loc_1C7F8
	move.b	#0,spindash_counter(a0)
 

Now find:

 
loc_1C7F8:
	move.b	(Ctrl_2_Press_Logical).w,d0
	andi.b	#$70,d0
	beq.w	loc_1C828
	move.w	#$900,anim(a0)
	move.w	#$E0,d0
	jsr	(PlaySound).l
	addi.w	#$200,spindash_counter(a0)
	cmpi.w	#$800,spindash_counter(a0)
	bcs.s	loc_1C828
	move.w	#$800,spindash_counter(a0)
 

and replace it with:

 
loc_1C7F8:
	move.b	(Ctrl_2_Press_Logical).w,d0
	andi.b	#$70,d0
	beq.w	loc_1C828
	move.w	#$900,anim(a0)
	move.w	#$E0,d0
	jsr	(PlaySound).l
	addi.b	#$1,spindash_counter(a0)
	cmpi.b	#$8,spindash_counter(a0)
	bcs.s	loc_1C828
	move.b	#$8,spindash_counter(a0)
	bra.s	loc_1C828
	move.b	#$8,spindash_counter(a0)
 

Now find:

 
Obj02_CheckGameOver_2Pmode:
	addq.b	#1,(Update_HUD_lives_2P).w
	subq.b	#1,(Life_count_2P).w
	bne.s	Obj02_ResetLevel
	move.w	#0,spindash_counter(a0)
	move.b	#$39,(Object_RAM+$80).w ; load Obj39
	move.b	#$39,(Object_RAM+$C0).w ; load Obj39
	move.b	#1,(Object_RAM+$C0+mapping_frame).w
	move.w	a0,(Object_RAM+$80+parent).w
	clr.b	(Time_Over_flag_2P).w
 

and replace it with:

 
Obj02_CheckGameOver_2Pmode:
	addq.b	#1,(Update_HUD_lives_2P).w
	subq.b	#1,(Life_count_2P).w
	bne.s	Obj02_ResetLevel
	move.b	#0,spindash_counter(a0)
	move.b	#$39,(Object_RAM+$80).w ; load Obj39
	move.b	#$39,(Object_RAM+$C0).w ; load Obj39
	move.b	#1,(Object_RAM+$C0+mapping_frame).w
	move.w	a0,(Object_RAM+$80+parent).w
	clr.b	(Time_Over_flag_2P).w
 

Now find:

 
Obj02_ResetLevel:
	tst.b	(Time_Over_flag).w
	beq.s	Obj02_ResetLevel_Part2
	tst.b	(Time_Over_flag_2P).w
	beq.s	Obj02_ResetLevel_Part3
	move.w	#0,spindash_counter(a0)
	clr.b	(Update_HUD_timer).w
	clr.b	(Update_HUD_timer_2P).w
	move.b	#8,routine(a0)
	rts
; ---------------------------------------------------------------------------
Obj02_ResetLevel_Part2:
	tst.b	(Time_Over_flag_2P).w
	beq.s	Obj02_ResetLevel_Part3
	move.w	#0,spindash_counter(a0)
	move.b	#$39,(Object_RAM+$80).w ; load Obj39
	move.b	#$39,(Object_RAM+$C0).w ; load Obj39
	move.b	#2,(Object_RAM+$80+mapping_frame).w
	move.b	#3,(Object_RAM+$C0+mapping_frame).w
	move.w	a0,(Object_RAM+$80+parent).w
	bra.s	Obj02_Finished
 

and replace it with:

 
Obj02_ResetLevel:
	tst.b	(Time_Over_flag).w
	beq.s	Obj02_ResetLevel_Part2
	tst.b	(Time_Over_flag_2P).w
	beq.s	Obj02_ResetLevel_Part3
	move.b	#0,spindash_counter(a0)
	clr.b	(Update_HUD_timer).w
	clr.b	(Update_HUD_timer_2P).w
	move.b	#8,routine(a0)
	rts
; ---------------------------------------------------------------------------
Obj02_ResetLevel_Part2:
	tst.b	(Time_Over_flag_2P).w
	beq.s	Obj02_ResetLevel_Part3
	move.b	#0,spindash_counter(a0)
	move.b	#$39,(Object_RAM+$80).w ; load Obj39
	move.b	#$39,(Object_RAM+$C0).w ; load Obj39
	move.b	#2,(Object_RAM+$80+mapping_frame).w
	move.b	#3,(Object_RAM+$C0+mapping_frame).w
	move.w	a0,(Object_RAM+$80+parent).w
	bra.s	Obj02_Finished
 

then find:

 
Obj02_Gone:
	tst.w	spindash_counter(a0)
	beq.s	+
	subq.w	#1,spindash_counter(a0)
	bne.s	+
	move.w	#1,(Level_Inactive_flag).w
+
	rts
 

and change it to:

 
Obj02_Gone:
	tst.b	spindash_counter(a0)
	beq.s	+
	subq.b	#1,spindash_counter(a0)
	bne.s	+
	move.w	#1,(Level_Inactive_flag).w
+
	rts
 

Doing this also helps to free up an OST Value. ($3B)

Now, if needed, you can add more spindash values by following these steps.

Replace:

 
Sonic_ChargingSpindash:			; If still charging the dash...
	tst.b	spindash_counter(a0)
	beq.s	+
	move.b	spindash_counter(a0),d0
	lsr.b	#5,d0
	sub.b	d0,spindash_counter(a0)
	bcc.s	+
	move.b	#0,spindash_counter(a0)
+
	move.b	(Ctrl_1_Press_Logical).w,d0
	andi.b	#$70,d0
	beq.w	Obj01_Spindash_ResetScr
	move.w	#$900,anim(a0)
	move.w	#$E0,d0
	jsr	(PlaySound).l
	addi.b	#$1,spindash_counter(a0)
	cmpi.b	#$8,spindash_counter(a0)
	bcs.s	Obj01_Spindash_ResetScr
	move.b	#$8,spindash_counter(a0)
	bra.s	Obj01_Spindash_ResetScr
	move.b	#$8,spindash_counter(a0)
 

and:

 
loc_1C7F8:
	move.b	(Ctrl_2_Press_Logical).w,d0
	andi.b	#$70,d0
	beq.w	loc_1C828
	move.w	#$900,anim(a0)
	move.w	#$E0,d0
	jsr	(PlaySound).l
	addi.b	#$1,spindash_counter(a0)
	cmpi.b	#$8,spindash_counter(a0)
	bcs.s	loc_1C828
	move.b	#$8,spindash_counter(a0)
	bra.s	loc_1C828
	move.b	#$8,spindash_counter(a0)
 

with:

 
Sonic_ChargingSpindash:			; If still charging the dash...
	tst.b	spindash_counter(a0)
	beq.s	+
	move.b	spindash_counter(a0),d0
	lsr.b	#5,d0
	sub.b	d0,spindash_counter(a0)
	bcc.s	+
	move.b	#0,spindash_counter(a0)
+
	move.b	(Ctrl_1_Press_Logical).w,d0
	andi.b	#$70,d0
	beq.w	Obj01_Spindash_ResetScr
	move.w	#$900,anim(a0)
	move.w	#$E0,d0
	jsr	(PlaySound).l
	addi.b	#$1,spindash_counter(a0)
	cmpi.b	#$X,spindash_counter(a0)
	bcs.s	Obj01_Spindash_ResetScr
	move.b	#$X,spindash_counter(a0)
	bra.s	Obj01_Spindash_ResetScr
	move.b	#$X,spindash_counter(a0)
 

and:

 
loc_1C7F8:
	move.b	(Ctrl_2_Press_Logical).w,d0
	andi.b	#$70,d0
	beq.w	loc_1C828
	move.w	#$900,anim(a0)
	move.w	#$E0,d0
	jsr	(PlaySound).l
	addi.b	#$1,spindash_counter(a0)
	cmpi.b	#$X,spindash_counter(a0)
	bcs.s	loc_1C828
	move.b	#$X,spindash_counter(a0)
	bra.s	loc_1C828
	move.b	#$X,spindash_counter(a0)
 

then add the needed speeds to equal $X to Sonic_SpindashSpeeds, SpindashSpeedsSuper, and Tails_SpindashSpeeds.

Here's a ROM File with all the changes made in this guide. (info)