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.

Thursday, June 29, 2017

A sense of urgency

In Sonic 1 and Sonic 2, the yellow HUD text blinks red when you have zero rings, and when you're running out of time to complete the level.


This works by simply switching the HUD sprites' palette back and forth between lines 0 and 1. The layouts for Sonic's palette and the enemy palette were specifically designed so the yellow colors line up perfectly with the red colors.


In Sonic 3, however, due to the contract between cutscene Knuckles and the HUD, the yellow color used by the HUD is no longer aligned with the red, because it's aligned with Knuckles' sock color instead.


Unfortunately, this means the original blinking effect is no longer possible. The compromise is to make the HUD sprites themselves blink in and out of existence rather than change their color.


Other sprites which took advantage of the red/yellow palette switch, such as red and yellow springs, had their graphics split into two copies, one for each palette. This actually allowed yellow springs to take advantage of all three shades of yellow, and red springs to use the shades of orange for some smoother lighting.

In particular, the star post was redesigned to only use Sonic's palette, even though they could've easily made it use the original yellow colors all the same. I guess they thought it looked better this way.

  

I think it looks better too.

Wednesday, June 28, 2017

Why is Knuckles colored different in cutscenes?

Because of the HUD.

During normal gameplay, palette line 0 holds Sonic's palette, which is used by both player characters, title cards, small animals, and several other common objects such as monitors, springs, spikes and star posts.


Meanwhile, palette line 1 holds the enemy palette, which as the name implies, is used primarily by enemies as well as other stage-specific objects. However, the three shades of yellow (two near the middle, and a third on the far right) are also used by a few common elements, such as rings, yellow springs, and the HUD.


When playing as Knuckles, Sonic's palette is replaced with Knuckles' palette, which is identical except Sonic's shades of blue are replaced by Knuckles' reds.


But now we have a conundrum. Sonic and Knuckles each have their own palette, so in order to display both characters at the same time, both palettes must be loaded at the same time. The solution: load Knuckles' palette over the enemy palette, and avoid placing objects that were using it near the cutscene site.

You might be able to spot a flaw in that plan.

The HUD uses the enemy palette, and the HUD always on screen. So a concession is made: one of Knuckles' colors is sacrificed to make room for the HUD's yellow color, and for better or worse, they chose the green used by his socks.


Of course, because they gave cutscene Knuckles his own palette, they also forgot to update it when they tweaked the main Knuckles' palette to make him less pink, so now we have this yellow-socked, pink doppelganger running around, setting up traps. Makes him even more of a dick, really.

Tuesday, June 27, 2017

Too tall Knuckles

For the most part, Knuckles' sprites in the cutscene art block are identical to his player sprites from Sonic & Knuckles. Apart from differences in the color palette, the only other changes are to his running animation: Knuckles is two pixels shorter in Sonic & Knuckles than he was in Sonic 3.


In the process of lowering Knuckles' center of gravity, they also took the opportunity to properly paint his fists over the sloppy remains of Sonic's stomach, from whence the sprites were obviously edited. But not over the tan pixels around his fists, though, that would've been far too classy.


And if that's still not enough class for you, they actually managed to keep the original Sonic 3 frames around somehow. The pre-rotated versions of the running sprites were made before these changes, and since no one bothered to update them after the fact, Knuckles gains an inch whenever he runs up walls. Must be the lighter load on his legs.

Monday, June 26, 2017

Unlike Sonic 3, I don't chuckle

User muteKi posted over at the Sonic Retro forums:
One of the rather defining bits of Sonic 3 is the many moments where Knuckles comes around to throw a curveball into your path through a stage, with a distinctive giggle animation. (...) Lava Reef, however? (...) He just stands there, like a schmuck. No giggle there.

In none of the S&K cutscenes does he giggle, despite it being such a distinctive animation in Sonic 3.

Knuckles isn't playable in standalone Sonic 3, so there's no reason to include all of his sprites in the Sonic 3 cartridge, especially since player graphics are uncompressed and thus take up a lot of space. Instead, the game only stores the sprites used in each of his cutscene appearances, including the trademark giggle.


