Fix Speed Bugs - RetroKoH/S1Fixed GitHub Wiki

(Based on a Sonic 2 guide by redhotsonic)
(Adapted to Sonic 1 by RetroKoH)
Source: Sonic 2 SCHG Page
Commit: cda63d2

In Sonic 1 and Sonic 2, there are multiple speed issue bugs with Sonic. It's uncommon, but not exactly rare, that Sonic's top speed, acceleration and deceleration can be set incorrectly. This can become even more apparent if you backport Super Sonic to Sonic 1. With the potential for hacks with speed shoes placed underwater and in various other places in their layouts, these bugs can happen more often. Such issues include, but are not limited to:

  • If you get speed shoes, then go underwater, speed shoes' speed is lost. Even if you get back out of the water, Sonic's speed is normal.
  • If underwater and you get speed shoes, Sonic's "out-of-water" speed shoes speeds apply, making you go way too fast underwater. Getting out of water and then going back in results in the speed shoes speeds being lost, even with speed shoes still applied.
  • If underwater and you get speed shoes, bug above applies; but if you never leave the water, once speed shoes wear off, Sonic's normal speed applies, even though you're still underwater, making you still too fast.
  • If underwater and then you transform into Super Sonic, Super Sonic's "out-of-water" speed applies, making you way too fast.
  • If you have speed shoes and then you transform into Super Sonic, Super Sonic's speed applies, making you lose speed shoes speed, making you that bit slower than you should be.
  • If underwater when you have speed shoes and then you transform into Super Sonic, Super Sonic's "out-of-water" speed applies, and you lose your speed shoes speed.
  • If underwater when you have speed shoes and you're Super Sonic and you transform back to normal Sonic, normal Sonic's "underwater" speed applies, making you lose your speed shoes.

A lot, eh? There are even more issues if you port Super Sonic into Sonic 1 as well! The main issue with Sonic 1 is that the game never checks to see if you have the speed shoes applied or not. Or when obtaining the speed shoes, it's not checking if underwater, etc, etc. The game would have to do a few checks to get it right and Sonic Team may not have had time to implement those checks as it wasn't particularly important. But we're going to fix these ourselves, and it's much easier than you think.

Part 1: Create a new speeds table

First, we need to make some new speed settings. Here is a table of speed settings we can use. The speeds are those used in Sonic 1 and Sonic 2.

; ===========================================================================
; ----------------------------------------------------------------------------
; Speed Settings Array

; This array defines what speeds the character should be set to
; ----------------------------------------------------------------------------
;		top_speed	acceleration	deceleration	; #	; Comment
Speedsettings:
	dc.w	$600,		$C,		$80		; $00	; Normal
	dc.w	$C00,		$18,		$80		; $08	; Normal Speedshoes
	dc.w	$300,		$6,		$40		; $16	; Normal Underwater
	dc.w	$600,		$C,		$40		; $24	; Normal Underwater Speedshoes

	dc.w	$A00,		$30,		$100		; $32	; Super
	dc.w	$C00,		$30,		$100		; $40	; Super Speedshoes
	dc.w	$500,		$18,		$80		; $48	; Super Underwater
	dc.w	$A00,		$30,		$80		; $56	; Super Underwater Speedshoes
; ===========================================================================

The bottom four rows don't need to be included unless you want to add Super Sonic to your hack. You CAN include them even if you haven't gotten around to adding Super Sonic just yet. It won't hurt anything by doing so. Also, S1Fixed has an additional byte at the start of its Speed Table, though this isn't required. I had a plan to do something with this, and might end up just removing it outright.

Part 2: Creating a new subroutine to apply the speeds

S1Fixed has this table located at _incObj/sub ApplySpeedSettings.asm. You can include the new subroutine in a file with the same name, or a different one if you'd prefer. Here's the subroutine we will use:

; ===========================================================================
; ---------------------------------------------------------------------------
; Subroutine to collect the right speed setting for a character
; a0 must be character
; a1 will be the result and have the correct speed settings
; a2 is characters' speed
; ---------------------------------------------------------------------------

; ||||||||||||||| S U B R O U T I N E |||||||||||||||||||||||||||||||||||||||

ApplySpeedSettings:
	moveq	#0,d0				; Quickly clear d0
	tst.w	obShoes(a0)			; Does character have speedshoes?
	beq.s	.noshoes			; If not, branch
	addq.b	#6,d0				; Quickly add 6 to d0

.noshoes:
	btst	#staWater,obStatus(a0)		; Is the character underwater?
	beq.s	.nowater			; If not, branch
	addi.b	#12,d0				; Add 12 to d0

