Difference between revisions of "Fix bugs relating to Super Sonic"
From Sonic Retro
(Created page with "{{GuideBy|MoDule}} Super Sonic from ''Sonic 2'' comes with quite a few bugs, some minor, while others [[Sonic_the_Hedgehog_2_%2816-bit%29_bug_l…") |
m (→BONUS: Optimization) |
||
(22 intermediate revisions by 7 users not shown) | |||
Line 1: | Line 1: | ||
− | {{GuideBy|MoDule}} | + | {{GuideBy|MoDule}} ''additional fixes by [[User:Shadow05|Shadow05]]'' |
− | Super Sonic from ''[[Sonic the Hedgehog 2 (16-bit)|Sonic 2]]'' comes with quite a few bugs, some minor, while others [[Sonic_the_Hedgehog_2_%2816-bit%29_bug_list#Super_Sonic_at_the_end_of_a_level|can render the game unbeatable]]. This guide will show how to fix some of them. | + | Super Sonic from ''[[Sonic the Hedgehog 2 (16-bit)|Sonic 2]]'' comes with quite a few bugs, some minor, while others [[Sonic_the_Hedgehog_2_%2816-bit%29_bug_list#Super_Sonic_at_the_end_of_a_level|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== | ==Stuck at the end of a level== | ||
Line 7: | Line 7: | ||
===Fixing the bug=== | ===Fixing the bug=== | ||
+ | ====Part 1==== | ||
Locate the '''Sonic_CheckGoSuper''' routine and add the following two lines at the beginning: | Locate the '''Sonic_CheckGoSuper''' routine and add the following two lines at the beginning: | ||
− | <asm> tst.b (Update_HUD_timer).w ; has Sonic reached the end of the act? | + | <syntaxhighlight lang="asm"> tst.b (Update_HUD_timer).w ; has Sonic reached the end of the act? |
beq.s return_1ABA4 ; if yes, branch | beq.s return_1ABA4 ; if yes, branch | ||
− | </ | + | </syntaxhighlight> |
The code should now look something like this: | The code should now look something like this: | ||
− | <asm> | + | <syntaxhighlight lang="asm">; loc_1AB38: test_set_SS: |
− | |||
− | ; loc_1AB38: test_set_SS: | ||
Sonic_CheckGoSuper: | Sonic_CheckGoSuper: | ||
tst.b (Update_HUD_timer).w ; has Sonic reached the end of the act? | tst.b (Update_HUD_timer).w ; has Sonic reached the end of the act? | ||
Line 25: | Line 24: | ||
cmpi.w #50,(Ring_count).w ; does Sonic have at least 50 rings? | cmpi.w #50,(Ring_count).w ; does Sonic have at least 50 rings? | ||
blo.s return_1ABA4 ; if not, branch | blo.s return_1ABA4 ; if not, branch | ||
− | </ | + | </syntaxhighlight> |
All this does is skip Sonic's transformation check if the level timer has stopped, which happens at the end of a level. | 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? | ||
+ | <syntaxhighlight lang="asm"> | ||
+ | beq.s return_1ABA4 ; if yes, branch | ||
+ | </syntaxhighlight> | ||
+ | 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: | ||
+ | <syntaxhighlight lang="asm"> 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 | ||
+ | </syntaxhighlight> | ||
+ | At the indicated line, add this: | ||
+ | <syntaxhighlight lang="asm"> cmpi.b #1,(Super_Sonic_palette).w ; is Super Sonic's transformation sequence finished? | ||
+ | beq.s return_1ABA4 ; if not, branch | ||
+ | </syntaxhighlight> | ||
+ | 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 bug explained=== | ||
− | + | The main offender is this line in the above routine. | |
− | <asm> move.b #$81,obj_control(a0) ; lock Sonic in place | + | <syntaxhighlight lang="asm"> move.b #$81,obj_control(a0) ; lock Sonic in place |
− | </ | + | </syntaxhighlight> |
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: | 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: | ||
− | <asm>; loc_1ABA6: | + | <syntaxhighlight lang="asm">; loc_1ABA6: |
Sonic_Super: | Sonic_Super: | ||
tst.b (Super_Sonic_flag).w ; Ignore all this code if not Super Sonic | tst.b (Super_Sonic_flag).w ; Ignore all this code if not Super Sonic | ||
Line 40: | Line 62: | ||
beq.s Sonic_RevertToNormal ; <-- if yes, branch | beq.s Sonic_RevertToNormal ; <-- if yes, branch | ||
− | + | [...] | |
Sonic_RevertToNormal: | Sonic_RevertToNormal: | ||
move.b #2,(Super_Sonic_palette).w ; <-- remove rotating palette | move.b #2,(Super_Sonic_palette).w ; <-- remove rotating palette | ||
− | + | [...] | |
− | </ | + | </syntaxhighlight> |
and here: | and here: | ||
− | <asm>; sub_213E: | + | <syntaxhighlight lang="asm">; sub_213E: |
PalCycle_SuperSonic: | PalCycle_SuperSonic: | ||
move.b (Super_Sonic_palette).w,d0 | move.b (Super_Sonic_palette).w,d0 | ||
Line 56: | Line 78: | ||
bne.s PalCycle_SuperSonic_revert ; <-- branch for values greater than 1 | 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 #-1,(Super_Sonic_palette).w ; mark fade-in as done | ||
move.b #0,(MainCharacter+obj_control).w ; <-- restore Sonic's movement | move.b #0,(MainCharacter+obj_control).w ; <-- restore Sonic's movement | ||
− | + | [...] | |
; loc_2188: | ; loc_2188: | ||
PalCycle_SuperSonic_revert: ; runs the fade in transition backwards | PalCycle_SuperSonic_revert: ; runs the fade in transition backwards | ||
− | </ | + | </syntaxhighlight> |
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. | 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: | ||
+ | <syntaxhighlight lang="asm">; 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 ; <-- | ||
+ | </syntaxhighlight> | ||
+ | Under the last shown line add this: | ||
+ | <syntaxhighlight lang="asm"> 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 | ||
+ | + | ||
+ | </syntaxhighlight> | ||
+ | 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 [[Sonic_the_Hedgehog_2_(16-bit)_bug_list#Super_Sonic_countdown|here]]. | ||
+ | |||
+ | ===Fixing the bug=== | ||
+ | Locate the '''Sonic_Super''' routine. It should look like this: | ||
+ | <syntaxhighlight lang="asm">; 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 | ||
+ | |||
+ | [...] | ||
+ | </syntaxhighlight> | ||
+ | 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: | ||
+ | <syntaxhighlight lang="asm">; 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 | ||
+ | |||
+ | [...] | ||
+ | </syntaxhighlight> | ||
+ | Here, replace the two lines | ||
+ | <syntaxhighlight lang="asm"> move.w (Sonic_acceleration).w,d5 | ||
+ | asr.w #1,d5 ; natural roll deceleration = 1/2 normal acceleration | ||
+ | </syntaxhighlight> | ||
+ | with | ||
+ | <syntaxhighlight lang="asm"> moveq #6,d5 ; natural roll deceleration = 1/2 normal acceleration | ||
+ | </syntaxhighlight> | ||
+ | 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: | ||
+ | <syntaxhighlight lang="asm">; 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) | ||
+ | |||
+ | [...] | ||
+ | </syntaxhighlight> | ||
+ | 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: | ||
+ | <syntaxhighlight lang="asm">; 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) | ||
+ | |||
+ | [...] | ||
+ | </syntaxhighlight> | ||
+ | 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: | ||
+ | <syntaxhighlight lang="asm">; 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 | ||
+ | </syntaxhighlight> | ||
+ | Add a branch to the line marked here: | ||
+ | <syntaxhighlight lang="asm">; 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 | ||
+ | </syntaxhighlight> | ||
+ | The final code should then look like this: | ||
+ | <syntaxhighlight lang="asm">; 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 | ||
+ | </syntaxhighlight> | ||
+ | ---- | ||
+ | ===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. | ||
+ | |||
+ | ===BONUS: Optimization=== | ||
+ | We can slighlty optimize the code to perform the cycling palette. Copy and paste the code below. | ||
+ | <syntaxhighlight lang="asm">; ||||||||||||||| S U B R O U T I N E ||||||||||||||||||||||||||||||||||||||| | ||
+ | |||
+ | ; 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 PalCycle_SuperSonic_palettes ; 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 | ||
+ | bra.s PalCycle_SuperSonic_palettes | ||
+ | / rts | ||
+ | ; =========================================================================== | ||
+ | ; 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 PalCycle_SuperSonic_palettes ; branch, if it isn't the first frame | ||
+ | move.w #0,(Palette_frame).w | ||
+ | move.b #0,(Super_Sonic_palette).w ; stop palette cycle | ||
+ | |||
+ | PalCycle_SuperSonic_palettes: | ||
+ | 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 | ||
+ | ; =========================================================================== | ||
+ | ; 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? | ||
+ | bls.s PalCycle_SuperSonic_palettes ; 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) | ||
+ | bra.s PalCycle_SuperSonic_palettes | ||
+ | ; End of function PalCycle_SuperSonic | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | All three routines end the same, so we simply just replaced duplicates with branches. | ||
+ | |||
+ | ==Super Sonic in 2-Player Mode== | ||
+ | It's possible to activate Super Sonic in 2-player mode. | ||
+ | |||
+ | ===Fixing the Bug=== | ||
+ | At ''titlescreen'', right below the ''clr.w (Game_Over_2P).w'' line, add this. | ||
+ | <syntaxhighlight lang="asm"> | ||
+ | clr.b (Emerald_Count).w</syntaxhighlight> | ||
+ | |||
+ | ===The bug explained=== | ||
+ | The game isn't clearing how many emeralds you have. | ||
+ | |||
+ | ==Skipping Fade in== | ||
+ | In all versions of Sonic 3/Sonic & Knuckles Super Sonic doesn't have a fade in after he transforms. | ||
+ | |||
+ | ===Fixing the bug=== | ||
+ | Locate the '''SuperHyper_PalCycle_SuperSonic''' routine. It should look like this: | ||
+ | <syntaxhighlight lang="asm">; loc_386E: | ||
+ | SuperHyper_PalCycle_SuperSonic: ; Tails' code falls back here so the Super Flickies' palette can update | ||
+ | ; run frame timer | ||
+ | subq.b #1,(Palette_timer).w | ||
+ | bpl.w locret_37EC | ||
+ | move.b #6,(Palette_timer).w | ||
+ | |||
+ | ; increment palette frame and update Sonic's palette | ||
+ | lea (PalCycle_SuperSonic).l,a0 | ||
+ | move.w (Palette_frame).w,d0 | ||
+ | addq.w #6,(Palette_frame).w ; next frame | ||
+ | cmpi.w #$36,(Palette_frame).w ; is it the last frame? | ||
+ | blo.s loc_3898 ; if not, branch | ||
+ | move.w #$24,(Palette_frame).w ; reset frame counter (Super Sonic's normal palette cycle starts at $24. Everything before that is for the palette fade) | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | Change the $36 to $3C. | ||
+ | |||
+ | ===The bug explained=== | ||
+ | The game is simply skipping the last 6 frames of the palette. | ||
+ | |||
+ | {{S2Howtos}} | ||
+ | |||
+ | {{S3KHowtos}} | ||
+ | |||
+ | |{{PAGENAME}}]] |
Latest revision as of 03:12, 16 November 2019
(Original guide by MoDule) additional fixes by Shadow05
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.
Contents
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.
BONUS: Optimization
We can slighlty optimize the code to perform the cycling palette. Copy and paste the code below.
; ||||||||||||||| S U B R O U T I N E |||||||||||||||||||||||||||||||||||||||
; 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 PalCycle_SuperSonic_palettes ; 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
bra.s PalCycle_SuperSonic_palettes
/ rts
; ===========================================================================
; 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 PalCycle_SuperSonic_palettes ; branch, if it isn't the first frame
move.w #0,(Palette_frame).w
move.b #0,(Super_Sonic_palette).w ; stop palette cycle
PalCycle_SuperSonic_palettes:
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
; ===========================================================================
; 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?
bls.s PalCycle_SuperSonic_palettes ; 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)
bra.s PalCycle_SuperSonic_palettes
; End of function PalCycle_SuperSonic
All three routines end the same, so we simply just replaced duplicates with branches.
Super Sonic in 2-Player Mode
It's possible to activate Super Sonic in 2-player mode.
Fixing the Bug
At titlescreen, right below the clr.w (Game_Over_2P).w line, add this.
clr.b (Emerald_Count).w
The bug explained
The game isn't clearing how many emeralds you have.
Skipping Fade in
In all versions of Sonic 3/Sonic & Knuckles Super Sonic doesn't have a fade in after he transforms.
Fixing the bug
Locate the SuperHyper_PalCycle_SuperSonic routine. It should look like this:
; loc_386E:
SuperHyper_PalCycle_SuperSonic: ; Tails' code falls back here so the Super Flickies' palette can update
; run frame timer
subq.b #1,(Palette_timer).w
bpl.w locret_37EC
move.b #6,(Palette_timer).w
; increment palette frame and update Sonic's palette
lea (PalCycle_SuperSonic).l,a0
move.w (Palette_frame).w,d0
addq.w #6,(Palette_frame).w ; next frame
cmpi.w #$36,(Palette_frame).w ; is it the last frame?
blo.s loc_3898 ; if not, branch
move.w #$24,(Palette_frame).w ; reset frame counter (Super Sonic's normal palette cycle starts at $24. Everything before that is for the palette fade)
Change the $36 to $3C.
The bug explained
The game is simply skipping the last 6 frames of the palette.
SCHG How-To Guide: Sonic the Hedgehog 3 and Knuckles |
---|
Fixing Bugs |
Fix Blue Knuckles | Fix Tails' Respawn Speeds | Fix Super Sonic Bugs |
Design Choices |
Fix Scattered Rings' Underwater Physics | Edit Level Select Text & Pointers | Work with Water | Make the Slots Bonus Game Rotate Smoothly |
Adding Features |
Restore (Sonic 2) Options Menu |
|Fix bugs relating to Super Sonic]]