Showing posts with label Angel Island Zone. Show all posts
Showing posts with label Angel Island Zone. Show all posts

Thursday, February 1, 2018

Trouble at the border

A while ago, we saw how in Sonic 3, Knuckles' boss area in Launch Base Zone 1 was originally a bit different, and that Sonic 3 & Knuckles modifies it by altering the level layout directly in RAM. Logically, the same changes are also applied to the matching area at the start of act 2, except that this time, the twin boxes are already open.


Interestingly, in Sonic 3 the checkered tube is still present in the act 2 layout, despite normally being removed when the boss is defeated. It's possible that the act transition was originally planned to happen up there, and the tube would only explode at the start of act 2.

However, this theory is challenged by the presence of hidden monitors, buried in the floor at the bottom of the room, as early as the Sonic 3 object layout.


On the other hand, assuming the tube was meant to explode in act 1, it would then suddenly reappear upon loading the act 2 layout, which wcould lead to this curious scenario.

This isn't the only level layout adjustment performed in Launch Base Zone 2, though: directly after Knuckles' act 2 boss area, the Sonic 3 layout cuts off rather abruptly. In Sonic 3 & Knuckles, a couple of extra chunks were added to end the pipe floor more naturally.


This change is purely cosmetic however, because Knuckles' end of level cutscene forces him to jump at the exact point where the floor would cut off, regardless.


If it's weird you're looking for, though, look no further than Angel Island Zone 2. For whatever reason, there are actually hidden monitors buried in the floor of Knuckles' boss area. In act 2.


What on earth could the developers have been planning here?

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.

Tuesday, January 9, 2018

Eosian Balls

An anonymous commenter asked:
Could you write about the rotating red sphere thing on AIZ1 (Sonic 3 alone debug)? Did that have any purpose other than destroy the game´s palette and framerates?
Directly after the surfboarding Sonic object, Angel Island Zone 1's debug reel once again treats us to an unusual sprite: a set of red spheres revolving around each other in three dimensions:


What on earth could be the purpose of this object? Here's what I conjectured when I was just a kid: it's only available in AIZ1's debug reel, it comes right after the surfboard, it's red and it messes with the HUD once it's placed. It must be the early equivalent of the cutscene Knuckles object! I was a dumb kid.

The truth makes considerably more sense: it is a test of the 3D sprite effect seen in the game's special stages. The fact that the object is part of AIZ1's debug reel seems to be entirely circumstantial; it was likely added to the first level of the game simply for ease of access. However, its origins in the special stage become evident when we look up the location of its graphics within the ROM:

NameAddress
ArtUnc_SStageTailstails0909E8
Map_SStageTailstails0910E8
PLC_SStageTailstails091106
ArtNem_SphereTest0911CE
ArtNem_SStageShadow091B2E
ArtNem_GetBlueSpheres091B70
ArtNem_GBSArrow091CF2

The object's overall behavior can actually be controlled by player 2. The controls, although a tad obtuse, are as follows:
  • Down/Left: moves the object up and down
  • Up/Right/B: toggles the rotation of each axis
  • A/C: shrinks/enlarges the object
  • Start: toggles between automatic and manual rotation

No doubt owing to its roots, the sphere test object stores its movement and rotation state in a region of RAM starting at address $E400, which is used by the special stage for the same purpose. In the main game however, the same area is used to maintain a buffer of the player's last few positions. As a result, exiting debug mode and moving around will very quickly make the object become all kinds of messed up.

Monday, January 8, 2018

The unused surfboard intro

In S3A only, if we go through the debug reel for Angel Island Zone 1, we eventually run across an unusual sprite, which when placed spawns the object seen below: a surfboarding Sonic that hovers around the player's position.


One thing which is immediately apparent about this object, is that much like the Flying Battery Zone monkey bar sprites before them, the surfboarding sprites are an almost perfect match for the standing sprite from Sonic 2:


When this object is spawned through debug mode, its subtype is always set to a non-zero value. And since it shares its VRAM address with the Sonic object, it corrupts Sonic's appearance as soon as it spawns.

However, when a subtype of 0 is supplied, the object actually turns Sonic invisible by setting his mapping frame to zero and disables his movement and animation routines by setting bits 0 and 1 of his object_control attribute:
Obj_SurfboardIntro:
    ...
    tst.b   $2C(a0)
    bne.s   loc_20B78
    lea     (Player_1).w,a1
    move.b  #0,$22(a1)
    move.b  #3,$2E(a1)

