Wednesday, January 31, 2018

The unused boss

A while ago, I noted how Knuckles' route at the end of Marble Garden Zone 2 is pretty much complete all the way up to the boss area, including the entire rising floor section. There are considerable differences in the object layout that allow you to quickly reach the end of the section and be left with nothing to do, which is probably why it got changed.


Once you reach the boss area, you may be disappointed (though not particularly surprised) that the boss doesn't show up regardless of character, as it does in Sonic 3 & Knuckles. In fact, there are no objects placed in the boss area at all.

As it turns out however, Knuckles' boss is actually present within the game's code in a half-finished state. The following PAR codes will add the boss to Marble Garden Zone 2's object layout at its usual coordinates from Sonic 3 & Knuckles, overwriting the last starpost in Sonic and Tails' route:
063822:3E78
063824:0000
063826:B100
When the boss spawns now, though, you'll find that the screen locks itself at an earlier part of the level, and the boss is moving around far below the usual boss area. It would seem the level layout was quite different at one point.


The screen lock can be modified using the code 04A490:3D78, but since the object has several hardcoded coordinates in its movement routines, it will quickly jump outside the screen and never return. This is fixed with the following codes:
04A572:3E18
04A578:FFA8
04B0C6:0038
04B0CE:3E18
04B176:00F8
Of course, now that we've done this, the boss is actually at a negative Y coordinate when it begins coiling its drill, which the spike chain objects interpret as a large positive value, causing the first row of spikes to be deleted prematurely. The final object fixes this by performing a signed comparison, which can be replicated using the code 04AA66:6D1A.

For convenience's sake, we can push up the bottom screen boundary by setting the Camera_target_max_Y_pos RAM variable to $28, which is achieved with the following codes:
FFEE12:0000
FFEE13:0028
We can now finally check out the boss in action, which as commenter Silver Sonic 1992 points out, correctly plays the act 2 boss music unlike in Sonic 3 & Knuckles.


As we can see, the behavior of the object is very similar to that of the final version, except for being displayed in front of the level blocks, and not producing any debris when passing through them. And although the player can damage it, the boss doesn't have a death handler, so it can't actually be defeated.

Tuesday, January 30, 2018

S3A harder bosses flag

Did you know? Some of the boss objects in S3A actually contain prototype versions of the harder attack patterns which are encountered when playing the final game as Knuckles. These patterns are enabled when the RAM flag at $FA81 is set, which can be done manually using the PAR code FFFA81:0001.

If this flag is set, the boss of Angel Island Zone 1 will launch the customary three bombs before flying overhead across the arena. The difference with these bombs is that rather than self-destructing when the boss is defeated, they fall back down where they stand, which is a problem because their graphics might get overwritten in the meantime.


Two bosses exhibit behavior indistinguishable from the final game: the Hydrocity Zone 2 boss produces a spout without descending into the water, while the Marble Garden Zone 1 boss introduces the usual spiked cylinder object, as well as the slightly altered movement that goes along with it.


The final boss affected by the flag is the Launch Base Zone 2 boss, Beam Rocket. When this boss is defeated, a sprite with broken mappings suddenly attaches itself to the Egg Mobile, and as the ship flies horizontally across the screen, it leaves behind an invisible object which produces a bunch of explosions.

If any of this sounds familiar, that's because this is a very early version of Knuckles' end of level cutscene. The invisible exploding object is the bomb, and the sprite with the broken mappings is the bomb chute!


Unfortunately, no more of the cutscene seems to be implemented. The player will still remain in control, and the screen lock just won't vanish. You'll just be trapped, forever.

Monday, January 29, 2018

Drowning during bosses

An anonymous reader asks:
I don't think you've talked about it yet so I'd like to ask a few things related to the Hydrocity Act 1 boss in S3A.
Sure, go ahead.
Why does it use the Act 2 boss BGM instead?
loc_47DBA:                                      loc_69F64:
    move.l  #Obj_HCZ_MinibossLoop,(a0)              move.l  #Obj_HCZ_MinibossLoop,(a0)
    moveq   #$19,d0                                 moveq   #$2E,d0
    jsr     (Play_Sound).l                          jsr     (Play_Sound).l
                                                    move.b  #$2E,($FFFFFF91).w

locret_47DC8:                                   locret_69F78:
    rts                                             rts
