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:
    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
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 30, 2017

Why does breaking a bunch of S monitors destroy the game?

Because S monitors are just a debug feature.

If you use debug mode to place a lot of Super monitors in one place, and then destroy them all at the same time, things go south pretty quick.

So what exactly is going on? When Sonic turns into Hyper Sonic, the game loads up an extra object to handle the large stars that start flying around him. This object is always loaded to the same RAM address, so there's no chance there'll ever be too many stars flying around.

However, here's the first thing the object does when it's loaded:
    lea (ArtKosM_HyperSonicStars).l,a1
    move.w  #$F380,d2
    jsr     (Queue_Kos_Module).l
If that looks familiar, that's because it's a PLC request. This is what the object is saying: "Hey, I know you're busy right now, but as soon as you have some time, I need you to decompress this art file to this address in VRAM. I'll go ahead and add this to your queue so you won't forget."

Every time you pop open a Super monitor, the object starts over, and adds another request to the queue. And another. And another. Until the queue runs over and spills into other bits of RAM, corrupting the game into a hard crash.

This bug never manifests itself during normal play because usually, you can't turn Hyper if you're already Hyper. They could have added an extra check for the sake of debug mode, but they didn't.

Because S monitors are just a debug feature.

Monday, May 29, 2017

Level Load Block

Rather than being made directly out of tiles, Sonic levels are built out of larger. precomposed tile patterns which are progressively assembled in order to generate the full layout. Specifically, combining four tiles makes a 16x16 "block", and combining 64 blocks makes a 128x128 "chunk". Chunks are then placed along a grid, forming the level's layout.

The array which holds tile, block and chunk data for each level is called the "level load block". Here's what it looks like in the current disassembly:
;   1st PLC         palette                          2nd 8x8 data                                     2nd 16x16 data                                      2nd 128x128 data
;           2nd PLC           1st 8x8 data                                    1st 16x16 data                                    1st 128x128 data

    levartptrs $B,  $B,  $A,  AIZ1_8x8_Primary_KosM, AIZ1_8x8_Secondary_KosM, AIZ1_16x16_Primary_Kos, AIZ1_16x16_Secondary_Kos, AIZ1_128x128_Kos,         AIZ1_128x128_Kos              ; ANGEL ISLAND ZONE ACT 1
    levartptrs $C,  $C,  $B,  AIZ2_8x8_Primary_KosM, AIZ2_8x8_Secondary_KosM, AIZ2_16x16_Primary_Kos, AIZ2_16x16_Secondary_Kos, AIZ2_128x128_Kos,         AIZ2_128x128_Kos              ; ANGEL ISLAND ZONE ACT 2
    levartptrs $E,  $F,  $C,  HCZ_8x8_Primary_KosM,  HCZ1_8x8_Secondary_KosM, HCZ_16x16_Primary_Kos,  HCZ1_16x16_Secondary_Kos, HCZ_128x128_Primary_Kos,  HCZ1_128x128_Secondary_Kos    ; HYDROCITY ZONE ACT 1
    levartptrs $10, $11, $D,  HCZ_8x8_Primary_KosM,  HCZ2_8x8_Secondary_KosM, HCZ_16x16_Primary_Kos,  HCZ2_16x16_Secondary_Kos, HCZ_128x128_Primary_Kos,  HCZ2_128x128_Secondary_Kos    ; HYDROCITY ZONE ACT 2

Yikes, it's a bit packed there, isn't it. levartptrs is a macro, and here's the definition:
; macro for declaring a "main level load block" (MLLB)
levartptrs macro plc1,plc2,palette,art1,art2,map16x161,map16x162,map128x1281,map128x1282
    dc.l (plc1<<24)|art1
    dc.l (plc2<<24)|art2
    dc.l (palette<<24)|map16x161
    dc.l (palette<<24)|map16x162
    dc.l map128x1281
    dc.l map128x1282
Okay, so each entry is made up of six longwords, each a pointer to some kind of level data. From the top, two pointers to 8x8 tile data, then two pointers to 16x16 block mappings, and finally two pointers to 128x128 chunk mappings.

Additionally, since the Mega Drive's ROM address space only goes up to 0x400000, the top byte in a longword pointer is never set, so we can sneak in some extra pointers: two PLC pointers and a palette pointer (which appears twice but only the first one is used).