loc_20B78:
    ...
So what's going on? It turns out that underneath its surface, this object contains code for an alternate intro sequence to Angel Island Zone 1. The presence of Sonic 2 sprites suggests that this sequence was the stage's original intro, before the plane intro seen in the final game was implemented.

We can restore the surfboard intro by simply changing which intro object is spawned at the start of the level:
SpawnLevelMainSprites:                          SpawnLevelMainSprites:
    ...                                             ...
    cmpi.w  #0,(Current_zone_and_act).w             cmpi.w  #0,(Current_zone_and_act).w
    bne.s   loc_4DFE                                bne.s   loc_4DFE
    cmpi.w  #2,(Player_mode).w                      cmpi.w  #2,(Player_mode).w
    beq.s   locret_4DFC                             beq.s   locret_4DFC
    move.l  #Obj_PlaneIntro,($FFFFB172).w           move.l  #Obj_SurfboardIntro,($FFFFB172).w
    clr.b   (Level_started_flag).w                  clr.b   (Level_started_flag).w
    rts                                             rts
Since we're going to make a lot of modifications to the game this time around, I'll put any relevant PAR codes alongside their respective snippets. Here are the codes for the above change:
004DF0:0002
004DF2:0B0E
That alone restores most of the scrapped intro, but there are still a bunch of rough spots. For instance, although the big waves have somehow fixed themselves, Sonic still has a splash of garbage trailing behind him, and as he jumps off the surfboard, it too turns into a garbled mess. This is because the graphics for these objects have not been loaded.


As part of the setup for his intro sequence, when Sonic first starts Angel Island Zone 1, the game loads PLC $A instead of the regular AIZ1 PLC. Just by looking at it, though, we can easily spot the problem: when the intro was reworked, the graphics for the surfboard and its splash were removed from the PLC:
PLC_0A: dc.w 0
        dc.l ArtNem_AIZIntroSprites
        dc.w $7A20
Unfortunately, there's no longer any room to add those graphics back to the PLC without stepping over the regular AIZ1 PLC. Fortunately however, PLC 0 is completely unused, so we can repurpose it for our needs:
PLC_00: dc.w 3                                  PLC_00: dc.w 2
        dc.l ArtNem_SonicLifeIcon                       dc.l ArtNem_SurfboardSplash
        dc.w $FA80                                      dc.w $A520
        dc.l ArtNem_Ring                                dc.l ArtNem_AIZIntroSprites
        dc.w $D780                                      dc.w $7A20
        dc.l ArtNem_EnemyPts                            dc.l ArtNem_Surfboard
        dc.w $BC80                                      dc.w $B0A0
        dc.l ArtNem_Monitors                            dc.l ArtNem_Monitors
        dc.w $9880                                      dc.w $9880
Below are the PAR codes for these changes. My apologies.
05B084:0002
05B086:0014
05B088:86E0
05B08A:A520
05B08C:0014
05B08E:81A0
05B090:7A20
05B092:0014
05B094:8978
05B096:B0A0
All we have to do now is make the intro code load PLC 0 instead of PLC $A:
loc_4756:                                       loc_4756:
    bsr.w   LevelLoad_ActiveCharacter               bsr.w   LevelLoad_ActiveCharacter
    tst.b   (Last_star_post_hit).w                  tst.b   (Last_star_post_hit).w
    bne.w   loc_4782                                bne.w   loc_4782
    cmpi.w  #0,(Current_zone_and_act).w             cmpi.w  #0,(Current_zone_and_act).w
    bne.s   loc_4782                                bne.s   loc_4782
    cmpi.w  #2,(Player_mode).w                      cmpi.w  #2,(Player_mode).w
    beq.s   loc_4782                                beq.s   loc_4782
    moveq   #1,d0                                   moveq   #1,d0
    jsr     (Load_PLC_2).l                          jsr     (Load_PLC_2).l
    moveq   #$A,d0                                  moveq   #0,d0
    bsr.w   Load_PLC                                bsr.w   Load_PLC
    bra.s   loc_479A                                bra.s   loc_479A
Which translates to the following PAR code:
00477A:7000
Should look mighty slick now, but we still have an additional issue: since the title card never comes down, the level gets stuck in this weird state where there's no HUD, rings are invisible, and most other objects appear as a garbled mess:


To fix this, we can simply replace the surfboard intro object's final call to the Sprite_OnScreen_Test function with a call to the last routine of the cutscene Knuckles object, which fixes everything for free.
loc_20D38:                                      loc_20D38:
    jmp     (Sprite_OnScreen_Test).l                jmp     (loc_448B0).l
Doing so gives us our final two PAR codes:
020D3A:0004
020D3C:48B0
That should about cover it. Let's finally take a look at our fully-restored surfboard intro sequence:


I know, it's not perfect. Since the game doesn't wait for the Nemesis queue to be empty before starting the level, the big waves still appear garbled for a while. However, fixing this would require much deeper changes to the game's code, the likes of which are beyond the scope of simple PAR codes.

Tuesday, November 21, 2017

Unused graphics: Knuckles' button?

Here's another one from Angel Island Zone. One of act 2's PLCs loads the following graphics to VRAM address $8700:


The first eight tiles are used by the background trees at the very end of the stage. The last seven tiles, however, cannot be seen in-game because they're instantly overwritten with the graphics for breakable floors.

No sprite mappings for these tiles seem to have survived in the final game, so we can only speculate about the object's intended appearance. However, by applying the player palette and arranging the tiles into sensible shapes, we arrive at the most likely configuration: a button.


Now, since these graphics are loaded at the start of the stage, they could have been meant for anything. But taking into account that they're included in the same art file as a tree sprite which only appears at the very end of the level, we can probably assume the button was also meant to be used near the end of the level.

Another clue comes from the graphics themselves, namely the blades of grass at the bottom, which are rendered using Tails' colors, no less. Normally, it would be unnecessary to bake the grass into the sprite like this: so long as the sprite's priority flag is clear, the object should naturally be drawn behind the level blocks.


The exception is at the very end of the level, where the background waterfall blends directly into the grassy floor. In this scenario, when a sprite's priority flag is clear, it gets drawn behind both the grass and the waterfall, and when it is set, it gets drawn in front of both. Baking the sprite is the only way to make it show up in front of the water and behind the tips of the grass which, you guessed it, perfectly match Tails' colors at this spot.

So the button was likely meant to be placed at the waterfall, possibly making it a precursor to the one used by Knuckles to drop Sonic and Tails into Hydrocity Zone. Here's how that could have looked:

Monday, November 20, 2017

Unused graphics: Tornado's shadow

As a change of pace, I thought it'd be nice to look at some of the graphics which go unused in the game. Below are the full contents of the Tornado art file used in Angel Island Zone's intro. Yes, Tails' head is baked directly onto the plane.


Surprisingly, the landing gear and rocket are separate sprites just like in Sonic 2, even though both of them are always attached to the plane in Sonic 3. More surprisingly, however, are the black oblong shapes at the end of the art file.

No objects in the game's code use these sprites, so one can only speculate about their intended use. My guess is they were meant to form a shadow, cast by the plane onto the ocean surface below. Here's a mock-up animation illustrating what it might have looked like:


Why this effect wasn't used, we will likely never know. Perhaps the developers weren't happy with how it looked. It does look rather strange; no other objects in the game cast any shadows, and it may have been too strange that Sonic didn't cast a shadow once he jumped off the plane.

Friday, October 6, 2017

Sonic 3 stages in S3&K: level layouts

Okay, so object and ring layouts can't be reused because they're not amenable to being patched, and they have to get patched for Knuckles. What about level layouts? Are they amenable to being patched?

Well, as we saw before, a level layout is pretty much just a flat array of chunk IDs, which is copied to RAM on level load to allow crazy stuff like copying chunks from one part of the layout to another in the middle of gameplay. They are most definitely amenable to being patched, so the ones in the Sonic 3 cartridge do just fine.

Do they require patching? Yes. For example, Launch Base Zone 1 has the following code at the start of its background init function. Note that during execution of screen and background event functions, the a3 register contains the address of the first row pointer for the respective level layout plane.
LBZ1_BackgroundInit:
    cmpi.w  #3,(Player_mode).w
    bne.s   loc_541C6
    movea.w (a3),a1
    addq.w  #2,a1
    move.b  #$E4,(a1)+
    move.b  #$E6,(a1)+
    move.b  #$E0,(a1)+
    movea.w 4(a3),a1
    addq.w  #2,a1
    move.b  #$EC,(a1)+
    move.b  #$EE,(a1)+
    move.b  #$E8,(a1)+
    ...
So what this code does is load the pointer to the first row of background chunks into the a1 register, skip over the first two chunks in the row, and then replace the next three chunks with different chunk IDs. Then it loads the pointer to the second row of chunks and does the same thing.

