Actions

SCHG How-to

Fix bugs relating to Super Sonic

From Sonic Retro

(Original guide by MoDule)

Super Sonic from Sonic 2 comes with quite a few bugs, some minor, while others can render the game unbeatable. This guide will show how to fix some of them and explain their cause. They have been arranged roughly by their severity in the event that they occur, starting at worst.

Stuck at the end of a level

At the end of a level, after reverting back to normal, if Sonic jumps he will trigger his transformation again and get stuck in the air. A full description of the bug can be found here.

Fixing the bug

Part 1

Locate the Sonic_CheckGoSuper routine and add the following two lines at the beginning:

	tst.b	(Update_HUD_timer).w	; has Sonic reached the end of the act?
	beq.s	return_1ABA4		; if yes, branch

The code should now look something like this:

; loc_1AB38: test_set_SS:
Sonic_CheckGoSuper:
	tst.b	(Update_HUD_timer).w	; has Sonic reached the end of the act?
	beq.s	return_1ABA4		; if yes, branch

	tst.b	(Super_Sonic_flag).w	; is Sonic already Super?
	bne.s	return_1ABA4		; if yes, branch
	cmpi.b	#7,(Emerald_count).w	; does Sonic have exactly 7 emeralds?
	bne.s	return_1ABA4		; if not, branch
	cmpi.w	#50,(Ring_count).w	; does Sonic have at least 50 rings?
	blo.s	return_1ABA4		; if not, branch

All this does is skip Sonic's transformation check if the level timer has stopped, which happens at the end of a level.

Note by ThomasSpeedrunner:

See this line?

        beq.s	return_1ABA4		; if yes, branch

Change the beq.s to beq.w to prevent a "branch out of range" error. You have to do this with every identical line in the Sonic_CheckGoSuper function or Sonic 2 will refuse to compile.


Part 2

A minor, non game breaking version of the bug can still occur at the end of an act after a boss battle, although it requires some very precise timing. Sonic needs to be with Tails and have 50 or more rings, but not be transformed. While Sonic is jumping up into the air to transform, Tails has to jump on the prison capsule and land on it just after Sonic's transformation animation starts.

To fix this bug, locate Sonic_Super, which should look like this:

	tst.b	(Super_Sonic_flag).w	; Ignore all this code if not Super Sonic
	beq.w	return_1AC3C
				; <--
	tst.b	(Update_HUD_timer).w
	beq.s	Sonic_RevertToNormal ; ?
	subq.w	#1,(Super_Sonic_frame_count).w

At the indicated line, add this:

	cmpi.b	#1,(Super_Sonic_palette).w	; is Super Sonic's transformation sequence finished?
	beq.s	return_1ABA4			; if not, branch

A side effect of this fix is that Sonic's ring drain counter doesn't start until his transformation sequence is finished.


The bug explained

The main offender is this line in the above routine.

	move.b	#$81,obj_control(a0)	; lock Sonic in place

What this does is stop Sonic's movement completely for the duration of his transformation sequence. This isn't usually a problem, because his movement is restored once the transformation is done. The other part of the problem comes from here:

; loc_1ABA6:
Sonic_Super:
	tst.b	(Super_Sonic_flag).w	; Ignore all this code if not Super Sonic
	beq.w	return_1AC3C
	tst.b	(Update_HUD_timer).w	; has level ended?
	beq.s	Sonic_RevertToNormal	; <-- if yes, branch

	[...]

Sonic_RevertToNormal:
	move.b	#2,(Super_Sonic_palette).w	; <-- remove rotating palette

	[...]

and here:

; sub_213E:
PalCycle_SuperSonic:
	move.b	(Super_Sonic_palette).w,d0
	beq.s	++	; rts			; return, if Sonic isn't super
	bmi.w	PalCycle_SuperSonic_normal	; branch, if fade-in is done
	subq.b	#1,d0
	bne.s	PalCycle_SuperSonic_revert	; <-- branch for values greater than 1

	[...]

	move.b	#-1,(Super_Sonic_palette).w	; mark fade-in as done
	move.b	#0,(MainCharacter+obj_control).w	; <-- restore Sonic's movement

	[...]

; loc_2188:
PalCycle_SuperSonic_revert:	; runs the fade in transition backwards

What's happening is that first Sonic jumps and starts the transformation sequence. This stops his movement. Then, since the timer has stopped, his palette cycle is set to fade out, but since it hasn't finished fading in, Sonic's movement is never restored. Thus he just hangs there forever.

Wrong speed when transforming under water

When transforming under water, Sonic's speed is set to his above water speed.

Fixing the bug

Locate the Sonic_CheckGoSuper routine. It should look like this:

; loc_1AB38: test_set_SS:
Sonic_CheckGoSuper:
	[...]

	move.b	#1,(Super_Sonic_palette).w
	move.b	#$F,(Palette_timer).w
	move.b	#1,(Super_Sonic_flag).w
	move.b	#$81,obj_control(a0)
	move.b	#AniIDSupSonAni_Transform,anim(a0)			; use transformation animation
	move.b	#ObjID_SuperSonicStars,(SuperSonicStars+id).w ; load Obj7E (super sonic stars object) at $FFFFD040

	move.w	#$A00,(Sonic_top_speed).w
	move.w	#$30,(Sonic_acceleration).w
	move.w	#$100,(Sonic_deceleration).w	; <--