Because for some reason, in S3A the act 1 boss requests the act 2 boss music.
Why, if you let the air countdown start and then get out of the water, does the S&K Act 1 boss BGM start to play? It's seems to be the only time where track 18 (I think it's 18) is used. (Apparently this glitch also happens in Act 2 but I haven't tried)
Because, to put it mildly, the code that's responsible for resuming a level's music after having previously switched to the drowning theme is a brittle piece of crap:
Player_ResetAirTimer:
    cmpi.b  #$C,$2C(a1)
    bhi.s   loc_1744C
    cmpa.w  #Player_1,a1
    bne.s   loc_1744C
    move.w  (Level_music).w,d0
    btst    #1,$2B(a1)
    beq.s   loc_17430
    move.w  #$2C,d0

loc_17430:
    tst.b   (Super_Sonic_Knux_flag).w
    beq.w   loc_1743C
    move.w  #$2C,d0

loc_1743C:
    tst.b   (Boss_flag).w
    beq.s   loc_17446
    move.w  #$18,d0

loc_17446:
    jsr     (Play_Sound).l

loc_1744C:
    move.b  #$1E,$2C(a1)
    rts
Let's go over what this code does. First, the current level's music is loaded from the Level_music RAM variable, which as we saw before, is pulled from the master playlist, based on the current zone and act. This value is then stored in the d0 register, from which it will eventually be read by the Play_Sound function called at loc_17446.

Before it can reach loc_17446 though, the value must survive a gauntlet, which begins by checking bit 1 of the player's status_secondary bitfield, which is set when the player is invincible. If the player is currently invincible, the contents of the d0 register are replaced with the value $2C, which is the ID for the invincibility music track.

Next, if the Super_Sonic_Knux_flag is set, the value of the d0 register is once again replaced with $2C, the ID for the invincibility music track. Note that this check is redundant: the player is always invincible when in their Super form.

Finally, the Boss_flag is checked. If the player is currently fighting a boss, then the game should continue playing boss music, and therefore the d0 register is overwritten with the value $18, which as the commenter duly notes, is the theme used by act 1 bosses in Sonic & Knuckles. No effort is made to pick specific themes for each boss, despite the fact that there are only two bosses in the entire game where the player can trigger the countdown music.

This is annoying for several reasons. In Sonic 3, both bosses play the act 2 boss theme, so they could've just made the code set d0 to $19 and be done with it: at least it would always resume the right track! Meanwhile, as we can see from the code near the start of this post, Sonic & Knuckles actually saves the current boss track to RAM address $FF91, but nothing in the game ever makes use of this value.

Why complicate matters, though? Just check the current act and use that information to pick one song or the other.
It's possible to drown during the score tally but in S3&K this was apparently fixed. Why does it happen and how it was fixed?
The question presupposes a falsehood: the only way to drown during the level results is by running out of air just as the results object slides into view, which works equally well in both Sonic 3 and Sonic & Knuckles.


In practice, however, the level results object prevents this by restoring both player objects' air reserves (at offset $2C of their SSTs) right as the stage clear theme begins playing:
Obj_LevelResultsWait:
    tst.w   $2E(a0)
    beq.s   loc_2CF4C
    subq.w  #1,$2E(a0)
    cmpi.w  #$121,$2E(a0)
    bne.s   locret_2CF8E                        ; Play after eh, a second or so
    move.b  #$1E,($FFFFB02C).w                  ; Reset air for Hydrocity
    move.b  #$1E,($FFFFB076).w
    moveq   #$29,d0
    jmp     (Play_Sound).l                      ; Play level complete theme
The title card object then repeats the process, ensuring the player always begins the second act with a full set of lungs.
Thanks in advance.
Thank you for the questions!

Friday, January 26, 2018

Too many tails

Reader muteKi asks:
Fun little thing I've been at least a little curious about (and got to thinking about it because of the recent posts on Big Arm / LBZ's end sequence) -- for some reason there's a regression with Tails's tails not getting removed at the end of the stage when the Death Egg falls, specifically in Tails alone mode in S3K.
So, to clarify: at the end of Launch Base Zone 2, the player automatically turns to face the Death Egg, which is falling in the background. Exclusively in Sonic 3 & Knuckles though, Tails' namesake appendages aren't hidden as his animation changes from looking up to the standing rotation, granting him an extra set of tails for the duration of the sequence.


In order to understand what's going on, let's first take a brief look at how Tails' tails operate. Each frame, the tails object looks at Tails' animation to determine which animation it should play.

For instance, when Tails is in his looking up animation (7), his tails will quickly flick up and down. Meanwhile, when he's in his walking animation (0), the object blanks itself out, because Tails' walking cycle already has the tails baked in.
; animation master script table for the tails
; chooses which animation script to run depending on what Tails is doing
Obj_Tails_Tail_AniSelection:
    dc.b    0,0     ; TailsAni_Walk,Run     -> Blank
    dc.b    3       ; TailsAni_Roll         -> Directional
    dc.b    3       ; TailsAni_Roll2        -> Directional
    dc.b    9       ; TailsAni_Push         -> Pushing
    dc.b    1       ; TailsAni_Wait         -> Swish
    dc.b    0       ; TailsAni_Balance      -> Blank
    dc.b    2       ; TailsAni_LookUp       -> Flick
    dc.b    1       ; TailsAni_Duck         -> Swish
    dc.b    7       ; TailsAni_Spindash     -> Spindash
    ...