In practice, the chunks being replaced are the ones that put the Death Egg on the background plane. Since Knuckles' story takes place after Sonic and Tails', during the course of which the Death Egg is launched back into outer space, it wouldn't make sense for it to appear in the background when playing as Knuckles.

As such, this:


becomes this:


Something similar happens at the very end of the level, after defeating Knuckles' boss. Here's the code, this time from the screen event function:
loc_53F50:
    movea.w $4C(a3),a1
    move.b  $78(a1),d0
    movea.w $50(a3),a1
    lea     $78(a1),a1
    move.b  d0,(a1)+
    move.b  d0,(a1)+
    move.b  d0,(a1)+
    jmp     Refresh_PlaneScreenDirect(pc)
This one is cute. First, it loads the pointer to a specific chunk row to register a1, then copies a specific chunk ID from that row into register d0. It's this one:


Next, it loads a different chunk row into a1, skips over the first 120 chunks, and pastes the previously copied chunk into the three slots that follow, erasing the checkerboard pipe from the level layout. Note how the background pattern isn't a perfect match; it's that way in-game as well:


Something doesn't seem right about the above two images, though. The twin boxes appear way too low. Well, if you go and check, they're actually like that in the Sonic 3 layout. It was Sonic & Knuckles that patched them.


The code responsible for doing so is in the screen init function for LBZ1:
LBZ1_ScreenInit:
    ...
    movea.w $48(a3),a1
    movea.w $4C(a3),a5
    move.b  $79(a1),$79(a5)
    move.b  #$DB,$79(a1)
    jsr     LBZ1_RotateChunks(pc)
    ...
Okay, so the first thing this code does is load the pointers to two chunk rows into registers a1 and a5. Then, it copies a chunk from the first row...


...and pastes it over the same chunk on the second row, also replacing the one on the first row with a chunk ID of $DB:


We seem to have placed a strange chunk up there. This is actually correct: the boss object displays the outside of the boxes using sprites, and the chunk is only used to show the inside of the boxes once they open.

Astute viewers will notice that the box is now too high. Argh. Best look inside that LBZ1_RotateChunks function:
LBZ1_RotateChunks:
    moveq   #$17,d2

loc_54148:
    lea     ($FF6E00).l,a1
    lea     -2(a1),a5
    move.w  (a5),d0
    moveq   #$3E,d1

loc_54156:
    move.w  -(a5),-(a1)
    dbf     d1,loc_54156
    move.w  d0,(a5)
    dbf     d2,loc_54148
    rts
Holy goodness, what is happening here?

What this function is actually doing is patching the chunk definition for chunk $DB. That is, it is changing the pattern of 16x16 blocks which make up the 128x128 chunk. More specifically, it's taking the bottom three rows of blocks...


...and throwing them on top of the chunk, bringing the remaining rows down. It's "rotating" the chunk in the sense that it's sliding all the blocks down by three rows:


This isn't the only instance of chunk definition patching in the game, though. In Angel Island Zone 1, it's used to remove the horizon line from the background while playing as Knuckles. Again, that's because Knuckles' story takes place after Sonic and Tails', in which the Master Emerald is returned to Angel Island, causing it to resume floating in the sky.

Note also how once again, some of the replacement blocks aren't a perfect match:


That's it for our brief look into Sonic 3 & Knuckles. Next up is the game born out of circumstance: Knuckles in Sonic 2.

Tuesday, August 29, 2017

Act transitions, part 2: Angel Island Zone

Angel Island Zone's act transition does not happen during the act 1 results screen. Instead, it takes place much earlier in the stage, at the act 1 boss cutscene encounter where the entire level catches on fire.


The cutscene area at the very end of the act 1 layout is interesting in that all the background tiles, including the sky and the horizon line, are actually part of the level's foreground plane. Furthermore, all tiles are set to low priority, as is every sprite that appears throughout the cutscene.


The reason for this should be clear: the game is about to pull some shenanigans using the background plane. Although they're usually drawn behind the foreground plane, by setting the background's tiles to high priority, we can make them render in front of the foreground. This trick is used to display the scrolling flames that cover up the entire screen, during which the act transition silently takes place.


Finally, there's an extra trick that brings the whole cutscene together. As the boss descends from the top of the screen, several more Fire Breath robots scroll across the sky in the background, logically going behind the palmtree on the left side of the screen. But as we know now, the background tiles in this area are all set to low priority, so the robots' sprites should be getting drawn in front of the tree!


