Showing posts with label Other. Show all posts
Showing posts with label Other. Show all posts

Sunday, February 24, 2019

Took you long enough

25 years ago to this day, the original Sonic 3 was released for the Sega Mega Drive across European territories. Being from Portugal myself, I still have a full boxed copy!


In celebration of this date, I am proud to announce that a disassembly of Sonic 3 is now available, more than ten years after the original Sonic & Knuckles disassembly was released.

...Wow, I don't even know where to begin. Just five or six years ago I was still exclusively messing around with graphics stuff, afraid to touch any of the scary assembly code. Before I knew it, I was programming entire objects from scratch, writing a blog which is at least 50% assembly code, massively improving the level editor experience, and now this.

None of these things would have happened if not for the people at my side each step of the way. My undying gratitude to Tiddles for always supporting me and getting me on this wild ride. Thanks to MainMemory for always being there to talk and share war stories. Thanks to the readers of my blog for the motivation and for showing interest in a Sonic 3 disassembly. And ultimately, thanks to flamewing for graciously approving my pull requests to the skdisasm repo.

I hope you will continue to support me in my future endeavors. The ride's not over by any stretch of the word.

Thursday, September 20, 2018

On the subject of bitwise operators in C#

This subject is a bit off-band for the blog, but I figured it could also double as a status update. The first draft of the object definitions is almost complete; only Death Egg Zone remains at the time of writing. After that, I'll probably go over the entire set and make everything a little bit more consistent, add a few more overlays here and there, etc. I'm currently aiming to get everything done early next month, so we'll see how that goes.

I've also made up my mind about what the focus of my hack will be, so I can't wait to jump on that as well. It's going to be a lot of work up front, but I'm hoping the payoff is worth it. Anyway, time for a rant.


As you may or may not be aware, SonLVL is programmed in C#. Due to this, the most powerful way of writing SonLVL object definitions is to just roll your own C# code against SonLVL's public API, which SonLVL then compiles on the fly by calling up the C# compiler at runtime.

This is good! C# is a great programming language, and one which I regularly work with in my day job, so being able to transfer my existing skill set certainly makes it easier on both sides.

Now, the greatest complexity in writing object definitions comes from wrangling subtypes. Apart from the X/Y flip flags, the subtype is the only way of instructing objects to serve up a different appearance or behavior. As such, more often than not, several different properties are packed into the individual bits of the subtype byte. And therein lies the rub: performing bitwise operations in C# is just sad.


Let's take, for example, the Automatic Tunnel object. These are the high speed chutes found in Launch Base Zone and Lava Reef Zone. They have three properties, which are encoded into the subtype as follows:

  • Bits 0-4 are the Path ID, which defines the set of waypoints the player will be sent through.
  • Bit 6 is the Launch flag; if set, the player will keep their momentum at the end of the tunnel.
  • Bit 7 is the Reverse flag; if set, the player will go through the waypoints in reverse order.
  • Bit 5 is unused.

Here's the above information in graphical form, because humans love graphics:
     0  0  0  0  0  0  0  0 

   Reverse     Launch     Path ID
Alright, so now let's say I want to have a property box where the user can change the path ID, without affecting the other flags. Sounds easy enough. Just blank out the path ID bits already in the subtype, truncate the user value to five bits, and join the two together. So let's write that.
    subtype = (subtype & 0xE0) | (value & 0x1F);
Hit compile and... compilation error. An expression of type int cannot be assigned to the variable subtype, which is of type byte. Oh right, the literals 0xE0 and 0x1F are of type int, so the AND operations are lifted to int: both subtype and value get promoted from byte to int and operator &(int a, int b) is called, which itself returns int. The two resulting ints are then ORed together, so the entire expression is of type int, which cannot be assigned to a variable of type byte.

There's actually no way to write a byte literal in C#; you are expected to cast the int literal to byte. The compiler will do the right thing and not insert a conversion operation, but work with byte from the start. So let's write that.
    subtype = (subtype & (byte)0xE0) | (value & (byte)0x1F);