Alright, now let's look at the changes made to the cutscene object between Sonic 3 (left) and Sonic & Knuckles (right):
loc_5117A:                                      loc_72C3C:
    move.l  #locret_511CC,(a0)                      move.l  #loc_72C68,(a0)
    clr.b   ($FFFFFA88).w                           clr.b   ($FFFFFA88).w
    clr.w   $1C(a1)                                 jsr     (Stop_Object).l
    clr.w   $18(a1)
    clr.w   $1A(a1)
    bclr    #0,4(a1)                                bclr    #0,4(a1)
    bclr    #0,$2A(a1)                              bclr    #0,$2A(a1)
    move.w  #$101,(Ctrl_1_logical).w                move.w  #$101,(Ctrl_1_logical).w
    st      (Ctrl_1_locked).w
    jsr     Create_New_Sprite
    bne.s   loc_511C4
    move.l  #loc_5182E,(a1)
    lea     (Player_1).w,a2
    move.w  $10(a2),$10(a1)
    move.w  $14(a2),$14(a1)

loc_511C4:
    lea     ChildObjDat_52010(pc),a2                lea     ChildObjDat_73806(pc),a2
    jmp     CreateChild6_Simple(pc)                 jmp     (CreateChild6_Simple).l
Okay, a lot of structural differences right off the bat. First, S3A stops the player by clearing their three velocity values in-line; S&K instead opts to call the Stop_Object function, which does the same exact thing.

The other big structural change is that the S3A object actually spawns another object to run the next part of the code at loc_5182E, while setting its own code pointer to a stub location. The S&K object cuts the middle man by setting its own code pointer directly to the next bit of code, at loc_72C68.

Beyond those points, the only difference so far is that S3A locks the player's controls by setting the Ctrl_1_locked flag.
loc_5182E:                                      loc_72C68:
    btst    #0,($FFFFFA88).w                        btst    #0,($FFFFFA88).w
    beq.w   locret_50DD2                            beq.s   locret_72C9C
    move.l  #loc_5185A,(a0)                         move.l  #loc_72C9E,(a0)
    move.l  #loc_51868,$34(a0)                      move.l  #loc_72CBE,$34(a0)
    clr.b   (Ctrl_1_locked).w
    lea     (Player_1).w,a1                         lea     (Player_1).w,a1
                                                    bsr.w   sub_72C8E
                                                    lea     (Player_2).w,a1
                                                    clr.b   $20(a0)

                                                sub_72C8E:
    move.b  #$53,$2E(a1)                            move.b  #$83,$2E(a1)
    move.b  #0,$20(a1)                              clr.b   $24(a1)
                                                    clr.b   $23(a1)

                                                locret_72C9C:
                                                    rts
Here we go. Apart from S3A now clearing the flag it had just previously set, and different flags being set on the player's object_control bitfield, the code was altered to account for player 2, which now joins player 1 during the Beam Rocket fight in a Sonic and Tails game.

Note how S3A sets the player's current animation (at offset $20 of their SST) to 0, walking, just as the standing rotation frames kick in. This has the effect of blanking out Tails' tails, according to the rules in the Obj_Tails_Tail_AniSelection lookup table. Note further how S&K tries to do the same thing just to player 2, but ends up pointing at register a0 rather than a1, clearing offset $20 of the cutscene object, rather than Tails' current animation.

Okay, so that line of code is wrong, but fixing it wouldn't solve our problem: when playing as Tails alone, the Tails object is in the player 1 slot, not the player 2 slot, and the code very specifically avoids clearing player 1's animation for some reason. (Why? Who knows.)

The weird part is that when you play as Sonic and Tails, the tails object IS blanked out properly.


The reason for this lies in the code which runs through the standing rotation frames:
loc_5185A:                                      loc_72C9E:
                                                   lea     (Player_1).w,a1
    lea     byte_52070(pc),a1                      lea     byte_7386A(pc),a2
    bsr.w   Animate_ExternalPlayerSprite           jsr     (Animate_ExternalPlayerSprite).l
    jmp     (Player_Load_PLC).l                    lea     (Player_2).w,a1
                                                   clr.b   $20(a1)
                                                   lea     byte_73874(pc),a2
                                                   jmp     (Animate_ExternalPlayerSprite).l
Clearly the developers also had no clue why Tails' tails weren't going away, so they just made it so Tails' animation gets cleared every frame from then on. Now that's class.

The correct solution is to fix the clr.b $20(a0) line and move it inside the sub_72C8E function so it affects both players.

Thursday, January 25, 2018

Why do the S monitors in S3A turn Tails into a block of mush?

Several weird things happen when Tails breaks open an S monitor in S3A. First off, he takes on this ungodly scrambled appearance, all while not receiving the customary 50 rings.


Then, although he does become invincible, his speed isn't increased, nor are rings ever drained from his count. He also doesn't glow at all, though by defeating some enemies, we can verify that Flickies still become yellow.


So what's going on here? Well, the short version is that Super Tails simply doesn't exist yet. To get into the nitty-gritty of it however, we need to take a look at the Monitor_Give_SuperSonic function in S3A.