In Sonic & Knuckles, however, the game has access to nearly all of these poses from Knuckles' main art block, so the cutscene block isn't included. As such, the giggle animation fell by the wayside.

The interesting bit is that in the earliest prototypes we have, he does giggle in Lava Reef.


In the April and May prototypes, the LRZ2 scene uses the cutscene art block, along with the punching animation from Launch Base 2. Starting from the 0606 prototype, it instead uses the player art block and a custom animation. Rather unsurprisingly, the 0606 prototype is also the first build which no longer includes the Sonic 3 half of the game.

My guess is, when it came time to remove all the Sonic 3 dependencies, it didn't make sense to keep the cutscene art block around for just one level, and it was too late in development to do something like split off the giggling sprites and their mappings, and rewrite the object so it switches art and mappings mid-execution.

Friday, June 23, 2017

Short people problems

If you play around in this part of Launch Base Zone 2 as Tails, you may find that when you attempt to progress to the right, the overhanging tube will be completely solid and push you straight into the spikes below.


What's happening is, right around this area is a collision change object that puts you on the front path when you cross over to the right, and gives you high priority so you go in front of the tube. However, the object is so poorly placed that Tails, being shorter than the other characters, misses it completely if he just walks along the floor.


Their first mistake was using the default 128 pixel length setting, which doesn't cover the 160 pixel gap between the floor and the ceiling. But once you cross over to the right, there's another problem: the entire tube is now intangible, which means you can just jump over the object and go back to the left side with the wrong priority.


Moral of the story is, placing collision change objects is a pain in the ass, and it's very easy to get it wrong. Especially when your level has complex overlapping routes and you're rushing to meet a McDonald's tie-in promotion.

Thursday, June 22, 2017

What's in a name?

In my last post I made it a point to use the term "collision change object" over the more popular "path swapper". And previously I made sure to use the term "sprite status table" over "object status table", even though it stores an object's state.

I do this out of respect for the the game's developers, because those were the names they originally used. The "path swapper" object was called "colichg" in the original source code, and the term "sprite status table" comes directly from a patent by Yuji Naka himself:
Sprites

A Sprite is defined through a Sprite attribute table entry which is stored in VRAM45 and a sprite status table stored in RAM42. The following sprite status table lists representative status information that is stored in the RAM42 for main character (hero) type sprites as well as for various other sprites such as enemies or moving platforms.
______________________________________

Sprite Status Table
No. of Bytes    Description
______________________________________
   1            Action Number
   1            Action Flags
   2            Offset in VRAM
   4            Address of pattern table
   4            X direction offset within playfield
   4            y direction offset within playfield
   2            ± x direction speed
   2            ± y direction speed
   1            vertical offset (in dots) from center
                of character to bottom of char.
   1            horizontal offset (in dots)
                from center of character to bottom of
                character.
   1            sprite priority
   1            horizontal width in dots
   1            pattern number
   1            pattern counter
   2            pattern change number
   1            pattern timer counter
   1            pattern timer master
   1            collision size
   1            collision counter
   1            Routine number 1
   1            Routine number 2
   2            angle of character through loop (not
                sloop)
   1            ride-on flag
   1            hit flag
   2            A/B type collision setting
______________________________________
And sure, "object" is a better term than "sprite" to describe data structures and code style that emulate object-oriented design, and I honor that term because the disassembly uses the "Obj" prefix for objects, but it very quickly gives up and starts calling them sprites anyway:
Obj_MechaSonicHeadMain:
    jsr     (Refresh_ChildPositionAdjusted).l
    tst.b   ($FFFFFA89).w
    bne.s   loc_67D3C
    jmp     (Draw_Sprite).l
; ---------------------------------------------------------------------------

loc_67D3C:
    jmp     (Delete_Current_Sprite).l
; ---------------------------------------------------------------------------
Monitors are called "item boxes" or just "items" in the Japanese manuals, and the power-ups Sonic gets from them are known as "barriers" rather than "shields". This one is particularly infuriating, because changing it to a word that doesn't start with the letter "B" means the design of the items in the bonus stage no longer makes any sense!