Hit compile, same error. As it turns out...

Pain point #1: There are no bitwise operators defined on byte


It's not the literals, it's the operators! There actually isn't such a thing as byte operator &(byte a, byte b) in C#; they go down to int and that's it. So when we write subtype & (byte)0xE0, the compiler promotes both bytes to int and then calls int operator &(int a, int b), once again resulting in a subexpression of type int.

The same thing goes for the OR operator, so no matter how we slice it, the whole expression will always evaluate to int. So the correct solution is to cast that instead:
    subtype = (byte)((subtype & 0xE0) | (value & 0x1F));
It's already getting hard to read through all the parentheses, but it's only going to get worse.

Pain point #2: Bitwise operations do not return bool


Let's turn our attention to the flags. In the case of the Reverse flag, I want the user value to be a yes/no toggle, so value is a bool. Then, depending on whether the bool is true or not, we set the relevant bit to 1 or 0. Let's write that.
    subtype = (byte)((subtype & 0x7F) | (value ? 0x80 : 0x00));
Alright, relatively painless. But what about the reverse operation, where we look up the current subtype and figure out the current state of the Launch flag? This time we're assigning to value, which is of type bool. So we write
    value = subtype & 0x80;
which again results in a compilation error, this time stating that an expression of type int cannot be assigned to a variable of type bool.

This is because in C#, unlike C and C++ before it, bools are strongly typed. They can only hold the values true and false, which alleviates the situation where 1 and 2 both mean true, but compare differently to one another. But that means there's no quick way to write a bit test in C#; one must append either != 0 or == 0x80, the former a tautology, the latter a repetition.

Now, since the Reverse flag happens to be the most significant bit, we can sidestep the issue by instead writing:
    value = subtype >= 0x80;
But in the case of the Launch flag, imagine my surprise when I write
    value = subtype & 0x40 != 0;
and I get yet another compilation error: operator & cannot be applied to operands of type byte and bool.

Pain point #3: Bitwise operators are also logical operators


If the previous point was to get rid of legacy C bullcrap, then this one enshrines it. Early versions of C did not have the logical operators && and ||, so to combine two or more equality comparisons into a single conditional expression, you would use the bitwise operators & and |, like so:
    if (day == 25 & month == 12) printf("It's Christmas!\n");
In order for this kind of expression to evaluate correctly, bitwise operations were given lower precedence than equality comparisons, so that the program would first check that the day is 25, then that the month is December, before it combines the results and decides whether it's Christmas or not. When bitwise operators were added to the C# specification, their precedence was kept the same, presumably in order to avoid "gotcha" scenarios when porting over legacy C and C++ code.

So above, when we wrote
    value = subtype & 0x40 != 0;
what the compiler actually does is check 0x40 and 0 for equality, and then attempt to combine the result with the value of subtype, which is the complete opposite of what we were trying to accomplish!

The solution is, again, to add more parentheses to the expression:
    value = (subtype & 0x40) != 0;
But here's the kicker: since in C#, equality comparisons result in bool, not int, they had to introduce separate, eager logical operators &(bool a, bool b) and |(bool a, bool b) to go along with the to the existing short-circuiting logical operators &&(bool a, bool b) and ||(bool a, bool b). So they could have avoided this whole disaster by simply giving the eager logical operators a different notation from the bitwise operators! Grrr.

With all that parenthesizing, it's no surprise that we end up with code that looks a little something like this:
properties[2] = new PropertySpec("Launch", typeof(bool), "Extended",
    "If set, the player will launch off at the end of the path.", null,
    (obj) => (obj.SubType & 0x40) != 0,
    (obj, value) => obj.SubType = (byte)((obj.SubType & 0xBF) | ((bool)value ? 0x40 : 0)));

And that's just a little bit sad.