As we previously saw, in S&K this function is broken up into three distinct code paths, and which path is taken depends on the player's current character. In S3A though, the same function is completely linear, which creates some interesting differences in its behavior when playing as Tails. S3A to the left, S&K to the right:
Monitor_Give_SuperSonic:                                Monitor_Give_SuperSonic:
    addq.w  #1,(a2)                                         addq.w  #1,(a2)
    addi.w  #50,(Ring_count).w                              addi.w  #50,(Ring_count).w
    move.b  #1,(Super_Hyper_palette_status).w               move.b  #1,(Super_Hyper_palette_status).w
    move.b  #$F,(Palette_timer).w                           move.b  #$F,(Palette_timer).w
    move.b  #1,(Super_Sonic_Knux_flag).w                    move.b  #1,(Super_Sonic_Knux_flag).w
    move.w  #60,(Super_Hyper_frame_count).w                 move.w  #60,(Super_Hyper_frame_count).w
    move.l  #Map_SuperSonic,(Player_1+mappings).w           ...
    move.b  #$81,(Player_1+object_control).w                move.b  #1,(Super_Tails_flag).w
    move.b  #$1F,(Player_1+anim).w                          move.b  #$29,(Player_1+anim).w
    move.l  #Obj_SuperSonic_Stars,(Super_stars).w
    move.w  #$A00,(Sonic_Knux_top_speed).w                  move.w  #$800,(Tails_top_speed).w
    move.w  #$30,(Sonic_Knux_acceleration).w                move.w  #$18,(Tails_acceleration).w
    move.w  #$100,(Sonic_Knux_deceleration).w               move.w  #$C0,(Tails_deceleration).w
                                                            move.l  #Obj_HyperTails_Birds,(Hyper_Sonic_stars).w
                                                            ...
                                                            move.b  #$81,(Player_1+object_control).w
    move.b  #0,(Player_1+invincibility_timer).w             move.b  #0,(Player_1+invincibility_timer).w
    bset    #Status_Invincible,status_secondary(a1)         bset    #Status_Invincible,status_secondary(a1)
    moveq   #$46,d0                                         moveq   #$9F,d0
    jsr     (Play_Sound_2).l                                jsr     (Play_Sound_2).l
    moveq   #$2C,d0                                         moveq   #$2C,d0
    jmp     (Play_Sound).l                                  jmp     (Play_Sound).l

First off, both versions of the function add 50 rings to the Ring_count RAM variable, no change there. In S3A however, Tails actually uses the Ring_count_P2 variable, a holdover from Sonic 2's 2P mode in which each character kept track of their own ring count. This accounts for Tails' unchanging ring count.

Next, S3A replaces player 1's mappings with the mappings for Super Sonic. In S&K, Sonic is the only character whose sprites change for his super form; S3A assumes that only Sonic will ever turn super, therefore the player should always receive the new mappings. This results in Tails' corrupted appearance.

Only S&K sets what would become the Super_Tails_flag, enabling Super Tails' rotating palette. However, both games set the Super_Sonic_Knux_flag, which enables Super Sonic's rotating palette and makes Flickies turn yellow.

S3A only writes Super Sonic's speed values to Sonic's speed variables, leaving Tails' values unaffected. And finally, as we've seen before, rather than play the regular transformation sound, S3A monitors play a whistling sound instead.


The sprite corruption can be avoided with debug mode. When the player enters debug mode, their mappings are saved to RAM address $FFCA, so they can be restored upon exiting the mode. By waiting out the transformation sequence in debug mode, Tails' old mappings are kept safe at $FFCA and will undo the damage done by the S monitor.

Wednesday, January 24, 2018

Have Tails activate star posts as quickly as possible.

Reader Brainulator9 asked:
Do you plan to discuss avoiding Tails getting stuck in passageways or getting stuck in traps by activating a Star Post or entering a Special Stage as quickly as possible?
Brainulator9 is talking about a passage that appears in page 27 of the US Sonic 3 manual, directly before the infamous "diabolical traps" line. Here's what it reads:
In the IceCap Zone and throughout the game, avoid having Tails get stuck in passageways or get caught in traps. Have Tails activate Starposts or enter into a Special Stage as quickly as possible.
What could this oddly specific phrasing be referring to? If the "diabolical traps" thing is any indication, this is likely a bug acknowledgment and its suggested workaround.

Well, there is indeed an S3A-exclusive bug regarding Tails in Icecap Zone, which occurs roughly 100% of the time. You see, Icecap Zone 1 is normally a vertically-wrapping level. However, when you first start the level as Tails, the level size is modified in order to have Tails fall from the top of the screen.


Evidently, the developers never bothered playing past this sequence, because at no point is the level size ever restored back to its regular state. As a result, when Tails reaches the point where the level should wrap upon itself, he's instantly killed by a mysterious, unseen force.


The workaround? Well, the level size is only supposed to change when Tails first enters the stage, so the relevant code only runs if the Last_star_post_hit variable is zero. The variable is set to a non-zero value when the player activates a star post or enters a special stage, so doing either will prevent the bug from reoccurring after the player respawns.

Tuesday, January 23, 2018

Tails' extra sprites

If you happened to follow my suggestion of loading a savestate of the S3&K level select in Sonic & Knuckles, you'll find that you can now circumvent the restrictions imposed by the S&K alone flag, and are able to play the game as Tails.

Note that to prevent the game from crashing, Japan mode must be enabled in the S3&K savestate. This is because the game tries to load Tails' lives counter from the Sonic 3 ROM, making the Nemesis decompressor choke on the garbage data. As we previously saw though, the Miles lives counter is part of the Sonic & Knuckles ROM, so that works out.


