don't click here

How to fix speed issues in Sonic 2

Discussion in 'Engineering & Reverse Engineering' started by redhotsonic, May 17, 2013.

  1. redhotsonic

    redhotsonic

    Also known as RHS Tech Member
    1,587
    10
    18
    United Kingdom
    YouTuber
    Hello, I've been meaning to make this guide for ages but because of life reasons, I just didn't have the time. But recently, I have found more time and decided to get on with this.


    In Sonic 2, there are multiple speed issue bugs with Sonic and Tails, but for now, let's look at it with Sonic's point of view. It's uncommon, but not exactly rare, and when Super Sonic is enabled, it becomes more apparent. But sometimes, Sonic's top speed, acceleration and deceleration can be set wrong. The most common place for the bugs to occur is around this area of ARZ1 due to where the speed shoes are placed.

    [​IMG]


    Like said, it can be uncommon, but with people's hacks putting speedshoes underwater and other places, the bugs can happen more often.



    There are several speed issues:

    • If Super Sonic and you hit speedshoes, his top speed goes a little bit higher which is fine, but his acceleration and deceleration decreases. Technically, it's applying normal Sonic's speedshoes speed.
    • If Normal Sonic and you get speedshoes, if you go underwater, speedshoes's speed are lost. Even if you get out of the water, Sonic's speed is normal.
    • If Super Sonic and you get speedshoes, if you go underwater, speedshoes's speed are lost. Even if you get out of the water, Super Sonic's speed is normal.
    • If underwater and you get speedshoes, Sonic's "out-of-water" speedshoes speed apply, making you go way too fast underwater. Getting out of water or even back in and the speedshoes speeds are lost, even with speedshoes applied.
    • If Super and underwater and you get speedshoes, Normal Sonic's "out-of-water" speedshoes speed apply, making you go way too fast underwater. Getting out of water or even back in and the speedshoes speeds are lost, even with speedshoes applied, but applies Super Sonic's speed settings.
    • If underwater and you get speedshoes, bug above applies but if you never leave the water, once speedshoes wear off, Sonic's normal speed applies, even though you're still underwater, making you still too fast.
    • If Super and underwater and you get speedshoes, bug above the above applies but if you never leave the water, once speedshoes wear off, Super Sonic's speed applies, even though you're still underwater, making you still too fast.
    • If underwater and then you transform into Super Sonic, Super Sonic's "out-of-water" speed applies, making you way too fast.
    • If you have speedshoes and then you transform into Super Sonic, Super Sonic's speed applies, making you lose speedshoes speed, making you that bit slower than you should be.
    • If underwater when you have speedshoes and then you transform into Super Sonic, Super Sonic's "out-of-water" speed applies, and you lose your speedshoes speed.
    • If underwater when you have speedshoes and you're Super Sonic and you transform back to normal Sonic, normal Sonic's "underwater" speed applies, making you lose your speedshoes.


    A lot, eh? The main issue is that the game never checks to see if you have the speedshoes applied or not. Or when becoming Super and etc, it's not checking if underwater, etc, etc. It would have to do a few checks itself to get it right and I suspect Sonic Team didn't have time to do it as it wasn't particularly important. But we're going to fix these ourselves, and it's easier than you think.

    Please note: I am using Xenowhirls disassembly, although this shouldn't be hard to follow if you're using the HG disassembly.




    Step 1 - Back-up your disassembly

    Like all guides you're about to follow, make a back-up before making any changes! I am also not held responsible for any troubles this may bring to your hack.




    Step 2 - Creating new speeds

    First of all, we need to make some new speed settings. Here are some of Sonic's speeds settings listed in Sonic 2 currently:

    Code (ASM):
    1. ; NORMAL
    2.     move.w  #$600,(Sonic_top_speed).w
    3.     move.w  #$C,(Sonic_acceleration).w
    4.     move.w  #$80,(Sonic_deceleration).w
    5.  
    6. ; NORMAL SPEED SHOES
    7.     move.w  #$C00,(Sonic_top_speed).w
    8.     move.w  #$18,(Sonic_acceleration).w
    9.     move.w  #$80,(Sonic_deceleration).w
    10.    
    11. ; SUPER SONIC
    12.     move.w  #$A00,(Sonic_top_speed).w
    13.     move.w  #$30,(Sonic_acceleration).w
    14.     move.w  #$100,(Sonic_deceleration).w
    15.    
    16. ; NORMAL UNDERWATER
    17.     move.w  #$300,(Sonic_top_speed).w
    18.     move.w  #6,(Sonic_acceleration).w
    19.     move.w  #$40,(Sonic_deceleration).w
    20.  
    21. ; SUPER SONIC UNDERWATER
    22.     move.w  #$500,(Sonic_top_speed).w
    23.     move.w  #$18,(Sonic_acceleration).w
    24.     move.w  #$80,(Sonic_deceleration).w

    As you can see, the game doesn't have speed settings for "Normal underwater with speedshoes", "Super Sonic with speedshoes" or "Super Sonic underwater with speedshoes". No wonder why all these bugs exist. Now, we have to come up with these speeds, which I have already done with some maths involved. To get "Normal underwater with speedshoes", I thought, how does normal Sonic get speedshoes speed? Well, everything is doubled except his deceleration, which stays the same. So I did the same and got this new speed setting.

    Code (ASM):
    1. ; NORMAL UNDERWATER SPEED SHOES
    2.     move.w  #$600,(Sonic_top_speed).w
    3.     move.w  #$C,(Sonic_acceleration).w
    4.     move.w  #$40,(Sonic_deceleration).w

    It's almost identical to Sonic's normal speed, except his deceleration is slower. To get "Super Sonic with speedshoes", I left the top speed setting it already does alone, but made it not decrease Super Sonic's acceleration and deceleration, making him that tiny bit faster:

    Code (ASM):
    1. ; SUPER SONIC SPEED SHOES
    2.     move.w  #$C00,(Sonic_top_speed).w
    3.     move.w  #$30,(Sonic_acceleration).w
    4.     move.w  #$100,(Sonic_deceleration).w

    To get "Super Sonic underwater with speedshoes", I followed the same method on how "Normal" does it, and got this:

    Code (ASM):
    1. ; SUPER SONIC UNDERWATER SPEED SHOES
    2.     move.w  #$A00,(Sonic_top_speed).w
    3.     move.w  #$30,(Sonic_acceleration).w
    4.     move.w  #$80,(Sonic_deceleration).w

    You do not need to add any of the ASM above into your hack as yet.




    Step 3 - Creating a new table

    Now we've got all our speeds, we need to convert it into a table. Which I have already done on your behalf:

    Code (ASM):
    1. ; ===========================================================================
    2. ; ----------------------------------------------------------------------------
    3. ; Speed Settings Array
    4.  
    5. ; This array defines what speeds the character should be set to
    6. ; ----------------------------------------------------------------------------
    7. ;       blank   top_speed   acceleration    deceleration    ; # ; Comment
    8. Speedsettings:
    9.     dc.w    $0, $600,       $C,     $80     ; $00   ; Normal
    10.     dc.w    $0, $C00,       $18,        $80     ; $08   ; Normal Speedshoes
    11.     dc.w    $0, $300,       $6,     $40     ; $16   ; Normal Underwater
    12.     dc.w    $0, $600,       $C,     $40     ; $24   ; Normal Underwater Speedshoes
    13.     dc.w    $0, $A00,       $30,        $100        ; $32   ; Super
    14.     dc.w    $0, $C00,       $30,        $100        ; $40   ; Super Speedshoes
    15.     dc.w    $0, $500,       $18,        $80     ; $48   ; Super Underwater
    16.     dc.w    $0, $A00,       $30,        $80     ; $56   ; Super Underwater Speedshoes
    17. ; ===========================================================================

    This will be our new table. We'll insert this into the ASM file shortly. First, let's go onto the next step.




    Step 4 - Creating a new subroutine to apply speeds

    So, we need to create a new subroutine to grab the right speed under Sonic's conditions. Here is what I come up with:

    Code (ASM):
    1. ; ===========================================================================
    2. ; ---------------------------------------------------------------------------
    3. ; Subroutine to collect the right speed setting for a character
    4. ; a0 must be character
    5. ; a1 will be the result and have the correct speed settings
    6. ; a2 is characters' speed
    7. ; ---------------------------------------------------------------------------
    8.  
    9. ; ||||||||||||||| S U B R O U T I N E |||||||||||||||||||||||||||||||||||||||
    10.  
    11. ApplySpeedSettings:
    12.     moveq   #0,d0               ; Quickly clear d0
    13.     tst.w   speedshoes_time(a0)     ; Does character have speedshoes?
    14.     beq.s   +               ; If not, branch
    15.     addq.b  #1,d0               ; Quickly add 1 to d0
    16. +
    17.     btst    #6,status(a0)           ; Is the character underwater?
    18.     beq.s   +               ; If not, branch
    19.     addq.b  #2,d0               ; Quickly add 2 to d0
    20. +
    21.     cmpa.w  #MainCharacter,a0       ; Is it Tails currently following this code?
    22.     bne.s   +               ; If so, branch and ignore next question
    23.     tst.b   (Super_Sonic_flag).w        ; Is the character Super?
    24.     beq.s   +               ; If not, branch
    25.     addq.b  #4,d0               ; Quickly add 4 to d0
    26. +
    27.     add.b   d0,d0               ; Multiply itself
    28.     add.b   d0,d0               ; And again
    29.     add.b   d0,d0               ; And again
    30.     lea Speedsettings(pc,d0.w),a1   ; Load correct speed settings into a1
    31.     addq.l  #2,a1               ; Increment a1 by 2 quickly
    32.     move.l  (a1)+,(a2)+         ; Set character's new top speed and acceleration
    33.     move.w  (a1),(a2)           ; Set character's deceleration
    34.     rts                 ; Finish subroutine
    35. ; ===========================================================================

    What this will do is ask questions like if you're underwater, have speedshoes or are Super and depending on the answers, it will add bytes to d0. Once finished with the questions, whatever d0 is, it will multiply itself 3 times. With the new result in d0 after that multiplication, it will then grab data from the table with its starting position being whatever d0 is. For example, say you're normal Sonic and underwater. After the questions, d0 should be 2. Multiply itself 3 times, d0 becomes 16. If you then look for 16 on the table, you'll see that it does indeed equal "Normal Underwater". So it got the right speed setting.


    So, here is our new code and table so far:

    Code (ASM):
    1. ; ===========================================================================
    2. ; ---------------------------------------------------------------------------
    3. ; Subroutine to collect the right speed setting for a character
    4. ; a0 must be character
    5. ; a1 will be the result and have the correct speed settings
    6. ; a2 is characters' speed
    7. ; ---------------------------------------------------------------------------
    8.  
    9. ; ||||||||||||||| S U B R O U T I N E |||||||||||||||||||||||||||||||||||||||
    10.  
    11. ApplySpeedSettings:
    12.     moveq   #0,d0               ; Quickly clear d0
    13.     tst.w   speedshoes_time(a0)     ; Does character have speedshoes?
    14.     beq.s   +               ; If not, branch
    15.     addq.b  #1,d0               ; Quickly add 1 to d0
    16. +
    17.     btst    #6,status(a0)           ; Is the character underwater?
    18.     beq.s   +               ; If not, branch
    19.     addq.b  #2,d0               ; Quickly add 2 to d0
    20. +
    21.     cmpa.w  #MainCharacter,a0       ; Is it Tails currently following this code?
    22.     bne.s   +               ; If so, branch and ignore next question
    23.     tst.b   (Super_Sonic_flag).w        ; Is the character Super?
    24.     beq.s   +               ; If not, branch
    25.     addq.b  #4,d0               ; Quickly add 4 to d0
    26. +
    27.     add.b   d0,d0               ; Multiply itself
    28.     add.b   d0,d0               ; And again
    29.     add.b   d0,d0               ; And again
    30.     lea Speedsettings(pc,d0.w),a1   ; Load correct speed settings into a1
    31.     addq.l  #2,a1               ; Increment a1 by 2 quickly
    32.     move.l  (a1)+,(a2)+         ; Set character's new top speed and acceleration
    33.     move.w  (a1),(a2)           ; Set character's deceleration
    34.     rts                 ; Finish subroutine
    35. ; ===========================================================================
    36.  
    37. ; ----------------------------------------------------------------------------
    38. ; Speed Settings Array
    39.  
    40. ; This array defines what speeds the character should be set to
    41. ; ----------------------------------------------------------------------------
    42. ;       blank   top_speed   acceleration    deceleration    ; # ; Comment
    43. Speedsettings:
    44.     dc.w    $0, $600,       $C,     $80     ; $00   ; Normal
    45.     dc.w    $0, $C00,       $18,        $80     ; $08   ; Normal Speedshoes
    46.     dc.w    $0, $300,       $6,     $40     ; $16   ; Normal Underwater
    47.     dc.w    $0, $600,       $C,     $40     ; $24   ; Normal Underwater Speedshoes
    48.     dc.w    $0, $A00,       $30,        $100        ; $32   ; Super
    49.     dc.w    $0, $C00,       $30,        $100        ; $40   ; Super Speedshoes
    50.     dc.w    $0, $500,       $18,        $80     ; $48   ; Super Underwater
    51.     dc.w    $0, $A00,       $30,        $80     ; $56   ; Super Underwater Speedshoes
    52. ; ===========================================================================



    Step 5 - Placing your new code in your ASM file

    Thanks to step 4, we've got the majority of the code sorted. You now want to place that new code in your ASM file. My suggestion is to put it between Sonic and Tails object, seeming as they're both the ones that are going to use it more often. So, search for "obj02:" and place it just above.

    Now, if you've put the code in between Sonic and Tails like I said above, the rest of my guide should be fine, although if you do decide to insert the code elsewhere, you may get some “branch out of range” errors, which you must fix yourself. Depending on how heavy you've edited Sonic and/or Tails code, you might get these errors anyway, but they're so simple to fix.




    Step 6 - Replacing some old code in certain places

    Now we have our new code, we need to make some jumps to it. So, let's go.


    First, go to "Obj01_Init:". You only need to do this bit if you have checkpoints starting underwater, otherwise, you can skip this bit of the step. You MUST follow the rest of this step from when you see "************" onwards. Anyway, under the label "Obj01_Init:", find and delete these lines:

    Code (ASM):
    1.     move.w  #$600,(Sonic_top_speed).w   ; set Sonic's top speed
    2.     move.w  #$C,(Sonic_acceleration).w  ; set Sonic's acceleration
    3.     move.w  #$80,(Sonic_deceleration).w ; set Sonic's deceleration
    and replace with this

    Code (ASM):
    1.     lea (Sonic_top_speed).w,a2  ; Load Sonic_top_speed into a2
    2.     bsr.w   ApplySpeedSettings  ; Fetch Speed settings

    "************"


    Under "Obj01_ChkShoes:" find and delete these lines:

    Code (ASM):
    1.     move.w  #$600,(Sonic_top_speed).w
    2.     move.w  #$C,(Sonic_acceleration).w
    3.     move.w  #$80,(Sonic_deceleration).w
    4.     tst.b   (Super_Sonic_flag).w
    5.     beq.s   Obj01_RmvSpeed
    6.     move.w  #$A00,(Sonic_top_speed).w
    7.     move.w  #$30,(Sonic_acceleration).w
    8.     move.w  #$100,(Sonic_deceleration).w

    and replace with this

    Code (ASM):
    1.     lea (Sonic_top_speed).w,a2  ; Load Sonic_top_speed into a2
    2.     bsr.w   ApplySpeedSettings  ; Fetch Speed settings

    Next, under "Obj01_InWater:" find these lines and delete:

    Code (ASM):
    1.     move.w  #$300,(Sonic_top_speed).w
    2.     move.w  #6,(Sonic_acceleration).w
    3.     move.w  #$40,(Sonic_deceleration).w
    4.     tst.b   (Super_Sonic_flag).w
    5.     beq.s   +
    6.     move.w  #$500,(Sonic_top_speed).w
    7.     move.w  #$18,(Sonic_acceleration).w
    8.     move.w  #$80,(Sonic_deceleration).w
    9. +

    and replace with:

    Code (ASM):
    1.     lea (Sonic_top_speed).w,a2  ; Load Sonic_top_speed into a2
    2.     bsr.w   ApplySpeedSettings  ; Fetch Speed settings

    Next, under "Obj01_OutWater:" find these lines and delete:

    Code (ASM):
    1.     move.w  #$600,(Sonic_top_speed).w
    2.     move.w  #$C,(Sonic_acceleration).w
    3.     move.w  #$80,(Sonic_deceleration).w
    4.     tst.b   (Super_Sonic_flag).w
    5.     beq.s   +
    6.     move.w  #$A00,(Sonic_top_speed).w
    7.     move.w  #$30,(Sonic_acceleration).w
    8.     move.w  #$100,(Sonic_deceleration).w
    9. +

    and replace with:

    Code (ASM):
    1.     lea (Sonic_top_speed).w,a2  ; Load Sonic_top_speed into a2
    2.     bsr.w   ApplySpeedSettings  ; Fetch Speed settings

    Next, go to "Sonic_CheckGoSuper:" and find and delete these lines:

    Code (ASM):
    1.     move.w  #$A00,(Sonic_top_speed).w
    2.     move.w  #$30,(Sonic_acceleration).w
    3.     move.w  #$100,(Sonic_deceleration).w

    and replace with:

    Code (ASM):
    1.     lea (Sonic_top_speed).w,a2      ; Load Sonic_top_speed into a2
    2.     bsr.w   ApplySpeedSettings      ; Fetch Speed settings

    Next, go to "Sonic_RevertToNormal:" and find and delete these lines:

    Code (ASM):
    1.     move.w  #$600,(Sonic_top_speed).w
    2.     move.w  #$C,(Sonic_acceleration).w
    3.     move.w  #$80,(Sonic_deceleration).w
    4.     btst    #6,status(a0)   ; Check if underwater, return if not
    5.     beq.s   return_1AC3C
    6.     move.w  #$300,(Sonic_top_speed).w
    7.     move.w  #6,(Sonic_acceleration).w
    8.     move.w  #$40,(Sonic_deceleration).w

    and replace with this slightly different code:

    Code (ASM):
    1.     lea (Sonic_top_speed).w,a2      ; Load Sonic_top_speed into a2
    2.     bra.w   ApplySpeedSettings      ; Fetch Speed settings and return


    Now onto Tails. Seeming as Tails uses the exact same speeds, we can use the exact same table. So, go to "Obj02_Init:". You only need to do this bit if you have checkpoints starting underwater, otherwise, you can skip this bit of the step. You MUST follow the rest of this step from when you see "~~~~~~~~~~~~" onwards. Anyway, under the label "Obj02_Init:", find and delete these lines:

    Code (ASM):
    1.     move.w  #$600,(Tails_top_speed).w   ; set Tails' top speed
    2.     move.w  #$C,(Tails_acceleration).w  ; set Tails' acceleration
    3.     move.w  #$80,(Tails_deceleration).w ; set Tails' deceleration

    and replace with this slightly different code:

    Code (ASM):
    1.     lea (Tails_top_speed).w,a2  ; Load Tails_top_speed into a2
    2.     bsr.w   ApplySpeedSettings  ; Fetch Speed settings

    "~~~~~~~~~~~~"


    Next, go to "Obj02_ChkShoes:" and find and delete these lines:

    Code (ASM):
    1.     move.w  #$600,(Tails_top_speed).w
    2.     move.w  #$C,(Tails_acceleration).w
    3.     move.w  #$80,(Tails_deceleration).w

    and replace with:

    Code (ASM):
    1.     lea (Tails_top_speed).w,a2  ; Load Tails_top_speed into a2
    2.     bsr.w   ApplySpeedSettings  ; Fetch Speed settings

    Next, find "Obj02_InWater:" and find and delete these lines:

    Code (ASM):
    1.     move.w  #$300,(Tails_top_speed).w
    2.     move.w  #6,(Tails_acceleration).w
    3.     move.w  #$40,(Tails_deceleration).w

    and replace with:

    Code (ASM):
    1.     lea (Tails_top_speed).w,a2  ; Load Tails_top_speed into a2
    2.     bsr.w   ApplySpeedSettings  ; Fetch Speed settings

    Next, under "Obj02_OutWater:", find and delete:

    Code (ASM):
    1.     move.w  #$600,(Tails_top_speed).w
    2.     move.w  #$C,(Tails_acceleration).w
    3.     move.w  #$80,(Tails_deceleration).w

    and replace with:

    Code (ASM):
    1.     lea (Tails_top_speed).w,a2  ; Load Tails_top_speed into a2
    2.     bsr.w   ApplySpeedSettings  ; Fetch Speed settings


    That's Tails. Let's edit the speed shoes object code. Locate label "super_shoes:" and find and delete these lines:

    Code (ASM):
    1.     move.w  #$C00,(Sonic_top_speed).w
    2.     move.w  #$18,(Sonic_acceleration).w
    3.     move.w  #$80,(Sonic_deceleration).w

    and replace with this new code:

    Code (ASM):
    1.     movem.l a0-a2,-(sp)     ; Move a0, a1 and a2 onto stack
    2.     lea (MainCharacter).w,a0    ; Load Sonic to a0
    3.     lea (Sonic_top_speed).w,a2  ; Load Sonic_top_speed into a2
    4.     jsr ApplySpeedSettings  ; Fetch Speed settings
    5.     movem.l (sp)+,a0-a2     ; Move a0, a1 and a2 from stack

    Then finally, go to "loc_12A10" and delete these lines:

    Code (ASM):
    1.     move.w  #$C00,(Tails_top_speed).w
    2.     move.w  #$18,(Tails_acceleration).w
    3.     move.w  #$80,(Tails_deceleration).w

    and replace with this slightly different new code:

    Code (ASM):
    1.     movem.l a0-a2,-(sp)     ; Move a0, a1 and a2 onto stack
    2.     lea (MainCharacter).w,a0    ; Load Tails to a0
    3.     lea (Tails_top_speed).w,a2  ; Load Tails_top_speed into a2
    4.     jsr ApplySpeedSettings  ; Fetch Speed settings
    5.     movem.l (sp)+,a0-a2     ; Move a0, a1 and a2 from stack

    All done. Please note, do not edit under the label "loc_3AA22:". This is the only speed settings that can be left alone. This is when Sonic jumps onto the Tornado at the end of WFZ. This code applies to Super Sonic too (so Super Sonic goes slower) but this is to stop Super Sonic over-shooting the Tornado and falling to his death. The speed only applies when Sonic is about to jump onto the plane.


    Be warned! If you have put in other places where you've made the characters change their speed, you'll want to edit them too!




    Step 7 - Fixing another bug

    With the above fixes applied, a new bug has been introduced. The bug? If you're Super Sonic and you die whilst Super, the "Super_Sonic_flag" will still remain set. This means, once the level restarts, the speed settings will fetch speeds with Super Sonic in mind, because of the flag. So if you go underwater or get sppedshoes, it will still think you're Super and take that into account. And if you followed the two steps for when Sonic and Tails get created, then the Super speeds will apply then too.


    Also whilst Super Sonic, if you pause the game and press A to quit, the flag will remain set. You could select Tails in options and Tails will have Super speeds. Anyway, let's fix this.


    Find "Level_ClrRam:" and directly underneath it before the ClearRAM macros, put this:

    Code (ASM):
    1.     clr.b   (Super_Sonic_flag).w

    There we go, all bugs should be fixed.



    Now, when you enter/exit water, enter/exit speedshoes and/or enter/exit Super, the speeds should all be set correctly!


    As per usual, here is a ROM with the bugs fixed.




    Sonic 1?

    Excluding Super Sonic speeds, these bugs technically exist in Sonic 1 too. Although because there isn't any speedshoes in LZ or SBZ3 (unless I'm mistaken but I don't think there is any), these bugs are never seen. But again, if you've made edits to these levels and added speedshoes about especially speedshoes in water, you'll notice these bugs. You can follow this guide as a guideline for a fix.




    Sonic 3 and Knuckles?

    To my knowledge, these bugs are fixed already. But I could be mistaken.




    If anyone wants to add this to the wiki, feel free.




    Cheers,
    redhotsonic


    EDIT: Fixed a bug that MoDule pointed out. ROM download also updated containing this new fix.
     
  2. MoDule

    MoDule

    Tech Member
    327
    24
    18
    Procrastinating from writing bug-fix guides
    I've been sitting on something like this for a while now, too, but never got around to posting it. In fact, our approaches are very similar. A few things, though:

    First of all, thanks. Your version has an optimization that I didn't think of (updating two stats with a long operation).
    Next, you can get away with a smaller data table without the blank words. Just multiply d0 with 6 instead of 8. Adding to that, I think you can save a few cycles by multiplying the bit-values themselves with 6, instead of doing it at the end. You'll need to change the second and third addq.w to addi.w, but the cycles those two changes add (4 each) are mitigated by not having to do three add.w's at the end (12 cycles, saving 4 cycles total). Admittedly, not much, and this isn't exactly a routine where optimization is very important, but you'll at least save some ROM space while you're at it.
    Finally, I think you're going to have to make the routine character-specific, or else Tails is going to use the wrong values when Sonic turns super.
     
  3. redhotsonic

    redhotsonic

    Also known as RHS Tech Member
    1,587
    10
    18
    United Kingdom
    YouTuber
    Thanks for the tip, but it's not much point changing it. My maths isn't too great and that's why I came up with the longword routine. But you can change it this way by all means in your hack :)



    ...shit. Didn't think of that. Never crossed my mind. Anyway, simple fix. Under the label "ApplySpeedSettings:", just before "tst.b (Super_Sonic_flag).w", add this:

    Code (ASM):
    1.     cmpa.w  #MainCharacter,a0       ; Is it Tails currently following this code?
    2.     bne.s   +               ; If so, branch and ignore next question

    So you end up with this:

    Code (ASM):
    1. ApplySpeedSettings:
    2.     moveq   #0,d0               ; Quickly clear d0
    3.     tst.w   speedshoes_time(a0)     ; Does character have speedshoes?
    4.     beq.s   +               ; If not, branch
    5.     addq.b  #1,d0               ; Quickly add 1 to d0
    6. +
    7.     btst    #6,status(a0)           ; Is the character underwater?
    8.     beq.s   +               ; If not, branch
    9.     addq.b  #2,d0               ; Quickly add 2 to d0
    10. +
    11.     cmpa.w  #MainCharacter,a0       ; Is it Tails currently following this code?
    12.     bne.s   +               ; If so, branch and ignore next question
    13.     tst.b   (Super_Sonic_flag).w        ; Is the character Super?
    14.     beq.s   +               ; If not, branch
    15.     addq.b  #4,d0               ; Quickly add 4 to d0
    16. +
    17.     add.b   d0,d0               ; Multiply itself
    18.     add.b   d0,d0               ; And again
    19.     add.b   d0,d0               ; And again
    20.     lea Speedsettings(pc,d0.w),a1   ; Load correct speed settings into a1
    21.     addq.l  #2,a1               ; Increment a1 by 2 quickly
    22.     move.l  (a1)+,(a2)+         ; Set character's new top speed and acceleration
    23.     move.w  (a1),(a2)           ; Set character's deceleration
    24.     rts                 ; Finish subroutine

    Fixed in first post. Thanks for pointing that bug out.
     
  4. RetroKoH

    RetroKoH

    Member
    1,662
    22
    18
    Project Sonic 8x16
    THIS must be in the SCHG. Won't have any time for the next couple of days, though I WILL get to it when I can. I'll also put in steps myself for Sonic's 1 and 3... on that note, I don't believe this bug was ever fixed for S3K either... I'll have to look.
     
  5. Tiddles

    Tiddles

    Diamond Dust Tech Member
    471
    0
    0
    Leicester, England
    Get in an accident and wake up in 1973
    Indeed, it is not fixed in S3K. Current, unreleased S3C fixes this using MoDule's earlier suggestion from here, which accounts for all the water stuff, but not the fact that speed shoes slow Super down; I fixed that much earlier by making speed shoes a no-op if you're transformed (fucker is glitchy enough, don't want him going any faster!)

    The main positive of RHS's approach here over that one, it seems to me, is efficiency; the way I'm doing it halves the speeds on the fly if underwater every time they're referenced, which isn't too expensive an operation, but has to happen a hell of a lot. On the other hand, it is more elegant code-wise and fixes a bunch of other stuff for free, such as Tails respawning at the wrong speed, ending up with the wrong speed through debugging out of water (mostly fixed in S3K anyway, but badly done, so you can end up with Super stuck at normal speeds), anything where the in/out of water functions get missed for some reason. On that basis I'll probably stick with that version, but if I get on an optimisation kick at some point I might think about using this method instead. I'm just lucky, Sonic 3 is already pretty fast...
     
  6. MoDule

    MoDule

    Tech Member
    327
    24
    18
    Procrastinating from writing bug-fix guides
    There's a couple other approaches that might be worth looking into. I've been toying with these for a while, but never got around to implementing them:

    1.
    The first one works a lot like RHS's solution above, except instead of calculating the bit values each time, we do it only once whenever something changes. Basically, we'd be saving the index into the speed stats table somewhere in RAM. In S2 we've still got a few free OST variables for Sonic and Tails, so that would work beautifully. Here's my idea in a little more detail:
    We'd add a word variable stat_flags to Sonic and Tails's OST definition. Then, let's say Sonic gets a speed shoes power up. What we do now is call a routine Sonic_GiveShoes:
    Code (ASM):
    1. Sonic_GiveShoes:
    2.     move.b  stat_flags(a0),d0
    3.     bset    #1+3,d0         ; set speed shoes bit
    4.     bne.s   +           ; branch, if Sonic already has speed shoes
    5.     move.b  d0,stat_flags(a0)   ; update flags
    6.     ext.w   d0
    7.     lea Sonic_SpeedStats(pc,d0.w),a1
    8.     move.l  (a1)+,(Sonic_top_speed).w
    9.     move.w  (a1),(Sonic_deceleration).w
    10. +
    11.     rts
    12.  
    13. ; ===========================================================================
    14. ; Sonic's speed stats data
    15. ;       top_speed   acceleration    deceleration    blank
    16. Sonic_SpeedStats:
    17.     dc.w    $600,         $C,        $80,          0    ; 000 ; normal
    18.     dc.w    $300,         $6,        $40,          0    ; 001 ; under water
    19.     dc.w    $C00,        $18,        $80,          0    ; 010 ; speed shoes
    20.     dc.w    $500,         $8,        $40,          0    ; 011 ; under water & speed shoes
    21. ; Super Sonic's speed stats data
    22.     dc.w    $A00,        $30,       $100,          0    ; 100 ; normal
    23.     dc.w    $500,        $18,        $80,          0    ; 101 ; under water
    24.     dc.w    $C00,        $30,       $100,          0    ; 110 ; speed shoes
    25.     dc.w    $600,        $18,        $80,          0    ; 111 ; under water & speed shoes
    26. ; ===========================================================================
    We're back to using blank table entries, but now we're only calculating what we have to (and you could use those blank table entries for something like jump height). To remove a stat, simply do a bclr. Which bits to set or clear is determined as follows: I've arranged the table so that bit 0 corresponds to being under water, bit 1 to having speed shoes, and bit 2 to being super. Since I know in advance that I always need to multiply the bit values with 8, I do so by adding 3 to the bit number (because 3 = log2(8)). You could take this one step further by using a single, non-powerup-specific routine that takes d1 as the bit to be set/cleared.

    2.
    Building upon the previous approach, if we sacrifice a little more RAM we can save the table address itself. So we'd use stat_addr instead of stat_flags. To change status here, we'd add or subtract 8, 16, or 32 to the saved table address. Here's some usage examples:
    Code (ASM):
    1.     ; changing stats
    2.     addi.l  #(1<<0)*8,stat_addr(a0)     ; character is under water
    3.     subi.l  #(1<<1)*8,stat_addr(a0)     ; remove speed shoes
    4.     addi.l  #(1<<2)*8,stat_addr(a0)     ; make Sonic super
    5.  
    6.     ; reading stats
    7.     movea.l stat_addr(a0),a1        ; load character's stats
    This method is a little more dangerous, since you could end up addressing data outside the table if you're not careful.

    3.
    Lastly, I've got an idea about how you could optimize the approach (mine, weeee) Tiddles linked to: have two sets of stats in RAM, one normal, and one for under water. Then, when it's time to move your character around, instead of reading the values and then shifting them, we just read the under water set of values. This is even easier in S3K, since all we have to do there is add 6 to a4.

    Actually, now that I think about it, the optimal solution could be a combination between 2 and 3. Save the entire stat table in RAM and use stat_addr to load a character's movement stats when they are needed. With this, you're still reading your stats from RAM (faster than reading from ROM), and you only need to use a single word per character to keep track of your stats.

    But again, this isn't the best place to look for optimizations. A character's stats don't get changed very often per frame. It's better to optimize things that happen multiple times per frame.
     
  7. redhotsonic

    redhotsonic

    Also known as RHS Tech Member
    1,587
    10
    18
    United Kingdom
    YouTuber
    Now why the hell did I not think of that? Would have saved the "addq.l #2,a1" but it's only 8 cycles saved, and like you mentioned, it's not like it has to do this every frame. Meh, can't be arsed to edit the guide; I'm sure people will spot this for themselves =P


    Also, that's a nice way of doing it, Module, but I wanted mine to be a bit more friendly for beginners (that's why I've gone overboard on explaining), and tried to fix these bugs without the use of creating more SST's and RAM addresses (not to mention I've practically used all my SST's in S2R and don't want to extend the tables).



    No rush.



    EDIT: I put that it saves 4 cycles accidently when it's 8. Whoops!
     
  8. Caverns 4

    Caverns 4

    Member
    346
    0
    16
    Sonic: Retold
    Okay, this is perfect, I've been bothered by the speed issues, and I've been meaning to do something.

    However, my something was make a bunch of constants for every speed-affecting situation, so this table is much more proper. Very nice work!

    Since Sonic is a bit faster than Tails in my hack, I had to make two separate tables, but I feel it's cleaner that way since each character is then a bit more self-contained.

    Not to mention that it's easier on the CPU to just grab data off of a table than redefine the same number multiple times.

    Thanks for the guide!
     
  9. RetroKoH

    RetroKoH

    Member
    1,662
    22
    18
    Project Sonic 8x16
    I copy/pasted the first post onto the wiki... I know, its about goddamned time. Sorry for the delay guys.

    I kinda rushed it too... some of the above mentioned optimizations could be added to this if deemed really necessary... Up to the rest of you guys.
     
  10. Clownacy

    Clownacy

    Tech Member
    1,061
    607
    93
    Bump. Found a bug (sorta).

    So, what if you merge this guide with this guide? You overwrite a1 and cause Tails to do some random nonsense such as falling through the ground whenever he respawns. Such nonsense convinced me to rebase my hack for the umpteenth time, only to find the cause halfway through rebasing. Just my luck.

    For anyone reading, if you're applying this to loc_1BC68, backup a1 like so:

    Code (ASM):
    1.     move.l  a1,-(sp)        ; Backup a1
    2.     lea (Tails_top_speed).w,a2  ; Load Tails_top_speed into a2
    3.     bsr.w   ApplySpeedSettings  ; Fetch Speed settings
    4.     move.l  (sp)+,a1        ; Restore a1
     
  11. redhotsonic

    redhotsonic

    Also known as RHS Tech Member
    1,587
    10
    18
    United Kingdom
    YouTuber
    Is that so? Thanks for that little bit of information. Surprised I never spotted that myself. And only just spotted you posting in this topic =P
     
  12. Lorenzo

    Lorenzo

    Member
    2
    0
    0
    Italy
    Hi.

    When I was working on a hack of mine and implementing this fix regarding speed, I tested it and noticed a bug in 2P mode. Perhaps not many people care about 2P mode, but I thought this might be worth mentioning.

    Look at this snippet of code where the speed table is used for Tails when getting the Speed Shoes:


    [68k]
    ;loc_12A10:
    super_shoes_Tails:
    movem.l a0-a2,-(sp)
    lea (MainCharacter).w,a0
    lea (Tails_top_speed).w,a2
    jsr ApplySpeedSettings
    movem.l (sp)+,a0-a2
    [/68k]


    This only works when you are using Tails only, thus when he's the main character. When in 2P mode, Tails can break the monitor, but the speed values are not applied for him because he's not the main character, but the "sidekick" (using the disassembly terminology).

    What I did to fix it was this:


    [68k]
    ;loc_12A10:
    super_shoes_Tails:
    movem.l a0-a2,-(sp)
    lea (MainCharacter).w,a0
    tst.w (Player_mode).w ; Test which characters we're using
    bne.s + ; If we're not using Sonic and Tails together, branch
    lea (Sidekick).w,a0 ; otherwise load sidekick in a0 (for Tails obviously)
    +
    lea (Tails_top_speed).w,a2
    jsr ApplySpeedSettings
    movem.l (sp)+,a0-a2
    [/68k]

    Simple enough. We use the MainCharacter RAM section when we're using Tails only, and we load the Sidekick RAM section in a0 when we're using both Sonic and Tails (for 2P not 1P mode).

    Since we added a + label, the bra.s instruction above the label won't branch correctly. So change

    [68k]
    movem.l a0-a2,-(sp) ; Move a0, a1 and a2 onto stack
    lea (MainCharacter).w,a0 ; Load Sonic to a0
    lea (Sonic_top_speed).w,a2 ; Load Sonic_top_speed into a2
    jsr ApplySpeedSettings ; Fetch Speed settings
    movem.l (sp)+,a0-a2 ; Move a0, a1 and a2 from stack
    bra.s +
    ; ---------------------------------------------------------------------------
    ;loc_12A10:
    super_shoes_Tails:
    movem.l a0-a2,-(sp) ; Move a0, a1 and a2 onto stack
    [/68k]

    to this:

    [68k]
    movem.l a0-a2,-(sp) ; Move a0, a1 and a2 onto stack
    lea (MainCharacter).w,a0 ; Load Sonic to a0
    lea (Sonic_top_speed).w,a2 ; Load Sonic_top_speed into a2
    jsr ApplySpeedSettings ; Fetch Speed settings
    movem.l (sp)+,a0-a2 ; Move a0, a1 and a2 from stack
    bra.s ++ ; <-- added a + to make it branch to the correct location
    ; ---------------------------------------------------------------------------
    ;loc_12A10:
    super_shoes_Tails:
    movem.l a0-a2,-(sp) ; Move a0, a1 and a2 onto stack
    [/68k]


    I tested it and everything should be OK. I hope it's clear enough.

    Since it's my first post here, I don't know how I should explain stuff, if I forgot to include something or if it's not detailed enough, for an asm edit that is.
     
  13. redhotsonic

    redhotsonic

    Also known as RHS Tech Member
    1,587
    10
    18
    United Kingdom
    YouTuber
    Just goes to show how many people care for the 2 player mode if this has only been discovered a year and a half later. Anyway, I haven't had time to try out your fix, but looks simple enough that I don't see it not working. Thanks for the fix, I can't be assed to edit my first post but no doubt people will see your fix :)