Here's what it looks like unpacked:
    dc.l HCZ_8x8_Primary_KosM+$E000000
    dc.l HCZ1_8x8_Secondary_KosM+$F000000
    dc.l HCZ_16x16_Primary_Kos+$C000000
    dc.l HCZ1_16x16_Secondary_Kos+$C000000
    dc.l HCZ_128x128_Primary_Kos
    dc.l HCZ1_128x128_Secondary_Kos
The reasoning for two of each level data comes from Sonic 3's act system. The second act of each Zone tends to shoot off and do its own thing, often requiring a new set of tiles, blocks and chunks. Almost inevitably though, some elements end up being present in both acts; they're the same Zone, after all.

Rather than duplicating the common bits in both act 1 and act 2's data, a more space-efficient solution is to split them into their own "primary" data, and then each act adds on its own "secondary" data as needed. A similar trick was used in Sonic 2's Hill Top Zone because geez, that stage did everything first.

The three byte pointers are indexes to arrays elsewhere in the ROM. The palette byte points to an array called, well:
    dc.l Pal_HCZ1
    dc.w $FC20
    dc.w $17
Each entry is made up of one longword and two words. The longword is a ROM pointer to the actual color data, the first word is a RAM pointer to the destination address, and the last word is the number of longwords to copy minus 1. Recall that Mega Drive colors are 16 bits each: each longword holds two colors, making $18 longwords hold $30 colors, which completely fills out three palette lines.

The PLC bytes point to the PLC offset array, which is itself a pointer array that points to individual PLCs:
    dc.w PLC_0E-Offs_PLC        ; HCZ 1 PLC 1
    dc.w PLC_0F-Offs_PLC        ; HCZ 1 PLC 2
Here's what a PLC looks like:
PLC_0E: plrlistheader
    plreq $45C, ArtNem_Bubbles
    plreq $3CA, ArtNem_HCZMisc
    plreq $426, ArtNem_HCZButton
    plreq $37A, ArtNem_HCZWaterRush
    plreq $42E, ArtNem_HCZWaveSplash
    plreq $43E, ArtNem_HCZSpikeBall

PLC_0F: plrlistheader
    plreq $44C, ArtNem_HCZDragonfly
Oh great, more macros. Can I just take a minute to ask, does anybody actually like these? I'm not even going to bother with the macro definition; it's gibberish for all I can tell. You'll just have to believe me when I say it unpacks to this:
    dc.w 5
    dc.l ArtNem_Bubbles
    dc.w $8B80
    dc.l ArtNem_HCZMisc
    dc.w $7940
    dc.l ArtNem_HCZButton
    dc.w $84C0
    dc.l ArtNem_HCZWaterRush
    dc.w $6F40
    dc.l ArtNem_HCZWaveSplash
    dc.w $85C0
    dc.l ArtNem_HCZSpikeBall
    dc.w $87C0
    dc.w 0
    dc.l ArtNem_HCZDragonfly
    dc.w $8980
PLCs, or Pattern Load Cues, are essentially pairs of pointers: the first pointer, a longword, is a pointer to graphic data in ROM; the second pointer, a word, is the destination address in VRAM. The first word in each PLC is the number of entries minus 1.

Sonic 3 loads most of its sprite graphics through PLCs. They allow graphics to be arranged and reused between levels, allowing elements to be shared between both acts of a Zone, or used globally such as spikes and springs.

Next time, we'll look at one way PLCs can go very, very wrong.

Friday, May 26, 2017

It's probably even cooler now, when you think about it

There's an additional element which Sonic 3 inherited from Sonic 2's lost Hidden Palace Zone, other than its name. No, I'm not talking about the the large green brilliant-cut gemstone surrounded by equally green crystals sitting on top of an ornate cylindrical structure.

Sonic 2's Hidden Palace had these neat glowing bridges in which each individual segment would light up as Sonic and Tails walked across them. This cool object was not forgotten, and was refurbished for use in Sonic 3's Icecap Zone.

Alright, that's enough Sonic 2 for now. Next week, we'll resume our primer series by looking at what makes up a level.

Thursday, May 25, 2017

Paradoxically both the oldest and youngest

Okay, I was lying when I said Flying Battery Zone is the only level which appears out of turn in the game's internal level order. At the very end of the list, four extra acts are clumped together, making up two extra zones. In order, these are:

  • Lava Reef Zone's boss act ("LRZ3")
  • Hidden Palace Zone
  • Death Egg Zone's final boss ("DEZ3")
  • Hidden Palace Zone when accessed from a special stage ring