When you do so, you might notice that Tails appears to be completely invisible. Sonic & Knuckles normally gets its Tails art from the Sonic 3 ROM, and since it's missing, the DMA transfers from that address space simply result in a string of zeroes, which translate to transparent pixels.

Knowing this, you may be surprised to learn that certain gimmicks in the later stages cause Tails to temporarily become visible again, such as the 3D pathways in Lava Reef Zone:


The reason for this lies in the Tails_Load_PLC function. When Tails' mapping frame is greater than $D1, the standard ArtUnc_Tails uncompressed art block is swapped out for the ArtUnc_Tails_Extra art block:
Tails_Load_PLC:
    moveq   #0,d0
    move.b  $22(a0),d0

Tails_Load_PLC2:
    cmp.b   ($FFFFF7DE).w,d0
    beq.s   locret_15CCE
    move.b  d0,($FFFFF7DE).w
    lea     (PLC_Tails).l,a2
    add.w   d0,d0
    adda.w  (a2,d0.w),a2
    move.w  (a2)+,d5
    subq.w  #1,d5
    bmi.s   locret_15CCE
    move.w  #$D400,d4
    move.l  #ArtUnc_Tails,d6
    cmpi.w  #$1A2,d0
    blo.s   loc_15CA6
    move.l  #ArtUnc_Tails_Extra,d6
It turns out the ArtUnc_Tails_Extra art block is actually part of the Sonic & Knuckles ROM, which is why Tails suddenly becomes visible when his mapping frame is set to a value higher than $D1. Unsurprisingly, those mapping frames don't exist in Sonic 3: they were added specifically for Sonic & Knuckles.

The implication here is that any gimmicks making use of these frames hadn't yet been designed at the time of Sonic 3's release, which is why the frames themselves are missing. On the other hand, over at Flying Battery Zone...


...Tails remains invisible in his monkey bar frames, indicating that those are read from the Sonic 3 cartridge, even when the two games are locked-on. Just another sign that Flying Battery Zone was all but meant for the first half of Sonic 3.

Monday, January 22, 2018

Japan mode

All three main series Sonic games have a routine which looks up the region code of the Mega Drive it's running on, and saves that information to a RAM variable when the game first starts up.

Besides handling the timing differences between PAL and NTSC systems, this variable is also used at various points to determine whether the game is running on a Japanese system, and if so, remove the trademark symbols from all Sonic and SEGA logos, since those trademarks don't apply in Japan.


This variable also controls the name which Tails goes by. Sonic Team originally named the character Miles Prower, and he was only given the "Tails" moniker at the request of Sega of America's marketing department. Despite this, Sonic 2's programmers made it so the original name is still used when the game is played on a Japanese system, a trait that was later carried over into Sonic 3.


In S3A however, this feature seems to be unfinished. No trademark symbols are removed when playing the game on a Japanese console, and Tails' name is only changed in the level results screen, and not in his lives counter:


Looking at the S3A ROM reveals why: the graphics for the alternate counter seem to be missing entirely! As a result, to finish this feature for Sonic 3 & Knuckles, the developers had to include the Miles lives counter within the S&K ROM.

Friday, January 19, 2018

You're going to pay for this!

An anonymous commenter asked:
Since you have covered the Surfboard and the Rotating spheres, could you talk about the "Eggman flying away" sprites?
Sure. At ROM offset $1796EE, there's this block of uncompressed art which doesn't look like anything at first, but if you spread out the tiles horizontally, apply Sonic's palette and then tilt your head sideways, they start looking a bit familiar...


Okay, so these sprites definitely resemble Eggman riding the Egg Mobile, but why are they sideways and stretched out like that? That's because they're actually meant to be rendered using a software scaling routine, the same one used by the Marble Garden Zone 2 boss, as well as the Egg Robo enemies in Sonic & Knuckles.

It turns out that, much like the surfboard intro before it, there's actually an unused, fully-programmed object which uses these sprites, exclusively in the Sonic 3 ROM at offset $51B4C.

No other code references this object, so it's impossible to know for sure when it was meant to appear, but its placement in the ROM directly after the Launch Base Zone 2 bosses, but before the exploding Death Egg object seems to indicate that it was meant for the game's ending sequence.

By using the PAR code 05165A:04F2, we can force the object to spawn as the sky begins to brighten:


As can be seen in the above video, the fleeing Eggman object actually matches the vertical movement of the exploding Death Egg quite well. Perhaps at one point, the Death Egg was further to the left on the screen, and this scene showed Eggman heading towards it, setting the stage for the second part of the story.

Thursday, January 18, 2018

Keep your arms inside at all times

One version difference that a lot of people latch onto is Launch Base Zone's final boss, Big Arm, and the behavior of its grabbing attack when the player is Super. In Sonic & Knuckles, this just bounces the player off the ground with a spring sound, but in Sonic 3, it causes Super Sonic to drop all of his rings and thus revert back to normal Sonic.