Update 27/02/2020: Eric Lippert expands on the last point over at his own blog. This post was mostly inspired by Eric's writings there and elsewhere on the the Internet, so being able to finally link back is incredibly delightful to me.

Friday, February 2, 2018

End of part one

On this day 24 years ago, Sonic the Hedgehog 3 was released for the Sega Genesis in North America. Happy birthday!


Coincidentally, today also marks my 200th post on this blog. Holy goodness, I swear I did not plan this far ahead when I started the blog nine months ago. It's been a lot of work writing a post every weekday, but also a lot of fun, especially when I was able to answer your questions. Doing so has made me learn a lot of things I previously did not know.

To all of my readers, especially those who have contributed to the discussion in the comments: thank you so much.


I would like to take this opportunity to make a couple of announcements. At various times, people have approached me on Twitter, YouTube, and even the Sonic Retro forums, asking me to work on a new version of Sonic 3 Complete, or to release my work in some other form. I'm not really interested in the former, because if I had my way, half of the stuff in that ROM hack would be thrown out, which would be a tremendous disservice to everyone who's ever contributed to it, as well as everyone who had played the previous versions.

I've also been reluctant on pursuing the latter, because "Sonic 3 Complete except with a lot less features" is a hard sell, both to the developer and the consumer. However, over the past few months I've been doing a lot of brainstorming, and I feel that I now have enough original ideas and a sufficiently unique direction to warrant pursuing them.

I'm excited to announce that starting today, I will begin development of my own Sonic 3 hack.


Which brings me to my second announcement. Like I've said, writing this blog has been very fun, but it has also been a challenge, and I have learned the hard way that I am not good with deadlines, even when it's doing something which I enjoy. Combined with the time I'll need to work on the hack, I am hereby suspending regular updates to this blog.

Let me make that perfectly clear: the blog isn't going anywhere. I still have more than 100 potential blog posts in my bag, and they will materialize sooner or later. Plus, working on a hack is bound to give me even more subject matter to work with. I just won't be posting regularly for the time being, that's all.

To that effect, I would advise you to follow me on Twitter, since I'll tweet out each new blog post. I'll probably also post some hack updates on there, and I promise I'll try to cut down on the memes.


I've gone back and tidied up the formatting on the older blog posts, and over the coming weeks I'd like to make the tags more helpful, as well as optimize the first animated GIFs I made, because they weigh a ton. Next thing I'll probably do however is write an updated about page to reflect the current nature of the blog, as well as the hack.

Again, thank you so much for reading this far, and I hope you'll stick around -- this ride ain't over by a long shot.

Monday, December 25, 2017

Christmas corrections

Today is Christmas Day, a holiday which is typically celebrated by showering the people you love the most with copious amounts of gifts. Keeping with tradition, then, I thought this would be the perfect opportunity to celebrate three gifts that my readers have graciously offered me through the comments section of this blog.

(Stuttering Craig voice) This is Sonic 3 Unlocked's 2017 Top 3 Christmas Corrections!



Number Three!

As part of my short series on Lock-on Technology, I pointed out a difference with Knuckles' climbing animation between Knuckles in Sonic 2 and Sonic & Knuckles: exclusively in the latter, whenever Knuckles stands still on a wall, he reverts back to the first frame of the climbing animation.


I chalked this up to a feature introduced in the S&K version of the Knuckles object, but later, an anonymous commenter performed their own analysis of the source code, which I present below. Turns out, it's not actually a feature, it's a bug:
I think the second behaviour quirk you mentioned in this post is the result of a bug. There's some code in S3K that isn't in KiS2, at loc_16E10 in the current S3K Git disasm. The equivalent label in KiS2's Git disasm is loc_315B04.

What I think this new code does is handle floor collision, because Knuckles still seems to move briefly after the player stops pressing the D-Pad. The issue is, this new code overwrites d1 with the distance Knuckles is from the floor. d1 is checked immediately afterwards, has Knuckles's frame ID added to it, and is then used to calculate which frame Knuckles should display.