The placement of these stages would seem to suggest they were very late additions to the game. Indeed, if you check the sound test, song $14 is Lava Reef 2, $15 is Sky Sanctuary and $16 is Death Egg 1. It would seem Sky Sanctuary was planned to be a single act from the start, while Hidden Palace Zone ended up never getting its own song, which is kind of ironic because it did get one in Sonic 2, only for it to end up unused when the stage itself got cut.

But if Hidden Palace was a late addition to Sonic 3, the idea of naming it after the lost Sonic 2 stage might have been born even later. The name does not appear in the level select, instead being cryptically listed under "Lava Reef 4". And should you choose that stage in the prototype dated April 8th, 1994 (or 0408 prototype for short), you're greeted by this wondrous sight:

It even plays Sky Sanctuary music, which makes more sense than the Lava Reef 2 music in the final game, but isn't as fitting. After this, act 2 logically follows:

By the time the prototype dated May 17th, 1994 (or 0517 prototype for short) rolls around, Sky Sanctuary Zone is now actless, with Sonic and Tails' score tally displaying "act 1" as in the final game. Hidden Palace Zone is also now called Hidden Palace Zone, however it still plays Sky Sanctuary music.

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.

Tuesday, May 23, 2017

Flying Battery Zone was originally Zone 5

While developing, we went snowboarding a lot at a nearby resort. People kept getting injured, though... Originally, this stage was planned to begin after Zone 8 (Flying Battery Zone). Sonic was going to break down the door from the airship and make a snowboard out of it on the way down. The other characters can fly, so they wouldn't appear in that event.
We're going to have to take Naka's word about the snowboarding bit. However, one look at standalone Sonic 3's level select and Flying Battery Zone's original place in the level order becomes evident:

There it is, smack dab between Carnival Night Zone and Icecap Zone. If you go and check the sound test, FBZ's music occupies slots $09 and $0A, right between CNZ and ICZ's music. In fact, Flying Battery Zone is the only level which appears out of turn in the game's internal level order. This suggests it was a relatively late change, a notion supported by the presence of fully-functional bosses in standalone Sonic 3.

If you have a Pro Action Replay or PAR-compatible emulator or flash cart, you can use the code FFE6CF:0004 to set the first Data Select slot to Zone 5. No, not Icecap Zone, the real Zone 5. It even comes with its own, otherwise unused preview icon, which is different to the one in Sonic & Knuckles.

The stage itself has been completely removed though, so if you try to play it, the game just freezes.

Shoot, so close!

Monday, May 22, 2017

Time travel in Sonic 2

We interrupt our regularly scheduled analysis of Sonic 3's interactions with the VDP for some very exciting news.

Earlier today, Hirokazu Yasuhara, lead game designer for Sonic 1, 2 and 3, was present at the video game developer conference Digital Dragons, where he held a presentation on how to make games "fun". Who in their right mind would have expected that after 25 years, all the way from Krakow, Poland, he would share with us these amazing production sketches for Sonic 2?

Here's a brief recap, adapted from articles on the Sonic Retro wiki. Back in 2005, vested Sonic researcher ICEknight managed to secure some enemy sketches from the Sonic 2 production staff. Between the six images, though, what got everybody's attention were the comments next to the sketch for the "Bumper" enemy:


Location of appearance: Desert Zone (present)
Step on it and it sends [Sonic] flying far away

Location of appearance: Rock Zone (past)
Property: Same as above (color change)

For the past twelve years, that little blurb was all the information we had on Sonic 2's early time travel elements. The sketches revealed today not only give us rough names for all the Zones that were planned, but also shed new light on a minor mystery with the final game.

Internally, Sonic 2's levels are stored in a seemingly nonsensical order, with several empty slots scattered about, not unlike monster species in the first generation of Pokémon games. Turns out, they line up more or less perfectly with the prototype order seen in the sketches, and the above timeline shown by Yasuhara during his presentation:
Internal order
00 - Emerald Hill Zone
01 - (unknown)
02 - (Wood Zone)
03 - (unknown)
04 - Metropolis Zone

05 - Metropolis Zone 3
06 - Wing Fortress Zone
07 - Hill Top Zone

08 - (Hidden Palace Zone)

09 - (unknown)
0A - Oil Ocean Zone
0B - Mystic Cave Zone