The reason for this is boring: in Sonic 3, regardless of whether the player is Super or not, the HurtCharacter function is called, which uh, hurts the character. Meanwhile, Sonic & Knuckles added a check for invincibility (not Super!) that calls up a separate code path if the appropriate bit is set. This suggests the original behavior was just an oversight.
loc_510AE:                          loc_74664:
    move.w  d0,$18(a0)                  move.w  d0,$18(a0)
    move.w  #-$600,$1A(a0)              move.w  #-$600,$1A(a0)
    move.w  #$3F,$2E(a0)                move.w  #$3F,$2E(a0)
                                        btst    #Status_Invincible,(Player_1+status_secondary).w
                                        bne.s   loc_7468C
    movea.l a0,a2                       movea.l a0,a2
    lea     (Player_1).w,a0             lea     (Player_1).w,a0
    jsr     (HurtCharacter).l           jsr     (HurtCharacter).l
    movea.l a2,a0                       movea.l a2,a0
    rts                                 rts
                                    
                                    loc_7468C:
                                        lea     (Player_1).w,a1
                                        clr.b   $2E(a1)
                                        neg.w   d0
                                        move.w  d0,$18(a1)
                                        move.w  #-$400,$1A(a1)
                                        bset    #1,$2A(a1)
                                        bclr    #3,$2A(a1)
                                        clr.b   $40(a1)
                                        clr.b   $3D(a1)
                                        move.b  #2,$20(a1)
                                        move.b  #2,5(a1)
                                        moveq   #-$4F,d0
                                        jmp     (Play_Sound_2).l
There's another, more interesting oversight with the Big Arm boss, which wasn't fixed in Sonic & Knuckles. It requires a second player, however in Sonic 3, the Tails object is deleted during the Egg Mobile ride, and in Sonic & Knuckles, only Knuckles fights the boss!

We can force the bug to occur in Sonic 3 by using the PAR code 05A8CC:6010, which prevents Tails from despawning at the end of Launch Base 2. Get the boss down to 1 HP, then let it grab you while having Tails deal the final hit.


When this happens, Sonic gets stuck in mid-air, at least until the ending sequence starts. The effects are more heinous in Sonic & Knuckles because a regular stage clear sequence was added to the level, which can't start because Sonic is not touching the floor.

What's interesting about this oversight is that both Sonic 3 and Sonic & Knuckles have code in place to prevent it. If the byte at offset $30 of Big Arm's SST is clear, then it calls the Restore_PlayerControl function, which clears the player's object_control flag and resets their animation:
loc_51D78:                                  loc_7506E:
    tst.b   $30(a0)                             tst.b   $30(a0)
    bne.s   loc_51D82                           bne.s   loc_7507A
    jsr     Restore_PlayerControl(pc)           jsr     (Restore_PlayerControl).l
                                            
loc_51D82:                                  loc_7507A:
    jmp     (SaveGame).l                        lea     ChildObjDat_75186(pc),a2
                                                jmp     (CreateChild1_Normal).l
Problem is, the byte at offset $30 is set when the boss is holding the player, which means the Restore_PlayerControl function is only called in the exact wrong situation: when the boss isn't holding the player.


This can be seen in both Sonic 3 and Sonic & Knuckles: upon dealing the final hit to the boss, the player's animation is reset, which results in them falling to the ground in their standing frame.

Wednesday, January 17, 2018

They just didn't care, part 2

Sonic 3 somehow managed to revive a bug from the prototype versions of Sonic 2: holding down a jump button while a level is loading causes the button press to be buffered until the player gains control of the character, making them jump as soon as the level starts.


As reader Brainulator9 points out, this works even when the player object spawns in mid-air, allowing you to do things you were never supposed to, such as turning into Super Sonic within a bonus stage.

Luckily, this was fixed in Sonic & Knuckles for all three player objects. However, there's one instance in which the game doesn't spawn the regular player objects. ...Yep, you know it.


Don't you just love Competition mode?

Tuesday, January 16, 2018

They just didn't care

There are three separate RAM flags which the game uses to track the state of debug mode: the debug cheat flag, the debug mode flag and debug placement mode.

  • The debug cheat flag is set when the player correctly inputs the debug cheat code, and is only cleared by a hard reset.
  • The debug mode flag is set when the player holds down the A button during level load, but only after the debug cheat flag has been set. It is cleared at the SEGA screen.
  • Finally, debug placement mode is set when the player presses the B button during a level, but only after the debug mode flag has been set. It is cleared when the player presses the B button again.

Sonic 3 and Sonic & Knuckles disagree on a few things regarding these flags. In Sonic 3 for instance, the debug cheat flag is set at the same time the level select is unlocked, but it has a separate cheat code in Sonic & Knuckles.

In particular though, just as with past Sonic games, Sonic 3 activates the debug HUD as soon as the debug mode flag is on, whereas Sonic & Knuckles waits until debug placement mode is also on, something which I've used extensively in the past to easily differentiate between S3A and S&K screenshots.


Of course, once the player exits object placement mode, the game must now restore the regular HUD. It can't just draw the score counter over the debug display, because any empty digits in the score counter would leave behind part of the debug display; it needs to wipe the whole thing clean first and draw the counters back in afterwards.

