Sonic CD Quirks (Sega CD Version)

Discussion in 'Discussion & Q&A' started by faith, Jul 11, 2022.

  1. faith

    faith Well-Known Member Member

    Joined:
    Aug 26, 2013
    Messages:
    1,209
    Sonic CD changed the roll check routine a little. 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:

    [​IMG]

    However, this code has a side effect where if you roll at a slow enough speed to the right, you get that boost, but not when rolling left:

    [​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.
     
  2. faith

    faith Well-Known Member Member

    Joined:
    Aug 26, 2013
    Messages:
    1,209
    In Palmtree Panic Act 2 Present, the bottom of the background has bad deformation at the bottom:
    [​IMG]

    This is due to a bug in the deformation routine. 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:
        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:
        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:
     
  3. Neotendo

    Neotendo Newcomer Trialist

    Joined:
    Nov 16, 2022
    Messages:
    7
    hi there, thank you for disassemble sonicCD, i was able to make a new intro fmv with sonic mania music and immersive sounds. changed the title screen, sonic is waving his finger the whole time, slower.
    (idk if i am allowed to post a YT link here)

    i even want to add the sonic 2 spindash move but for that we need all levels as far as i know.
    i added the SonicCD++ Patch to my iso so i have the spindash in all levels and my modifications in titlescreen and intro fmv

    i keep following this thread, great work
     
    Last edited: Nov 25, 2022
  4. Neotendo

    Neotendo Newcomer Trialist

    Joined:
    Nov 16, 2022
    Messages:
    7
    @⸸ devon ⸸

    Can you tell me please if we can make the intro fmv with more than 8 FPS? i tried it but the fmv played too slow, looks like the source code is caping it to 8 FPS but i can't find where in the code.

    And i need to know how to add .mmd into source code, i want to playback an fmv before the title screen but something goes wrong. i only able to playback the stock .stm files, it looks like the source code is not finding my .stm file if i add my own.

    thanks in advance
     
  5. faith

    faith Well-Known Member Member

    Joined:
    Aug 26, 2013
    Messages:
    1,209
    You would need to redo the FMV playback engine and change the format to fit more data in a CD read packet. As it stands, the data is uncompressed, and the CD drive can only read so much at once (it uses 1M/1M mode so that it can display the frames stored in a bank of Word RAM while the CD drive reads into the other, and a bank is only $20000 bytes in size).

    To add an additional FMV playback program, you'll need to add both the Main CPU, Sub CPU, and data files to the list of files in the beginning system program, assign them IDs in _Include/System.i, and add a command (and ID) for loading it in the system program.
     
    Last edited: Nov 25, 2022
  6. Neotendo

    Neotendo Newcomer Trialist

    Joined:
    Nov 16, 2022
    Messages:
    7
    A1: ahhh, ok, so the stm intro file can't be have more FPS cause cdrom speed is too slow. ok, fine.

    A2:
    i did copy Sub (Opening).asm to Sub (Intro).asm and Main (Opening).asm to Main (Intro.asm).
    added FID_INTROSTM, FID_OPENMAIN, FID_OPENSUB, SCMD_INTRO in System.i.
    File_IntroData, File_IntroMain, File_IntroSub in SPX.asm.

    but i do not get to work :(

    can you tell me more about that please
    " and add a command (and ID) for loading it in the system program."
    sorry, i never programmed for the segaCD, hahaha

    IPX.asm
    ...
    tst.b d0 ; Was it a succes?
    beq.s .GetSaveData ; If so, branch
    bset #0,saveDisabled ; If not, disable saving to Backup RAM

    .GetSaveData:
    bsr.w ReadSaveData ; Read save data

    <Here was my jump call to the stm player, opn.stm is playing but not my intro.stm>

    .GameLoop:
    move.w #SCMD_INITSS2,d0 ; Initialize special stage flags
    bsr.w SubCPUCmd

    ...
     
    Last edited: Nov 25, 2022
  7. faith

    faith Well-Known Member Member

    Joined:
    Aug 26, 2013
    Messages:
    1,209
    See "SPCmd_LoadOpening" for reference in SPX.asm. Make sure that your entry for your FMV loading routine is listed in ".SPCmds" at the appropriate offset for your ID.
     
  8. Neotendo

    Neotendo Newcomer Trialist

    Joined:
    Nov 16, 2022
    Messages:
    7
    thank you very much, i missed this code section , i will try to add my intro.
     
    Last edited: Nov 25, 2022
  9. Neotendo

    Neotendo Newcomer Trialist

    Joined:
    Nov 16, 2022
    Messages:
    7
    i think i got it working. need some finetuning with animation and sound.
     
  10. faith

    faith Well-Known Member Member

    Joined:
    Aug 26, 2013
    Messages:
    1,209
    There are some sprites that can be found in the 1996 PC version, but NOT the Sega CD version. These rolling sprites do exist in the v0.02 prototype, albeit unused. The shrinking sprites only exist via leftover mappings data in Tidal Tempest since 510; no tile data can be found.

    [​IMG]

    Also, made my own rips of Sonic's sprites in the final's regular stages.
    [​IMG]
    [​IMG]
    [​IMG]
    [​IMG]

    v0.02 rip
    [​IMG]

    510 rips
    [​IMG]
    [​IMG]
    [​IMG]
    [​IMG]

    v0.51 rips
    [​IMG]
    [​IMG]
    [​IMG]
    [​IMG]

    712 rips
    [​IMG]
    [​IMG]
    [​IMG]
    [​IMG]

    1996 PC version rip
    [​IMG]

    510, 0.51, 712, and the final sprite sets are almost the same as reach other, minus some missing sprites, and also flipped balancing and skidding animations. The Tidal Tempest set are very much based on v0.02's set, with the mappings for the 3D rolling sprites being left over.
     
    Last edited: Nov 27, 2022
  11. faith

    faith Well-Known Member Member

    Joined:
    Aug 26, 2013
    Messages:
    1,209
    So, the game features a downward speed cap of 16 when you are in the air, so that it would prevent you from clipping through floors or messing up vertical wrapping if you were to fall for an extended period of time.

    However, this only applies when Sonic is in a normal state, where he uses the standard movement+gravity routine to handle falling. However, like in the other Genesis games, in his hurt state, they use a different gravity value than the one hardcoded in the standard function, so they just use the standard non-gravity movement routine and handle apply gravity manually, and they did not put the speed cap there.

    In the 510 prototype, in the beginning section of Tidal Tempest Act 2, there used to be enemies, and it's possible to get knocked back by one and glitch the vertical wrapping and die:
    [​IMG]

    The enemies placed in that area were removed, presumably for that reason.

    In the final, if you time and position yourself right in Wacky Workbench, you can get knocked back and clip through the floor:
    [​IMG]
     
  12. Neotendo

    Neotendo Newcomer Trialist

    Joined:
    Nov 16, 2022
    Messages:
    7
    nice find
     
  13. faith

    faith Well-Known Member Member

    Joined:
    Aug 26, 2013
    Messages:
    1,209
    I made some hackish modifications to Lapper and Mercury's collision overlay for Sonic 1 and got it working in Collision Chaos Act 1 Present:


    Wanna mention the pinball flippers. There's actually 2 sets of slope data for them: one for when you're falling/standing, and one for when you're moving upwards, specifically for hitting them from below.
    [​IMG] [​IMG]

    However, it should be worth noting that collision detection is only done at the center of Sonic's feet/head, and does not take into account his full hitbox width. As a result, the collision width of the flippers are effectively shrunken down like so:
    [​IMG] [​IMG]

    [​IMG]

    Also, bumper hitboxes and Sonic's hitbox are larger in the 2011 remake than in the original version:
    [​IMG] [​IMG]

    Another fun one: the game still retains the code from Sonic 1 that shrinks Sonic's object collision hitbox if he's ducking... except that they forgot to update the sprite frame ID. As a result, it doesn't work as intended, and the hitbox will shrink when an angled walking sprite is displayed.
    [​IMG] [​IMG]

    An addendum to this: turns out this is only an issue if you roll into a monitor from the left side. This is actually because the point where it would activate the solid collision, but not the destruction collision is actually rested up against the side of the monitor. In solid object collision, if that happens and you are left to the object, it's considered a collision. This happen because it checks if the distance from the object on the left is > 0. On the right side, it checks if the distance is >= 0 instead (Sonic 1, on the contrary, checks > 0 for the right side, too).
     
    Last edited: Dec 25, 2022
    Pacca, MrLordSith, ProjectFM and 5 others like this.
  14. Neotendo

    Neotendo Newcomer Trialist

    Joined:
    Nov 16, 2022
    Messages:
    7
    faith likes this.
  15. faith

    faith Well-Known Member Member

    Joined:
    Aug 26, 2013
    Messages:
    1,209
    As it is documented, later prototypes of Sonic CD included PCM music data that would bridge the CDDA music loop and make it seem seamless.



    Both the v0.51 and 712 prototypes feature this data, but it turns out that v0.51 actually has (unused) code that handles playing them!

    [​IMG]

    What's going on is that, basically, it waits for CDDA music to be playing. Once it does, it stops it and starts playing the PCM loop section. While the PCM music is playing, it waits for the CD drive to be fully stopped. Once it has, it prepares to play the CDDA music by seeking to it via MSCPLAY1, and then pausing as soon as it even starts playing. The PCM music sets a special flag when it's done playing. This routine checks that flag after the CDDA music has been paused, and if it's been set, it unpauses the CDDA music and lets it play, and then the cycle repeats.

    However, this routine is bugged. Since it immediately resets the routine cycle after unpausing the CDDA music, it'll activate the check for if CDDA music is playing, and if so, it'll just play the PCM loop section again. The result of this is that the PCM loop section will just play over and over again. Even then, it only plays Palmtree Panic's CDDA music, indicating to me that this routine isn't finished. This code is not referenced by anything and goes totally unused, as far as I know.
     
    Last edited: Dec 21, 2022
  16. faith

    faith Well-Known Member Member

    Joined:
    Aug 26, 2013
    Messages:
    1,209
    The 1996 PC version of Sonic CD adds additional code in the floor collision function that fixes a slight visual and physics bug for when you're on a flat surface, but the left sensor is detecting a block with a steep enough angle.

    [​IMG]

    I will use Sonic 1 as a quick example to illustrate my point, as that is what's most convenient at this moment. Here, I have placed these 2 blocks next to each other. The left block has a flat surface after the sloped part.

    [​IMG]

    Under normal circumstances, when both sensors are on a flat surface, it'll use the angle from the left sensor. In this case, if Sonic's left sensor is on that left block, his angle will be set to the angle of that slope.

    [​IMG]

    With the fix in place, Sonic can walk on that flat bit on the left block just fine.

    [​IMG]

    This fix is NOT present in the Sega CD version of the game.

    Here's the bug in action with Lapper and Mercury's overlay script running. Look at Sonic's left floor sensor as it approaches the surface of the left block.

    [​IMG]

    By the way, I ported the fix to 68000 assembly. In the floor mode, place this between where the sensors are checked (after the 2 calls to FindFloor) and the call to the function that picks a sensor to use ("bsr.w Sonic_Angle" in Sonic 1):
    Code:
                    cmpi.b  #-1,(a4)                        ; Is the left sensor's angle set to snap to a flat surface?
                    beq.s   .NoFix                          ; If so, branch
                    move.w  d0,d2                           ; Are we on a completely flat surface?
                    or.w    d1,d2
                    bne.s   .NoFix                          ; If not, branch
                    cmpi.b  #-$10,(a4)                      ; Is the left sensor's angle set to a steep enough angle?
                    bge.s   .NoFix                          ; If it's not steep enough, branch
                    cmpi.b  #-$40,(a4)
                    blt.s   .NoFix                          ; If it's too steep/out of range, branch
                    st      (a4)                            ; Force left sensor's angle to snap to a flat surface
                  
    .NoFix:
     
    Last edited: Dec 26, 2022
  17. faith

    faith Well-Known Member Member

    Joined:
    Aug 26, 2013
    Messages:
    1,209
    Continuing the topic of collision detection changes, the Sega CD version of Sonic CD adds additional code that fixes a bug when Sonic is standing on top of a column with a negative height (starts from the top and points downwards).

    [​IMG]

    In this example, Sonic is standing on top of a block that's solid in the top half, so each column in the block has a negative height. The block's angle is also set to 0x80, which would normally make Sonic go upside down when he touches it.

    [​IMG]

    Without this fix, Sonic would react like this when colliding with that log.

    [​IMG]

    None of the other Genesis titles fix this bug.
     
    Pacca, ProjectFM, JGamer2151 and 3 others like this.
  18. faith

    faith Well-Known Member Member

    Joined:
    Aug 26, 2013
    Messages:
    1,209
    Let's talk about level map data. The original Sega CD version allocates double the memory for map data from Sonic 1, and thus allows for levels to be double the height... kinda, not really actually...

    [​IMG]

    The collision detection takes advantage of this, bar Tidal Tempest, which is set to the original limitations due to level wrapping.

    [​IMG]

    ...except that the drawing functions don't, and still retain the original limits.

    [​IMG]

    The map data itself also does not go beyond the original limitations, either. There is not a single stage that is taller than 0x800 pixels.

    As a side note, when calculating the X position index for getting map data, it limits it down to 0-0x7F, which is not correct, because each row in the map data is actually 2 64 chunk wide rows interlaced with each other, with the first for plane A, and the second for plane B. The limit should be 0x3F.

    [​IMG]



    Now, let's discuss how the 1996 PC version handles getting map data for collision. It's a bit of a doozy.

    [​IMG]

    A few things were added. Such as preventing the position from going negative in Palmtree Panic Act 1 and Metallic Madness. There's also what appears to be a first attempt to port over the original m68k code that's bugged, and is made redundant by what's next.

    The original AND masks are used for calculating the position indices, but are effectively overridden. The PC version doesn't not actually have the double allocated map memory and sticks to the Sonic 1 limitations. What was originally a Tidal Tempest only limitation is effectively put into use in all levels with the "iYwork &= 7" line, and it also fixes the horizontal out of bounds issue by adding "iWork &= 0x3F".

    Finally, it... does the negative position check again for Palmtree Panic Act 1, which is completely redundant considering that the calculated indices won't even be negative.
     
  19. faith

    faith Well-Known Member Member

    Joined:
    Aug 26, 2013
    Messages:
    1,209
    I probably mentioned this elsewhere and forgot, but I found this darkly amusing. According to the Gems Collection symbols, the official names of the functions that removes animals and enemies according to the type of future are "friend_suicide" and "enemy_suicide" respectively, and they are found in a file called "SUICIDE.C".

    [​IMG]

    Of course, I understand that these were written by Japanese developers, and they most likely didn't understand the implications, but holy shit, lol. This is basically Sonic CD's "Genocide City" moment in a way.

    Speaking of which, when a badnik is removed when a good future is achieved, they're supposed to play the explosion sound effect if they were on screen. However, the check for if they're on screen is called AFTER they are destroyed, so the on screen flag is cleared, and the sound is never played.

    [​IMG]

    As a side note, the PC/Gems Collection version of Sonic CD moves the A+B+C button check for when your paused to restart the level outside of the pause check, which would make it possible to press A+B+C to restart the level even when not paused...

    [​IMG]

    However, the PC and GameCube versions only allocate 1 button for jumping, and the PS2 version only allocates A to the cross button and B to the rest of the face buttons, so it's not possible to trigger anyways.

    The music pause function in the PC version is also bugged. It uses an incorrect operator when checking if the flag is already set, and also gets the check for if the invincibility or speed shoes music is playing in the past backwards, as "sub_sync(0x90)" is for pausing PCM music.

    [​IMG]

    The only reason why music pausing even works is because the pause handler manually calls "CDPause()" itself. "sub_sync" doesn't even handle command IDs 0x90 and 0xD5 in the port!

    The music unpause function has the first check correct, but the invincibility/speed shoes music check when in the past is still wrong.

    [​IMG]

    And again, IDs 0x91 and 0xD6 aren't even handled in sub_sync, so this is all redundant anyways.
     
    Last edited: Dec 27, 2022
  20. Clownacy

    Clownacy Retired Staff lolololo Member

    Joined:
    Aug 15, 2014
    Messages:
    962
    Ring Sound Bug
    Here's a relevant cross-post of an old bug explanation of mine:

    Every time the player collects a ring, the ring sound plays on a different speaker, alternating between left and right. Sonic CD's SFX sound driver does this in a boneheaded way. The way that it works in Sonic 1/2/3 is that there are two separate ring sounds, and the driver decides which one is played when a ring is collected. Both versions of the ring sound are different: each one plays on a different speaker (of course), runs on a different FM channel, and one of them even plays for slightly longer (except for in Sonic 3).

    If the two sounds were to use the same FM channel, then you could only ever hear one of them at a time. This means that if you were to grab two rings in quick succession, then instead of hearing the ring sound in both speakers, you'll only hear it in one.

    This is what Sonic CD screws up, but the reason behind it is the real dumb part:

    Instead of the driver alternating the ring sounds, Sonic CD makes the sounds themselves do it. Now, whenever a ring sound plays, it always tries to play the one that uses the rightmost speaker. The right sound then uses a custom command which I've named 'smpsDoRing':
    Code:
    ; FM4 Data
    RingR_FM4:
       smpsDoRing
       smpsSetvoice        $00
       smpsPan             panRight, $00
       dc.b   nE5, $05, nG5, $05, nC6, $1B
       smpsStop
    What this flag does is force the game to start playing the left ring sound every other time the command is ran. This has the effect of making the left ring sound play every other time a ring sound plays.

    This should work, right? Nope.

    Because every attempt to play the ring sound will first cause the right ring sound to play, and it's only when the right right sound starts to play that it tells the driver to play the left ring sound instead, the developers needed to make the right ring sound stop playing so that the right ring sound doesn't play every time the left ring sound plays. To do this, they made both ring sounds play on the same FM channel. This causes the two sounds to always cancel each other every time they play, meaning that it's impossible to hear both speakers play the ring sound when collecting two rings at once.

    This issue is at its worst when the player quickly collects a line of rings, as each ring sound will be abruptly cut off by the next one, meaning that the only ring sound that you get to hear finish is the last one. This can be heard as early as the start of the first level. In Sonic 1/2/3, a ring sound can only be cut off by itself, meaning that ring sounds get cut off every two rings that the player collects, not one.

    Sound Priority Bug
    Sonic CD's sound driver has a second bug: SMPS has a sound priority system, which prevents lower-priority sounds from interrupting higher-priority sounds. This is useful, for example, for preventing the ring-pickup sound from stopping and interrupting the much-louder ring-loss sound. The way this is done is with a big array, where each value in the array belongs to a certain sound ID. So, for instance, sound 0x90 would get the first value in the array, 0x91 would get the second value, etc. The problem is that Sonic CD only has enough entries in this table for sound IDs 0x90 through to 0xDF, despite using higher sound IDs for the 'stop all sounds' command. This causes the driver to read a nonsensical priority value from past the end of the priority array every time the stop-all-sounds command is given. If the nonsensical priority value is lower than the value priority of the currently-playing sound, then the stop-all-sounds command will not take effect.

    This might sound like a non-issue: after all, what are the chances of this having a noticeable effect? When does the game even submit a stop-all-sounds command anyway?

    During time travel - that's when. And of course this bug happens to be an important part of it.

    Yes, you read that right: the time travel scene depends on this bug in order to work properly. As the screen fades to white, the game submits a stop-all-sounds command, but because the time travel sound's priority is higher than the command's broken priority, it doesn't actually do anything. If this bug were to be fixed, then the time travel sound would abruptly cut off long before it normally does.

    I discovered both of these bugs while making my replacement Sonic CD SFX sound driver. It's based on Sonic & Knuckles' driver, which lacks this bug, causing the time travel sound to end earlier as described above. Replacing this bug exactly the same as it is in Sonic CD's driver does not work, as the nonsensical priority data after the end of the priority array is actually the start of sound 0x90's track data (the skidding sound). Because the data of a sound varies from driver to driver, this bug simply cannot be accurately recreated in a different driver by making the priority table too short.

    So how did I eventually recreate this bug in my replacement driver? I copied the raw data of sound 0x90 as it was in the original driver, and added it to the end of priority table. I'd essentially converted a partial copy of sound 0x90 into priority data. With this, the behaviour caused by the bug is retained without the actual bug existing.

    If you're wondering why I was creating a replacement for Sonic CD's SFX sound driver, it was so I could restore the original jump sound. You can read more about it here.
     
    Last edited: Dec 29, 2022
    Pacca, ProjectFM, JGamer2151 and 2 others like this.