Showing posts with label Marble Garden Zone. Show all posts
Showing posts with label Marble Garden Zone. Show all posts

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.

Thursday, December 28, 2017

Roll height bugs, part 2

Objects that take control away from the player generally fail to handle the case in which Sonic is spinning. For instance, with the tube elevators I talked about last time, when you spindash directly into one, Sonic's feet will dig into the bottom of the capsule because his hitbox size and Y position aren't reset to their usual values.


This sort of oversight is normally harmless because Sonic only sinks into the object by about five pixels, a short enough distance that he ends up getting pushed out in the same direction he came from.

Play around with fire however, and eventually you'll get burned. When the player rides on one of Marble Garden Zone's spinning tops, it executes this interesting snippet of code:
loc_35048:
    move.b  $44(a1),d0
    addi.b  #$18,d0
    move.b  d0,$1E(a1)
    ...
Offsets $44 and $45 of the player's SST hold the default values of their y_radius and x_radius attributes. They are set once when the player object is first initialized and henceforth only read from, to restore the player's hitbox after rolling.

The spinning top object isn't messing with these values, which is good. But it is taking the default Y radius, adding 24 to it, and writing the new value to the player's y_radius attribute, which effectively changes the size of Sonic's hitbox from the usual 20×40 pixels to 20×88 pixels. This way lies madness.


Why does the object do this? To trick the solid object code into thinking the bottom of Sonic's hitbox is all the way at the bottom of the spinning top. You see, the game is written to process two kinds of solid collisions: object-to-level collision, and player-to-object collision. It is not equipped to process solidity between any two arbitrary objects.

However, by hijacking the player's own hitbox, the spinning top object can simulate floor collision between itself and the tops of other objects, which is all it really needs, without having to mess around with any of the underlying code.

Except the underlying code just so happens to contain this fragment (relevant bits in bold):
Sonic_TouchFloor:
    move.b  $1E(a0),d0
    move.b  $44(a0),$1E(a0)
    move.b  $45(a0),$1F(a0)
    btst    #2,$2A(a0)
    beq.s   loc_121D8
    bclr    #2,$2A(a0)
    move.b  #0,$20(a0)
    sub.b   $44(a0),d0
    ext.w   d0
    tst.b   (Reverse_gravity_flag).w
    beq.s   loc_121C4
    neg.w   d0

loc_121C4:
    move.w  d0,-(sp)
    move.b  $26(a0),d0
    addi.b  #$40,d0
    bpl.s   loc_121D2
    neg.w   (sp)

loc_121D2:
    move.w  (sp)+,d0
    add.w   d0,$14(a0)

loc_121D8:
    ...
When bit 2 of the player's status bitfield (aka their rolling flag) is set, the above code must shift their Y position up a few pixels in order to account for the change in their hitbox size. It calculates the number of pixels by subtracting the default Y radius from the current collision height, which is usually 30 pixels while rolling.

The current collision height is 88 pixels.


Here's the rundown: if Sonic starts riding the spinning top while rolling, then landing on a solid object will make the floor collision code attempt to shift him up by five pixels. However, since his current Y radius is an insane value, this ends up shifting him down by 24 pixels, which is enough to clear the sinking mud object and send him right through the floor.


It doesn't stop there. Because Sonic never dismounted the spinning top properly, his hitbox is still abnormally tall, which causes him to touch the floor much sooner than he normally would. Fortunately, the floor collision code restores Sonic's hitbox size whenever he lands, so there's no long-term damage.

Friday, September 1, 2017

Act transitions, part 5: horizontal underflow

Marble Garden Zone's act 1 boss is interesting in that it is already waiting for you on screen as you walk up to it, much like his earlier appearances in the level. This creates an opportunity for a standalone Sonic 3 exclusive bug to emerge. Once again, this was documented by the legendary GoldS in his glitches and oversights video series.


Essentially, because the boss spawns within the level's boundaries, a second player can use Tails to run up ahead and start damaging the boss before player 1 advances the camera into its final position. If the boss is defeated under these circumstances, then when the act transition occurs, all sprites are deleted from the screen and the game locks up.


So what's happening here? The image below once again illustrates where the camera usually locks in the act 1 layout.


Whereas the image below illustrates the matching position in the act 2 layout, once the act transition has taken place.


Uh-oh. Unlike Mushroom Hill Zone, the act 2 layout starts at the exact point of act 1's camera lock, with no grace region preceding it. As such, if act 1 is cleared before the camera scrolls into place, the transition will set the camera's position to a negative value, which is interpreted as a really high positive value, effectively removing the left bound on the object and ring object loading routines, causing them to try and load everything, which quickly destroys the game.