To that effect, the object placement exit code calls the HUD_DrawInitial function, which is used to initialize the HUD at level load. This is why the timer temporarily reverts to 0:00 until the next second rolls over.
    moveq   #0,d0
    move.w  d0,(Debug_placement_mode).w
    move    #$2700,sr
    jsr     (HUD_DrawInitial).l
    move.b  #1,(Update_HUD_score).w
    move.b  #-$80,(Update_HUD_ring_count).w
    move    #$2300,sr
    lea     (Player_1).w,a1
    move.l  ($FFFFFFCA).w,$C(a1)
    move.w  ($FFFFFFCE).w,$A(a1)
Unfortunately, nobody bothered to test this change in Competition mode. The regular HUD is never meant to be shown here, and so when HUD_DrawInitial is called, something goes wrong, and the player object is never properly restored, permanently removing them from the game.


This oversight definitely makes my Top 5 Stupidest Bugs in Sonic 3 list, since it makes debug mode totally useless in the Competition stages. Not only can you never interact with the items you place, but you can't even use debug mode's most basic functionality, free movement, because you can't place yourself back at the new position. Sheesh.

Monday, January 15, 2018

No fun allowed

In S3A only, if you defeat the Blastoid enemies that are placed directly over the special stage rings in Hydrocity Zone 1, you can actually fall through a hole in the floor directly into the ring's secret room.


For whatever reason, in Sonic 3 & Knuckles, these holes were hastily patched up with invisible collision objects, and as a result, Knuckles can't glide-land or climb up on this ledge properly.


Then again, in S3A you can defeat Blastoids by just walking into them, likely because they don't use the regular enemy collision type. So uh, pick your poison, I guess.

Friday, January 12, 2018

Can't escape!

Reader Silver Sonic 1992 asks:
Also, do you know what the "Special Stage 1" is for in the level select? Is it remnant of the super emerald special stages?
Doesn't look like it, but it's interesting nonetheless.

The special stage entries in the level select actually refer to an invalid zone ID, zone $40. When this zone is selected, a special handler prevents the invalid zone from getting loaded, and instead modifies the value of the Game_mode RAM variable, setting it to $2C for act 1, and $34 for act 2:
LevelSelect_PressStart:
    move.w  ($FFFFFF82).w,d0
    add.w   d0,d0
    move.w  LS_Level_Order(pc,d0.w),d0
    bmi.w   LevelSelect_Return
    cmpi.w  #$5555,d0                   ; This is used for the S&K zones
    beq.w   LevelSelect_Main
    cmpi.w  #$4001,d0                   ; Is Special Stage 2 selected?
    beq.w   LevelSelect_SpecialStage    ; If so, branch
    cmpi.w  #$4000,d0                   ; Is Special Stage 1 selected?
    bne.w   LevelSelect_StartZone       ; If not, branch
    move.b  #$2C,(Game_mode).w
    rts
; ---------------------------------------------------------------------------

LevelSelect_SpecialStage:
    move.b  #$34,(Game_mode).w
    rts
Once this variable is changed, the game instantly switches away from the current mode, $28 (level select) to whichever one got picked above. In Sonic & Knuckles, mode $2C refers to the title screen for the Blue Sphere bonus game, but in S3A, both it and $34 point at the special stage game mode.
GameModes:
    dc.l Sega_Screen                ;   0
    dc.l Title_Screen               ;   4
    dc.l Level                      ;   8
    dc.l Level                      ;  $C
    dc.l JumpToSegaScreen           ; $10
    dc.l ContinueScreen             ; $14
    dc.l JumpToSegaScreen           ; $18
    dc.l LevelSelect_S2Options      ; $1C
    dc.l S3Credits                  ; $20
    dc.l LevelSelect_S2Options      ; $24
    dc.l LevelSelect_S2Options      ; $28
    dc.l SpecialStage               ; $2C
    dc.l SpecialStage               ; $30
    dc.l SpecialStage               ; $34
    dc.l Competition_Menu           ; $38
    dc.l Competition_PlayerSelect   ; $3C
    dc.l Competition_LevelSelect    ; $40
    dc.l Competition_Results        ; $44
    dc.l SpecialStage_Results       ; $48
    dc.l SaveScreen                 ; $4C
    dc.l TimeAttack_Records         ; $50
At the end of the special stage mode's main loop, the Game_mode RAM variable is checked. If it's anything other than $34, then instead of jumping back to the start of the loop, execution progresses into a second loop, in which the screen progressively fades to white. The equivalent code can be found at loc_851A in the S&K disassembly.
    cmpi.b  #$34,(Game_mode).w
    beq.s   loc_77D2
    tst.w   (Demo_mode_flag).w
    beq.s   loc_7828
    move.b  #0,(Game_mode).w

loc_7828:
    move.w  #$3C,(Demo_timer).w
    move.w  #$3F,(Palette_fade_info).w
    clr.w   ($FFFFF794).w