.nowater:
	btst	#sta2ndSuper,obStatus2nd(a0)	; Is the character Super?
	beq.s	.nosuper			; If not, branch
	addi.b	#24,d0				; Add 24 to d0

.nosuper:
	lea	Speedsettings(pc,d0.w),a1	; Load correct speed settings into a1
	move.l	(a1)+,(a2)+			; Set character's new top speed and acceleration
	move.w	(a1),(a2)			; Set character's deceleration
	rts					; Finish subroutine
; ===========================================================================

NOTE: The three lines under .nowater can be omitted if you are not using Super Sonic.

Now, we need to make sure to include the file in the main assembly file itself. Open sonic.asm and include the new file somewhere. I chose to include it after Sonic's split files, just before the file containing Object 0A:

	include "_incObj/sub ApplySpeedSettings.asm"	; New subroutine
	include	"_incObj/0A Drowning Countdown.asm"

Part 3: Replacing Code

Now we have our new code, we need to make some jumps to it. There are 5 files we need to modify, and the necessary modifications for each are nearly identical.

sonic.asm

Go to Sonic_Main. You'll see these lines where Sonic's speed values are loaded. Replace them with an address load of the speed variable, and a call to the new subroutine:

-	move.w	#$600,(v_sonspeedmax).w	; change Sonic's top speed
-	move.w	#$C,(v_sonspeedacc).w	; change Sonic's acceleration
-	move.w	#$80,(v_sonspeeddec).w	; change Sonic's deceleration

+	lea     (v_sonspeedmax).w,a2	; Load Sonic_top_speed into a2
+	bsr.w   ApplySpeedSettings	; Fetch Speed settings

_incObj/2E Monitor Content Power-Up.asm

Find Pow_ChkShoes. Just like before, you'll see three lines loading speed values for Sonic, only they're slightly higher as this is where Sonic's speed is increased to give the effect of the Speed Shoes. Remove those lines and replace them with a few new lines:

-	move.w	#$C00,(v_sonspeedmax).w	; change Sonic's top speed
-	move.w	#$18,(v_sonspeedacc).w	; change Sonic's acceleration
-	move.w	#$80,(v_sonspeeddec).w	; change Sonic's deceleration

+	movem.l a0-a2,-(sp)		; Move a0, a1 and a2 onto stack
+	lea     (v_player).w,a0		; Load Sonic to a0
+	lea	(v_sonspeedmax).w,a2	; Load Sonic_top_speed into a2
+	jsr	ApplySpeedSettings	; Fetch Speed settings
+	movem.l (sp)+,a0-a2		; Move a0, a1 and a2 from stack

We must move a0-a2 to the stack, because v_player must be loaded to a0 for the subroutine to work correctly. We restore a0-a2 after the speed adjustments are finished.

_incObj/DebugMode.asm

Now, we are going to add a jump to ApplySpeedSettings in our Debug Mode code, as we will restore Sonic's speeds upon returning from Debug Mode. Go to .backtonormal and find two lines that restore level boundaries. We are going to add our call just before those lines, like so:

+	lea     (v_sonspeedmax).w,a2			; Load Sonic_top_speed into a2
+	jsr	(ApplySpeedSettings).l			; Fetch Speed settings
	move.w	(v_limittopdb).w,(v_limittop2).w	; restore level boundaries
	move.w	(v_limitbtmdb).w,(v_limitbtm1).w

_incObj/Sonic Display.asm

Find .chkshoes. This is identical to what we did when we initialized Sonic's speeds in Sonic_Main, because this is where we reset Sonic's speed when the Speed Shoes expire. Make the same replacement as before:

-	move.w	#$600,(v_sonspeedmax).w	; change Sonic's top speed
-	move.w	#$C,(v_sonspeedacc).w	; change Sonic's acceleration
-	move.w	#$80,(v_sonspeeddec).w	; change Sonic's deceleration

+	lea     (v_sonspeedmax).w,a2	; Load Sonic_top_speed into a2
+	bsr.w   ApplySpeedSettings	; Fetch Speed settings

_incObj/Sonic Water.asm

In this file, we are going to make two edits. Search for v_sonspeedmax, and you'll see the usual 3 instructions where we load speed values. Replace each set of instructions with the call to our new subroutine:

	lea     (v_sonspeedmax).w,a2	; Load Sonic_top_speed into a2
	bsr.w   ApplySpeedSettings	; Fetch Speed settings

There we go. Now, when you enter/exit water, enter/exit speed shoes and/or enter/exit Super, the speeds should all be set correctly!