The trick here is to introduce an extra sprite where the robots intersect with the tree. This sprite also needs to be set to low priority so it's covered by the background flames, but by giving it a low priority value on its SST, we can ensure it's drawn on top of the robots, but still behind Sonic and everything else.

Wednesday, August 16, 2017

Contextual graphics, part 2: frozen graphics

The entrance to the boss in Hydrocity Zone 1 is quite similar to the ghost capsule in Sandopolis Zone 2. You go around a loop and then fall to the room below, where once again, new graphics have stealthily been loaded through a PLC call, in order to render an object which is immediately visible. This time it's the agitator at the center of the boss area.


As for the mechanism itself, however, it could not be more different. The graphics for the boss and the agitator are both in the same Nemesis archive, and it's the actual boss object located beneath the loop which spawns the agitator object and loads the PLC, soon after it is scrolled within the range of the object manager.


There's a snag, though. The boss loads its graphics over the DMA region for the animated elements earlier in the level, such as conveyor belts and the pseudo-3D waterline. If those elements continue running, they'll end up overwriting the boss art, so before loading it the boss sends out a signal to stop all of the level's animated PLCs, which works, but also causes the pendulum cages right before the loop to suddenly freeze in whichever frame they were at the time.


Bizarrely enough, the same thing occurs at the boss fight in Angel Island Zone 1, and I can't figure out why. The plants in the foreground and the flames in the background all stop moving as soon as the screen locks, and then resume once the boss is defeated. Maybe there was an art conflict here at one point?

Friday, August 11, 2017

What is a fake star post?

First, an overview of how star posts work. Each star post in a level is numbered sequentially, more or less according to their relative placement in the level. This number is stored in the star post object's subtype. When an inactive star post is touched, it writes its number to the Last_star_post_hit RAM address. The numbering starts at 1, and 0 serves as a sentinel value meaning "no star posts hit in the current level."

However, a star post's number isn't actually used when respawning the player; all it does is cause every star post with a number lower or equal to Last_star_post_hit already be activated on spawn. Instead, the player's position, as well as a bunch more information, is stored to a backup area in RAM. Note that this is different from the backup area used for big rings, because we still want the player to respawn at the last star post even after entering a Special Stage.
sub_2D164:
    move.b  $2C(a0),(Last_star_post_hit).w
    move.w  $10(a0),(Saved_X_pos).w
    move.w  $14(a0),(Saved_Y_pos).w

Save_Level_Data:
    move.b  (Last_star_post_hit).w,(Saved_last_star_post_hit).w
    move.w  (Current_zone_and_act).w,(Saved_zone_and_act).w
    move.w  (Apparent_zone_and_act).w,(Saved_apparent_zone_and_act).w
    move.w  (Player_1+art_tile).w,(Saved_art_tile).w
    move.w  (Player_1+top_solid_bit).w,(Saved_solid_bits).w
    move.w  (Ring_count).w,(Saved_ring_count).w
    move.b  (Extra_life_flags).w,(Saved_extra_life_flags).w
    move.l  (Timer).w,(Saved_timer).w
    move.b  (Dynamic_resize_routine).w,(Saved_dynamic_resize_routine).w
    move.w  (Camera_max_Y_pos).w,(Saved_camera_max_Y_pos).w
    move.w  (Camera_X_pos).w,(Saved_camera_X_pos).w
    move.w  (Camera_Y_pos).w,(Saved_camera_Y_pos).w
    move.w  (Mean_water_level).w,(Saved_mean_water_level).w
    move.b  (Water_full_screen_flag).w,(Saved_water_full_screen_flag).w
    rts
Here we can see that the code which saves the player's state is split into two sections: first, a bit where the star post's subtype and X/Y position are backed up, falling directly into Save_Level_Data, which backs up everything else. When a level starts, the game checks the value of Last_star_post_hit: if it is non-zero, it initializes everything to the contents of the backup RAM area.