Some of these are too far gone and aren't worth fighting for. The cheat that allows you to move freely and place objects within a level has always been known as "edit mode" in Japan, and is even listed as such in 1997's Sonic Jam, but it's forever ingrained as "debug mode" in the western world.


The offset stored in an object's "art tile" attribute is called a pattern index because it literally indexes a "pattern", which is the term used in official Mega Drive documentation for an 8x8 pixel block. However, the term "tile" was adopted from the early years of the ROM hacking scene and we haven't looked back since.

It's not all bad, though. The use of the term "beta" to describe a prototype version of a game has pretty much died out, and I honestly couldn't be more proud.

Wednesday, June 21, 2017

The collision change object

The mechanism responsible for changing which collision patterns are currently active is the collision change object. It is more commonly known as the "path swapper" object, and it is labelled Obj_PathSwap in the current disassembly, but it was called "colichg" in the original source code, so I'm gonna go with that.
Obj_PathSwap:
    move.l  #Map_PathSwap,$C(a0)
    move.w  #$26BC,$A(a0)
    ori.b   #4,4(a0)
    move.b  #$40,7(a0)
    move.b  #$40,6(a0)
    move.w  #$280,8(a0)
    ...
Though normally invisible, this object appears as a line of four rings when debug is enabled: in Sonic 3 & Knuckles, the player must be in object placement mode, whereas in standalone Sonic 3 just having the cheat on will suffice.


The placement of the ring sprites gives away the object's length and its orientation. These are set through the object's subtype: bits 0 and 1 define the length, from the possible values of 32, 64, 128 or 256 pixels long. The longest variant is rarely used, however, and it's even harder to track down because it actually uses the same sprite mappings as the second longest for some reason.

Meanwhile, bit 2 sets the object's orientation: vertical if the bit is clear, and horizontal if the bit is set.


The object's behavior is decidedly simple: if the player crosses over the object within the span of its length, their current collision path, as well as their sprites' priority, will change to whatever the object says it should. This is controlled by the next four bits in the subtype, as follows:

  • Bit 3 is the collision path chosen when the player crosses over to the right of the object, or below when the object is horizontal.
  • Bit 4 is the collision path chosen when the player crosses over to the left of the object, or above when the object is horizontal.
  • Bit 5 is the sprite priority chosen when the player crosses over to the right of the object, or below when the object is horizontal.
  • Bit 6 is the sprite priority chosen when the player crosses over to the left of the object, or above when the object is horizontal.

Finally, if bit 7 is set, the object will only take action if the player was on the ground when they crossed it. This behavior is what prevents you from being able to change which path you're on by simply jumping around inside loops: since the collision change object is only touching the top of the loop and you need to be on the ground to activate it, the only way it can possibly happen is if you're running along the ceiling of the loop when you cross it.

Tuesday, June 20, 2017

Level collision

Solidity information for level terrain is assigned per 16x16 block. Each block picks out from a global collision array that contains the 256 patterns used throughout the entire game. Each pattern is comprised of a 16 pixel-wide "height mask" that defines the shape of the ground, and a slope value which is used to calculate speed, acceleration, and initial jump angle from the given surface.


In turn, for each of their 16x16 blocks, 128x128 chunk definitions have a pair of flags that control their solidity. The first flag makes the block solid from the top, the second flag makes it solid from the sides and from the bottom, and both put together make the block completely solid. SonLVL, a popular level editing program, respectively displays level collision in white, yellow and black, depending on the value of these flags.


Furthermore, each 16x16 block actually picks two collision patterns from the global collision array. By changing which of the two is currently active, the game can essentially set the player on one of two different "paths", which may have distinct solidity. This is what allows loops to work: at the peak of their circumference, the collision is silently swapped, closing off the side from which the player just came, and opening up the path ahead.


Next time, the yellow devil that glues this all together.

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.

Friday, June 16, 2017

Watch out for those pattern bits

