don't click here

Some changes and fixes for Sonic 2

Discussion in 'Engineering & Reverse Engineering' started by Esrael, Jun 7, 2012.

  1. Make sense, although I do have another scrolling bug; for whatever reason, going backwards in certain levels (Death Egg or even Hidden Palace if you restored it) causes parts of the background to disappear. Even stranger, it doesn't happen to HPZ in the prototypes.
  2. Clownacy


    Tech Member
    It's been a long time since I looked into that, but I believe that the solution was to make the background repeat all the way to the right end of the layout file. Levels like Mystic Cave Zone do this, presumably for its single-player variant which uses dynamic background loading. If I remember right, the Sonic 1 level layout format avoids the need for this for some reason. I'll look into this issue when I get a chance.

    EDIT: Oooooh, I get it: Sonic 1's layout loading code was modified in Sonic 2's prototypes to specifically repeat the layouts to fill the full width of the level. This means that the game automatically repeated the game's backgrounds, and whatever tool Alex Field used to convert Hidden Palace Zone's layout failed to recreate this behaviour, likely because it was emulating Sonic 1's behaviour instead.
    Last edited: Apr 25, 2022
  3. I do remember seeing that (mainly when converting HPZ to the final format; trying to extend its background is a nightmare); there's also one last bug, the worst of them all.


    Sonic's arm not appearing fast enough on the title screen.
  4. Clownacy


    Tech Member
    That appears to be a weird prototype leftover in Sonic's animation script: at the end of 'byte_1368E', there's a reference to sprite 8, which is just an incomplete duplicate of sprite $12, which Sonic is set to when his arm spawns. Because this duplicate sprite is here, it creates an extra frame before Sonic's arm appears where he's using the sprite that should be used after the arms appears. Not only that, but because this earlier frame is incomplete, Sonic ends up missing *both* of his arms during this frame.

    EDIT: Speaking of buggy sprites: sprite 5 appears to be one pixel too high, causing Sonic's lower half to be visibly cut-off for a few frames.
    Last edited: Apr 22, 2022
  5. Not sure what they were going to do there, unless they were going to make a sprite with both the body AND the face.

    Also, I might've already mentioned this, but the Obj0E never sets Tails' priority during the intro, meaning a few pixels of his hair (fur?) gets covered by Sonic (yet the priority IS set when skipping it...).
  6. I used LevelConverter; should probably tell MainMemory to fix it.
  7. Clownacy


    Tech Member
    Here's a blink-and-you-miss-it bug: the Egg Prison sprite is incomplete: sprites 1 and 2 have their 'total sprite pieces' value set too low, causing part of the sprite to not appear:
    To fix this, hex-edit the file 'mappings/sprite/obj3E.bin' so that bytes $47 and $89 are 08 instead of 07.

    Issues like this suggest that the mappings were written in assembly (likely macros): they weren't machine-produced binary files like they are in the disassemblies. Other indicators of this are Obj63's mappings, whose frames aren't sorted from first to last, and the unfinished mappings of Marble Zone's sideway spike pistons in Sonic 1, which have a similar bug to this one.
    Last edited: May 3, 2022
    • Informative Informative x 8
    • Like Like x 3
    • List
  8. Brainulator


    Regular garden-variety member Member
    They were indeed written in assembly, whether by hand or by a program; the Sonic 2 Nick Arcade prototype's symbol lists contain names for both the mapping table (suffixed with "pat", as in "pattern") and the mappings themselves (suffixed with "sp" and then the frame number). I've considered requesting that the Sonic 2 disassembly shift to ASM mappings for this reason.
    • Informative Informative x 3
    • List
  9. MainMemory


    Kate the Wolf Tech Member
    I wouldn't really consider this a bug? The program is just converting the data that is in the file. I guess I could add a hack for LevelConverter with S2NA layouts.
  10. DeltaW


    Originally a Wooloo Member
    Quick question: has anyone managed to figure out the issue caused by this bug and a fix for it? When Sonic spin-dashes off a see-saw in Hill Top Zone, the spindash isn't cleared, and he spin-dashes when on-ground.


    So, logically, you should be able to clear the spindash flag before Sonic springs up. First, I tried finding a proper fix here, which did not work. Then, I tried reproducing the same bug in Sonic 1 and tinkered around with its see-saw object to see if I could fit it, but no dice.
  11. nineko


    I am the Holy Cat Tech Member
    The best part about that bug is that something similar still happens in Ice Cap Zone, so Sega never fixed it.

    I've seen many hacks (of both Sonic 1 and Sonic 2) which fix it, though. Puto even fixed it in my hack.
  12. DeltaW


    Originally a Wooloo Member
    Indeed quite many Sonic 1 hacks fix this issue. But the way it's handled is by clearing the spindash in mid-air, even though Sonic 2 retains your spindash if you perform it while a platform collapses. So it feels like a feature rather than a bug since you don't lose the spindash, and the way the SCHG guide documents it fixes the see-saw bug but doesn't fix the bug as mentioned earlier/feature Sonic 2 has.
  13. Clownacy


    Tech Member
    I fixed it in my Fine2uned hack by adding 'clr.b spindash_flag(a2)' to the start of 'Obj14_LaunchCharacter'.
    • Like Like x 1
    • Informative Informative x 1
    • List
  14. Hitaxas


    Retro 80's themed Twitch streamer ( on hiatus) Member
    Here is a change to something that some may consider a bug, but is actually more like an oversight.

    When Sonic jumps and lands from the jump while holding down, for one frame, he is in his walking animation. This is something that also happens in Sonic 1, Sonic 3 and Sonic & Knuckles.

    Edit: This can and should be applied to other characters in your hack if you apply this to Sonic.

    As a visual example:

    This is quite easy to fix/change.

    Navigate to

    Code (Text):
    1. Sonic_ResetOnFloor:

    As you can see, in all of the code for Sonic to reset on the floor, he is always set to walk.
    The code for him to roll on the ground is only initiated after this reset, and therefore he is briefly put into the walk animation.

    To resolve this, we need to add a new supplementary subroutine.

    Under the aforementioned label, under the branch to

    Code (Text):
    1. Sonic_ResetOnFloor_Part3

    And before the line that sets his walk animation

    add this directional input check

    Code (Text):
    1.     btst    #1,(Ctrl_1_Held_Logical).w            ; is down being pressed?
    2.     bne.w    Sonic_ResetOnFloor_ContRolling        ; if yes, branch    

    After this, head to the bottom of

    Code (Text):
    1. Sonic_ResetOnFloor_Part3

    And add

    Code (Text):
    1. bra.w    return_1B11E

    Next, we need to set up a roll status check for this subroutine.

    Remove this line from Sonic_ResetOnFloor_Part3

    Code (Text):
    2.     move.b  #0,flip_turned(a0)

    Then, after the line that moves 0 to flips_remaining, and before the line that moves 0 to Sonic_Look_Delay_Counter, paste this small bit of code

    Code (Text):
    2.     btst    #2,status(a0) ; is status set to rolling?
    3.     beq.s   return_1B11E
    4.     move.b  #0,flip_turned(a0)

    Sonic_ResetOnFloor_Part3 should now look like this

    Code (Text):
    2. Sonic_ResetOnFloor_Part3:
    3.     bclr    #1,status(a0)   ; clear in air status
    4.     bclr    #5,status(a0)   ; clear pushing status
    5.     bclr    #4,status(a0)   ; clear rolljump status
    6.     move.b  #0,jumping(a0)  ; clear jumping flag
    7.     move.w  #0,(Chain_Bonus_counter).w
    8.     move.b  #0,flip_angle(a0)
    9.     move.b  #0,flips_remaining(a0)
    10.     btst    #2,status(a0) ; is status set to rolling?
    11.     beq.s   return_1B11E
    12.     move.b  #0,flip_turned(a0)
    13.     move.w  #0,(Sonic_Look_delay_counter).w
    14.     cmpi.b  #$14,anim(a0)
    15.     bne.w   return_1B11E
    16.     move.b  #0,anim(a0)
    17.     bra.w   return_1B11E

    Now, after the end of Sonic_ResetOnFloor_Part3,
    and before the label for return_1B11E, add this chunk of code

    Code (Text):
    2. ;=====================================================================================================================
    3. ; (Hitaxas) Fix for awful uncurling frame when a character lands on the ground from a jump
    4. ; or if they walked off an object or edge of a floor while holding down.
    5. ; this "bug" is present in S1-S3K.
    6. ;=====================================================================================================================
    7. Sonic_ResetOnFloor_ContRolling:
    8.         jsr     Obj01_DoRoll
    9.         bra.w   Sonic_ResetOnFloor_Part3
    The final results should look like so:


    Edit: Cleaned up code to remove redundancy, and fixed a bug where the roll SFX would play even if landing while holding down from a momentumless jump.

    Edit 2: I am going to completely rewrite this whole post soonish. I have realized there are a lot of optimizations that could be made.
    Last edited: Aug 2, 2022
    • Like Like x 2
    • Useful Useful x 1
    • List
  15. flamewing


    Emerald Hunter Tech Member
    Sonic Classic Heroes; Sonic 2 Special Stage Editor; Sonic 3&K Heroes (on hold)
    Here is another 1-frame crash when killing a boss: in the HTZ boss, if you deal the eighth hit on Eggman right as the flamethrower finishes and he is about to hover for a bit, the game will crash. In order to fix it, find this:

    Code (Text):
    1. loc_2FDAA:
    2.     bsr.w   loc_300A4
    3.     bsr.w   loc_2FEDE
    4.     lea (Ani_obj52).l,a1
    5.     bsr.w   AnimateBoss
    6.     jmpto   (DisplaySprite).l, JmpTo36_DisplaySprite
    and change it to this:

    Code (Text):
    1. loc_2FDAA:
    2.     bsr.w   loc_2FEDE
    3.     lea (Ani_obj52).l,a1
    4.     bsr.w   AnimateBoss
    5.     bsr.w   loc_300A4
    6.     jmpto   (DisplaySprite).l, JmpTo36_DisplaySprite
    The issue here is that loc_300A4 checks for hits and sets boss_routine to 8 if you beat the boss. But AnimateBoss can also increment boss_routine by 2 when the animation of the flamethrower finishes. This causes Obj52_Mobile to select an invalid entry on off_2FD0E to branch to, which crashes.
    • Informative Informative x 3
    • Like Like x 1
    • List
  16. Technodevster


    Sonic 4 SNES, Genesis/32X and the Sonic 1 Proto disassembly
    Have any of you noticed how the Game/Time Over text flashes for 1 frame when it stops moving? The cause is this. In loc_13FCC the game checks if the text has reached its destination but the code that it branches to when the text stops moving is missing a call to DisplaySprite, resulting in the text disappearing for 1 frame and then reappearing after the routine counter is incremented. The fix is simple. Go to loc_13FE2 and replace the rts below there with this:

    Code (Text):
    1.     bra.w   DisplaySprite
    This bug is in Sonic 1 and S3K as well.
  17. OrionNavattan


    Tech Member
    Ported it to Tails, and eliminated some redundancy while I was at it.

    Code (Text):
    2. Tails_ResetOnFloor:
    3.    tst.b   pinball_mode(a0)
    4.    bne.s   Tails_ResetOnFloor_Part3
    5.    btst    #1,(Ctrl_1_Held_Logical).w            ; is down being pressed?
    6.    bne.w   Tails_ResetOnFloor_ContRolling        ; if yes, branch    
    7.    move.b   #AniIDSonAni_Walk,anim(a0)
    9. ; loc_1CB5C:
    10. Tails_ResetOnFloor_Part2:
    11.    btst   #2,status(a0)
    12.    beq.s   Tails_ResetOnFloor_Part3
    13.    bclr   #2,status(a0)
    14.    move.b   #$F,y_radius(a0) ; this slightly increases Tails' collision height to standing
    15.    move.b   #9,x_radius(a0)
    16.    move.b   #AniIDSonAni_Walk,anim(a0)   ; use running/walking/standing animation
    17.    subq.w   #1,y_pos(a0)   ; move Tails up 1 pixel so the increased height doesn't push him slightly into the ground
    18. ; loc_1CB80:
    20. Tails_ResetOnFloor_Part3:
    21.    bclr   #1,status(a0)
    23. Tails_ResetOnFloor_Part4:  
    24.    bclr   #5,status(a0)
    25.    bclr   #4,status(a0)
    26.    move.b   #0,jumping(a0)
    27.    move.w   #0,(Chain_Bonus_counter).w
    28.    move.b   #0,flip_angle(a0)
    29.    move.b   #0,flips_remaining(a0)
    30.    move.w   #0,(Tails_Look_delay_counter).w
    31.    if fixBugs
    32.    btst   #2,status(a0) ; is status set to rolling?
    33.    beq.s   return_1CBC4  
    34.    move.b   #0,flip_turned(a0)
    35.    endif
    36.    cmpi.b   #AniIDSonAni_Hang2,anim(a0)
    37.    bne.s   return_1CBC4
    38.    move.b   #AniIDSonAni_Walk,anim(a0)
    40. return_1CBC4:
    41.    rts
    42. ; End of subroutine Tails_ResetOnFloor
    45. Tails_ResetOnFloor_ContRolling:
    46.    move.b  #AniIDSonAni_Roll,anim(a0)      ; play rolling animation
    47.    btst    #2,status(a0)                   ; was status set to rolling?
    48.    beq.s   Tails_ResetOnFloor_Part3        ; if it was, branch
    49.    bset    #2,status(a0)                   ; otherwise, set roll status
    50.    move.b  #$E,y_radius(a0)                ; adjust character's y_radius
    51.    move.b  #7,x_radius(a0)                 ; same for x_radius
    52.    addq.w  #5,$C(a0)                       ; move character 5 pixels down, so they aren't floating
    53.    move.w  #SndID_Roll,d0
    54.    jsr     (PlaySound).l                   ; play rolling sound - required because this is not part of the normal roll code                      
    55.    bra.s   Tails_ResetOnFloor_Part4
    It affects every character object, and will need to be ported to each of them.
  18. Hitaxas


    Retro 80's themed Twitch streamer ( on hiatus) Member
    I've done so as well, I should have mentioned it could be applied to Tails as well. I implemented this for both Knuckles and Blaze as well in my hack.

    There is one more thing that I believe I had overlooked prior. You might notice if you jump straight up without any horizontal momentum, hold down and land... The roll SFX will play even though the character is now ducking.

    You can easily resolve this by checking for if the character is actually moving using x_vel.

    Code (Text):
    1. Tails_ResetOnFloor_ContRolling:
    2.     move.b  #AniIDSonAni_Roll,anim(a0)      ; play rolling animation
    3.     btst       #2,status(a0)                            ; was status set to rolling?
    4.     beq.s     Tails_ResetOnFloor_Part3            ; if it was, branch
    5.     bset       #2,status(a0)                            ; otherwise, set roll status
    6.     move.b  #$E,y_radius(a0)                       ; adjust character's y_radius
    7.     move.b  #7,x_radius(a0)                         ; same for x_radius
    8.     addq.w  #5,y_pos(a0)                             ; move character 5 pixels down, so they aren't floating
    9.     tst.w      x_vel(a0)                                   ; is Tails moving?
    10.     beq.w    Tails_ResetOnFloorNotContRolling ; if not, branch
    11.     move.w  #SndID_Roll,d0
    12.     jsr          (PlaySound).l                             ; play rolling sound - required because this is not part of the normal roll code                  
    14. Tails_ResetOnFloorNotContRolling:    
    15.     bra.s   Tails_ResetOnFloor_Part3
    I also noticed one slight issue with your edit, and I have adjusted this to fix that issue.
    By branching to Tails_ResetOnFloor_Part4 at the end of this subroutine, you are skipping the clearing of the inair status bit. Doing this will cause the character to remain in the roll animation if you are holding down while landing from a momentumless jump.

    I have edited the initial post to reflect the cleanup. Everything should work correctly.
    Last edited: Jul 25, 2022
  19. OrionNavattan


    Tech Member
    There is one small problem with this.
    By jumping straight to DisplaySprite, the path swappers will not be able to delete themselves (which is done in MarkObjGone3), leading to a memory leak as they fill up the Dynamic Object RAM. Instead of DisplaySprite, it should be a jump to MarkObjGone (which is exactly what the Simon Wai prototype did).

    If you’ve spent any length of time accessing debug mode in Sonic 2, you may well have noticed a small (and in my case, annoying) inconsistency between the sound tests on the Options Screen and the Level Select, specifically, the behavior of the A button when the current index is $71-7F. On the Level Select, it index wraps (e.g., $7A to $0A), but on the options screen, it resets to $00.

    So what exactly is going on? Let's look at the code for handling the A Button on each screen.

    Code (Text):
    2. OptionScreen_Controls:
    3. ...
    4.    btst   #button_A,d0
    5.    beq.s   +
    6.    addi.b   #$10,d2
    7. ; There is something missing here...
    8.    cmp.b   d3,d2
    9.    bls.s   +
    10.    moveq   #0,d2
    Code (Text):
    2. LevSelControls_CheckLR:
    3.  ...
    4.    btst   #button_A,d1
    5.    beq.s   +
    6.    addi.b   #$10,d0
    7.    andi.b   #$7F,d0  ; It's this!
    Both add $10 to the index, but the level select ANDs the new value with 7F, changing $80-8F to $00-0F. The options menu on the other hand does not, and the index will subsequently be reset to $00 if it is greater than $7F (a check that prevents the options from going to invalid values).

    Fixing this is simple; just AND the sound index in the same way the level select does:

    Code (Text):
    2. OptionScreen_Controls:
    3. ...
    4.    btst   #button_A,d0
    5.    beq.s   +
    6.    addi.b   #$10,d2
    7.    andi.b   #$7F,d2
    8.    cmp.b   d3,d2
    9.    bls.s   +
    10.    moveq   #0,d2
    (Do not touch the cmp, bls, or moveq; they are still required to prevent the other options from going to invalid values.)
    Last edited by a moderator: Jul 29, 2022
    • Like Like x 1
    • Informative Informative x 1
    • List
  20. Hitaxas


    Retro 80's themed Twitch streamer ( on hiatus) Member
    Previously, I had posted some design choice changes to modify Sonic's ResetOnFloor function to prevent him from uncurling when landing from a jump and holding down while moving.

    I have taken everything a step further here, optimized the code and added a bit more to the changes.

    Now Sonic will no longer uncurl in this situation, but will also no longer display a single walking frame if landing from a standstill. In addition to this, Sonic can now instantly duck or look up if holding up or down when landing from said standstill jump. This allows the player to peelout or spindash the moment they land on the floor.

    In all, it resolves these issues that have been pointed out in these tweets:

    If you want to apply the changes to your own Sonic 2 hack, here is the process. I was going to make this a bit more in-depth... but I figure this is more or less sufficient enough.


    Go to

    Code (Text):
    1. Sonic_ResetOnFloor


    Code (Text):
    1. move.b    #AniIDSonAni_Walk,anim(a0)
    with this chunk:

    Code (Text):
    2. ;=====================================================================================================================
    3. ; if up is being held while landing from a standstill jump, play look up animation.
    4. ; allows for activating peelout immediately
    5. ; however, to do so properly requires a frame perfect input... (enjoy, speedrunners!)
    6. ;=====================================================================================================================
    7.         btst    #button_up,(Ctrl_1_Held_Logical).w      ; is up being pressed?
    8.         beq.s   .CheckIfDucking                         ; if not, branch
    9.         move.b  #7,anim(a0)                             ; use look up animation
    10.         bra.w   Sonic_ResetOnFloor_Part2
    11. ;=====================================================================================================================
    12. ; if down is being held while landing from a standstill jump, play ducking animation.
    13. ; allows for activating spindash immediately
    14. ; however, to do so properly requires a frame perfect input... (enjoy, speedrunners!)
    15. ;=====================================================================================================================
    16.         .CheckIfDucking:
    17.                 btst    #button_down,(Ctrl_1_Held_Logical).w    ; is down being pressed?
    18.                 beq.s   .ReturnToWalkRunDash                    ; if not, branch
    19.                 tst.w   inertia(a0)                             ; is character moving horizontally?
    20.                 bne.s   .ContinueRolling                        ; if so, branch
    21.                 tst.w   y_vel(a0)                               ; is character moving vertically?
    22.                 bne.s   .ContinueRolling                        ; if so, branch
    23.                 bclr    #2,status(a0)                           ; clear rolling status
    24.                 move.b  #$13,y_radius(a0)                       ; this increases Sonic's collision height to standing
    25.                 move.b  #9,x_radius(a0)                         ; adjust Sonic's collision width to standing
    26.                 jsr     Sonic_Duck
    27.                 bra.w   Adjust_Y_Pos
    29.         .ContinueRolling:
    30.                 jsr     Obj01_DoRoll                            ; make character roll
    31.                 bra.w   Sonic_ResetOnFloor_Part3                ; still need to clear some flags, etc.
    33.         .ReturnToWalkRunDash:
    34.                 move.b  #AniIDSonAni_Walk,anim(a0)
    Now proceed to

    Code (Text):
    1. Sonic_ResetOnFloor_Part2
    And replace everything up until the label for

    Code (Text):
    1. Sonic_ResetOnFloor_Part3

    with this chunk:

    Code (Text):
    2. ;=====================================================================================================================
    3. ; this code is called by the code that handles player standing on objects, like platforms or bridges
    4. ; some routines outside of Tails' code can call Sonic_ResetOnFloor_Part2
    5. ; when they mean to call Tails_ResetOnFloor_Part2, so fix that here
    6. ;=====================================================================================================================
    7. ; loc_1B0AC:
    8. Sonic_ResetOnFloor_Part2:
    9.                 cmpi.l  #Obj_Tails,id(a0)                       ; is this object ID Tails?
    10.                 beq.w   Tails_ResetOnFloor_Part2                ; if so, branch to the Tails version of this code
    11.                 btst    #2,status(a0)                           ; is rolling status set?
    12.                 beq.w   Sonic_ResetOnFloor_Part3                ; if so, branch
    13.                 bclr    #2,status(a0)                           ; clear rolling status
    14.                 move.b  #$13,y_radius(a0)                       ; this increases Sonic's collision height to standing
    15.                 move.b  #9,x_radius(a0)                         ; adjust Sonic's collision width to standing
    16.                 tst.w   x_vel(a0)                               ; is character moving?
    17.                 bne.s   .ReturnToWalkRunDash                    ; if so, branch
    19.         .ReturnToIdle:
    20.                 move.b  #$5,anim(a0)                            ; use standing/idle animation
    21.                 bra.s   Adjust_Y_Pos
    23.         .ReturnToWalkRunDash:
    24.                 move.b  #AniIDSonAni_Walk,anim,anim(a0)         ; use running/walking/standing animation
    26.         Adjust_Y_Pos:
    27.                 subq.w  #5,y_pos(a0)                            ; move Sonic up 5 pixels so the increased height doesn't push him into the ground
    Finally, you need to replace

    Code (Text):
    1. Sonic_ResetOnFloor_Part3

    with this chunk here:

    Code (Text):
    2. ;=====================================================================================================================
    3. ; clear flags, and signify a true reset on floor
    4. ;=====================================================================================================================
    5. ; loc_1B0DA:
    6. Sonic_ResetOnFloor_Part3:
    7.         bclr    #1,status(a0)                           ; clear in air status
    8.         bclr    #5,status(a0)                           ; clear pushing status
    9.         bclr    #4,status(a0)                           ; clear rolljump status
    10.         move.b  #0,jumping(a0)                          ; clear jumping flag
    11.         move.w  #0,(Chain_Bonus_counter).w
    12.         move.b  #0,flip_angle(a0)
    13.         move.b  #0,flips_remaining(a0)
    14. ;=====================================================================================================================
    15. ; if rolling status was already set, we want to branch away from the end of this code
    16. ; this prevents an issue where holding down would cause the character to duck rather than roll in most cases
    17. ;=====================================================================================================================
    18.         btst    #2,status(a0)                           ; is status set to rolling?
    19.         beq.s   return_1B11E
    20. ;=====================================================================================================================
    21. ; clear a few more flags
    22. ;=====================================================================================================================
    23.         move.b  #0,flip_turned(a0)
    24.         move.w  #0,(Sonic_Look_delay_counter).w
    25.         cmpi.b  #$14,anim(a0)
    26.         bne.s   return_1B11E
    27.         move.b  #AniIDSonAni_Walk,anim(a0)
    28.         bra.s   return_1B11E
    29. ;=====================================================================================================================
    30. ; The end
    31. ;=====================================================================================================================

    If there are more optimizations that could be done to this, I would love to know about them. I feel like I did a decent job compared to the previous post.

    Also; I have applied these changes to Sonic, Tails and Knuckles in a personal fork of Sonic 2 Community's Cut. Which can be viewed here:

    Additionally, I have applied some more bug fixes from this thread to it. Along with some other things, and will update it once in a while to fix more bugs, or add more features. So use it as you wish, or contribute to the bug fixes.

    Edit: Changed some .w branches to .s

    Edit 2: Forgot to account for vertical movement in the duck/roll code, which caused a rare case where sometimes the character would duck when landing on a slope while holding down, instead of rolling like they should.
    Last edited: Jan 3, 2023