Under the last shown line add this:

	btst	#6,status(a0)	; Check if underwater, return if not
	beq.s	+
	move.w	#$500,(Sonic_top_speed).w
	move.w	#$18,(Sonic_acceleration).w
	move.w	#$80,(Sonic_deceleration).w
+

Now Super Sonic will have the right speed when transforming under water. There might be a branch distance out of range error here. To fix this, either change the branch length from .s to .w a use a closer label with an rts after it.


The bug explained

The game doesn't check if Sonic is under water when transforming, so the speed values it gives him are too high.

Ring countdown too slow

The counter that keeps track of when to subtract a ring while super counts for 61 frames instead of 60 as seen explained here.

Fixing the bug

Locate the Sonic_Super routine. It should look like this:

; loc_1ABA6:
Sonic_Super:
	tst.b	(Super_Sonic_flag).w	; Ignore all this code if not Super Sonic
	beq.w	return_1AC3C
	tst.b	(Update_HUD_timer).w	; has level ended?
	beq.s	Sonic_RevertToNormal	; if yes, branch
	subq.w	#1,(Super_Sonic_frame_count).w
	bpl.w	return_1AC3C			; <--
	move.w	#60,(Super_Sonic_frame_count).w	; <-- Reset frame counter to 60

	[...]

Here either change the bpl to a bhi or the 60 to a 59.


The bug explained

The branch condition used allows for a range of 0 to 60, which is 61 elements long, thus the timer runs for 61 frames, instead of the intended 60.

Decelerate too quickly when rolling

Sonic decelerates considerably faster when rolling if he is super. He can actually come to a stop on shallow downward slopes.

Fixing the bug

Locate the Sonic_RollSpeed routine. It should look like this:

; loc_1A7C6:
Sonic_RollSpeed:
	move.w	(Sonic_top_speed).w,d6
	asl.w	#1,d6
	move.w	(Sonic_acceleration).w,d5
	asr.w	#1,d5	; natural roll deceleration = 1/2 normal acceleration
	move.w	#$20,d4

	[...]

Here, replace the two lines

	move.w	(Sonic_acceleration).w,d5
	asr.w	#1,d5	; natural roll deceleration = 1/2 normal acceleration

with

	moveq	#6,d5	; natural roll deceleration = 1/2 normal acceleration

Now Sonic will always have the same roll deceleration. That includes when he has speed shoes.


The bug explained

The main idea behind the way this was coded was probably to have Sonic's deceleration be proportional to his normal acceleration. The problem is when his acceleration is increased, which makes him decelerate more quickly, even when it seems like he shouldn't.

Transforming twice in the same level

If during the same level and on the same life Sonic reverts and transforms a second time his transformation sequence will be wrong. He won't be suspended in mid air long enough and his palette cycle will show one incorrect color.

Fixing the bug

Locate the label PalCycle_SuperSonic_revert. The code should look like this:

; loc_2188:
PalCycle_SuperSonic_revert:	; runs the fade in transition backwards
	; run frame timer
	subq.b	#1,(Palette_timer).w
	bpl.s	-	; rts
	move.b	#3,(Palette_timer).w

	; decrement palette frame and update Sonic's palette
	lea	(CyclingPal_SSTransformation).l,a0
	move.w	(Palette_frame).w,d0
	subq.w	#8,(Palette_frame).w	; previous frame
	bcc.s	+			; branch, if it isn't the first frame
	move.b	#0,(Palette_frame).w	; <--
	move.b	#0,(Super_Sonic_palette).w	; stop palette cycle
+
	lea	(Normal_palette+4).w,a1
	move.l	(a0,d0.w),(a1)+
	move.l	4(a0,d0.w),(a1)

	[...]

Change the move.b at the indicated line to move.w. That's it.


The bug explained

The palette cycle frame counter is reset incorrectly. Due to the way the routine handles the counter, on the last iteration it turns negative. The code attempts to fix this, but it does it wrong. Since only the first byte of the word length counter is set to zero, the next time it's read the game will get a wrong palette index that's far too high, accounting for both the wrong palette and the missing movement lock.

Skipping one palette frame

The last entry in Super Sonic's palette cycle is skipped. Although this is barely noticeable with the original palette, if one were to change it to use more distinct colors it would become much more obvious.

Fixing the bug

Locate the label PalCycle_SuperSonic_normal. The code should look like this:

; loc_21E6:
PalCycle_SuperSonic_normal:
	; run frame timer
	subq.b	#1,(Palette_timer).w
	bpl.s	-	; rts
	move.b	#7,(Palette_timer).w

	; increment palette frame and update Sonic's palette
	lea	(CyclingPal_SSTransformation).l,a0
	move.w	(Palette_frame).w,d0
	addq.w	#8,(Palette_frame).w	; next frame
	cmpi.w	#$78,(Palette_frame).w	; is it the last frame?
	blo.s	+			; <-- if not, branch
	move.w	#$30,(Palette_frame).w	; reset frame counter (Super Sonic's normal palette cycle starts at $30. Everything before that is for the palette fade)