Ball Launcher, the first boss in Launch Base 2's gauntlet, has this "lid" that swings open before each of its titular balls are launched, which also doubles as a platform Sonic and Tails can use to reach the boss. When the boss is defeated, the lid's segments break off from one another and fall in the water, but in doing so also take on a strange color:


The offending code is at loc_740AA:
loc_740AA:
    move.l  #MoveChkDel,(a0)
    bset    #7,$2A(a0)
    move.w  #-$5BDB,$A(a0)
    ...
Argh, another one of those goofy numbers that aren't really negative. This one gives us some insight, though. Setting the sign bit on $A(a0) equals setting the priority flag on the object's sprites, which lines up with what we see in-game: the lid segments usually disappear behind the wall, but pop in front of it when we defeat the boss.

If we flip the signage, we get the value $A425. Unpacking this gives us base tile $425, no X/Y flip, and color palette 1. Let's compare this with the setup code for the object.
loc_73CD8:
    lea     ObjDat3_74140(pc),a1
    jsr     (SetUp_ObjAttributes).l
    ...

ObjDat3_74140:
    dc.l Map_LBZEndBoss
    dc.w $4425
    dc.w $280
    dc.b 8
    dc.b 8
    dc.b $E
    dc.b 0
The second entry in ObjDat3_74140 holds the value $4425, which when unpacked gives us base tile $425, no X/Y flip, and color palette 2. And therein lies the problem: when the boss is defeated, the lid segments try to set their priority bit, but also accidentally change from palette 2 to palette 1 in the process.

A possible explanation is that the object did use palette 1 at one point, but when it changed, they neglected to update the value at loc_740AA. Either that, or it was just human error inputting the new value by hand. A more sane approach would be to just use a bset or ori instruction to set the sign bit without messing with the rest of the attribute.

Thursday, June 15, 2017

Sprite Status Tables, part 3

As you may have gathered, given that the term "sprite status table" refers to the structure that records the status of an object, the two terms are mostly interchangeable. This is because objects are the game's main way of drawing sprites, although several objects do stuff in the background without any visual representation.

In order for an object to be rendered as a sprite, it must explicitly request so by calling the Draw_Sprite function once per frame, which is typically the last step of an object's execution. This will add the object's SST to the list of sprites to be rendered during that frame.
loc_81E3C:
    moveq   #0,d0
    move.b  5(a0),d0
    move.w  off_81E50(pc,d0.w),d1
    jsr     off_81E50(pc,d1.w)
    jmp     (Draw_Sprite).l
; ---------------------------------------------------------------------------
off_81E50:
    dc.w loc_81E54-off_81E50
    dc.w loc_81E72-off_81E50
; ---------------------------------------------------------------------------

loc_81E54:
    lea     ObjDat3_831CC(pc),a1
    jsr     SetUp_ObjAttributes(pc)
    ...
Later, when the sprite processor walks through the list, it looks at a bunch of attributes on the SST to figure out how to draw the object. Two of these attributes are the object's X and Y coordinates, which are loaded to $10(a0) and $14(a0) when the object is first loaded from the layout.

Since setting the rest of the attributes is such a common procedure, several objects call a function that initializes them to values from an "object data" structure loaded to register a1:
SetUp_ObjAttributes:
    move.l  (a1)+,$C(a0)    ; Mappings location
    move.w  (a1)+,$A(a0)    ; VRAM offset
    move.w  (a1)+,8(a0)     ; Priority
    move.b  (a1)+,7(a0)     ; Width
    move.b  (a1)+,6(a0)     ; Height
    move.b  (a1)+,$22(a0)   ; Mappings frame
    move.b  (a1)+,$28(a0)   ; Collision Number
    bset    #2,4(a0)        ; Object uses world coordinates
    addq.b  #2,5(a0)        ; Increase routine counter
    rts