0C - Casino Night Zone
0D - Chemical Plant Zone
0E - Death Egg Zone
0F - Aquatic Ruin Zone
10 - Sky Chase Zone
グリーン (Green)
オーシャン (Ocean)
ウッド (Wood)
サンド (Sand)
メトロポリス (Metropolis)

トロピカルサン (Tropical Sun)
ブルーオーシャン (Blue Ocean)
ヒルトップ (Hill Top)

ロック (Rock)
オイル (Oil)
ダスト (Dust)
デスエッグ (Death Egg)

ヒルトップ (Hill Top)
ブルー (Blue)

カジノ (Casino)
ケミカル (Chemical)
ジェノサイド (Genocide)
ネオデスエッグ (Neo Death Egg)

Green Hill Zone
Ocean Wind Zone
Woods Zone
Sand Shower Zone
Metropolis Zone

Tropical Plant Zone
Blue Lake Zone
Hill Top Zone
Rock World Zone

Rock World Zone
Oil Ocean Zone
Dust Hill Zone

Casino Night Zone
Chemical Plant Zone
Genocide City 1 Zone
Genocide City 2 Zone

In the next few articles, we'll look at Sonic 3's own internal level order and the stories it tells us.

Friday, May 19, 2017

Where you're going you don't need dust

You might have noticed that when you spindash underwater, sometimes no dust cloud appears behind your character.

The spindash dust and the drowning numbers actually DMA their graphics to the same location in VRAM, and the two objects cooperate over custody of the slot: if the player is running out of air, then the spindash dust silently kills itself to avoid the art conflict.

Thursday, May 18, 2017

Direct Memory Access

DMA is a feature that allows the VDP to very quickly copy a large amount of data to an arbitrary location in VRAM. It is the method by which the sprite and background tables are written to VRAM every frame, but it can just as well be used to dynamically load tiles.

One of the main uses of DMA in Sonic games is to load the main character graphics as they're needed. Since only one of Sonic's sprites is ever visible at a time, new tiles can be loaded over the old ones, saving VRAM. Special Stage rings temporarily overwrite other graphics when they appear; using DMA minimizes the number of affected tiles.

DMA is also used to animate the background planes. By periodically writing new tiles over specific regions of the main level art, static elements such as the plants in Angel Island Zone and the mesh cylinders in Flying Battery Zone can be made to move. The same principle is used to achieve the extra background layer trick we previously saw.

One drawback of using DMA is that the source tile data must be uncompressed, which takes up a lot of storage space. As an example, Knuckles' sprites alone take up a whopping 1/16 of the 2 megabyte Sonic & Knuckles cartridge. In fact, uncompressed art takes up almost 20% of the combined S3&K ROM!

The rolling jump Bonus Stage is pretty smart about this. The graphics for its complex animated background are actually compressed within the ROM, but the game first decompresses them to 68k RAM, from where they can then be DMA'd to the appropriate location in VRAM.

Wednesday, May 17, 2017

When sprites use background art

This is something I came across while messing around with Flying Battery Zone's palette. There's this flat-colored tile which appears in the foreground with palette line 1, but also appears in the background with palette line 3. I guess the color index just happened to line up.

When I messed with the layout of palette line 1, suddenly the colors didn't line up anymore, requiring said tile to be split in two, one for the foreground and another for the background. Undeterred, I went looking for another tile I could usurp.

Well, that didn't take long. I replaced the second of the seemingly redundant tiles, and everything seemed fine until I ran into this monstrosity:

Long story short, since those collapsing platforms look a lot like the rest of the level, their tiles are stored along with the main level art. That way, they can be reused by the background planes without wasting extra VRAM. Apparently this is common practice; Hydrocity Zone has collapsing platform graphics at the start of the main level art in a similar manner.

The presence of duplicate tiles gives it away: sprites need all of their tiles to come in sequence, so the art file caters to them. Background planes aren't bound to such restrictions, so they end up using only the first tile, and as such survive my hamfisted experiments.

Tuesday, May 16, 2017

Order matters

Setting the priority bit ensures a sprite is displayed in front of others, but the level of layering afforded by this feature isn't nearly fine-grained enough for most scenarios.

In the above screenshot, Sonic appears behind the teacup lift, and the lift's grip appears in front of the purple screw. As they rotate around the screw, Sonic and the teacup alternate between going in front of both the grip and the screw, and then behind them. But when the lift passes through the floor, it goes behind the foreground plane, meaning the priority bits on all these sprites have to be off.

