So, I took a look into this, and it might be a bug? Here's the animation table for when Sonic is running on the ground. The format is that each row represents the direction Sonic is tilted, and each entry in the row represents the speed. Here's animations 2 and 3 for example, the slowest animations for turning slightly left and right. The sprites used normally are facing right, but animation 2 flips them to face left instead (by setting a bit in the highest byte of a sprite pointer, ala the "|$1000000"). Although, I cannot 100% say that it really is a bug. Maybe it's just a weird choice made by the devs, maybe it's an oversight. In the end, it's just some food for thought, I suppose.
Where are these sprites stored in the Special Stage file? TCRF claims that it's for Sonic leaping and its purpose is unknown, but judging from how Sonic's feet are positioned in the first two sprites these might be unused sprites for Sonic's tripping animation, as they line up. The third one I have no clue...
In SPSS: 1. 0x272B8 2. 0x27538 3. 0x28418 I need to get around to documenting the unused animations for Sonic, too. I do have the IDs for them: 07, 08, 0C, 0F, 1F, 20, 21, 22, 23 EDIT: lol done
See: https://forums.sonicretro.org/index.php?threads/rare-sonic-cd-art.38792/ That concept art shows the exact same pose as the middle sprite, suggesting that they were intended for that scene. In it Sonic is leaping off a ramp, which fits better than the tripping interpretation IMO. 1st sprite he's about to jump, 2nd is after he jumped forward, 3rd he's falling down.
So, apparently, originally, the developers did not know that objects could just be flipped with a flag in a level's object position data, so some earlier made objects actually have dedicated subtype definitions and animations for flipped objects, and even copied and pasted routines for handling them! One such object was this spring board object: As shown here, there are dedicated routines for when it's flipped. The only difference between them and the "normal" routines is that it uses the flipped animations and hitboxes. Code (Text): .Index: dc.w ObjSpringBoard_Init-.Index dc.w ObjSpringBoard_NormalMain-.Index dc.w ObjSpringBoard_FlipMain-.Index dc.w ObjSpringBoard_NormalMain2-.Index dc.w ObjSpringBoard_FlipMain2-.Index dc.w ObjSpringBoard_NormalFling-.Index dc.w ObjSpringBoard_FlipFling-.Index Later on in development, they did seem to figure out that objects can just simply be flipped, but instead of cleaning up the code, they just added another check to see if the object is flipped using the flag, and if so, set it to use the flipped subtype. This check did not exist in v0.02. Code (Text): move.b #3,d0 ; Normal animation move.b #2,d1 ; Normal routine tst.b oSubtype(a0) ; Are we flipped? bne.s .Flip ; If so, branch btst #0,oSprFlags(a0) ; Is our sprite flipped? beq.s .SetRoutine ; If not, branch .Flip: move.b #4,d0 ; Flipped animation move.b #4,d1 ; Flipped routine bclr #0,oSprFlags(a0) ; Clear horizontal flip flags bclr #0,oFlags(a0) .SetRoutine: move.b d0,oAnim(a0) ; Set animation move.b d1,oRoutine(a0) ; Set routine Also, as it is documented, v0.02 has a functional player 2. Various objects were programmed to check collision with both the main player and player 2. One such object was this floating block: As stated above, it did just that in v0.02: Code (Text): ObjFloatBlock_TopSolid: lea objPlayerSlot.w,a1 bsr.w .Check lea objPlayerSlot2.w,a1 .Check: move.w oX(a0),d3 move.w oY(a0),d4 jmp TopSolidObject However, when they decided that there was not going to be a player 2, they removed all the checks. In this particular object, they didn't exactly do a good job, as they only got rid of the line that sets which object to check: Code (Text): ObjFloatBlock_TopSolid: lea objPlayerSlot.w,a1 bsr.w .Check .Check: move.w oX(a0),d3 move.w oY(a0),d4 jmp TopSolidObject As a result, in the final, this object checks collision with Sonic twice in 1 frame.
So, I was looking at the SolidObject routine in Sonic CD, and noticed 1 thing: in the other games, you would manually calculate the collision width (preferably, the player's X radius + the object's X radius). Sonic CD does it automatically by taking the object's width setting, and then adding 10 (9 (Sonic's normal X radius) + 1 (offsets the object's width setting)). I also noticed that the object specific checks were also moved inside SolidObject as well, including the monitor. However, what I found out is that it only deactivates collision for monitors if you hit it from the top or bottom when rolling, BUT NOT THE SIDES. But, somehow the game still allows Sonic to roll into the monitors? This is because the code for checking if Sonic should destroy a monitor is handled first in the frame, inside Sonic's object code. So, when a collision occurs, the destruction would take place first before the solid collision detection would have a chance to take into effect. There's 1 small issue though: the destruction check's hitbox has Sonic's X radius set to 8, not 9. Note how I said that it uses 9 for Sonic's X radius for solid collision detection. This means, if you position yourself right, you can make it so that Sonic lands in that 1 pixel gap where the solid collision will be detected, but not the destruction.
What was even the point of the second player any way? Were they trying to add support for multiplayer or a partner like system with Tails or something?
So that explains that oddity. That's one of the things that always made CD feel more janky than the MD games to me, even S1. I assume the change was done as an optimization? Or was it based on older S1 code? Say, do you know why when you start rolling at minimal while facing left, Sonic gains a minor burst of speed, while doing the same while facing right keeps the prior momentum?
This solid object function is unique to Sonic CD. Well, you have your directions backwards, but I do have the code that explains what's going on: If you are moving left, your momentum is left unaffected, but when you are moving right, it checks if your speed is < $200, and if so, it caps it at $200. In Sonic 1, 2, and 3 (alone), you're not allowed to roll unless you're moving with a speed of at least $80 in either direction, and will only boost you at a speed of $200 to the right if you're in a roll tunnel and you stop. In Sonic (3) & Knuckles, the minimum speed is $100. There is no minimum speed in CD, and you must be moving at a speed < $200 to the right or be stopped to be boosted, and is the result of that little side effect. Even then, not all roll tunnels use the Sonic 1 method of setting a special chunk to force Sonic to stay in a ball, nor do the roll tunnel objects that make Sonic go into a ball when touched set any special flags.
Right, got the directions backwards, derp. Thanks for the explanation. So that "feature" was there for the tunnels, only checking right because all tunnels go right? And it was present in the MD games but was only activated during the tunnels? Do we know if CD is based on a mid-development S1 build, explaining "regressions" like these?
I think it's more some kind of weird super early attempt at messing with the behaviors of the tunnels before eventually scrapping it in favor of a completely different method. In Sonic 1, you would be able to set 2 chunk IDs in a zone to force Sonic into a ball if he's in the chunk. The code for handling that is still in Sonic CD, but is mostly left unused as they later settled for the dynamic method of using an object to make Sonic roll. Sonic 2 also does that, but it also sets a flag in Sonic 2 to keep Sonic in a ball until he hits another force roll object. Sonic CD doesn't, and as such, Sonic can uncurl in a tunnel. No, it's based on REV01.
I noticed an interesting quirk the other day. Specifically on PPZ2, if you scroll the screen up (by spin dashing up a ramp or something) and the lowest part of the screen contains the far background tiles (the mountains, not the sea which is in front of this layer), the bottom row will glitch out. Curious if I have a bad dump or have stumbled across an emulator bug, or if this is a real thing? It doesn't behave this way on the 96 PC port or the remaster.
I mean, the later releases have completely different drawing systems, so I'd be surprised if a bug in one of them happened in the others.
This is due to a bug in the deformation code. If there's an 8px section at the bottom that's only partially on screen (it will only process a maximum of 28 8px sections on screen, if the camera is not aligned at an 8px position vertically, then 29 will be visible due to the first row still being visible, while an additional pops up), then it's not processed and the water deformation code kicks in its place (which ALWAYS runs, regardless of whether the water is actually on screen or not). Code (Text): move.w d0,d3 ; Get top 8px row lsr.w #1,d3 moveq #(288/8)-1,d1 ; Size of clouds/mountains in 8px rows (d1 used for dbf) ; BUG: Max number of 8px rows on screen should be 29 moveq #28,d5 ; Max number of 8px rows on screen sub.w d3,d1 ; Get number of remaining 8px rows in clouds/mountains bcs.s .ScrollWater ; If only the water is visible, branch cmpi.w #28-1,d1 ; Is the number of 8px rows too large to be all on screen? bcs.s .ScrollCloudsMtns ; If not, branch moveq #28-1,d1 ; If so, cap it .ScrollCloudsMtns: sub.w d1,d5 ; Get number of visible 8px rows for the water lea (a2,d0.w),a2 ; Scroll clouds/mountains bsr.w ScrollRows .ScrollWater: ; WARNING: Runs even if the water if offscreen. Requires that the number of 8px rows left for the ; for the water section be at least 1, as there is no check for if it's less! ; This is also why they subtract the offset (by 1 for dbf) number of 8px rows for the ; clouds/mountains from the absolute number of visible 8px rows on screen, to ensure that at least ; 1 row is available for the water. move.w cameraBg2X.w,d0 ; Get water scroll accumulator move.w cameraX.w,d2 sub.w d0,d2 ext.l d2 asl.l #8,d2 divs.w #$100,d2 ext.l d2 asl.l #8,d2 moveq #0,d3 ; Get top water scanline offset move.w d0,d3 move.w d5,d1 ; Convert number of 8px rows to scroll to lines lsl.w #3,d1 subq.w #1,d1 ; Subtract 1 for dbf .ScrollWaterLoop: move.w d3,d0 ; Set scanline offset for water neg.w d0 move.l d0,(a1)+ swap d3 ; Add water scroll accumulator add.l d2,d3 swap d3 dbf d1,.ScrollWaterLoop ; Loop until water is scrolled rts Other deformation routines in other levels use 29 instead of 28. Act 1 uses a more complex routine, where there are multiple water sections, for the 3D loop effect. The 1996 PC version actually uses the same deformation routine as in the Sega CD version, just ported to C. HOWEVER, they did add a small bit of code that makes it so that the number of rows for the clouds and mountains is incremented to 29 if it's 28 (aka if the water is offscreen). Code (Text): movsx eax, word ptr [esp+28h+cloudMtnRows] ; Get number of 8px rows for the clouds/mountains sub ebx, ecx ; Get number of visible scanlines in the first 8px row mov [esp+28h+cloudMtnRows2], eax cmp word ptr [esp+28h+cloudMtnRows], 28-1 ; Are there 28 8px rows to scroll for the clouds/mountains? jnz short loc_10012E14 ; If not, branch inc [esp+28h+cloudMtnRows2] ; If so, scroll an additional row to keep the water deformation offscreen loc_10012E14:
Thanks @Devon for the in depth investigation, that was really interesting. Another thing I've been trying to do is run through the game collecting all 1 up items which has thrown up another quirk. The original game has shared objects for all time periods and objects collected in one future are considered collected in both good and bad futures. But the lives caps at 9 which is a bit unsatifying. The taxman remake uncaps the lives, but also decouples future objects. Good and bad future would need to be done separately which is alot of time travel! Which version to try, choices choices...
This is because internally, the good and bad future are considered the same (all the time zone settings are just past, present, and future). They are only separated by a flag, which really just affects which file is loaded (All time zones in a stage share the same object layout data, by the way. Each object in every time zone is stored, with time zone flags set for each of them.) and some various little visual things. This is only visual. You can still get more than 9 lives, it just won't show in the HUD (only 1 8x16 digit is allocated in VRAM for the lives counter). However, they did change the behavior of checking if you have no lives left. In the other games, it checks if it's gone to 0 and 0 only (which is why underflowing to 255 doesnt cause a game over). Sonic CD checks for underflowing, which effectively makes the lives counter a signed value, which means you will get a game over if you lose a life and end up with 0 or 128-255 lives. This makes the maximum amount of lives you can have 128 (since losing a life will get you down to 127).
Does anyone know why Sonic Team looked to use the ASIC chip for the clouds on the the title screen. Its a lovely effect but Sonic Team made no use of it in-game
Probably because they thought it would look nice. (Also for future reference, should note that the ASIC is the entire Sega CD Gate Array, not just the graphics operation) It's complicated. The graphics operation merely just generates tile data from a source image and transformation table to be manually coped into VRAM by the Genesis side. The Sega CD does not add an additional layer or anything like that, it just performs calculations and generates tile data for the Genesis VDP. Spoiler: Side note In fact, I don't think the expansion port can even send a video signal. The cartridge port has some pins for helping an external video processor with timing, but it doesn't take in an actual video feed, which is why the 32X requires you to connect the Genesis video output to it. Audio signals can be sent through the ports, though, which is how both CD audio (if you aren't connecting the model 1 Genesis' stereo headphone output to the Sega CD) and 32X PWM audio are fed into the Genesis. The clouds in the title screen is rendered to a 256x96px buffer, which takes up $3000 bytes of VRAM out of the total $10000 available. On top of that, to avoid tearing, it's double buffered, so a total of $6000 bytes is taken up for the clouds in the title screen (the horizontal resolution being set to 256px allows the double buffer to work without eating up even more VRAM). The main gameplay as-is already uses up a lot of VRAM for the stage tiles, objects, sprite table, horizontal scroll table, and tilemaps, so without having to completely redo how VRAM is managed, implementing such a thing is just unrealistic. On top of that, since the render is manually copied into VRAM, $3000 bytes is a lot to be transferred, even with DMA. This bottleneck, on top of the generally unoptimized code in the game, will impact performance. Hell, for the clouds or even the special stages, it doesn't even transfer the full render into VRAM in 1 frame because of how badly that would affect performance. They opt to split the transfer into 2 DMAs across 2 frames (the only reason it doesn't then run at 30 FPS instead of 20 moreso comes down to the code setup).
I just felt letdown because the game made lovely use of the ASIC chip for the clouds on the the title screen and D.A Garden but basically sod all else other than the pretty poor Special stages. When the game really should have been showcase for the ASIC chip and it put to full use on the boss fights Ect. Still it was fun to mess around with the clouds mind , it also looked like a Saturn VDP2 effect in parts