Here's a quick rundown:
  • $C(a0) is the object's mappings. This is a pointer to a list of sprite mappings, each of which defines a set of sprite pieces and assembles them into the sprite's final shape.
  • $22(a0) is the current mapping frame. This acts as an index to the above list, and can be changed to pick which of the sprite mappings to display.
  • $A(a0) is the art tile. This is a VDP pattern index of the form PCCYXAAAAAAAAAAA and defines the base priority flag, color palette, X/Y flip and VRAM offset for each sprite piece in the mappings.
  • 8(a0) is the object's priority value, set in increments of $80. Objects with a lower value are drawn on top of ones with a higher value.
  • 6(a0) and 7(a0) are the object's height and width in pixels. These are used along with the object's X and Y coordinates to decide whether the object is on screen. If the object is off-screen, it is not rendered.
  • 4(a0) are the object's render flags. This is a set of flags which, appropriately enough, affect how the object is rendered:
    • Bits 0 and 1 are the horizontal/vertical flip flags. These are automatically set when the object is first loaded from the layout, and flip the entire mapping frame on itself.
    • Bit 2 is the coordinate system flag. If set, the object's X and Y coordinates define the object's position within the level, otherwise they define its position on the screen.
    • Bit 5 is the static mappings flag. If set, the object's mappings point directly to a single mapping rather than a list, and the mapping frame is not used.
    • Bit 6 is the multi-sprite flag. If set, the object is rendered as mutliple sprites, with the mapping frame and X/Y coordinates for the additional sprites pulled from elsewhere in the SST.
    • Bit 7 is the on-screen flag. This will be set or cleared depending on whether the object was on-screen, and thus rendered, during the previous frame.

The sonic3k.constants.asm file in the current disassembly defines human-friendly names for almost every attribute in an object's SST. However, very few objects in the disassembly have actually been refactored to make use of them, so it's always good to know the more common ones by heart.

Wednesday, June 14, 2017

Sprite Status Tables, part 2

More often than not, objects need to go through some initialization at the start of their life, such as setting up their own attributes, loading a palette or queueing a PLC. In this scenario, an object may elect to overwrite its own code pointer by writing to (a0), which points at the base address of its SST.
Obj_MechaSonicHead:
    lea     ObjDat_MechaSonicHead(pc),a1
    jsr     (SetUp_ObjAttributes).l
    move.l  #Obj_MechaSonicHeadMain,(a0)
    lea     (ArtKosM_MechaSonicHead).l,a1
    move.w  #-$5A40,d2
    jmp     (Queue_Kos_Module).l
; ---------------------------------------------------------------------------

Obj_MechaSonicHeadMain:
    jsr     (Refresh_ChildPositionAdjusted).l
    ...
In the example above, Obj_MechaSonicHead replaces its own pointer with the address for Obj_MechaSonicHeadMain. Starting the very next frame, the object processor will instead jump to that address, so the init code will no longer run.

Objects that follow this pattern frequently let control fall through directly to the main bit of code, as seen in the example below. This ensures the object is running the moment it is created.
Obj_AIZHollowTree:
    move.b  #$D0,width_pixels(a0)
    move.l  #loc_1F752,(a0)

loc_1F752:
    bsr.w   sub_1F7B8
    ...
An alternative pattern is to use the routine attribute, stored in the 5th byte of an object's SST. The overall convention is shown below: the routine byte, which must always be even, is used as an index to an offset table stored directly below. The resulting offset is then used to perform a relative jump from the table to the appropriate routine, hence the name.
Obj_MonitorContents:
    moveq   #0,d0
    move.b  5(a0),d0
    move.w  off_1D7C8(pc,d0.w),d1
    jmp     off_1D7C8(pc,d1.w)
; ---------------------------------------------------------------------------
off_1D7C8:
    dc.w loc_1D7CE-off_1D7C8
    dc.w loc_1D81A-off_1D7C8
    dc.w loc_1DB2E-off_1D7C8
; ---------------------------------------------------------------------------

loc_1D7CE:
    addq.b  #2,5(a0)
    move.w  #$84C4,$A(a0)
    ...
This pattern is frequent in bosses and similarly complicated objects which cycle through a large number of states. It is also common to see it in "old" objects, such as rings and monitors, because this was the only pattern in Sonic 1 and 2. In those games, SSTs did not directly store a code pointer: it was read from the object list every time.

