don't click here

Sonic CD Quirks/Deconstruction

Discussion in 'Engineering & Reverse Engineering' started by Devon, Jul 11, 2022.

  1. Devon

    Devon

    help me, i am in hell Tech Member
    1,408
    1,707
    93
    your mom
    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.
    [​IMG]

    Here's animations 2 and 3 for example, the slowest animations for turning slightly left and right.
    [​IMG]

    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.
     
  2. Chimes

    Chimes

    The One SSG-EG Maniac Member
    886
    617
    93
    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...
    [​IMG]
     
  3. Devon

    Devon

    help me, i am in hell Tech Member
    1,408
    1,707
    93
    your mom
    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
     
    Last edited: Nov 2, 2022
  4. Blue Spikeball

    Blue Spikeball

    Member
    2,517
    1,060
    93
    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.
     
    Last edited: Nov 2, 2022
    • Informative Informative x 1
    • List
  5. Devon

    Devon

    help me, i am in hell Tech Member
    1,408
    1,707
    93
    your mom
    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:
    [​IMG]

    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):
    1. .Index:
    2.     dc.w    ObjSpringBoard_Init-.Index
    3.     dc.w    ObjSpringBoard_NormalMain-.Index
    4.     dc.w    ObjSpringBoard_FlipMain-.Index
    5.     dc.w    ObjSpringBoard_NormalMain2-.Index
    6.     dc.w    ObjSpringBoard_FlipMain2-.Index
    7.     dc.w    ObjSpringBoard_NormalFling-.Index
    8.     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):
    1.     move.b   #3,d0                ; Normal animation
    2.     move.b   #2,d1                ; Normal routine
    3.     tst.b    oSubtype(a0)         ; Are we flipped?
    4.     bne.s    .Flip                ; If so, branch
    5.     btst     #0,oSprFlags(a0)     ; Is our sprite flipped?
    6.     beq.s    .SetRoutine          ; If not, branch
    7.  
    8. .Flip:
    9.     move.b   #4,d0                ; Flipped animation
    10.     move.b   #4,d1                ; Flipped routine
    11.     bclr     #0,oSprFlags(a0)     ; Clear horizontal flip flags
    12.     bclr     #0,oFlags(a0)
    13.  
    14. .SetRoutine:
    15.     move.b   d0,oAnim(a0)          ; Set animation
    16.     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:
    [​IMG]

    As stated above, it did just that in v0.02:
    Code (Text):
    1. ObjFloatBlock_TopSolid:
    2.     lea      objPlayerSlot.w,a1
    3.     bsr.w    .Check
    4.     lea      objPlayerSlot2.w,a1
    5.  
    6. .Check:
    7.     move.w   oX(a0),d3
    8.     move.w   oY(a0),d4
    9.     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):
    1. ObjFloatBlock_TopSolid:
    2.     lea      objPlayerSlot.w,a1
    3.     bsr.w    .Check
    4.  
    5. .Check:
    6.     move.w   oX(a0),d3
    7.     move.w   oY(a0),d4
    8.     jmp      TopSolidObject

    As a result, in the final, this object checks collision with Sonic twice in 1 frame.
     
    Last edited: Nov 4, 2022
    • Informative Informative x 6
    • Like Like x 2
    • List
  6. Devon

    Devon

    help me, i am in hell Tech Member
    1,408
    1,707
    93
    your mom
    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.

    [​IMG]
     
    • Like Like x 4
    • Informative Informative x 4
    • List
  7. evilhamwizard

    evilhamwizard

    Researcher
    1,393
    468
    63
    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?
     
  8. Blue Spikeball

    Blue Spikeball

    Member
    2,517
    1,060
    93
    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?
     
  9. Devon

    Devon

    help me, i am in hell Tech Member
    1,408
    1,707
    93
    your mom
    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:

    [​IMG]

    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.

    [​IMG]

    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.
     
    Last edited: Nov 6, 2022
    • Like Like x 3
    • Informative Informative x 2
    • List
  10. Blue Spikeball

    Blue Spikeball

    Member
    2,517
    1,060
    93
    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?
     
  11. Devon

    Devon

    help me, i am in hell Tech Member
    1,408
    1,707
    93
    your mom
    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.
     
    • Like Like x 2
    • Informative Informative x 1
    • List
  12. Bobblen

    Bobblen

    Member
    429
    216
    43
    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.
    upload_2022-11-15_9-21-28.png
     
  13. MainMemory

    MainMemory

    Kate the Wolf Tech Member
    4,789
    370
    63
    SonLVL
    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.
     
  14. Devon

    Devon

    help me, i am in hell Tech Member
    1,408
    1,707
    93
    your mom
    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):
    1.     move.w  d0,d3                           ; Get top 8px row
    2.     lsr.w   #1,d3
    3.     moveq   #(288/8)-1,d1                   ; Size of clouds/mountains in 8px rows (d1 used for dbf)
    4.  
    5.     ; BUG: Max number of 8px rows on screen should be 29
    6.     moveq   #28,d5                          ; Max number of 8px rows on screen
    7.     sub.w   d3,d1                           ; Get number of remaining 8px rows in clouds/mountains
    8.     bcs.s   .ScrollWater                    ; If only the water is visible, branch
    9.     cmpi.w  #28-1,d1                        ; Is the number of 8px rows too large to be all on screen?
    10.     bcs.s   .ScrollCloudsMtns               ; If not, branch
    11.     moveq   #28-1,d1                        ; If so, cap it
    12.  
    13. .ScrollCloudsMtns:
    14.     sub.w   d1,d5                           ; Get number of visible 8px rows for the water
    15.     lea     (a2,d0.w),a2                    ; Scroll clouds/mountains
    16.     bsr.w   ScrollRows
    17.  
    18. .ScrollWater:
    19.     ; WARNING: Runs even if the water if offscreen. Requires that the number of 8px rows left for the
    20.     ; for the water section be at least 1, as there is no check for if it's less!
    21.     ; This is also why they subtract the offset (by 1 for dbf) number of 8px rows for the
    22.     ; clouds/mountains from the absolute number of visible 8px rows on screen, to ensure that at least
    23.     ; 1 row is available for the water.
    24.     move.w  cameraBg2X.w,d0                 ; Get water scroll accumulator
    25.     move.w  cameraX.w,d2
    26.     sub.w   d0,d2
    27.     ext.l   d2
    28.     asl.l   #8,d2
    29.     divs.w  #$100,d2
    30.     ext.l   d2
    31.     asl.l   #8,d2
    32.  
    33.     moveq   #0,d3                           ; Get top water scanline offset
    34.     move.w  d0,d3
    35.  
    36.     move.w  d5,d1                           ; Convert number of 8px rows to scroll to lines
    37.     lsl.w   #3,d1
    38.     subq.w  #1,d1                           ; Subtract 1 for dbf
    39.  
    40. .ScrollWaterLoop:
    41.     move.w  d3,d0                           ; Set scanline offset for water
    42.     neg.w   d0
    43.     move.l  d0,(a1)+
    44.  
    45.     swap    d3                              ; Add water scroll accumulator
    46.     add.l   d2,d3
    47.     swap    d3
    48.  
    49.     dbf     d1,.ScrollWaterLoop             ; Loop until water is scrolled
    50.     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):
    1.     movsx   eax, word ptr [esp+28h+cloudMtnRows]            ; Get number of 8px rows for the clouds/mountains
    2.     sub     ebx, ecx                                        ; Get number of visible scanlines in the first 8px row
    3.     mov     [esp+28h+cloudMtnRows2], eax
    4.     cmp     word ptr [esp+28h+cloudMtnRows], 28-1           ; Are there 28 8px rows to scroll for the clouds/mountains?
    5.     jnz     short loc_10012E14                              ; If not, branch
    6.     inc     [esp+28h+cloudMtnRows2]                         ; If so, scroll an additional row to keep the water deformation offscreen
    7.  
    8. loc_10012E14:
     
    Last edited: Nov 15, 2022
    • Informative Informative x 5
    • Like Like x 1
    • List
  15. Bobblen

    Bobblen

    Member
    429
    216
    43
    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...
     
  16. Devon

    Devon

    help me, i am in hell Tech Member
    1,408
    1,707
    93
    your mom
    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).
     
    Last edited: Nov 16, 2022
    • Like Like x 4
    • Informative Informative x 2
    • List
  17. 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 :(
     
  18. Devon

    Devon

    help me, i am in hell Tech Member
    1,408
    1,707
    93
    your mom
    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.
    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).
     
    Last edited: Nov 17, 2022
  19. 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

     
  20. Hivebrain

    Hivebrain

    Administrator
    3,066
    204
    43
    53.4N, 1.5W
    Github
    I imagine it would work with something much smaller, like a 64x64px rotating moon/planet.