Incidentally, if you defeat the boss while it's still off screen, but then move all the way to the right before the level results start, you survive the act transition, since the camera's position is set to zero. However, the left boundary is still stuck at a negative value, so when the level results end and the player is released, the left boundary pushes him far beyond the end of the level, which is counteracted by the right boundary pushing him back, making the camera scroll to the left and once again leading the ring loading routine into unbounded territory.

Sonic & Knuckles fixed this by forcing the left boundary to move into place when the boss is defeated, but it introduced another bug: first the boundary moves, and then it checks if it needs to continue moving. So if it was already in the right position, it will nonetheless move once, making the level one pixel narrower than the actual screen, resulting in a jittery camera lock when you move from one side of the screen to the other.

Wednesday, August 23, 2017

The game is confused

Although dynamic resize routines are only meant for, well, dynamically resizing the level, there's nothing stopping them from handling related events. For instance, the resize routines for both Angel Island Zone 1 and Marble Garden Zone 2 manually spawn the boss object into the level once the left boundary is locked in place.
MGZ_Resize:
    moveq   #0,d0
    move.b  (Dynamic_resize_routine).w,d0
    move.w  off_1C92A(pc,d0.w),d0
    jmp     off_1C92A(pc,d0.w)
; ---------------------------------------------------------------------------
off_1C92A:
    dc.w loc_1C930-off_1C92A
    dc.w loc_1C96E-off_1C92A
    dc.w locret_1C9C8-off_1C92A
; ---------------------------------------------------------------------------
    ...

loc_1C96E:
    cmpi.w  #$3A00,(Camera_X_pos).w
    blo.s   loc_1C9A8
    move.w  #$3C80,d0
    cmp.w   (Camera_X_pos).w,d0
    bhi.s   locret_1C9C6
    move.w  d0,(Camera_min_X_pos).w
    move.w  d0,(Camera_target_min_X_pos).w
    jsr     (Create_New_Sprite).l
    bne.s   loc_1C9A2
    move.l  #Obj_A1_1_MGZ2_Boss,(a1)
    move.w  #$3D20,$10(a1)
    move.w  #$668,$14(a1)

loc_1C9A2:
    addq.b  #2,(Dynamic_resize_routine).w
    rts
I intentionally sneaked in a weird quirk in my previous post related to this. Can you figure out what it is? I'll wait.

...

...

...

Welcome back.
    dc.w MGZ_Resize-LevelResizeArray
    dc.w MGZ_Resize-LevelResizeArray
For whatever reason, Marble Garden Zone 1 uses the same set of dynamic resize routines as Marble Garden Zone 2. In practice, this does not affect act 1 at all, because act 2 is longer than act 1, and all the interesting stuff only happens beyond the natural end of act 1.


However, because the developers were lazy setting most of the level sizes, so as long as you avoid spawning the act 1 boss object, going beyond the end of the level will put you in the act 1 loopback. And sure enough, once you're there, if you make your way to the right coordinates, the act 2 boss will spawn in act 1.

This is used in the current speedrun world record in order to completely skip over Marble Garden Zone 2.

Friday, August 18, 2017

Contextual graphics, part 4: mind your head

In standalone Sonic 3, if you go to the entrance of Knuckles' route in Marble Garden Zone 2, you'll find that the bottom camera boundary gets locked in anticipation of the cutscene boss encounter. In Sonic 3 & Knuckles, however, this was modified to lock the top boundary as well.


The reason for this becomes evident when you enter the cutscene area in standalone Sonic 3. Yes, the cutscene does work; in fact, the entirety of Knuckles' route all the way up to the boss area works just fine, and interestingly, the object layout is actually slightly different to the one in S3&K. Be sure to check it out!


Anyway, the problem is, when the boss loads its graphics, it overwrites the graphics used by the Relief enemy, causing it to appear as a jumbled mess if it scrolls into view. In order to quickly work around this, the developers made it so the top camera boundary locks along with the bottom one for this cutscene only.


What's interesting is how they handle releasing the camera lock. If you hold up the entire time, you'll realize the camera remains locked for a seemingly arbitrary duration after the cutscene has already ended. Well, the problem was showing the Relief while its graphics aren't loaded, right?

So in this case, the game waits for the Nemesis decompression queue to be empty before releasing the camera. Once the queue is empty, the Relief's graphics are definitely done loading, which means it's safe to scroll it back into view.

Wednesday, July 19, 2017

Bop it! Twist it! Pull it! Shake it!

When you encounter the Marble Garden Zone 1 miniboss near the start of the level, if you quickly run away as soon as it begins burrowing into the ceiling, you'll get caught in a permanent earthquake until something else causes the screen to stop shaking.