d1 will always be a positive number, usually a large one depending on how far Knuckles is from the ground. This means, when Knuckles's frame ID is added to it, it goes well beyond the ceiling value of $BC, causing the game to reset it to $B7, making Knuckles display the first frame of his animation. Chances are the number could overflow, too, causing him to display his last frame instead.

Safe to say, editing the code to properly back up d1 causes it to behave like KiS2 instead.
Let's take a look at the code mentioned. Knuckles in Sonic 2 to the left, Sonic & Knuckles to the right. Changes in bold:
loc_315B04:                                     loc_16E10:
                                                    move.b  (Ctrl_1_logical).w,d0
                                                    andi.b  #3,d0
                                                    bne.s   loc_16E34
                                                    move.b  $46(a0),d5
                                                    move.w  $14(a0),d2
                                                    addi.w  #9,d2
                                                    move.w  $10(a0),d3
                                                    bsr.w   sub_F828
                                                    tst.w   d1
                                                    bmi.w   loc_16D6E

                                                loc_16E34:
    tst.w   d1                                      tst.w   d1
    beq.s   loc_315B30                              beq.s   loc_16E60
    subq.b  #1,$1F(a0)                              subq.b  #1,$25(a0)
    bpl.s   loc_315B30                              bpl.s   loc_16E60
    move.b  #3,$1F(a0)                              move.b  #3,$25(a0)
    add.b   $1A(a0),d1                              add.b   $22(a0),d1
    cmp.b   #$B7,d1                                 cmpi.b  #$B7,d1
    bcc.s   loc_315B22                              bhs.s   loc_16E52
    move.b  #$BC,d1                                 move.b  #$BC,d1

loc_315B22:                                     loc_16E52:
    cmp.b   #$BC,d1                                 cmpi.b  #$BC,d1
    bls.s   loc_315B2C                              bls.s   loc_16E5C
    move.b  #$B7,d1                                 move.b  #$B7,d1

loc_315B2C:                                     loc_16E5C:
    move.b  d1,$1A(a0)                              move.b  d1,$22(a0)
In Sonic 2, when loc_315B04 is reached, the d1 register is set to 1, -1, or 0 depending on whether Knuckles is moving up, moving down, or standing still. Assuming neither branch to loc_315B30 is taken, Knuckles' current mapping frame is added to the value in d1, and then two bound checks are made before writing the resulting value back into Knuckles' mapping frame: if the value is less than $B7, d1 is set to $BC, and if it's greater than $BC, d1 is set to $B7.

The gist of it is: while Knuckles is climbing up a wall, his mapping frame gets progressively incremented, but when he's climbing down, it gets decremented instead. And if the mapping frame ever steps outside of the $B7-$BC range, it gets wrapped around to the other end of the range, in order to loop the animation.

In Sonic & Knuckles though, a call to sub_F828 was introduced, causing the FindFloor function to be called whenever the player is holding neither up nor down on the directional pad. The FindFloor function calculates an object's distance to the floor directly below it, and stores the result in register... d1.

The inevitable result follows: when the player lets go of the directional pad, sub_F828 is called and the value in d1 gets overwritten with the distance between the center of the Knuckles object and the floor. Knuckles' current mapping frame is then added to this value, which always produces a value greater than $BC. This triggers the bounds check, resetting Knuckles' mapping frame back to $B7, the first frame of the climbing animation.

In other words, the anonymous commenter's analysis is 100% correct. Good work!



Number Two!

On the subject of triggering slope glitch in Ice Cap Zone by having Tails break an ice block while Sonic is standing on it, Brainulator9 asked whether Tails could get slope glitch by instead breaking the block as Sonic. In Sonic 3 & Knuckles, this is impossible because player 2's status bits always get set, regardless of who breaks the blocks, and regardless of whether the Tails object is even present in the player 2 slot.


