Showing posts with label Competition. Show all posts
Showing posts with label Competition. Show all posts

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, October 23, 2017

Too many colors

The VDP's color RAM is working overtime while displaying Competition mode's level select screen. When first entering this screen, the first palette line holds colors for the background and menu elements, the second and third lines contain Sonic and Knuckles' palettes respectively, and the fourth line is used to display the grayscale level previews.


Then, once the characters have been selected and their sprites scrolled off-screen, Knuckles' colors are replaced with the colors for the currently highlighted level, while Sonic's palette continues being used to display level names and the best time text on the right hand side. The color palettes for the level previews have to be carefully designed in order to match the single grayscale palette used by every preview picture.


There's something subtly wrong with the grayscale palette itself, though. Do you see it?


Looking at the preview picture for Azure Lake, both the clouds and the part of the sky near the mountains appear pitch black under the grayscale palette.


If we examine the colors in the palette, we find the second color is $EEE, white, the fourth color is $AAA, light gray, but the color between those two is $000, black. If we replace that color with the more logical $CCC light gray, Azure Lake's grayscale preview picture looks a lot better:


Perhaps most dramatic is Desert Palace's preview picture. The original version (center, below) almost resembles an ink blot when compared with what it's actually supposed to look like:

Thursday, September 28, 2017

Rings are fundamentally broken in 2P mode

I know what you're thinking. "There are rings in Competition mode!?" Yes, starting from lap 4, a ring appears as an item on the item wheel, and on lap 5, there's even an enemy there which can cause you to take damage and drop it!

If you actually do so though, you'll find that the ring you drop is massively hosed. First off, it takes on the appearance of a garbled goal post. Then, even if player 2 happens to pick it up, the ring will go right back into player 1's item slot.


It's even worse when player 2 drops it. The ring doesn't disappear from player 2's item slot, and when someone picks it back up, it once again goes right into player 1's item slot, effectively duplicating it.


So what's going on? Well, several things, really. The bouncing ring object used in 2P mode is the same one used in 1P mode, which does not adequately account for a second player. As a change of pace, I thought it might be interesting to not only explain where things go wrong, but also to work out an adequate solution. Let's get to it!

When deciding how many ring objects to spawn, the ring-spawning object runs the following code fragment:
loc_1A68C:
    movea.l a0,a1
    moveq   #0,d5
    move.w  (Ring_count).w,d5
    tst.b   $3F(a0)
    beq.s   loc_1A69E
    move.w  (Ring_count_P2).w,d5
    ...
Apparently, when the ring-spawning object is initialized, byte $3F of its SST is set depending on which player took a hit, and the above code uses that to determine whose ring count should be considered. Surprisingly, a short while later, the same object uses this code to clear out the player's ring count:
loc_1A738:
    move.w  #$FFB9,d0
    jsr     (Play_Sound_2).l
    move.w  #0,(Ring_count).w
    ...