Tuesday, June 13, 2017

Sprite Status Tables, part 1

A sprite status table, or SST, is a data structure which records all the information about an object's state. It is 74 bytes long, of which 70 bytes are effectively a blank canvas the object can use to store all sorts of data. The first longword in an SST, however, stores the object's code pointer, which is typically the ROM address where the object's code starts.

Once every frame, the object processor walks through object RAM, which begins at address $B000 and holds a total of 110 SSTs. For each object slot, the base address of its SST is loaded into the a0 register, and the object's code pointer is retrieved. If the pointer is set to zero, the slot is considered empty, and is not processed. Otherwise, control jumps to the address stored in the pointer, and the object begins executing its own code.
Process_Sprites:
    tst.b   (Teleport_active_flag).w
    bne.s   locret_1AB0C
    lea     (Object_RAM).w,a0
    ...
    moveq   #$6D,d7

sub_1AAFC:
    move.l  (a0),d0
    beq.s   loc_1AB04
    movea.l d0,a1
    jsr     (a1)

loc_1AB04:
    lea     next_object(a0),a0
    dbf     d7,sub_1AAFC

locret_1AB0C:
    rts
This brings us to our first convention: the a0 register starts off pointing at the object's own SST, and the object can use it to read from and write to its own attributes. For instance, when an object is first loaded from the layout, its subtype is copied to offset $2C of its SST. Later, when the object executes, it can fetch its own subtype by reading from $2C(a0).
Obj_14_2:
    move.b  $2C(a0),d0
    andi.w  #$7F,d0
    lsl.w   #3,d0
    move.w  d0,$36(a0)
    addi.w  #$10,d0
    move.w  d0,$38(a0)
    move.l  #loc_3FBAC,(a0)

loc_3FBAC:
    moveq   #0,d2
    lea     (Player_1).w,a1
    bsr.w   sub_3FBC4
    lea     (Player_2).w,a1
    bsr.w   sub_3FBC4
    jmp     (Delete_Sprite_If_Not_In_Range).l
Furthermore, most functions called by objects assume a0 is pointing to the SST of the calling object, so as a general rule, objects never write to a0. In the event the object needs to call a function which expects something else in a0, the current value of a0 is backed up before the function call and restored immediately after.

Monday, June 12, 2017

Object and ring layouts

Data for object and ring placement is read directly from ROM as you move through a level. Objects and rings are kept in separate layouts, with the format for ring layouts being fairly straightforward: each ring definition is composed of two words, which correspond to the ring's horizontal (X) and vertical (Y) coordinates within the level.

Object layouts are slightly more complex. Some additional flags are stored in the upper bits of the Y coordinate, such as horizontal and vertical flip: this is possible because there's a maximum level height of $1000 pixels. Two extra bytes define which object to spawn at those coordinates, which correspond to the object's ID and its subtype.

The game only loads objects inside the same "vertical slice" of the level as the player. To that effect, object definitions appear ordered by their X coordinate, so that parsing of the layout can stop as soon as the first out-of-range object is encountered. An additional optimization in Sonic 3 prevents objects from being allocated until they're also in the same "horizontal slice" as the player; this behavior can be disabled by setting the high bit on the Y coordinate.

The ID byte is used as an index to a list of pointers to the actual object code. Sonic 3 & Knuckles actually has two of these lists, one for the Sonic 3 levels and another for the Sonic & Knuckles levels. Competition stages, bonus stages and Flying Battery Zone all use the Sonic 3 list.
Sprite_Listing3:
    dc.l    Obj_Ring                    ; 0
    dc.l    Obj_Monitor                 ; 1
    dc.l    Obj_PathSwap                ; 2
    dc.l    Obj_AIZHollowTree           ; 3
    dc.l    Obj_CollapsingPlatform      ; 4
    dc.l    Obj_AIZLRZEMZRock           ; 5
    dc.l    Obj_AIZRideVine             ; 6
    dc.l    Obj_Spring                  ; 7
    dc.l    Obj_Spikes                  ; 8
    ...