The reason for this should be clear, given what we've learned. The miniboss object has two distinct code paths through which it deletes itself: one for when it's scrolled off-screen and needs to be respawned, and another for when it finishes burrowing into the ceiling and is gone forever.

Unfortunately, both code paths are active at the same time, and the earthquake flag is only cleared when the latter path completes, so if you scroll the object off-screen after the quake starts but before it stops digging, it deletes itself without clearing the flag, which results in endless shaking.

You can confirm this hypothesis by going back to the spot where you encountered the miniboss: the object will respawn because it deleted itself through the on-screen test. If you let it finish digging, then it will no longer appear.

Monday, June 19, 2017

Tails' Adventures

This past weekend, I posted a short video that managed to garner a lot of attention. For today's post, I decided to look for the same kind of oversight in Sonic 3, though I apologize in advance if this isn't as amazing as the Sonic 2 clip.


Over the next few posts, we'll look at terrain collision and how crazy stuff like this can happen.

Wednesday, May 31, 2017

Too eager boss

You may have noticed that after you sit through one of Marble Garden Zone 2's drilling Eggman cutscenes, if you leave the room really quickly, there'll be some funky stuff going on with the level graphics.


When the boss graphics get loaded, they actually overwrite some bits in the lower half of the background. This is fine, because Sonic and Tails' boss takes place high in the sky, and both Knuckles' boss and the mid-level cutscenes take place indoors, where the background is hidden from view.

There's really no reason why monitors should be getting messed up though, so I looked into it. Here's what the drilling Eggman object loads when it boots up:
Obj_MGZ2DrillingEggman:
    ...
    lea     (ArtKosM_MGZEndBoss).l,a1
    move.w  #$67E0,d2
    jsr     (Queue_Kos_Module).l
    lea     (ArtKosM_MGZEndBossDebris).l,a1
    move.w  #-$7440,d2
    jsr     (Queue_Kos_Module).l
    moveq   #$6D,d0
    jsr     (Load_PLC).l
    lea     Pal_MGZEndBoss(pc),a1
    jmp     (PalLoad_Line1).l
; ---------------------------------------------------------------------------
Two inline PLC requests, one for the MGZ end boss art, and another for the end boss debris. The latter is a misnomer: the same debris is also used by the act 1 boss. Anyway, nothing weird here, but we haven't actually loaded Robotnik or his ship yet, so let's keep going.

After the first two requests, we have a call to the Load_PLC function with the value $6D. This function actually queues up a PLC from the Offs_PLC array, which we already know from the Level Load Block. Turns out, $6D is the PLC slot used by the actual MGZ end boss:
PLC_6D: plrlistheader
    plreq $52E, ArtNem_RobotnikShip
    plreq $500, ArtNem_BossExplosion
    plreq $494, ArtNem_EggCapsule
PLC_6D_End
In short, each time a drilling Eggman cutscene starts, the game queues up the MGZ end boss PLC. The end boss PLC loads the animal capsule and boss explosions, which are useless and overwrite springs, spikes and monitors, so when the cutscene ends, the game has to load them all back.

Mind you, this is a drop in the ocean, because it pretty much has to load everything back. But avoidable all the same.

Tuesday, May 9, 2017

The solid background flag

During normal operation, Plane A shows the foreground layer the player can interact with, whereas Plane B shows a parallax background which has no influence on gameplay. However, the two planes are completely interchangeable, except for the order the VDP in which draws them.


When you step inside this indoor section, the background in Plane B can no longer be seen, so the game can get rid of it. Instead of scrolling at a slower speed, Plane B is made to scroll at the exact same rate as Plane A. The background patterns are temporarily shifted aside and replaced with a pile of sand, carefully positioned so the highest point lines up with the cork object. Finally, the game turns on the titular background collision flag, which instructs Sonic to also check for collision with Plane B in addition to Plane A.


Everything is ready, so when you destroy the cork object, all the game has to do is start scrolling Plane B up. All the sand patterns have their high priority flags set, so they appear in front of Plane A's tunnel background. However, the terrain patterns in Plane A are also set to high priority, so it covers the rising sand. The result is that the sand appears to emerge from the ground, ostensibly piling up from the nearby torrent.

This trick was first used in Sonic 2 for Hill Top Zone's earthquake sections. In order for the transition to be seamless, the background must first be completely hidden from view, but Marble Garden Zone goes the extra mile.


The stage's regular background is attached to the top of the rising floor, and although vertical scrolling is sacrificed, the horizontal parallax keeps running like normal. While the whole thing is moving upwards. I don't even understand how they got that to work!