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.
Yeah, I thought it was some kind of overflow at first, I found out it was deliberate only eventually.
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.
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.
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): #define POCKY_RABBIT 0 #define CUCKY_CHICKEN 1 #define PECKY_PINGUIN 2 #define ROCKY_SEAL 3 #define FLICKY_FLICKY 5 #define RICKY_SQUIRREL 6 unsigned char zone_friend_data[24][2] = { { FLICKY_FLICKY, CUCKY_CHICKEN }, // Angel Island { POCKY_RABBIT, ROCKY_SEAL }, // Hydrocity { FLICKY_FLICKY, CUCKY_CHICKEN }, // Marble Garden { POCKY_RABBIT, FLICKY_FLICKY }, // Carnival Night { RICKY_SQUIRREL, FLICKY_FLICKY }, // Flying Battery { PECKY_PINGUIN, ROCKY_SEAL }, // Icecap { FLICKY_FLICKY, CUCKY_CHICKEN }, // Launch Base { RICKY_SQUIRREL, CUCKY_CHICKEN }, // Mushroom Hill { POCKY_RABBIT, CUCKY_CHICKEN }, // Sandopolis { FLICKY_FLICKY, CUCKY_CHICKEN }, // Lava Reef { POCKY_RABBIT, FLICKY_FLICKY }, // Sky Sanctuary { RICKY_SQUIRREL, CUCKY_CHICKEN }, // Death Egg { RICKY_SQUIRREL, FLICKY_FLICKY }, // Doomsdag { FLICKY_FLICKY, CUCKY_CHICKEN }, { FLICKY_FLICKY, CUCKY_CHICKEN }, { FLICKY_FLICKY, CUCKY_CHICKEN }, { FLICKY_FLICKY, CUCKY_CHICKEN }, { FLICKY_FLICKY, CUCKY_CHICKEN }, { FLICKY_FLICKY, CUCKY_CHICKEN }, { FLICKY_FLICKY, CUCKY_CHICKEN }, { FLICKY_FLICKY, CUCKY_CHICKEN }, { FLICKY_FLICKY, CUCKY_CHICKEN }, { FLICKY_FLICKY, CUCKY_CHICKEN }, { FLICKY_FLICKY, CUCKY_CHICKEN }, }; 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!
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.
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.
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): typedef enum zone_id { ANGEL_ISLAND = 0x00, HYDROCITY = 0x01, MARBLE_GARDEN = 0x02, CARNIVAL_NIGHT = 0x03, FLYING_BATTERY = 0x04, ICECAP = 0x05, LAUNCH_BASE = 0x06, MUSHROOM_HILL = 0x07, SANDOPOLIS = 0x08, LAVA_REEF = 0x09, SKY_SANCTUARY = 0x0A, DEATH_EGG = 0x0B, DOOMSDAY = 0x0C, ANGEL_ISLAND_DUMMY_AND_ENDING = 0x0D, AZURE_LAKE = 0x0E, BALLOON_PARK = 0x0F, DESERT_PALACE = 0x10, CHROME_GADGET = 0x11, ENDLESS_MINE = 0x12, ZONE_GUMBALL_MACHINE = 0x13, ZONE_SLOT_MACHINE = 0x14, ZONE_ROLLING_JUMP = 0x15, LAVA_REEF_BOSS_AND_HIDDEN_PALACE = 0x16, DEATH_EGG_BOSS_AND_MASTER_EMERALD_SHRINE = 0x17 } 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?
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!
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.
Also, https://info.sonicretro.org/Sonic_Physics_Guide#Collision. While it doesn't contain code (I think), it should help you figure out what happens and why.
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.
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): short unknown_distance = distances.left_edge; if (IS_HFLIP(p_actwk->actflg)) { unknown_distance = ~unknown_distance; unknown_distance += width; } unknown_distance /= 2; short shift = slope_heights[unknown_distance] - slope_heights[0]; 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.
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.