When the object comes into range, its code pointer, X/Y coordinates, horizontal/vertical flip flags and subtype are all copied to a data structure in RAM, the sprite status table. From there, the object is ready to start fending for itself.

Next, we'll begin our look at the conventions of Sonic 3 object programming.

Friday, June 9, 2017

The out-of-bounds layout copy area

If you go beyond the natural end of Launch Base Zone 1, you'll come across this area before you reach the loopback: a bunch of perfectly coherent buildings that are nonetheless glued together in a nonsensical manner.


These are actually the same buildings you go through during the course of the act, as they appear from the inside and from the outside. When you enter one, the game copies the appropriate indoor section from this area over to the main layout, revealing the building's interior. When you leave, it copies over the matching outdoor section to hide it again.

Act 2 also has an out-of-bounds building, but it's tricky to reach because halfway through the level, the Death Egg bit erases all the regular chunks from the layout. The simplest solution is to scroll all the way to the end of the level, place a Star Post, hit it and die. You'll respawn in the loopback, and the Death Egg is gone until you exit the level. Now you can just go back to the left until you find it:


Unlike the ones in act 1, this one only has the outside version. This is because once you exit the building, there's no way to go back inside, so the game doesn't need to remember what the inside looks like.

Flying Battery Zone uses the same trick for when you enter and exit the ship. The size for this stage is set properly, so you can't go beyond the end of the level, but as it happens, the first copy area is right underneath the starting point!


Inside... and outside. Neat!

Another one that's within bounds is Angel Island Zone 1. Right at the start of Sonic's intro sequence, if you go directly upward, you'll find the interior version of the hollowed-out tree from later in the stage. Unfortunately, the right graphics aren't loaded, making it pretty unrecognizable. But at least you can stand on it.


Once again, only the inside version is present because once the interior is revealed, it's never covered up again, so the game doesn't need to recall its outside appearance.

Thursday, June 8, 2017

What are sewers?

The term "sewer" was also coined by GoldS in his legendary Sonic 3 and Knuckles Glitches and Oversights series, and refers to the area below the level where the normal layout ends, and insanity ensues.


The reason why this happens is that, much like before, the developers didn't set the vertical size for levels they thought you would never get to the bottom of, leaving it at the default value of $1000 pixels. But what exactly is down there?

First, you might've noticed I left something out when I talked about loopbacks being shifted up from the actual level:


So what's in that little corner, past the actual level and underneath the loopback? Well, turns out that after the layout for Plane A comes the layout for Plane B, so if you go down there, you can see background chunks in the foreground layer and get a different perspective on the background tile replacement trick.


If you go below this area, though, you'll find the same pattern repeating over and over. It repeats because it's reading the same row of chunks over and over, and that brings me to the other thing I glossed over in the last post: how does the game "know" where each row is supposed to start?

Sonic Retro wiki gives us the answer. Before the actual chunk data, the layout contains 64 word-sized pointers which don't make much sense inside the ROM, but when the layout is copied to RAM address $8000, they become pointers to the start of each chunk row in RAM, alternating between foreground and background rows:
 00 70  00 04  00 18  00 08  80 88  8B 08  80 F8  8B 0C 
 81 68  8B 10  81 D8  8B 14  82 48  8B 18  82 B8  8B 1C 
 83 28  8B 20  83 98  8B 24  84 08  00 00  84 78  00 00 
 84 E8  00 00  85 58  00 00  85 C8  00 00  86 38  00 00  
 86 A8  00 00  87 18  00 00  87 88  00 00  87 F8  00 00 
 88 68  00 00  88 D8  00 00  89 48  00 00  89 B8  00 00 
 8A 28  00 00  8A 98  00 00  00 00  00 00  00 00  00 00 
 00 00  00 00  00 00  00 00  00 00  00 00  00 00  00 00 
 00 00  00 00  00 00  00 00  B4 26  00 00  38 38  00 00

        Foreground rows             Background rows  