However, as Brainulator9 pointed out, the same isn't true of standalone Sonic 3, in which Sonic can indeed break Tails' gravity. Below is the relevant code: Sonic 3 to the left, Sonic & Knuckles to the right, once again changes in bold.
loc_58B3C:                                      loc_8B384:
    move.b  ($FFFFB020).w,$3A(a0)                   move.b  ($FFFFB020).w,$3A(a0)
    move.b  ($FFFFB06A).w,$3B(a0)                   move.b  ($FFFFB06A).w,$3B(a0)
    moveq   #$23,d1                                 moveq   #$23,d1
    moveq   #$10,d2                                 moveq   #$10,d2
    moveq   #$10,d3                                 moveq   #$10,d3
    move.w  $10(a0),d4                              move.w  $10(a0),d4
    jsr     (SolidObjectFull).l                     jsr     (SolidObjectFull).l
    bsr.w   sub_58B62                               bsr.w   sub_8B3AA
    jmp     (Sprite_OnScreen_Test).l                jmp     (Sprite_OnScreen_Test).l

sub_58B62:                                      sub_8B3AA:
    move.b  $2A(a0),d0                              move.b  $2A(a0),d0
    btst    #3,d0                                   btst    #3,d0
    beq.s   loc_58B78                               beq.s   loc_8B3C0
    lea     (Player_1).w,a1                         lea     (Player_1).w,a1
    cmpi.b  #2,$3A(a0)                              cmpi.b  #2,$3A(a0)
    beq.s   loc_58B8A                               beq.s   loc_8B3D2

loc_58B78:                                      loc_8B3C0:
    btst    #4,d0                                   btst    #4,d0
    beq.s   locret_58BD0                            beq.s   locret_8B430
    lea     (Player_1).w,a2                         lea     (Player_2).w,a1
    cmpi.b  #2,$3B(a0)                              cmpi.b  #2,$3B(a0)
    bne.s   locret_58BD0                            bne.s   locret_8B430

loc_58B8A:                                      loc_8B3D2:
    bset    #2,$2A(a1)                              bset    #2,$2A(a1)
    move.b  #$E,$1E(a1)                             move.b  #$E,$1E(a1)
    move.b  #7,$1F(a1)                              move.b  #7,$1F(a1)
    move.b  #2,$20(a1)                              move.b  #2,$20(a1)
    move.w  #-$300,$1A(a1)                          move.w  #-$300,$1A(a1)
    bset    #1,$2A(a1)                              bset    #1,$2A(a1)
    bclr    #3,$2A(a1)                              bclr    #3,$2A(a1)
    move.b  #2,5(a1)                                move.b  #2,5(a1)
                                                    btst    #4,$2A(a0)
                                                    beq.s   loc_8B41A
                                                    lea     (Player_2).w,a1
                                                    bset    #1,$2A(a1)
                                                    bclr    #3,$2A(a1)

                                                loc_8B41A:
    lea     ChildObjDat_58C20(pc),a2                lea     ChildObjDat_8B480(pc),a2
    jsr     CreateChild1_Normal(pc)                 jsr     CreateChild1_Normal(pc)
    moveq   #$6E,d0                                 moveq   #$6E,d0
    jsr     (Play_Sound_2).l                        jsr     (Play_Sound_2).l
    jsr     (Go_Delete_Sprite).l                    jsr     (Go_Delete_Sprite).l

locret_58BD0:                                   locret_8B430:
    rts                                             rts
Both versions of the code call the SolidObjectFull function, and then check bits 3 and 4 of the status bitfield along with the animation of the corresponding player, which is previously backed up to offsets $3A and $3B, in order to determine whether the player landed on the object whilst in their rolling animation.