This explains why when player 2 takes damage, the ring doesn't disappear from their item slot. The game is attempting to remove it from player 1's item slot! Collecting the ring (thus duplicating it) and then taking damage again (say, on the underside of Chrome Gadget's moving platform) confirms this.

We can fix it by simply checking byte $3F again, as was done before:
Obj_2PRing_Done:
    move.w  #$FFB9,d0
    jsr     (Play_Sound_2).l
    tst.b   $3F(a0)
    beq.s   .notP2
    move.w  #0,(Ring_count_P2).w
    bra.s   Obj_2PRing_Main
; ---------------------------------------------------------------------------
.notP2:
    move.w  #0,(Ring_count).w
    ...
The reason dropped rings look like a garbled mess is because in Competition mode, the ring art is loaded to a different VRAM address. Furthermore, in all of the Competition levels, the ring colors are actually in palette line 3, since line 1 is already holding Knuckles' palette.

We can fix that by changing the line which sets the object's art_tile from this:
    move.w  #$A6BC,$A(a1)
to this:
    move.w  #$E3C6,art_tile(a1)
Then, as we muck around in the object's init code, we come across this line:
    move.b  #$47,$28(a1)
At offset $28 is the object's collision_flags attribute, and if we look at the collision type encoded in the top two bits, we find that it's set to 01, just as expected.

When the bouncing ring object is touched, it will be set to routine 4, which looks like this:
loc_1A7C2:
    addq.b  #2,5(a0)
    move.b  #0,$28(a0)
    move.w  #$80,8(a0)
    bsr.w   GiveRing
    ...
The GiveRing function gives player 1 a single ring, adding in extra logic to also award pending 1ups, and to make sure the counter doesn't overflow. Once again, the object is blindly affecting player 1's ring count, without taking into account who actually collected the ring. To fix this, we can change the object's collision type to one which distinguishes between players. Touch_Special seems to fit the bill.
    move.b  #$C7,collision_flags(a1)
Then, in the object's main routine we check (and clear) the collision_property attribute via the canonical method. Note that we are now forced to add the invulnerability timer check on the object's side of things:
Obj_2PRing_Main:
    bclr    #1,collision_property(a0)
    beq.s   .notP2
    cmpi.b  #90,(Player_2+invulnerability_timer).w
    bhs.s   .notP2
    bsr.w   GiveRing_P2
    bra.s   .collect
; ---------------------------------------------------------------------------
.notP2:
    bclr    #0,collision_property(a0)
    beq.s   .keepbouncing
    cmpi.b  #90,(Player_1+invulnerability_timer).w
    bhs.s   .keepbouncing
    bsr.w   GiveRing

.collect:
    addq.b  #2,routine(a0)
    move.b  #0,collision_flags(a0)
    move.w  #$80,priority(a0)
    ...
Having done the necessary changes, we build the ROM, load it up on our emulator of choice, and verify that everything works properly now.


Also note that there is now a race condition: if both players touch the ring on the exact same frame, the above code will award the ring to player 2. Curious, I decided to check out how Sonic 2 handles this scenario. Here is the relevant code from Sonic 2's version of Touch_ChkValue:
    move.w  (MainCharacter+invulnerable_time).w,d0
    tst.w   (Two_player_mode).w
    beq.s   +
    move.w  invulnerable_time(a0),d0
+
    cmpi.w  #90,d0
    bhs.w   +
    move.b  #4,routine(a1)  ; set the object's routine counter
    move.w  a0,parent(a1)
+
    rts
WOW, that's a lot simpler than what I came up with. The RAM address of the player who touched the ring is copied into the "parent" attribute of the ring object's SST. Later, all the ring has to do is check the attribute to find out grabbed it:
CollectRing:
    tst.b   parent+1(a0)            ; did Tails collect the ring?
    bne.s   CollectRing_Tails       ; if yes, branch
    ...
What's interesting is how the race condition is still there, and happens to have the same exact outcome. When player 2 processes the collision response list, they will overwrite the "parent" attribute set by player 1, laying claim to the ring.

Wednesday, September 20, 2017

Three more goofs in the Competition levels

Speaking of oddities present in the Competition levels, here are three more goofs I've come to notice over the years:


In Azure Lake, one of the blocks in this overhanging ledge doesn't have any collision assigned to it. The height mask is actually set properly, it's just that the block doesn't have either solidity bit set.


Balloon Park has a bunch of identical-looking flags waving in the background, and exactly one of them is blowing in the opposite direction of the others. This probably happened accidentally when mirroring a bunch of level blocks in order to fill out the rest of the background plane.

Finally, also in Balloon Park, my favorite. First off, in Competition mode, Sonic's palette is loaded to the would-be player palette in line 0, whereas Knuckles' palette is loaded to the would-be enemy palette in line 1.


Meanwhile, although the bumper objects in Balloon Park are set to use colors from palette line 1 (left below), I find that they end up looking much better when they're changed to use line 0 (right):


Here's my theory. The bumper's graphics were originally designed to use Sonic's palette, but the object ended up being programmed to use the enemy palette, like most stage-specific objects do. The error was never caught because unlike most situations, Sonic's palette and the enemy palette are almost completely identical, and as such the bumper doesn't look particularly wrong when the wrong one is applied.

Tuesday, September 19, 2017

Wall running woes, part 2

In standalone Sonic 3, the design flaws in the level collision code are even more pronounced than in Sonic & Knuckles. While running on the ceiling, or a wall on the left side, level collisions are completely ignored, so you'll sail right through solid parts of the level. Obviously, this can easily cause you to become irreparably stuck in a wall- er, I mean, watch out for Dr. Robotnik's diabolical traps.


Unfortunately, although otherwise quite welcome, the changes to the algorithm in Sonic & Knuckles caused a breaking change in one of the Competition stages. (left: Sonic 3 alone; right: Sonic 3 & Knuckles)


In Sonic 3 & Knuckles, you can no longer backtrack through the loop in Desert Palace after you cross it. The reason for this becomes evident once we open up the level in SonLVL and take a gander at the collision data:


The loop is comprised entirely of blocks solid from all sides! The only reason the thing worked in the first place was due to Sonic 3's lax attitude while running on left-hand side walls. Changing the collision on the walkway threading the loop from fully solid to top-only would fix this issue.

Tuesday, August 8, 2017

Welcome to the next level

Internet's out, so I'm posting this from my phone. Earlier today we got our first look at Sonic Mania's competition mode. Through sheer coincidence, this lines up with a subject I was saving for a rainy day.


The Sonic 3 title card object has unused title card definitions for Competition mode, hidden behind a flag which is never enabled. Normally they don't display correctly due to VRAM conflicts with the level graphics, though; the above video is what they look like once those conflicts are resolved.

Friday, June 2, 2017

Direct addressing of KosM pieces: the bane of novice Sonic 3 hackers

Today's topic is something I personally fell prey to when I started hacking. Sonic Retro user VMordecai semi-recently ran into a problem while working on their Sonic 3 hack:
I've encountered some bugs on my way, and I have no idea what to do with them yet. Like this sprite for special stage, that consists of two files. Second half doesn't read properly.
The sprites in question are compressed in Kosinski Moduled format. Recall that in the KosM format, data is first split into $1000 byte-long pieces, and then each piece is compressed using regular Kosinski compression. Jet's continue icon (Sonic's in the original) happens to straddle the $1000 byte boundary:


However, if you look through the files in the disassembly, you won't actually find these graphics in the KosinskiM Art folder. The definition for ArtKosM_SSResults in sonic3k.asm reveals why:
ArtKosM_SSResults:
    dc.b  $12, $A0
ArtKos_SSResultsGeneral:
    binclude "General/Special Stage/Kosinski Art/SSResults General.bin"
    even
ArtKos_SSResultsTKIcons:
    binclude "General/Special Stage/Kosinski Art/SSResults Tails Knuckles Icons.bin"
    even
Holy goodness, what is happening here?

Recall that in the KosM format, data is first split into $1000 byte-long pieces, and then each piece is compressed using regular Kosinski compression. This means that each individual piece is a actually a valid Kosinski archive, and as such can be decompressed directly by the regular Kosinski decompressor!


And that's exactly what happens in the Competition results screen. You see, some clever programmer thought, "gosh, we already have the continue icons from the Special Stage results, so I'll just use those. But I don't need the letters or the Chaos Emeralds, so I'll decompress the individual KosM pieces to RAM and then copy the fully assembled icons from RAM to VRAM."

The solution to this madness is to compress the whole thing directly in KosM format and compare it to the split version: there's usually some padding that needs to be added at the end of the split version's first piece.

The better solution is to give the Competition screen its own set of icons. If it hurts when you do that then don’t do that.

Wednesday, May 24, 2017

Now you know your ABCs

The five stages in Competition mode have a bit of a cute naming scheme: the first letter in each of their names make up the first few letters of the alphabet.


I guess if there were a sixth stage, it would've been called Final *something*, but then you'd have to explain how the characters escaped from the "Endless" Mine...

Anyway, the interesting bit I wanted to mention is that, going by the internal level order, Desert Palace actually comes before Chrome Gadget! This would indicate the two stages were swapped around to facilitate the naming convention, and if so, it's pretty surprising that they made it all work out without having to reorder any more levels.


One reasoning for the slightly esoteric nomenclature might be for the score card at the end of Grand Prix mode. Letters work just as well as numbers for identifying a small quantity of stages, and the added flair helps Competition stand out from the main game. Note how even the player characters are labelled "A Player" and "B Player", rather than the more traditional Player 1 and Player 2.

Friday, May 12, 2017

Tearing it apart

Once again, the VDP does something that shouldn't be possible. In Competition mode, the screen is split down the middle and both halves scroll independently.


The VDP can only scroll entire rows or columns, so when the entire screen is split horizontally like this, it should be impossible for one half to scroll vertically without bringing the other half along with it.

Once again, much like we saw before, the software is cheating the hardware. Halfway through drawing the screen, the game disables the video output and quickly replaces all the background and sprite data. This takes some time, leaving a gap across the middle of the screen where nothing is drawn. Then, it enables the output again, and the VDP dutifully resumes drawing the screen, unaware that someone swapped the pictures while the lights were out.


Competition mode isn't the only place where this trick is used, though. The flying ships at the end of Angel Island Zone and Mushroom Hill Zone are actually rendered using the foreground layer, so if left unchecked, they would move the floor up and down as they bob in the air.

Unlike in Competition mode, players may cross the screen boundary when they jump, so the strategy here is to replace only the Plane A vertical scroll data, during the gap between the ship and the floor where Plane A has nothing to draw anyway. This allows sprites and the background to seamlessly extend across the entire screen, but as a consequence, the attached bomb and propeller sprites have to be manually moved up and down in order to keep them synchronized with the rest of the ship.