So what is happening here? Well, as we previously learned, the VDP renders the sprite layer by walking down a list of sprites and drawing them one by one. The order in which sprites are added to this list turns out to be quite important, because it defines the order in which they'll be drawn: sprites higher up on the list will overlap sprites further down.

Sonic games take advantage of this by tracking an additional value for each sprite, which is confusingly also referred to as "priority" in the current disassembly. Sprites are added to the list in the order of their priority: the lower the value, the higher up the sprite will appear on the list. Sprites with the same priority are generally added in the order they were first scrolled into view.

The priority value also affects the behavior of the sprite limit. Since sprites with higher priority are higher up on the list, when the limit hits, sprites with low priority are discarded because the VDP stops walking the list before getting to them.

In the above screenshot, since rings have higher priority than spikes, having more than eight of them aligned with the twelve pairs of spikes on the floor causes the sprite limit to kick in and discard the spikes on the far right, as those were the last to be scrolled into view. The enemy on the left has lower priority than spikes, so despite having been scrolled into view before them, it too gets discarded.

Monday, May 15, 2017

An introduction to Mega Drive graphics, part 3

Mega Drive sprites are a very versatile feature. They can be displayed at arbitrary screen coordinates, completely independent from the background planes and from one another, making them useful for displaying all sorts of things: static objects which scroll along with the level, HUD elements which remain in place as the level scrolls by, and the player characters, which do a bit of both.

Sprites are small rectangular blocks which combine anywhere from one to 16 separate tiles. They come in 16 possible shapes, ranging from a 1x1 to a 4x4 square, and everything in between:

A single VDP pattern index defines the appearance of the entire sprite, which implies a couple of things. First, every tile in a sprite will be displayed with the same palette and same priority level. Second, when you set the horizontal/vertical flip bits, the entire shape will be mirrored. This is actually quite useful for side-scrolling games: characters can be made to face the opposite direction by just flipping a bit.

Because the default set of shapes is quite limited, a common practice is to combine several sprites into more complex objects. Sonic games handle this with "sprite mappings", simple lists of sprite definitions and their relative positioning to one another. When displaying these objects, the target screen coordinate is added to each individual piece, which are then rendered by the VDP as completely separate sprites.

Since there's only one pattern index per sprite, there's also only a single tile address, which becomes the sprite's base tile address. Tiles are consecutively pulled starting from this location in VRAM and used to fill out a sprite's shape, first top to bottom, then left to right as seen above. This does mean that if a sprite wants to use the same tile more than once, it has to be duplicated in VRAM, wasting precious space.

Entries in the VDP's sprite table have a "link field", meaning the whole thing is actually a linked list. The VDP renders the sprite layer by walking down this list once every scanline, reading up to 80 entries and drawing up to 20 sprites. Sprites are only drawn if they cross the current scanline, giving us the Mega Drive's sprite limit: a global maximum of 80 sprites, and a maximum of 20 sprites per pixel row.

Next time, we'll look at sprite sorting.

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.

Thursday, May 11, 2017

Extra layers via tile replacement

Here's what the background scroll in Launch Base Zone 1 looks like: some basic parallax where the shrubbery scrolls faster than the construction towers, which in turn scroll faster than the mountains... wait a minute.

The mountains are scrolling behind the towers! How can this be possible without a third background plane?

The effect in question was previously discussed in this Sega 3D Classics interview for 3D Shinobi III. (Scroll down to "What it took to make 3-1 3D".) Essentially, as the background plane scrolls, the game directly overwrites the mountain graphics in VRAM with one of the following patterns, based on the camera's horizontal position:

Because each pattern is identical to the last, except shifted horizontally by a single pixel, running them in the opposite direction of the camera creates the illusion of an additional layer further back, scrolling at an even slower speed.

This trick was first seen in Sonic 2's Hill Top Zone, but Sonic 3 elevates it to an art form, using it in almost every stage. No more than eight separate patterns are ever needed; since tiles are 8 pixels across, switching everything one tile over ensures the proper continuation.

Wednesday, May 10, 2017

Night Mode

With the debug cheat active in either Sonic 2 or Sonic 3, holding down the C button while a level is starting enables one of the VDP's most esoteric features: Shadow/Highlight mode.