loc_7838:
    move.b  #$1C,(V_int_routine).w
    bsr.w   Wait_VSync
    jsr     (Process_Sprites).l
    bsr.w   Animate_SSRings
    bsr.w   sub_8C1A
    jsr     (Render_Sprites).l
    jsr     Draw_SSSprites(pc)
    bsr.w   sub_8B9A
    bsr.w   sub_89E2
    bsr.w   Process_Nem_Queue_Init
    jsr     (Process_Kos_Module_Queue).l
    subq.w  #1,($FFFFF794).w
    bpl.s   loc_787C
    move.w  #2,($FFFFF794).w
    bsr.w   Pal_ToWhite

loc_787C:
    tst.w   (Demo_timer).w
    bne.s   loc_7838
Now, if the debug cheat is enabled, then pressing the A button while the game is paused makes the game jump directly to the level select. This is achieved by setting the Game_mode variable to $28, the value for the level select mode:
Pause_Loop:
    move.b  #$10,(V_int_routine).w
    bsr.w   Wait_VSync
    tst.b   (Slow_motion_flag).w
    beq.s   Pause_NoSlowMo
    btst    #6,(Ctrl_1_pressed).w
    beq.s   Pause_ChkFrameAdvance   ; branch if A isn't pressed
    move.b  #$28,(Game_mode).w
    nop
    bra.s   Pause_ResumeMusic
And that's what that check in the special stage mode was for. When the Game_mode variable changes from its regular value of $34, the game knows that it has to exit the special stage and go someplace else (for instance, the level select) so it begins fading to white, and once the fade is over, it jumps to the new game mode.

Except that, when we choose the "special stage 1" option, the Game_mode variable starts off with the value $2C. This calls up the special stage mode all the same, except the check fails immediately, triggering a fade out. Once the fade is over, the game jumps to the "new" game mode, $2C... which calls up the special stage mode all over again.

The cherry on top of the cake is this code at the end of that second loop:
loc_787C:
    tst.w   (Demo_timer).w
    bne.s   loc_7838
    addq.b  #1,($Current_special_stage).w
    cmpi.b  #7,(Current_special_stage).w
    bcs.s   locret_7894
    move.b  #0,(Current_special_stage).w

locret_7894:
    rts
This code ensures that the current stage value is properly incremented when the player exits the stage using the debug shortcut. That way, next time a special stage is accessed, the game will advance to the next stage in line, just like if the previous one had completed normally.

Inadvertently, this causes the "special stage 1" bug to cycle through each stage in an infinite loop. In Sonic & Knuckles, not only was the bug fixed, but the debug shortcut now leads to the SEGA screen, so the above code was removed.

Thursday, January 11, 2018

Welcome to the secret special stage

A while ago, I mentioned how there's this obscure feature of debug mode wherein if you hold down the A button while a special stage is loading, which stage is actually chosen is determined by the value of the Sound Test option rather than which emeralds have been collected. This wraps around every eight values, so $00 and $08 both yield stage 1, etc.

For this reason, whenever the Sound Test value ends in either a 7 or an F, the player is taken to a secret special stage, stage 8. Curiously, despite the fact that this stage is never seen during normal gameplay, its palette is actually different between Sonic 3 (left) and Sonic & Knuckles (right):


If you manage to clear this excruciatingly long stage, you're rewarded with the nonexistent eighth chaos emerald, which shares its color with the Flying Battery Zone-themed stage 5:


However, when you clear the same stage in Sonic & Knuckles, the emerald you're awarded is yellow instead:


This is interesting because in Sonic 3, the yellow emerald is the one obtained in the Hydrocity Zone-themed stage 2! In Sonic & Knuckles, it was changed so that stage 2's emerald is orange instead:


So what is the reason for this whole switcheroo? Well, it turns out, the super emeralds from Sonic & Knuckles reuse the same set of palettes as the regular emeralds.


What probably happened is the yellow super emerald was made orange in order to match its sprite from Hidden Palace Zone, and this change retroactively affected the color of the regular emerald as well. Then, since it didn't have a unique color of its own, the eighth emerald was given the (now unused) yellow palette.

Wednesday, January 10, 2018

The hidden homage

While on the subject of special stages, have you ever noticed how the palettes for the special stages in S3A are loosely based on those of game's main levels?


Stage 1 forgoes the color of Angel Island Zone's grass, but the blue skies make it clear, this is act 1 we're talking about. Meanwhile, stage 2 seems to reference Hydrocity Zone's underwater colors rather than its above-water palette:


Stage 3 is based off Marble Garden Zone, but because it's missing both the green grass and the grayish-purple marble colors, you wouldn't really know that from just looking at it:


On the other hand, stage 4's red and white motif under a black sky is clearly reminiscent of Carnival Night Zone:


Stage 6's bright blue and aqua colors are based off the act 2 background of Icecap Zone:


Finally, stage 7 celebrates the checkerboards prevalent throughout Launch Base Zone, substituting white for the yellow sandstone color seen in both acts, but characteristic of the brick buildings from act 1:


Similar connections exist between Sonic & Knuckles' special stages and that game's levels, though generally speaking, they're not nearly as obvious.

But wait, I completely skipped over stage 5! Turns out, it's a clear match for the interior sections of Flying Battery Zone:


This further cements the fact that Flying Battery Zone was originally Zone 5, and to my knowledge it's the only instance in which this fact is exposed to the player without their use of cheat codes.