Note how thoroughly botched the checks for player 2 are in Sonic 3, though: player 1's RAM address is loaded instead of player 2's, and it gets loaded to register a2 rather than register a1. The only reason this code works at all is because the SolidObjectFull function itself sets a1 to player 2's RAM address during the course of its execution, and then exits without overwriting the contents of the register with something else:
SolidObjectFull:
    lea     (Player_1).w,a1
    moveq   #3,d6
    movem.l d1-d4,-(sp)
    bsr.s   sub_1BA2A
    movem.l (sp)+,d1-d4
    lea     (Player_2).w,a1
    tst.b   4(a1)
    bpl.w   locret_1BA6A
    addq.b  #1,d6

sub_1BA2A:
    ...
That isn't the problem in and of itself, however: the problem is that the code at loc_58B8A only runs for a single player, which leaves the other player hanging if they happened to also be standing on the ice block at the time. Rather than fix this properly, Sonic 3 & Knuckles simply forces player 2 to fall off the block either way, resulting in the strange, lopsided behavior where player 1 can get slope glitch but not player 2.



Number One!

Finally, regarding the Japanese characters in the slot machine bonus stage, another anonymous commenter points out that if you read them vertically, top to bottom, then left to right, they make up the first sixteen letters of the Iroha.


Now, what is the Iroha? It is an ancient Japanese poem, which has the unique characteristic of using every single kana character exactly once. (The title refers to the first three characters used in the poem.)

γ‚€γƒ­γƒγƒ‹γƒ›γƒ˜γƒˆ iro ha nihoheto
チγƒͺγƒŒγƒ«γƒ²   chirinuru wo
ワカヨタレソ  wa ka yo tare so
γƒ„γƒγƒŠγƒ©γƒ    tsune naramu
γ‚¦γƒ°γƒŽγ‚ͺγ‚―γƒ€γƒž uwi no okuyama
ケフコエテ   kefu koete
γ‚’γ‚΅γ‚­γƒ¦γƒ‘γƒŸγ‚· asaki yume mishi
ヱヒヒセス   wehi mo sesu

Since each character only appears once, the Iroha serves as an alternative to the usual gojΕ«on ordering, but both work equally well as placeholder graphics for a level's animated PLCs.



That's all I've got. Thank you all so much for the valuable feedback; I hope every single one of you has a terrific holiday season, and don't forget:

Tuesday, December 12, 2017

The lost helper item

While on the subject of weird animations, I figured I should talk about this. Apart from the character-specific animations, Sonic has an unused animation in slot $A, which has the following definition:
byte_12BA8:     dc.b    9, $BA, $C5, $C6, $C6, $C6, $C6, $C6, $C6, $C7, $C7, $C7, $C7, $C7, $C7,
                dc.b  $C7, $C7, $C7, $C7, $C7, $C7, $FD,   0
What's interesting is that, beyond the regular standing sprite at mapping frame $BA, the animation is comprised entirely of otherwise unused sprites! Sonic is really the only character with unused graphics like that. This is what the animation looks like, which shouldn't surprise most of you lot:


Here we can see Sonic whistling. Calling someone, perhaps? Anyway, back when these sprites were first discovered, a few observant individuals made a connection between them and a certain debug element in standalone Sonic 3.

In Sonic 3, when using an S monitor, a whistling sound will be heard instead of the regular Super transformation sound:


What is the significance of this? Well, back in February 1994, German magazine Sega Magazin published their hands-on impressions of the game then known as Sonic 3 Part One. Specifically, in page 97 they describe the new power-ups in the game, providing descriptions of the three elemental barriers as well as a fourth item not found in the final game:

Water shield: Sonic can now breathe under water, and he also can use the Squat-Attack.
Fire shield: Is very effective against fire attacks. Sonic can use the Flying Attack with it.
Lightning-Shield: Sonic can electrocute near enemies.
Help!-Item: Tails helps him in dangerous situations.
Translation: Oerg866 (emphasis added)
And so a theory was born: originally, the S monitor wasn't a debug-only item that granted Sonic his super form, it was a helper item that triggered the whistling animation, which would summon a flying Tails to carry Sonic out of a jam. That's why the S monitor plays the whistling sound in Sonic 3: it's a leftover from the helper item the developers forgot to fix.

