don't click here

Sonic & Knuckles Collection C port

Discussion in 'Engineering & Reverse Engineering' started by BenoitRen, Jan 11, 2024.

  1. That One Guy Josh

    That One Guy Josh

    Speaks English AND Balanese. Member
    I remember this happening to me too in Sandopolis Act 2. Kept getting lost and I just BARELY managed to beat the boss before time killed me. Let's just say... I got shocked with the time bonus.
     
  2. nineko

    nineko

    I am the Holy Cat Tech Member
    6,312
    489
    63
    italy
    Yeah, I thought it was some kind of overflow at first, I found out it was deliberate only eventually.
     
  3. saxman

    saxman

    Oldbie Tech Member
    I never knew this was a thing! I know Sonic 2's code all too well to know it's not in there. But I'm curious if this is specific to Sonic & Knuckles Collection, or if the same thing happens in Sonic 3 and Sonic & Knuckles on the Genesis.
     
  4. Kilo

    Kilo

    That inbetween sprite from S&K's title screen Tech Member
    359
    356
    63
    Canada
    It does happen on the Genesis, and it was carried over to Mania.
     
  5. muteKi

    muteKi

    Fuck it Member
    7,852
    131
    43
    So I learned in Sonic Triple Trouble that you get a bonus if you finish the special stages at exactly 0:00 on the clock, and from that tried to do the same in main stages in it and at least Sonic Chaos to see if you got a bigger bonus (which may not have been true, though both gave you some extra points for doing so). From that I decided to see if I could also hit 9:59 in Sonic 3 and get a bonus, and did there too.
     
    • Informative Informative x 1
    • List
  6. That One Guy Josh

    That One Guy Josh

    Speaks English AND Balanese. Member
    Just to show that it's actually possible:
    S3K-240409-115958.png
     
  7. BenoitRen

    BenoitRen

    Tech Member
    440
    188
    43
    I added a file to keep external resources that don't seem to be placed with object code. The most interesting piece of data is the following table that stores which animals can pop out of enemies and the end-of-level capsule per zone:
    Code (C):
    1. #define POCKY_RABBIT 0
    2. #define CUCKY_CHICKEN 1
    3. #define PECKY_PINGUIN 2
    4. #define ROCKY_SEAL 3
    5. #define FLICKY_FLICKY 5
    6. #define RICKY_SQUIRREL 6
    7. unsigned char zone_friend_data[24][2] = {
    8.   { FLICKY_FLICKY,  CUCKY_CHICKEN }, // Angel Island
    9.   { POCKY_RABBIT,   ROCKY_SEAL    }, // Hydrocity
    10.   { FLICKY_FLICKY,  CUCKY_CHICKEN }, // Marble Garden
    11.   { POCKY_RABBIT,   FLICKY_FLICKY }, // Carnival Night
    12.   { RICKY_SQUIRREL, FLICKY_FLICKY }, // Flying Battery
    13.   { PECKY_PINGUIN,  ROCKY_SEAL    }, // Icecap
    14.   { FLICKY_FLICKY,  CUCKY_CHICKEN }, // Launch Base
    15.   { RICKY_SQUIRREL, CUCKY_CHICKEN }, // Mushroom Hill
    16.   { POCKY_RABBIT,   CUCKY_CHICKEN }, // Sandopolis
    17.   { FLICKY_FLICKY,  CUCKY_CHICKEN }, // Lava Reef
    18.   { POCKY_RABBIT,   FLICKY_FLICKY }, // Sky Sanctuary
    19.   { RICKY_SQUIRREL, CUCKY_CHICKEN }, // Death Egg
    20.   { RICKY_SQUIRREL, FLICKY_FLICKY }, // Doomsdag
    21.   { FLICKY_FLICKY,  CUCKY_CHICKEN },
    22.   { FLICKY_FLICKY,  CUCKY_CHICKEN },
    23.   { FLICKY_FLICKY,  CUCKY_CHICKEN },
    24.   { FLICKY_FLICKY,  CUCKY_CHICKEN },
    25.   { FLICKY_FLICKY,  CUCKY_CHICKEN },
    26.   { FLICKY_FLICKY,  CUCKY_CHICKEN },
    27.   { FLICKY_FLICKY,  CUCKY_CHICKEN },
    28.   { FLICKY_FLICKY,  CUCKY_CHICKEN },
    29.   { FLICKY_FLICKY,  CUCKY_CHICKEN },
    30.   { FLICKY_FLICKY,  CUCKY_CHICKEN },
    31.   { FLICKY_FLICKY,  CUCKY_CHICKEN },
    32. };
    For fun, let's see how often each animal is represented:
    • Pocky the rabbit: 4
    • Cucky the chicken: 7
    • Pecky the pinguin: 1
    • Rocky the seal: 2
    • Flicky the flicky: 7
    • Ricky the squirrel: 3
    Cucky and Flicky are clearly overrepresented. And that's without counting their entries in zones after Doomsday Zone!
     
  8. BenoitRen

    BenoitRen

    Tech Member
    440
    188
    43
    I've been focusing on porting code related to Angel Island Zone. The code for its four enemies have been ported! Those being Cyclone/Rhinobot, Tullipon/Bloominator, Saruder/Monkey Dude, and Meramora/Catakiller Jr.

    Tullipon might be the simplest enemy in the game. It doesn't move. All it does is wait to appear on the screen, start a timer, then once that reaches -1 it'll shoot on frame 6 and 14. When it has finished animating, it waits once more. If you want to get an idea of how Sonic 3 works internally, this is a good place to start.

    There are two interesting things about Saruder.

    First, we all know it throws a coconut, right? Well, the game's labels don't refer to a coconut, but a banana. Maybe it was meant to throw that instead at some earlier point in development?

    Possibly related to the banana, the enemy's code has two data structures that seem to go unused. The first is called saru00_arm_set_tbl2, and it's meant to be used with function set_act00. Its data does nothing more than assign saru00_banana as the routine pointer. The second is saru00_arm_set_tbl2. I don't know what it's supposed to be used with yet. All I can see is that it has a pointer to saru00_arm. But each arm already has a structure with initialisation data.

    Then again, I can't determine where the coconut or banana is instantiated or even thrown (though there's plenty of code to move the arm when it takes a shot), so maybe that's done somewhere else and I have yet to uncover that reference.
     
  9. Mr. Cornholio

    Mr. Cornholio

    Member
    99
    66
    18
    That's interesting! If anything, I kinda figured Saruder was built on top of/heavy referenced Coconuts' code from Sonic 2 where that character does throw a coconut. All the stuff we have for Sonic 2 seems to suggest that's how that character was always intended to attack and Sonic 3 seems to have different naming conventions altogether skimming this thread. So...that's something.

    I kinda wonder if it was changed because the coconut just seemed more harmful to Sonic or it was easier to see in a busy environment.

    Nothing I'm saying here is terribly handy for your C port, but I did want to at least stop by and say thanks for your efforts. Your finds have been fun reads.
     
  10. BenoitRen

    BenoitRen

    Tech Member
    440
    188
    43
    I've been looking at the code for the zone title cards, and am now wondering if the zone order I have for the 2P zones is correct.

    This is the enum for the zone order I have at the moment:
    Code (C):
    1. typedef enum zone_id {
    2.   ANGEL_ISLAND = 0x00,
    3.   HYDROCITY = 0x01,
    4.   MARBLE_GARDEN = 0x02,
    5.   CARNIVAL_NIGHT = 0x03,
    6.   FLYING_BATTERY = 0x04,
    7.   ICECAP = 0x05,
    8.   LAUNCH_BASE = 0x06,
    9.   MUSHROOM_HILL = 0x07,
    10.   SANDOPOLIS = 0x08,
    11.   LAVA_REEF = 0x09,
    12.   SKY_SANCTUARY = 0x0A,
    13.   DEATH_EGG = 0x0B,
    14.   DOOMSDAY = 0x0C,
    15.   ANGEL_ISLAND_DUMMY_AND_ENDING = 0x0D,
    16.   AZURE_LAKE = 0x0E,
    17.   BALLOON_PARK = 0x0F,
    18.   DESERT_PALACE = 0x10,
    19.   CHROME_GADGET = 0x11,
    20.   ENDLESS_MINE = 0x12,
    21.   ZONE_GUMBALL_MACHINE = 0x13,
    22.   ZONE_SLOT_MACHINE = 0x14,
    23.   ZONE_ROLLING_JUMP = 0x15,
    24.   LAVA_REEF_BOSS_AND_HIDDEN_PALACE = 0x16,
    25.   DEATH_EGG_BOSS_AND_MASTER_EMERALD_SHRINE = 0x17
    26. }
    27. zone_id;
    What confuses me is that in the disassembly, Balloon Park is followed by Chrome Gadget, whereas in the list on Sonic the Hedgehog 3/Hidden content, it's followed by Desert Palace.

    Which one is right?
     
  11. Brainulator

    Brainulator

    Regular garden-variety member Member
    It's ABDCE in the internal order, but ABCDE in gameplay, if it makes any sense.
     
    • Informative Informative x 1
    • List
  12. BenoitRen

    BenoitRen

    Tech Member
    440
    188
    43
    Now the code for the title cards has been committed!

    This is the second unlabelled object I've ported. The disassembly wasn't as documented for this one, resulting in lots of unknown values, which frustrated me. However, when I sat down to study them, I noticed similarities with actclear (end-of-act results tallying), and in the end they've all been documented. Phew!
     
  13. BenoitRen

    BenoitRen

    Tech Member
    440
    188
    43
    I've been porting the code for springs, which more than once calls collision checking code. So I figured I'd also look at that to get a better understanding of what parameters to send and what to expect back.

    Almost 500 lines of code later: it's a mess, and I don't get it (which should be obvious by how much I just plainly transcribed the ASM to C). That portion was not labelled, after all, and documentation in the disassemblies is sparse.

    Can anyone familiar with how Sonic 2 or Sonic 3 handle collision lend me a hand?

    EDIT: Oh, I didn't see that Sonic 2's disassembly has loads of notes at
    SolidObject_OnScreenTest. Will have to take a closer look.
     
    Last edited: May 3, 2024 at 9:54 PM
  14. nineko

    nineko

    I am the Holy Cat Tech Member
    6,312
    489
    63
    italy
  15. BenoitRen

    BenoitRen

    Tech Member
    440
    188
    43
    So far the notes in the Sonic 2 disassembly have helped me clarify the collision checking with solid objects. From a cursory glance after reading the two introductory chapters, the physics guide seems to follow the same logic.

    There are some differences between Sonic 2 and Sonic 3 at the code level.

    If the player is not standing on the solid object, it will check if there's a collision. To do this, it calculates the player's distance to the left edge of the object. In Sonic 2, it checks if this distance is negative, while Sonic 3 skips this check. Next it checks if this same distance is greater than the object's width to see if the player is to the right of the object.

    Why does Sonic 3 skip the first check? Because the second check is unsigned (in both games, I might add). If the distance to the left edge is negative, that translates to a big unsigned number, which will always be bigger than the object's width. In other words, the second check does both checks at the same time!

    A similar check is skipped in Sonic 3 when checking the vertical axis, likely for the same reason.

    Another difference is with the calculation of the minimum distance for a bottom collision.

    In Sonic 2, it just multiplies the maximum distance for a top collision by 2. In Sonic 3, the calculation is: player's height + solid object's airborne vertical height (*) + maximum distance for top collision. The maximum distance for top collision is the addition of the solid object's airborne vertical height (*) the player's vertical radius.

    So, the difference seems to be that in Sonic 3, the player's vertical radius is substituted for the player's height in one half of that calculation.

    In double-checking this, it would seem that the order of sprhsize and sprvsize is reversed in my sprite status object. The order hasn't changed between the original Sonic and Sonic 3, so I guess that means that the version of Sonic CD PC of the sprite status changed it for some reason.

    (*): It's unclear to me whether the object's airborne vertical height is actually half of that value, or not. The given value when hitchk is called is based on sprvs, which the wiki notes is the height in pixels. But Sonic 2's disassembly says that the value given in that register is the height divided in two.
     
  16. BenoitRen

    BenoitRen

    Tech Member
    440
    188
    43
    The collision detection for solid objects is finished. The comments in the Sonic 2 disassembly were very helpful. There were some blanks near the end, but those were filled up by the Sonic Physics Guide.

    I've just finished the collision detection for sloped objects. The Sonic 2 disassembly has no comments on that part of the code, unfortunately, but according to the physics guide it's very similar. Noteworthy is that the Sonic 3 version of this code does not have merged checks; it has the same checks as Sonic 2.

    The main difference is the use of what it calls a "height array" to shift the Y position of the object that's being checked against. It explains the concept well, but what I'm missing is how the index into this array is calculated. As a result, there's one variable I don't what it represents:
    Code (C):
    1.   short unknown_distance = distances.left_edge;
    2.   if (IS_HFLIP(p_actwk->actflg)) {
    3.     unknown_distance = ~unknown_distance;
    4.     unknown_distance += width;
    5.   }
    6.   unknown_distance /= 2;
    7.   short shift = slope_heights[unknown_distance] - slope_heights[0];
    8.   short yposi = LONG_HIGH(p_actwk->yposi) - shift;
    Is it the position of the player on the slope?

    The latest version of this code has been committed, by the way.
     
  17. BenoitRen

    BenoitRen

    Tech Member
    440
    188
    43
    I've finished the rest of the collision checking code yesterday and committed the final version.

    Today I've put the finishing touches on the spring code, and committed that as well.

    Finally, I've clarified even more bit field manipulations in my continuing quest to have the code be as readable as possible.