After a while, the pointers peter out and are all left at zero, which accounts for the repeating chunk rows in the sewers. But what is a zeroed pointer pointing at?

A naïve answer would be to say that since these are RAM pointers, and RAM address $0000 stores chunk definitions, the sewer rows are paradoxically reading chunk definitions as a chunk layout. In reality, though, when the pointers are loaded to an address register, they get sign-extended to 32-bit addresses. Because the pointers are normally $8000-based, they get sign-extended to address $FFFF8000, which maps to 68000 RAM. The zeroed pointers however, are sign-extended to address $00000000, which ends up mapping to the beginning of the ROM!

That means when you're in the sewers, what you're looking at, and possibly even walking and jumping on, is actually the 68000 vector table at the start of the ROM.
Vectors:    dc.l    $00000000,      EntryPoint,     ErrorTrap,      ErrorTrap       ; 0
            dc.l    ErrorTrap,      ErrorTrap,      ErrorTrap,      ErrorTrap       ; 4
            dc.l    ErrorTrap,      ErrorTrap,      ErrorTrap,      ErrorTrap       ; 8
            dc.l    ErrorTrap,      ErrorTrap,      ErrorTrap,      ErrorTrap       ; 12
            dc.l    ErrorTrap,      ErrorTrap,      ErrorTrap,      ErrorTrap       ; 16
            dc.l    ErrorTrap,      ErrorTrap,      ErrorTrap,      ErrorTrap       ; 20
            dc.l    ErrorTrap,      ErrorTrap,      ErrorTrap,      ErrorTrap       ; 24
            dc.l    H_int_jump,     ErrorTrap,      V_int_jump,     ErrorTrap       ; 28
            dc.l    ErrorTrap,      ErrorTrap,      ErrorTrap,      ErrorTrap       ; 32
            dc.l    ErrorTrap,      ErrorTrap,      ErrorTrap,      ErrorTrap       ; 36
            dc.l    ErrorTrap,      ErrorTrap,      ErrorTrap,      ErrorTrap       ; 40
            dc.l    ErrorTrap,      ErrorTrap,      ErrorTrap,      ErrorTrap       ; 44
            dc.l    ErrorTrap,      ErrorTrap,      ErrorTrap,      ErrorTrap       ; 48
            dc.l    ErrorTrap,      ErrorTrap,      ErrorTrap,      ErrorTrap       ; 52
            dc.l    ErrorTrap,      ErrorTrap,      ErrorTrap,      ErrorTrap       ; 56
            dc.l    ErrorTrap,      ErrorTrap,      ErrorTrap,      ErrorTrap       ; 60

Wednesday, June 7, 2017

A break

Things are kind of on fire right today and I don't have time to write a post for you guys. Instead, watch this crazy tool-assisted speedrun by Evil_3D, WST and marzojr (better known as flamewing).


Why is this still in submissions!?

Tuesday, June 6, 2017

What is a loopback?

The term "loopback" was first coined by GoldS in his legendary Sonic 3 and Knuckles Glitches and Oversights video series. Essentially, if you go beyond the natural end of some levels, you'll find yourself in an empty copy of the layout that's completely devoid of objects and shifted up for some reason.


So what exactly is going on, and why does it happen?

Well, the second question is easy. It happens because the developers neglected to properly set the horizontal size for levels in which they thought you would never be able to reach the physical end of. Generally speaking, this means it's left at the default value of $6000 pixels.


As for what's going on, think about it: a level layout is little more than an array of chunk IDs. Mapping that array across multiple horizontal rows is just one way to interpret it, kind of like word-wrapping some text over several lines. And just as with word wrap, an equally valid way of displaying the same data is to just ignore the limit and keep adding onto the same row. Which is what happens with level layouts, except for every row:


Notice how by simply continuing to draw chunks beyond the natural end of the level, we've managed to create an exact replica, except every chunk has been shifted up a single row, which lines up perfectly with what we see in-game. There are no objects because this is just a quirk in the level layout format: for all the game knows, the level really is that long; there's just nothing placed in that last bit.