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.