This is a reasonable hypothesis. It would certainly explain how precious VRAM got wasted on a monitor icon which can never be seen outside of debug mode.

I think that last point might be a red herring, though. The whistling sound also shows up in Sonic & Knuckles, where it's used in The Doomsday Zone. It is equally likely that the developers originally repurposed the whistling sound for Super transformations, but later changed their minds and forgot to update the S monitor. At least until Sonic & Knuckles.

Rather, looking at the order of the sounds in the Sound Test tells a different story:

IDDescription
$3EFlame Barrier item box
$3FAqua Barrier item box
$40(unused)
$41Thunder Barrier item box
$42Double Spin Attack
$43Flame Barrier Attack
$44Aqua Barrier Attack
$45Thunder Barrier Attack
$46Whistle

Note how the whistling sound appears directly after the sounds used for Sonic's elemental barrier attacks, which in turn are ordered the same as their respective item box sounds. Except for the unused sound effect in slot $40, that is, which actually sounds considerably more "electric" than the strange "ding" used by the Thunder Barrier item box.

My guess is that sound effect $40 was originally meant for the Thunder Barrier item box, whereas sound effect $41 was meant for the helper item's item box. The whistling sound (and matching animation) would only play later, once the item was actually used. Whether that was automated like the Super monitor, or something that could be stored for later, we'll probably never know.

I should point that Sonic Generations uses sound $40 for the Thunder Barrier item box, but whether that was by design or due to lack of attention to the source material is up for grabs.

Monday, December 4, 2017

Hydrocity Zone intro area

The opening area of Hydrocity Zone 1 is home to a rather complex and detailed introductory sequence, which uses of a lot of different mechanics. At the start of the stage, our heroes fall into a large pool of water: the basin of the waterfall at the end of Angel Island Zone 2. Venturing to the right, they run across a conspicuous button next to a stone door, which blocks a conspicuously dry tunnel visible only to the player.


When they press the button, the stone door slides open and water rushes into the previously dry tunnel. Our heroes get pulled along by the current, avoiding some minor obstacles as water fills the chamber. Finally, the immense pressure of the rushing liquid causes the far wall to break apart, and a jet of water tosses our heroes into the stage proper amidst a shower of stone bricks.


Over the next couple of posts, I'll go into detail regarding every one of these elements, showing how they work together to achieve the final effect, and explaining the oversights in each of them. Most of those were already documented eight years ago by GoldS in his legendary Glitches and Oversights video series, so go watch that in advance.

Monday, October 2, 2017

Lock-on Technology

23 years ago, on October 18, Sonic & Knuckles was released on the Sega Genesis and Mega Drive worldwide. As sort of a celebration, I thought it'd be neat to do a short series about the game which not only served as a conclusion to the adventure that started in Sonic 3 a few months prior, but also introduced the concept of add-on content to video games as early as 1994.

Let's start with a little history: how exactly did Sonic & Knuckles come to be?

Fresh off the heels of Sonic 2's commercial success in 1992, Sega and Sonic Team set out to surpass themselves and produce the biggest, most polished Sonic game ever made. After the dubious outcome of the Sega Technical Institute's call for local graphic designers, which resulted in Sonic 2 having a rougher, inconsistent visual style, Sonic Team chose to once again use a team of solely Japanese artists to bring Sonic 3's worlds to life.


However, in the process of creating the largest Sonic levels ever, and employing a more cinematic, story-driven design, the development team ran into issues. Cartridges were expensive to produce, with ROM space at a premium. Sonic 2 was released on an 8 megabit cartridge, whereas Sonic 3 was projected to require a 24 megabit cartridge, three times as large. It also became apparent that development would take longer than had previously been anticipated.