In this mode, every color appears dimmed, except for sprites and background patterns which are set to high priority. The practical effect is that a black, semi-transparent layer is inserted in front of the low priority layers, but behind the high priority ones. This is an extremely useful tool for debugging the priority data in a level: in the screenshot above, you can tell Sonic will walk in front of the blue flowers but behind the palmtree, without actually having to check.

The debug shortcut was removed in Sonic & Knuckles, likely because it has no real value as a cheat code.

Besides debugging, Shadow/Highlight finds use in the character select screen for Competition mode. The entirety of Plane A is set to high priority except for the square which contains the player sprites. The selected player is set to high priority, so it's shown at full brightness. The other sprites aren't, so they get shaded along with the background.

The drop shadows seen in a special stage use another feature of S/H mode: sprites painted with colors 14/15 of palette line 3 (the last two colors in CRAM) won't actually display those colors, but rather shade/highlight the underlying pixels. Shadow through color 14 works exactly the same as it does through priority, but offers better control over the resulting shape. Highlight isn't used in anywhere in Sonic games. It works the same as shadow does, but brightens colors rather than darken them.

Perhaps due to this feature, color 14 fails to be shaded correctly. This can be seen above in the first two screenshots, where it affects the darkest grey in the player sprites. The Competition menu avoids placing background colors in those indexes: they hold the colors for the yellow frames, which never get shaded anyway.

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!

Monday, May 8, 2017

An introduction to Mega Drive graphics, part 2

Last time we learned how the VDP uses pattern tables stored in VRAM to define the contents of the display area. Specifically, it renders patterns to four separate planes,which are composited into the final picture shown on the screen. These are two background planes, a sprite plane, and a "window" plane.

The sprite plane can display up to 80 sprites of varying shapes at arbitrary coordinates on the screen. more on those later. The window plane splits part of the screen into a static element. It is never used and we won't speak of it again. Since the location of pattern tables in VRAM is programmable, Sonic games incur no penalty for not using it.

The two remaining planes, A and B, are background planes which render an array of VDP patterns to the screen as a flat grid. They can scroll independently from one another, lending themselves well for displaying a foreground layer which the player can interact with, and a decorative background that scrolls at a slower rate, giving the illusion of depth.

Apart from scrolling the entire screen, background layers can also scroll individual rows and columns at their own rate. Vertical scrolling can only be done in pairs of tiles (16 pixels across) and thus infrequently used, but horizontal scrolling can be done down to each individual row of pixels, making it very useful for the parallax effects seen in Sonic games. Horizontal scrolling is applied after vertical scrolling.

Before rendering the final image, the entire screen is filled with a "background color", including the overscan region. Pixels painted with color index 0 are considered transparent, which brings the on-screen color limit from 64 down to 60. However, a common pattern is to set the background color to one of the four transparent colors, increasing the number of useful colors back to 61. Sonic 3 keeps the background color set to black at all times, except when the entire screen fades to white.

The final image is rendered as follows: first Plane B is drawn, then Plane A, then the sprite plane. Patterns marked with the high priority flag are pushed to the top, which essentially results in a second pass where Plane B's high patterns are drawn, then Plane A's high patterns, then finally high sprites. Not only does this let background tiles overlap sprites, it also allows Plane B to overlap Plane A, which can be used for some clever effects.

Next time, we'll look at one such case.

Friday, May 5, 2017

The night sky we don't see

One advantage of using indexed color graphics is that you can display the same object with completely different colors by simply using a different palette. Those palettes can potentially be loaded at the same time to separate palette lines, so the object can be displayed in several different colors simultaneously.

For instance, the square blocks in the slot machine bonus stage have a lot of individual rotations which cut down on the available VRAM, so it's nice that a single set of art is good for three different colors.

Sometimes the art reuse isn't immediately obvious, though. Here are the contents of CRAM during a special stage:

On the left side we can see the colors for the four kinds of spheres, followed by... the same colors? Except for white? This was really confusing, so I tried replacing the duplicate colors with something else. Some green should stand out.

And suddenly it became obvious.

Thursday, May 4, 2017

Fading circumstances

The fade effect seen when a stage starts is accomplished by changing the contents of CRAM every frame. Since Mega Drive colors have only 3 bits per channel, for a total of 8 distinct shades, increasing the brightness on every channel at once would result in a very quick, or if slowed down, choppy-looking fade.