+
	lea	(Normal_palette+4).w,a1
	move.l	(a0,d0.w),(a1)+
	move.l	4(a0,d0.w),(a1)

	[...]

Change the blo to bls. Done.


The bug explained

The branch condition is wrong. $78 is a valid index in Super Sonic's palette cycle, but it's skipped over due to the branch not including it.

No transformation cycle under water

When Sonic transforms under water the initial palette cycle is not displayed.

Fixing the bug

Locate the PalCycle_SuperSonic routine. The instruction are already given in the disassembly:

; sub_213E:
PalCycle_SuperSonic:
	move.b	(Super_Sonic_palette).w,d0
	beq.s	++	; rts			; return, if Sonic isn't super
	bmi.w	PalCycle_SuperSonic_normal	; branch, if fade-in is done
	subq.b	#1,d0
	bne.s	PalCycle_SuperSonic_revert	; branch for values greater than 1

	; fade from Sonic's to Super Sonic's palette
	; run frame timer
	subq.b	#1,(Palette_timer).w
	bpl.s	++	; rts
	move.b	#3,(Palette_timer).w

	; increment palette frame and update Sonic's palette
	lea	(CyclingPal_SSTransformation).l,a0
	move.w	(Palette_frame).w,d0
	addq.w	#8,(Palette_frame).w	; 1 palette entry = 1 word, Sonic uses 4 shades of blue
	cmpi.w	#$30,(Palette_frame).w	; has palette cycle reached the 6th frame?
	blo.s	+			; if not, branch
	move.b	#-1,(Super_Sonic_palette).w	; mark fade-in as done
	move.b	#0,(MainCharacter+obj_control).w	; restore Sonic's movement
+
	lea	(Normal_palette+4).w,a1
	move.l	(a0,d0.w),(a1)+
	move.l	4(a0,d0.w),(a1)
	; note: the fade in for Sonic's underwater palette is missing.	; <--
	; branch to the code below (*) to fix this
/	rts

Add a branch to the line marked here:

; loc_2188:
PalCycle_SuperSonic_revert:	; runs the fade in transition backwards
	; run frame timer
	subq.b	#1,(Palette_timer).w
	bpl.s	-	; rts
	move.b	#3,(Palette_timer).w

	; decrement palette frame and update Sonic's palette
	lea	(CyclingPal_SSTransformation).l,a0
	move.w	(Palette_frame).w,d0
	subq.w	#8,(Palette_frame).w	; previous frame
	bcc.s	+			; branch, if it isn't the first frame
;	move.b	#0,(Palette_frame).w
	move.w	#0,(Palette_frame).w		; [MoDule] correctly reset the frame index
	move.b	#0,(Super_Sonic_palette).w	; stop palette cycle
+
	lea	(Normal_palette+4).w,a1
	move.l	(a0,d0.w),(a1)+
	move.l	4(a0,d0.w),(a1)
	; underwater palettes (*)	; <--
	lea	(CyclingPal_CPZUWTransformation).l,a0
	cmpi.b	#chemical_plant_zone,(Current_Zone).w
	beq.s	+
	cmpi.b	#aquatic_ruin_zone,(Current_Zone).w
	bne.s	-	; rts
	lea	(CyclingPal_ARZUWTransformation).l,a0
+	lea	(Underwater_palette+4).w,a1
	move.l	(a0,d0.w),(a1)+
	move.l	4(a0,d0.w),(a1)
	rts

The final code should then look like this:

; sub_213E:
PalCycle_SuperSonic:
	[...]

	lea	(Normal_palette+4).w,a1
	move.l	(a0,d0.w),(a1)+
	move.l	4(a0,d0.w),(a1)

	bra.s	PalCycle_SuperSonic_water
/	rts
; ===========================================================================
; loc_2188:
PalCycle_SuperSonic_revert:	; runs the fade in transition backwards
	[...]

	lea	(Normal_palette+4).w,a1
	move.l	(a0,d0.w),(a1)+
	move.l	4(a0,d0.w),(a1)

PalCycle_SuperSonic_water:
	lea	(CyclingPal_CPZUWTransformation).l,a0
	cmpi.b	#chemical_plant_zone,(Current_Zone).w
	beq.s	+
	cmpi.b	#aquatic_ruin_zone,(Current_Zone).w
	bne.s	-	; rts
	lea	(CyclingPal_ARZUWTransformation).l,a0
+	lea	(Underwater_palette+4).w,a1
	move.l	(a0,d0.w),(a1)+
	move.l	4(a0,d0.w),(a1)
	rts

The bug explained

It was most likely simply an oversight as it was fixed in later games. The code to fade Sonic's under water palette is there, it simply isn't used in this case.

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 | Add beta spindash to Sonic 2 | Change spike behaviour in Sonic 2
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