So what is a fake star post? A fake star post is any other code that writes to Last_star_post_hit, saves a custom set of X/Y positions, and then calls Save_Level_Data. There are six such spots in the game, mostly to permanently skip over lengthy cutscenes. All fake star posts in the game occur at the start of the level and set Last_star_post_hit to 1, with the actual star post numbering in those levels starting at 2 instead.

  • The first one occurs in Angel Island 1, when you leave the starting area and enter the main level. This skips over the Knuckles cutscene when playing as Sonic.
  • The next one is in Mushroom Hill 1, but only in Sonic 3 & Knuckles. It happens when the cutscene showing Knuckles leaving the big ring room ends, and skips it on subsequent times.
  • Also in Mushroom Hill 1, but only in standalone Sonic & Knuckles. When Knuckles' intro ends, a star post is activated and the level restarts in order to spawn Knuckles in the main level rather than the intro area.
  • In Mushroom Hill 2, when Sonic and Tails are blasted up into the autumn section of the level. This does end up making a couple of rings permanently missable.
  • In Sky Sanctuary as Sonic and Tails, the same fake star post is activated twice for some reason: once when the bridge off the intro section extends, and another once Knuckles dissappears.

And now, although I've already told you the locations of five of the six fake star posts in the game, I'm delaying the last one over to next week. Sorry about that; as a consolation, please enjoy this video of a cat playing the piano.

Thursday, July 20, 2017

Hide and seek

You might have noticed that, if you bump your head on the ceiling and fail to collect it the first time around, this Special Stage ring halfway through Angel Island Zone 1 likes to disappear until you land back on the floor proper.


While they're on screen, Special Stage rings DMA their graphics over the area normally taken up by enemy explosions. The explosion graphics aren't reloaded until the ring is deleted, which is probably why unlike almost every other object, the ring deletes itself when you go too far away from it vertically as well as horizontally.

I say almost, because this behavior isn't unknown. Throwaway object such as enemy projectiles, or crumbling platform debris all exhibit it, using some variation on the Sprite_CheckDeleteXY function shown below. Special Stage rings are particularly interesting though, because they're meant to respawn once you scroll them back into range.
Sprite_CheckDeleteXY:
    move.w  $10(a0),d0
    andi.w  #-$80,d0
    sub.w   (Camera_X_pos_coarse_back).w,d0
    cmpi.w  #$280,d0
    bhi.w   Go_Delete_Sprite
    move.w  $14(a0),d0
    sub.w   (Camera_Y_pos).w,d0
    addi.w  #$80,d0
    cmpi.w  #$200,d0
    bhi.w   Go_Delete_Sprite
    jmp     (Draw_Sprite).l
Once per frame, the object manager calculates the Camera_X_pos_coarse_back RAM variable to define the range of objects which is currently active. It's basically the live camera position rounded down to the nearest 128 pixel boundary, as seen below. Each time the camera moves 128 pixels horizontally, the range slides over and new objects are loaded.
loc_1B7F2:
    move.w  (Camera_Y_pos).w,d1
    subi.w  #$80,d1
    andi.w  #$FF80,d1
    move.w  d1,(Camera_Y_pos_coarse_back).w
    move.w  (Camera_X_pos).w,d1
    subi.w  #$80,d1
    andi.w  #$FF80,d1
    move.w  d1,(Camera_X_pos_coarse_back).w
Already we can see a discrepancy, though. The object manager maintains coarse values for both the camera's X and Y positions, but the Sprite_CheckDeleteXY function above uses the live value for the Y position. The result is that unless it's vertically aligned with a 128 pixel boundary, there's a mismatch between the range at which the object deletes itself, and the range at which it is respawned by the object manager.

Which is exactly what afflicts our buddy up there.

       Object deleted at this camera position       Object respawned at this camera position

Friday, June 30, 2017

A shadow or an approximation thereof

The HUD uses four colors from the enemy palette. The text is yellow with a dark gray shadow, while the numbers are white with an olive shadow. Yellow and white, the two main colors, are safe picks since they're almost never tampered with. The other two colors aren't necessarily as lucky.


When cutscene Knuckles appears, the enemy palette is essentially replaced with the player palette. Usually, the enemy palette has three gray colors in the same place as the ones in the player palette, except the ones in the player palette are a bit brighter. Additionally, the player palette is black where the enemy palette is olive, so when Knuckles shows up, the shadow on the HUD's numbers becomes stronger, while the shadow on the lettering becomes lighter.


Angel Island Zone's enemy palette is maddening. There's a perfectly decent (if a bit too blue) dark gray right before the three-piece gradient, but they decided to put a light gray in the HUD shadow slot, creating this abomination:


Starting from Sky Sanctuary Zone, the game sort of stops respecting the placement of the grays and just lays them out whichever way it sees fit. The proper color is right there, but since they shifted the whole gradient over, we end up with a pitch black HUD shadow.