Sonic games work around this by fading each channel individually, extending the total fade length from 7 to 21 frames long. I was curious as how it worked, so I looked up the relevant code in the current S&K disassembly. This is the function responsible for fading a single color:
        move.w  (a1)+,d2
        move.w  (a0),d3
        cmp.w   d2,d3
        beq.s   loc_3B7C
        move.w  d3,d1
        addi.w  #$200,d1
        cmp.w   d2,d1
        bhi.s   loc_3B6A
        move.w  d1,(a0)+
; ---------------------------------------------------------------------------

        move.w  d3,d1
        addi.w  #$20,d1
        cmp.w   d2,d1
        bhi.s   loc_3B78
        move.w  d1,(a0)+
; ---------------------------------------------------------------------------

        addq.w  #2,(a0)+
; ---------------------------------------------------------------------------

        addq.w  #2,a0
Here's the breakdown:
  • Load the target color from the address stored in a1 into d2, then increment a1. Load the current color from the address stored in a0 into d3.
  • Compare d2 and d3. If they're equal, we're done, so branch to loc_3B7C, increment a0 and return. Both a0 and a1 are now pointing at the next color in line.
  • Copy the current color from d3 to d1 and add $200 to it.
  • If d1 is higher than d2, branch to loc_3B6A. Otherwise write d1 to the current color, increment a0 and return.
  • Copy the current color from d3 to d1 and add $20 to it.
  • If d1 is higher than d2, branch to loc_3B78. Otherwise write d1 to the current color, increment a0 and return.
  • Add 2 to the current color address, increment a0 and return.

In other words, for each color, blue is incremented until it reaches the target blue, then green until it reaches the target green, and finally red until the two colors are equal. A consequence of this algorithm is that brighter colors, and colors with several color components take longer to fade in than darker or primary colors.

For instance, in the example below, the grass fades in almost instantly because it's mostly pure green, despite being fairly bright. On the other hand, the brightest spots of the ground pattern have a lot of different color components to them, so they take quite longer than the surrounding terrain.

Here's the part I didn't expect: using the same algorithm, if we tried to reverse the order and increment red first, and red happened to already be maxed out, the value would actually overflow into green and not be caught by the naïve "higher than" check. So the order they chose to fade the colors might have simply been for the sake of writing simpler code, rather than the aesthetic effect.

Showing the title card during the fade is a bit of a conundrum. We fill CRAM with black to make the screen black, but we also need colors in there to display the title card with! The solution: load Sonic's palette right away, use it to display the title card, and only fade in the stage's colors. The player characters sort of pop into existence, but since they occupy a relatively small part of the screen, this isn't very noticeable.

Wednesday, May 3, 2017

There's something in the water

Last time we saw that the Mega Drive has a theoretical limit of 64 colors on screen at any given point. In reality, it's even lower than that. But let's take a look at this screenshot:

If you were to count them, you'd find that there are 68 unique colors in this image alone. So what's going on? Did we find some extra CRAM hidden somewhere? There's no way the hardware is doing the things it is doing.

And in fact, it's not. What's actually happening is that the game is overwriting CRAM while the screen is being drawn. It just so happens the VDP renders left to right, top to bottom like your average CRT television, so if we swap in some wet-looking colors all at once, we end up with a nice translucent water effect which extends all the way to the bottom of the screen.

It's not all roses, though. This is what the water surface looks like in Hydrocity Zone, unchanged from all previous Sonic titles: a set of repeating waves which, given the rate they flicker at, almost look transparent.

A decent visual effect, but one we could surely live without. I mean, the Angel Island screenshot looks fine, right? However, this is what the same scene would look like on actual hardware, if you made the waves invisible.

What are those?

Turns out, the VDP doesn't actually like it if you mess with the colors while it's busy drawing the screen. Every time you write to CRAM, a block of garbage appears over the spot the VDP's proverbial electron beam is aiming at. This is a seldom known "feature" which, to my knowledge, isn't replicated in any emulator as of writing.

The transparent waves are a nice effect, but they were actually introduced to mask the CRAM corruption. The garbage blocks are still there, but it's a lot harder to notice them when all the surrounding pixels are flickering at 60 frames per second.

"But none of the other stages have waves and yet there's no garbage!" Actually, it's still there. By timing the CRAM writes very carefully and spreading them over several lines, Sonic 3's interrupt code manages to trap the garbage pixels in the overscan region left of the screen. This does mean that some colors are invariably copied over much sooner than others, resulting in an uneven water surface.

So why is Hydrocity the only stage not to use the trick? Beats me. Maybe they just thought it looked better that way.