Meanwhile, Sega of America had organized various promotions, including a tie-in with McDonald's Happy Meal, which could not be rescheduled. Sonic Team faced the difficult decision to split the game in two parts, the first of which would be released in February 1994. This became Sonic the Hedgehog 3.


As development continued, a question remained: how would the full version of the game be delivered to customers? A 24 megabit "Limited Edition" was considered but dropped, likely to keep production costs low. Releasing "Part Two" as a standalone title would be far more viable, but crush any possibility of playing Sonic 3 as a single, continuous game.

Luckily, Sega's hardware division would find a way for Sonic Team to both have their cake and eat it. And thanks to the same marketing team that popularized terms as such "16-bit graphics" and "blast processing", it had a name:


Lock-on Technology was a groundbreaking feature built directly into the Sonic & Knuckles cartridge. Like cheat devices that were available at the time, such as Action Replay and Game Genie, the top of Sonic & Knuckles's cartridge had an additional slot that allowed players to insert another game cartridge, effectively "locking on" the two titles.

Unlike such devices though, which only made basic modifications to a game, Sonic & Knuckles could lock on with other Sonic cartridges to create a game greater than the sum of its parts. When locked on with Sonic 3, the full version of the game was unlocked, seamlessly joining the two halves together, and allowing Knuckles to play in Sonic 3's stages. And when locked on with Sonic 2, it would somehow retroactively add Knuckles as a playable character in Sonic 2.


But how does it work?

Over the next few posts, I'll explain how Lock-on Technology works from a software perspective, how Sonic & Knuckles uses it to combine two games into one, and how Sonic Team was able to patch their titles on original hardware.

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 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!?

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:

SONIC 2 ENEMY PICTURES

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

OLD BUMPER
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
Timeline
γ‚°γƒͺーン (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)

Sketches
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
Olympus

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.

Monday, May 1, 2017

Welcome to Sonic 3 Unlocked!

Updated every weekday, this blog aims to catalog all sorts of minutiae about the 1994 titles Sonic the Hedgehog 3 and Sonic & Knuckles, be it easter eggs, oversights, or just how certain effects are accomplished under the various hardware and software constraints.

If there’s a particular subject or question you’d like to see answered, please leave a comment on this post. I have enough material to keep this train going for a while, but you might come up with something I hadn’t considered. Either way, your input will surely influence which topics I talk about first.


Why did I decide to start this blog? Well, the classic Sonic trilogy is my favorite video game series of all time. For the longest time, I followed the Sonic hacking and speedrunning communities as they digged deeper and deeper into what makes these games tick. I finally got into hacking with Sonic 3 Complete, originally just offering suggestions and tweaking various graphics stuff.

I soon got ambitious in what I wanted to do, and started messing with the split disassembly and reading the game’s code. I moved onto fixing bugs and developing new features, tremendously increasing my knowledge about the game and appreciation for what it does behind the scenes. Eventually the things I wanted to do no longer lined up with what Sonic 3 Complete was doing, so I decided to take a break.

Thinking what my next project should be, I realized the value of documenting what I had learned thus far. TCRF is a great resource for unused content in video games, but the scope is too narrow. Sonic Retro is good for sharing bugs and code snippets, but not suitable for the volume of information I wanted to share. I considered doing a video series, inspired by pannenkoek2012’s SM64 analysis, but realized 90% of the work would be video recording and editing.

Instead, I turned to programmer blogs. The episodic format allows individual topics to be discussed in their own post, over time forming a large resource that’s nonetheless easy for new readers to get into. In particular, Raymond Chen’s blog The Old New Thing manages to blend technical knowledge and real life anecdotes into a compelling narrative style that I can only hope to one day imitate.

On a more personal note, I'm not the world's most confident writer, and I obsess over details, so posting regularly on this blog will be a fun way to exercise that part of my brain.

Thanks for reading, and hope to have you along for the ride!