don't click here

Update: How to port S3K Priority Manager into S2

Discussion in 'Engineering & Reverse Engineering' started by redhotsonic, Jun 24, 2012.

  1. redhotsonic

    redhotsonic

    Also known as RHS Tech Member
    1,587
    10
    18
    United Kingdom
    YouTuber
    WARNING: Please back-up your disassembly before attempting to use this guide. I will not be held responsible for anything that goes wrong.


    This guide is mainly following XenoWhirl's 2007 disassembly. But, if you're an SVN user, I have tried to make this guide as friendly as possible for you.

    Anything in red is for Xenowhirls 2007 disassembly users only

    Anything in green is for SVN disassembly users only

    Anything not coloured (white), means it's for both disassemblies.


    Also, EVERY SST MUST BE EQUATED. So, if you're using Hivebrain's disassembly, you won't be able to follow this. Also, for any objects you've made yourself or have ported, make sure that it's all equated (so instead of $18(a0), it should be priority(a0).) Anything that has not been equated may start to cause problems; you have been warned!



    Today, I bring you probably the best guide I've typed-up to-date:






    How to port S3K's Priority Manager into S2






    What is priority?
    In all Sonic games, there is a universal SST called Priority. Each sprite has one. The higher the priority number, the lower priority it has. Then the objects can be displayed accordingly. For example, if Sonic has a priority of 2, and Dr.Eggman's priority is 3, then when both sprites are displayed over each other, Sonic will ALWAYS be displayed in front of him.


    Differences between both managers
    In Sonic 2, the priority's universal SST is a byte. It's start's from #0 (highest) upto #7 (lowest). When each object jumps to the "DisplaySprite" subroutine, it firsts, converts the priority byte into a word. And then it can check whether to display the sprite, and if so, display it. It has to do this every frame for every object for the sprite to be displayed.


    In Sonic 3 and Knuckles, priority is already a word, again, it's start's from #0 (highest) but goes upto #$380 (lowest). That doesn't mean there's $380 types of priority. It goes up in $80's (#0, $80, $100, $180, etc). Because it's already set as a word, when it jumps to the "DisplaySprite" subroutine, it doesn't have to do them calculations, and can just check whether to display the sprite or not and if so, display it.


    So basically, S3K's "DisplaySprite" has shaven a few commands away. Because S3K does not have to do them calculations changing from a byte to a word like S2 does on every single frame, it can save a lot of time, and can use the processor on other things instead. When you see a lot of sprites on screen, this can make a huge difference to gameplay.


    [​IMG]

    Look at this picture. In every single frame, the game has to display each scattered ring (15 of them there), each and every single bubble, and also the bubble maker on the gorund, and the two sharks, and Sonic, and Tails, and Tails' tails, and the rotating platforms on the side, and the HUD, and if we were a bit higher, the water line. Sonic 2 will have to do these calculations for every single little object on screen here; every single frame, before asking whether to display it or not, and if so, display it. Wow, that's going to take a lot of time. Whereas S3K, it already knows the word for each object, so it doesn't need to do the calculations for every object in every frame. So it can just get on and ask whether to display it or not and if so, display it. Saving a HUGE amount of time.



    Porting S3K's Priority Manager into S2 can help get rid of some of the lag your hack may be experiencing. If you follow this guide step-by-step carefully, and make sure you have some time on your hands because it can take a little while, everything should go fine.


    Please be aware, once you've started this, you cannot rebuild your ROM until you've finished. Otherwise, you'll get errors and crashes.






    Step 1 - Getting a free universal SST


    The biggest problem is freeing a universal SST, seeming as they are all being used. Luckily, quite a while back, I showed a guide on how to free two universal SST's! If you have already done part 2 of this guide, you do not need to carry on with this step. Part 2 is the essential SST we need for this guide. Part 1 isn't as important.


    If you haven't done this already, you will need to do so. Here is a quote from the guide On how to free up ONE universal SST.




    Because $19 is univerally free, priority can now be used as a word ($18 and $19)!






    Step 2 - Setting up DisplaySprite and DisplaySprite2


    You should now have width_pixels at $14 instead of $19. Because priority is at $18, and $19 is now free, theoretically, the priority SST can now be used as a word.


    Next, we need to change DisplaySprite and DisplaySprite2. Do NOT edit/change or replace DisplaySprite3! Leave that as it is.


    At "DisplaySprite:", you'll see this:

    Code (ASM):
    1.     move.w  priority(a0),d0
    2.     lsr.w   #1,d0
    3.     andi.w  #$380,d0
    4.     adda.w  d0,a1
    These are the lines that does the calculations every single frame. This is what converts the byte into a word. Like said in S3K, as priority is already a word, these lines are now useless. As this is what we're trying to acheive, we can do the same thing.



    So, at "DisplaySprite:", change this:

    Code (ASM):
    1. ; ||||||||||||||| S U B R O U T I N E |||||||||||||||||||||||||||||||||||||||
    2.  
    3. ; sub_164F4:
    4. DisplaySprite:
    5.     lea (Sprite_Table_Input).w,a1
    6.     move.w  priority(a0),d0
    7.     lsr.w   #1,d0
    8.     andi.w  #$380,d0
    9.     adda.w  d0,a1
    10.     cmpi.w  #$7E,(a1)
    11.     bcc.s   return_16510
    12.     addq.w  #2,(a1)
    13.     adda.w  (a1),a1
    14.     move.w  a0,(a1)
    15.  
    16. return_16510:
    17.     rts
    18. ; End of function DisplaySprite
    19.  
    20. ; ---------------------------------------------------------------------------
    21. ; Subroutine to display a sprite/object, when a1 is the object RAM
    22. ; ---------------------------------------------------------------------------
    23.  
    24. ; ||||||||||||||| S U B R O U T I N E |||||||||||||||||||||||||||||||||||||||
    25.  
    26. ; sub_16512:
    27. DisplaySprite2:
    28.     lea (Sprite_Table_Input).w,a2
    29.     move.w  priority(a1),d0
    30.     lsr.w   #1,d0
    31.     andi.w  #$380,d0
    32.     adda.w  d0,a2
    33.     cmpi.w  #$7E,(a2)
    34.     bcc.s   return_1652E
    35.     addq.w  #2,(a2)
    36.     adda.w  (a2),a2
    37.     move.w  a1,(a2)
    38.  
    39. return_1652E:
    40.     rts
    41. ; End of function DisplaySprite2

    To this:

    Code (ASM):
    1. ; ||||||||||||||| S U B R O U T I N E |||||||||||||||||||||||||||||||||||||||
    2.  
    3. ; sub_164F4:
    4. DisplaySprite:
    5.     lea (Sprite_Table_Input).w,a1
    6.     adda.w  priority(a0),a1
    7.     cmpi.w  #$7E,(a1)
    8.     bcc.s   return_16510
    9.     addq.w  #2,(a1)
    10.     adda.w  (a1),a1
    11.     move.w  a0,(a1)
    12.  
    13. return_16510:
    14.     rts
    15. ; End of function DisplaySprite
    16.  
    17. ; ---------------------------------------------------------------------------
    18. ; Subroutine to display a sprite/object, when a1 is the object RAM
    19. ; ---------------------------------------------------------------------------
    20.  
    21. ; ||||||||||||||| S U B R O U T I N E |||||||||||||||||||||||||||||||||||||||
    22.  
    23. ; sub_16512:
    24. DisplaySprite2:
    25.     lea (Sprite_Table_Input).w,a2
    26.     adda.w  priority(a1),a2
    27.     cmpi.w  #$7E,(a2)
    28.     bcc.s   return_1652E
    29.     addq.w  #2,(a2)
    30.     adda.w  (a2),a2
    31.     move.w  a1,(a2)
    32.  
    33. return_1652E:
    34.     rts
    35. ; End of function DisplaySprite2

    Both of these subroutines are now shorter. May not look by much, but remember, as this is getting repeated all the time for EACH object for each frame, this will make a lot of difference.


    DisplaySprite3 does not need changing. This is because it already does a similar thing. Some objects move a priority word to d0, then DisplaySprite3 uses that d0. As it's already a word, nothing needs changing.






    Step 3 - Set objects that uses tables for priority to the new manager


    Now, a lot of objects just move a byte to the object's priority. But some objects use a table for priority, width, mappings, etc.


    Now, editing the table can cause problems, it can start making the objects code out of line, or even the rest of the game, which will cause the game to randomly crash at certain times. But it has to be a word, it cannot stay as a byte.


    So, the best thing I found it to actually use them calculations from Sonic 2's DisplaySprite to convert them into a word. So, when creating the object, it will have the priority as a byte, and then do Sonic 2's calculations to convert it into a word. Priority will then remain as a word for the rest of the time that object is there for. Because of this, it will only need to do this calculation once, then carry on the S3K way. Doing it once then the S3K way, is better then doing them calculations all the time, right?


    So, first, go to "loc_112A4:" ("Obj1C_Init:") This is "Object 1C - Bridge stake in Emerald Hill Zone and Hill Top Zone, falling oil in Oil Ocean Zone" object.


    You can see it's moving a lot of data to mappings and etc. It does this because there's so many subtypes to the object, and this is the best and quickest way for that object to load.



    So, underneath "move.b (a1)+,priority(a0)", add this:

    Code (ASM):
    1.     move.w  priority(a0),d0
    2.     lsr.w   #1,d0
    3.     andi.w  #$380,d0
    4.     move.w  d0,priority(a0)

    So you have something like this (SVN user, your code will look slightly different, this is only a reference):

    Code (ASM):
    1. loc_112A4:
    2.     addq.b  #2,routine(a0)
    3.     moveq   #0,d0
    4.     move.b  subtype(a0),d0
    5.     move.w  d0,d1
    6.     lsl.w   #3,d0
    7.     lea dword_111E6(pc),a1
    8.     lea (a1,d0.w),a1
    9.     move.b  (a1),mapping_frame(a0)
    10.     move.l  (a1)+,mappings(a0)
    11.     move.w  (a1)+,art_tile(a0)
    12.     bsr.w   Adjust2PArtPointer
    13.     ori.b   #4,render_flags(a0)
    14.     move.b  (a1)+,width_pixels(a0)
    15.     move.b  (a1)+,priority(a0)
    16.     move.w  priority(a0),d0
    17.     lsr.w   #1,d0
    18.     andi.w  #$380,d0
    19.     move.w  d0,priority(a0)
    20.     lea byte_1128E(pc),a1
    21.     move.b  (a1,d1.w),d1
    22.     beq.s   BranchTo_MarkObjGone
    23.     move.b  d1,y_radius(a0)
    24.     bset    #4,render_flags(a0)

    Whatever data is grabbed from the table, it's now converted it to a word, and it will never do it again. It will always remain as a word. So, when it goes to DisplaySprite over and over, it can just get on with it.



    Next, go to "loc_1131A:" ("Obj71_Init:") (Object 71 - Bridge stake and pulsing orb from Hidden Palace Zone) and do the same thing, so you end up with this (SVN users, reference only):

    Code (ASM):
    1. loc_1131A:
    2.     addq.b  #2,routine(a0)
    3.     move.b  subtype(a0),d0
    4.     andi.w  #$F,d0
    5.     lsl.w   #3,d0
    6.     lea dword_11302(pc),a1
    7.     lea (a1,d0.w),a1
    8.     move.b  (a1),mapping_frame(a0)
    9.     move.l  (a1)+,mappings(a0)
    10.     move.w  (a1)+,art_tile(a0)
    11.     bsr.w   Adjust2PArtPointer
    12.     ori.b   #4,render_flags(a0)
    13.     move.b  (a1)+,width_pixels(a0)
    14.     move.b  (a1)+,priority(a0)
    15.     move.w  priority(a0),d0
    16.     lsr.w   #1,d0
    17.     andi.w  #$380,d0
    18.     move.w  d0,priority(a0)
    19.     move.b  subtype(a0),d0
    20.     andi.w  #$F0,d0
    21.     lsr.b   #4,d0
    22.     move.b  d0,anim(a0)


    Go to "loc_3F228:" (Object 3E - Egg prison) and almost do the same thing again. Just look at the registers though, as they are different (it's a1 instead of a0). You should end up with this (SVN users, reference only):

    Code (ASM):
    1. loc_3F228:
    2.     _move.b 0(a0),0(a1) ; load obj
    3.     move.w  x_pos(a0),x_pos(a1)
    4.     move.w  y_pos(a0),y_pos(a1)
    5.     move.w  y_pos(a0),objoff_30(a1)
    6.     move.l  #Obj3E_MapUnc_3F436,mappings(a1)
    7.     move.w  #$2680,art_tile(a1)
    8.     move.b  #$84,render_flags(a1)
    9.     moveq   #0,d0
    10.     move.b  (a2)+,d0
    11.     sub.w   d0,y_pos(a1)
    12.     move.w  y_pos(a1),objoff_30(a1)
    13.     move.b  (a2)+,routine(a1)
    14.     move.b  (a2)+,width_pixels(a1)
    15.     move.b  (a2)+,priority(a1)
    16.     move.w  priority(a1),d0
    17.     lsr.w   #1,d0
    18.     andi.w  #$380,d0
    19.     move.w  d0,priority(a1)
    20.     move.b  (a2)+,mapping_frame(a1)


    And finally, go to "LoadSubObject_Part3:". Now this loads all the priorities, widths, mappings, etc, to all objects past Obj8C. This is a life saver. So this will save us a lot of time from doing it to a lot of other objects.



    So, change this:

    Code (ASM):
    1. LoadSubObject_Part3:
    2.     move.l  (a1)+,mappings(a0)
    3.     move.w  (a1)+,art_tile(a0)
    4.     jsr Adjust2PArtPointer
    5.     move.b  (a1)+,d0
    6.     or.b    d0,render_flags(a0)
    7.     move.b  (a1)+,priority(a0)
    8.     move.b  (a1)+,width_pixels(a0)
    9.     move.b  (a1),collision_flags(a0)
    10.     addq.b  #2,routine(a0)
    11.     rts

    to this:

    Code (ASM):
    1. LoadSubObject_Part3:
    2.     move.l  (a1)+,mappings(a0)
    3.     move.w  (a1)+,art_tile(a0)
    4.     jsr Adjust2PArtPointer
    5.     move.b  (a1)+,d0
    6.     or.b    d0,render_flags(a0)
    7.     move.b  (a1)+,priority(a0)
    8.     move.w  priority(a0),d0
    9.     lsr.w   #1,d0
    10.     andi.w  #$380,d0
    11.     move.w  d0,priority(a0)
    12.     move.b  (a1)+,width_pixels(a0)
    13.     move.b  (a1),collision_flags(a0)
    14.     addq.b  #2,routine(a0)
    15.     rts

    That's half the objects done already!






    Step 4 - Set the rest of the objects to the new manager


    Now, this step is easy, but time consuming. You will be a while doing this. You need to search through the whole of your asm file, and change all priority's to a word. Here is an example.



    At "Obj5E:", you'll see this:

    Code (ASM):
    1.     move.b  #0,priority(a0)

    Change it to this:

    Code (ASM):
    1.     move.w  #0,priority(a0)

    The move command has now changed from .b to .w as priority is now a word. The number will also need changing, but #0 in S2 is the same as S3K. So 0 doesn't need changing here.



    Another example. Go to "loc_7158:" ("Obj5F_Init:") and change this:

    Code (ASM):
    1.     move.b  #1,priority(a0)

    to this:

    Code (ASM):
    1.     move.w  #$80,priority(a0)

    Again, the .b has change to .w (this must be done ALL the time, even if the number is 0). This time, the number is 1. After calculations (or in S3K), the number will be $80. So it's been changed to $80. Remember, in S2, the numbers were only #. With the S3K way (unless 0), it will need to be #$.


    You need to find all these and change all .b to .w and change the numbers to the relavant number it would have become after the original calculations.


    To help you, I've made this table:

    Code (Text):
    1. ; ===============================
    2. ; TABLE CONVERSION FROM S2 TO S3K
    3. ; ===============================
    4. ; |   | S 2 | S3K |
    5. ; |---|-----|-----|
    6. ; | ~ |  #  |  #$ |
    7. ; | P |  0  |  0  |
    8. ; | R |  1  |  80 |
    9. ; | I |  2  | 100 |
    10. ; | O |  3  | 180 |
    11. ; | R |  4  | 200 |
    12. ; | I |  5  | 280 |
    13. ; | T |  6  | 300 |
    14. ; | Y |  7  | 380 |
    15. ; ===============================


    Want one more example? Okay. Go to "ObjDB_Sonic_Init:" and change this:

    Code (ASM):
    1.     move.b  #2,priority(a0)

    to this:

    Code (ASM):
    1.     move.w  #$100,priority(a0)

    It's that simple. Now go! Convert all them priorities!


    Tip - click spoiler
    To make this step miles quicker, you can do a search and replace. Just be careful when doing this. Make sure you do it right. Remember that not all priorities use a0. Some use a1, or a2. You could do something similar to this:


    Search:
    Code (ASM):
    1. move.b  #2,priority(a
    Replace with:
    Code (ASM):
    1. move.w  #$100,priority(a

    ...and etc. That way, it will replace them all, no matter what register it has. Do the same from #0 - #7.

    Be warned though! If you've edited the tabs (spaces) on any coding, or on your own made objects, the "Search and Replace" may not be the best way to go, and you may have to do this manually.

    If you do do the "search and replace" idea, I'd highly reccommend you still search through the ASM file after to check you've done it right.






    Step 5 - Make sure the copiers are copying correctly


    At these locations:

    XenoWhirl users:
    • loc_10B9E:
    • loc_15E46:
    • Obj79_MakeSpecialStars:
    • loc_25C24:
    • loc_28A6E:



    SVN users ONLY:
    For first label, do a search for this line:
    Code (ASM):
    1.     _move.b d4,id(a1) ; load obj1F
    Second label is "BreakObjectToPieces_InitObject:"

    Third label is "Obj79_MakeSpecialStars:"

    Fourth Label is "loc_25C24:"

    And fifth label is "Obj73_LoadSubObject:"




    Under each of these labels, you should see this line:

    Code (ASM):
    1.     move.b  priority(a0),priority(a1)

    Some objects like these, move the priority to a1 from a0. As you can see, it's only moving a byte. Change the line at all these locations to this:

    Code (ASM):
    1.     move.w  priority(a0),priority(a1)
    All the registers at these locations are the same, so the above line is fine to use.






    Step 6 - Make sure some objects are calculating correctly


    Similar to step 5, some objects copy the priority to another address register or data register, then does a small subtraction, then moves it back. As it's going by S2's priority, this needs changing also.



    Go to "loc_34864:" (Tails in Special Stage) and change this bit only:

    Code (ASM):
    1.     move.b  priority(a0),priority(a1)
    2.     subi.b  #1,priority(a1)

    to this:

    Code (ASM):
    1.     move.w  priority(a0),priority(a1)
    2.     subi.w  #$80,priority(a1)

    So you have this (SVN users, reference only):

    Code (ASM):
    1. loc_34864:
    2.     move.w  #$400,objoff_32(a0)
    3.     move.b  #$40,angle(a0)
    4.     move.b  #1,($FFFFF7DE).w
    5.     clr.b   collision_property(a0)
    6.     clr.b   respawn_index(a0)
    7.     bsr.w   loc_349C8
    8.     movea.l #Object_RAM+$180,a1
    9.     move.b  #$63,(a1) ; load obj63 (shadow) at $FFFFB180
    10.     move.w  x_pos(a0),x_pos(a1)
    11.     move.w  y_pos(a0),y_pos(a1)
    12.     addi.w  #$18,y_pos(a1)
    13.     move.l  #Obj63_MapUnc_34492,mappings(a1)
    14.     move.w  #$623C,art_tile(a1)
    15.     move.b  #4,render_flags(a1)
    16.     move.w  #$200,priority(a1)
    17.     move.l  a0,objoff_38(a1)
    18.     movea.l #Object_RAM+$1C0,a1
    19.     move.b  #$88,(a1) ; load obj88
    20.     move.w  x_pos(a0),x_pos(a1)
    21.     move.w  y_pos(a0),y_pos(a1)
    22.     move.l  #Obj88_MapUnc_34DA8,mappings(a1)
    23.     move.w  #$4316,art_tile(a1)
    24.     move.b  #4,render_flags(a1)
    25.     move.w  priority(a0),priority(a1)
    26.     subi.w  #$80,priority(a1)
    27.     move.l  a0,objoff_38(a1)
    28.     movea.l a1,a0
    29.     move.b  #1,($FFFFF7DF).w
    30.     clr.b   respawn_index(a0)
    31.     movea.l objoff_38(a0),a0 ; load 0bj address
    32.     rts

    Now, it's moving a WORD to a1, then subtracting $80 instead of 1. So now it's going how S3K would do it.



    Similar story here. Go to "Obj88:" (Tails' tails in Special Stage) and change this bit only:

    Code (ASM):
    1.     move.b  priority(a1),d0
    2.     subq.b  #1,d0
    3.     move.b  d0,priority(a0)

    to this:

    Code (ASM):
    1.     move.w  priority(a1),d0
    2.     subi.w  #$80,d0
    3.     move.w  d0,priority(a0)


    One more. Go to "loc_3C1F4:" (Breakable plating from WFZ) and change this bit only:

    Code (ASM):
    1.     move.b  priority(a0),d4
    2.     subq.b  #1,d4
    to this:

    Code (ASM):
    1.     move.w  priority(a0),d4
    2.     subi.w  #$80,d4


    There's a little more to this object. Next, go to "loc_3C20E:" and change this bit only:

    Code (ASM):
    1.     move.b  d4,priority(a1)
    to this:

    Code (ASM):
    1.     move.w  d4,priority(a1)

    These objects will now display correctly.






    Step 7 - Make sure EHZ's boss uses the right compares


    At these locations:

    • loc_2F714:
    • loc_2F77E:
    • loc_2F7A6:


    The EHZ boss is showing these commands:

    Code (ASM):
    1.     cmpi.b  #2,priority(a0)

    Change them to this:

    Code (ASM):
    1.     cmpi.w  #$100,priority(a0)
    They were still treating priority as a byte, so we just needed to change these to a word.






    Step 8 - Fix ARZ, CNZ, MCZ and OOZ's boss's priority


    These are a pain in the ass to fix! These bosses use the priority and (the old) width_pixel's SST's together for a complete different reason (The hammer, drills and catchers). It's moves odd numbers to the priority SST (well, it's actually positions, but the number is odd to DisplaySprite) and because of this, it cannot display the word any longer and therefore, the game freezes pretty much instantly when you approach one of these bosses.

    Luckily, there are many objects that do a similar thing; using priority for a complete different reason. This is where "DisplaySprite3" comes into play. Objects that use priority SST for other reasons, instead, moves a word to d0, then jumps to DisplaySprite3. So, we're going to do something similar for ARZ, CNZ, MCZ and OOZ's bosses.



    ARZ boss

    Go to "loc_304D4:" and delete the line:

    Code (ASM):
    1.     move.w  #$100,priority(a0)

    This is no longer needed, as it's going to be replaced with other things later in the ARZ boss code anyway.



    Still at this label, you should also see these commands:

    Code (ASM):
    1.     move.w  #$200,priority(a1)
    Code (ASM):
    1.     move.w  #$100,priority(a1)

    You might as well get rid of them too.



    Make sure you do NOT delete this line:

    Code (ASM):
    1.     move.w  #$488,priority(a0)

    This is for the y_pos of the hammer, one of the reason why the priority needs changing of the boss.




    Next, go to "loc_30BC8:" and find and delete the line:

    Code (ASM):
    1.     move.w  #$200,priority(a0)

    Again, no point in this!



    Next, find the label "JmpTo37_DisplaySprite". Only the ARZ boss jumps to this location for displaying a sprite, so we know we can freely edit this for our ARZ boss without affecting anything else. Anyway, you should see this (SVN user, you're will look ever so slightly different, but you can still copy and paste my fix that I'm about to show):

    Code (ASM):
    1. JmpTo37_DisplaySprite
    2.     jmp DisplaySprite

    Now, because them odd numbers are set as priority, when it jumps to DisplaySprite, it will try to process it and freeze. So, this is what we're going to do. Change it to this:

    Code (ASM):
    1. JmpTo37_DisplaySprite
    2.     move.w  #$200,d0    ; move $200 to ARZ boss' priority
    3.     jmp DisplaySprite3  ; Dispaly it

    Now, everytime the boss needs displaying, instead of grabbing the data from the priority SST, it will process what we've just moved to d0. #$200 seems good, I use that.


    I would have tried changing these priority commands to a different universal SST, but the ARZ boss unfortunately uses them all, so there's nothing free. So, the priority for Eggman, arrows, hammer and totem poles now have to be the same priority, but really, you won't notice any different to the way the boss displays it's sprites.

    Viola! ARZ boss is sorted and will no longer freeze.



    CNZ boss

    The problem with this boss is the exact same problem as ARZ. Priority is being used for something else. Same sort of fix applies here.



    Go to "loc_31904:" and delete the line:

    Code (ASM):
    1.     move.w  #$180,priority(a0)


    Also, go to "loc_31F48:" and delete the line:

    Code (ASM):
    1.     move.w  #$380,priority(a0)


    Next, find the label "JmpTo39_DisplaySprite". Only the CNZ boss jumps to this location for displaying a sprite, so again, we know it's safe to edit. You should see this (SVN user, you're will look ever so slightly different, but you can still copy and paste my fix that I'm about to show):

    Code (ASM):
    1. JmpTo39_DisplaySprite
    2.     jmp DisplaySprite

    Change it to this:

    Code (ASM):
    1. JmpTo39_DisplaySprite
    2.     move.w  #$180,d0    ; move $180 to CNZ boss' priority
    3.     jmp DisplaySprite3  ; Dispaly it

    CNZ boss should no longer freeze. #$180 seems good here. Again, CNZ boss shares the same problem as ARZ boss, so Eggman, the catchers and spikeball have to be the same priority, and you shouldn't notice any difference.



    MCZ boss

    Same issue. Priority is being used for something else. Same sort of fix applies here. But luckily, all the priorties are #$180, so at least the priority won't change after this edit unlike the previous two bosses we've edited.



    Go to "loc_30FB8:" and delete the line:

    Code (ASM):
    1.     move.w  #$180,priority(a0)


    Also, go to "loc_313DA:" ("Obj57_LoadStoneSpike:") and delete the same(ish) line (I say 'ish' because it's using a1 instead of a0):

    Code (ASM):
    1.     move.w  #$180,priority(a1)


    Next, find the label "JmpTo38_DisplaySprite". Only the MCZ boss jumps to this location for displaying a sprite, so again, we know it's safe to edit. You should see this (SVN user, you're will look ever so slightly different, but you can still copy and paste my fix that I'm about to show):

    Code (ASM):
    1. JmpTo38_DisplaySprite
    2.     jmp DisplaySprite

    Change it to this:

    Code (ASM):
    1. JmpTo38_DisplaySprite
    2.     move.w  #$180,d0    ; move $180 to MCZ boss' priority
    3.     jmp DisplaySprite3  ; Dispaly it

    Seeming as both priority lines we just deleted was #$180, we know that we can use #$180 here. MCZ boss should no longer freeze.



    OOZ boss

    This boss uses Priority as a x_pos for the sub objects. Again, we're going to have to do a similar thing like we did with the other bosses.



    Go to "loc_32FA8:" ("Obj55_Init:")and delete the line:

    Code (ASM):
    1.     move.w  #$180,priority(a0)


    Also, go to "loc_33586:" ("Obj55_Laser_Init:") and delete the line:

    Code (ASM):
    1.     move.w  #$200,priority(a0)


    Also, go to "loc_33640:" ("Obj55_Laser_CreateWave:") and delete the line:

    Code (ASM):
    1.     move.w  #$100,priority(a1)


    Next, find the label "JmpTo41_DisplaySprite". Only the OOZ boss jumps to this location for displaying a sprite, so again, we know it's safe to edit. You should see this (SVN user, you're will look ever so slightly different, but you can still copy and paste my fix that I'm about to show):

    Code (ASM):
    1. JmpTo41_DisplaySprite
    2.     jmp DisplaySprite

    Change it to this:

    Code (ASM):
    1. JmpTo41_DisplaySprite
    2.     move.w  #$180,d0    ; move $180 to OOZ boss' priority
    3.     jmp DisplaySprite3  ; Dispaly it

    OOZ boss should no longer freeze. #$180 seems good here. Again, all boss objects have to be the same priority, and you shouldn't notice any difference.



    Notice: I have compared the priorities to the original Sonic 2's priorities for these bosses with our new "set" priorities (that we've moved to d0) and I saw no difference whatsover. So all the above is fine. For example, with Sonic 2's original priorities set, Sonic was always in front of the ARZ boss, hammer, totem poles and arrows. He was never behind them. The way we have set up the bosses above, this is still the case. That's why you shouldn't notice any difference.






    Step 9 - Fix MTZ's boss's priority


    You're probably thinking, "What? This boss gets it's very own step?" Yup, and the reason why, is because there is a free universal SST! Hoozah! That means we can fix this boss without making everything use the same priority!


    Like the others, the boss uses priority for other things (you know the little flame on Eggman's ship? Priority is used for that y_pos. But luckily for us, anim_frame_duration's SST is completely free. Also, the SST after it (objoff_1F) is also free! So we can use this as a word!


    Unfortunately, it seems that I couldn't make the flame use "anim_frame_duration" and I'm not sure why. So instead, we're going to make the DisplaySprite read from anim_frame_duration.


    Okay, let's fix this damn boss.



    Go to "loc_3229E:" ("Obj54_Init:") and change this:

    Code (ASM):
    1.     move.w  #$180,priority(a0)

    to this:

    Code (ASM):
    1.     move.w  #$180,anim_frame_duration(a0)


    Still at this location, change:

    Code (ASM):
    1.     move.w  #$300,priority(a1)

    to this:

    Code (ASM):
    1.     move.w  #$300,anim_frame_duration(a1)


    Make sure you do NOT edit/change this line:

    Code (ASM):
    1.     move.w  y_pos(a0),priority(a0)
    This is for the y_pos of the flame.




    Go to these locations:

    XenoWhirl users:
    • loc_32966:
    • loc_32B1A:
    • loc_32B34:
    • loc_32B42:
    • loc_32B56:
    • loc_32CC0:




    SVN users ONLY:
    For first label, do a search for this line:
    Code (ASM):
    1.     move.l  objoff_34(a0),objoff_34(a1)
    Second label is "Obj53_SetAnimPriority:"

    Third label is directly underneath the "Obj53_SetAnimPriority:" label

    Fourth Label is directly underneath the previous label we were at

    Fifth label is directly underneath the previous label we were at

    And sixth label is "Obj54_Laser_Init:"




    And change the priority to anim_frame_duration. For example (SVN users, reference only):

    Code (ASM):
    1. loc_32B34:
    2.     move.b  #4,mapping_frame(a0)
    3.     move.w  #$100,anim_frame_duration(a0)
    4.     rts


    Next, find the label "JmpTo40_DisplaySprite". Only the MTZ boss jumps to this location for displaying a sprite, so again, we know it's safe to edit. You should see this (SVN user, you're will look ever so slightly different, but you can still copy and paste my fix that I'm about to show):

    Code (ASM):
    1. JmpTo40_DisplaySprite
    2.     jmp DisplaySprite

    Change it to this:

    Code (ASM):
    1. JmpTo40_DisplaySprite
    2.     move.w  anim_frame_duration(a0),d0
    3.     jmp DisplaySprite3

    There we go! This boss is now fixed and will no longer freeze. And the priorities haven't been changed! Hooray! If only the other bosses we had to edit would play ball.





    Step 10 update (15/07/12) - Fix the priority for when the main character is dead


    As previously stated, some objects use the priority SST for other purposes. That's why a lot of them use DisplaySprite3 instead.


    The problem is, when Sonic (main character) dies, all objects freeze (well, most of them anyway). When they've frozen, they all jump to "RunObjectDisplayOnly:"

    Code (ASM):
    1. RunObjectDisplayOnly:
    2.     moveq   #0,d0
    3.     move.b  (a0),d0 ; get the object's ID
    4.     beq.s   +   ; if it's obj00, skip it
    5.     tst.b   render_flags(a0)    ; should we render it?
    6.     bpl.s   +           ; if not, skip it
    7.     bsr.w   DisplaySprite
    8. +
    9.     lea next_object(a0),a0 ; load 0bj address
    10.     dbf d7,RunObjectDisplayOnly
    11.     rts
    12. ; End of function RunObjectDisplayOnly

    As you can see, ALL objects will jump to DisplaySprite. This wasn't a problem before because it just took a byte from the priority and then convert it to a word, which was always even. But now, it doesn't with our new manager. It just takes the word. So, if the priority SST is odd, it will still try to display it, and then freeze.


    Most of the time, when Sonic dies, everything will be fine. But if Sonic dies near the EHZ bridge, CNZ spring, ARZ boss, most stuff that use DisplaySprite3, the game will freeze. Because instead of displaying whatever is at d0, it's jumping to DisplaySprite, and taking it from priority SST instead, which is more-than-likely odd (because it was used for something else).


    What we need to do, is set a new priority for objects that use priority for other reasons when Sonic is dead. So, we're going to change the branch to DisplaySprite to something else. So, go to "RunObjectDisplayOnly:" and change it to this:

    Code (ASM):
    1. RunObjectDisplayOnly:
    2.     moveq   #0,d0           ; Clear d0 quickly
    3.     move.b  (a0),d0         ; get the object's ID
    4.     beq.s   ++          ; if it's obj00, skip it
    5.     tst.b   render_flags(a0)    ; should we render it?
    6.     bpl.s   ++          ; if not, skip it
    7.     move.w  priority(a0),d0     ; move object's priority to d0
    8.     btst    #6,render_flags(a0) ; is the compound sprites flag set?
    9.     beq.s   +           ; if not, branch
    10.     move.w  #$200,d0        ; move $200 to d0
    11. +
    12.     bsr.w   DisplaySprite3      ; Display the object with whatever is set at d0
    13. +
    14.     lea next_object(a0),a0  ; load 0bj address
    15.     dbf d7,RunObjectDisplayOnly
    16.     rts
    17. ; End of function RunObjectDisplayOnly

    So now, when Sonic dies, all objects that use priority for something else that are frozen will have their priority set to #$200. Any objects that use priority for priority, will use their original priority. This is what most objects use anyway. I didn't actually notice any difference to when Sonic normally dies.


    Further explaination; when all objects jump here, it will ask first, does it have the compound sprite flag set? If not, branch and continue, and use the object's original priority. If it IS set, it means that the current object's status table also contains information about other child sprites which need to be drawn using the current object's mappings. In other words, for our sake, it's using priority for something different. Therefore, it will NOT branch and it will move $200 to d0 instead.


    Because of this now, objects that were originally DisplaySprite3, when Sonic dies, it won't try taking the priority SST anymore, and just use the #$200 we've set. If you enter debug when in the middle of dying, then exit so you're back alive, all objects will start using their orignial priority (or d0 if they were using DisplaySprite3).






    Step 11 - Fix the priority for Special Stages


    This took me fucking forever to fix! But luckily for you, I've figured it out without having to change any of the priorities (unlike the bosses we did earlier). It's going to be easy for you!


    Playing the special stages as they are now will make the game freeze within 2 seconds. That can't be good, right? We will need to fix this.



    Go to "Obj87:". This is the "Number of rings in Special Stage" object. This is what cause the special stage to freeze in a matter of seconds.


    You may notice that it's not moving anything to a priority SST. That's because the priority for this object is 0. The problem is, it's moving numbers from SST $19(a0) onwards. Mainly at $19(a0) is #$20. Now, because we've got the new priority manager, it will grab it's priority (which is 0), but as it's now a word, it will grab that #$20. So, it will have the data #$0020. DisplaySprite cannot process that, and crashes. We need to make it grab #$0000 and not #$0020, but we can't change anything at SST $19(a0), otherwise the special stage will start to misbehave. The fix? It's quite simple.



    At these locations:

    XenoWhirl users:
    • loc_7536:
    • loc_758C:
    • loc_75BC:
    • loc_75C8:
    • loc_7648:



    SVN users ONLY:

    First label is "loc_7536:"

    For second label, do a search for this line:
    Code (ASM):
    1. move.b  d2,sub2_mapframe-sub2_x_pos(a1) ; sub2_mapframe
    Third label is directly underneath the previous label we were at

    Fourth Label is directly underneath the previous label we were at

    And, for the fith label, do a seach for this line:
    Code (ASM):
    1.     move.w  #$D8,(a1)   ; sub?_x_pos
    and it's the + label directly underneath it




    You will see the code branching (in some way) to "JmpTo_DisplaySprite". We're going to create a new label for this object to jump to instead. So, at these locations, change all:

    Code (ASM):
    1. JmpTo_DisplaySprite

    to this:

    Code (ASM):
    1. JmpTo_DisplaySpriteSpecial

    Example (SVN users, reference only):

    Code (ASM):
    1. loc_7536:
    2.     move.b  d3,objoff_F(a0)
    3.     bra.w   JmpTo_DisplaySpriteSpecial

    Make sure you only change it to the new label at these locations!



    Next, go to the label itself "JmpTo_DisplaySprite" to see this (SVN user, you're will look ever so slightly different, but you can still copy and paste my fix that I'm about to show):

    Code (ASM):
    1. JmpTo_DisplaySprite
    2.     jmp DisplaySprite.l

    Directly underneath it, add this:

    Code (ASM):
    1. JmpTo_DisplaySpriteSpecial
    2.     move.w  #0,d0
    3.     jmp DisplaySprite3.l

    So you end up with this (SVN users, reference only):

    Code (ASM):
    1. JmpTo_DisplaySprite
    2.     jmp DisplaySprite.l
    3.    
    4. JmpTo_DisplaySpriteSpecial
    5.     move.w  #0,d0
    6.     jmp DisplaySprite3.l

    Now, obj87 only, will jump here for displaying the sprite. It will force #$0000 to be displayed instead. And it doesn't affect any of the object itself. We couldn't edit the "JmpTo_DisplaySprite" label itself because other objects are jumping to that label, and we don't want to edit other object's priority.


    The special stage will no longer freeze within seconds...



    But hold on there!



    I'm sorry, the special stage can still freeze halfway through. Yeah, another object does not want to play ball. The "Messages/checkpoint from Special Stage" object.


    This object displays the "Rings to go!" text and the "Collect 40 rings" text and to make them fly apart from each other. It also displays the "Cool" and "Not enough rings" text and the hand with the blue emblem, etc. To be totally honest, this object is almost perfect, it's the "Rings to go" that's causing the freezing. The problem is pretty much the same as "Number of rings in Special Stage" object.



    Go to "loc_357B2:" ("Obj5A_FlashMessage:") And you should see (SVN users, reference only):

    Code (ASM):
    1. loc_357B2:
    2.     tst.b   ($FFFFDBA0).w
    3.     bne.w   return_357D0
    4.     tst.b   ($FFFFDBA6).w
    5.     bne.s   return_357D0
    6.     move.b  ($FFFFFE0F).w,d0
    7.     andi.b  #7,d0
    8.     cmpi.b  #6,d0
    9.     bcs.w   JmpTo44_DisplaySprite

    That branch to "JmpTo44_DisplaySprite" is the problem. Looking at the object, this bit's priority is always 0. So, change the "JmpTo44_DisplaySprite" to "JmpTo_DisplaySpriteSpecial2", so you have this (SVN users, reference only):

    Code (ASM):
    1. loc_357B2:
    2.     tst.b   ($FFFFDBA0).w
    3.     bne.w   return_357D0
    4.     tst.b   ($FFFFDBA6).w
    5.     bne.s   return_357D0
    6.     move.b  ($FFFFFE0F).w,d0
    7.     andi.b  #7,d0
    8.     cmpi.b  #6,d0
    9.     bcs.w   JmpTo_DisplaySpriteSpecial2

    Yes, we're creating another new label! It's the best way to do it. Next, go to "loc_35F76:". Right above that label, paste this:

    Code (ASM):
    1. JmpTo_DisplaySpriteSpecial2:
    2.     move.w  #0,d0
    3.     jmp Displaysprite3.l

    So you end up with this (SVN users, reference only):

    Code (ASM):
    1. JmpTo_DisplaySpriteSpecial2:
    2.     move.w  #0,d0
    3.     jmp Displaysprite3.l
    4.  
    5. loc_35F76:
    6.     add.w   d0,d0
    7.     move.w  d0,d1
    8.     add.w   d0,d0
    9.     add.w   d1,d0
    10.     move.w  word_35F92(pc,d0.w),(Normal_palette_line4+$16).w
    11.     move.w  word_35F92+2(pc,d0.w),(Normal_palette_line4+$18).w
    12.     move.w  word_35F92+4(pc,d0.w),(Normal_palette_line4+$1A).w
    13.     rts

    Fixed! That part of the object will be forced to use #$0000 again and to display it. The rest of the object is completely fine though, and does not need editing (plus, the rest of it isn't using 0).






    Step 12 - Fix the CNZ pull-spring


    [​IMG]


    This CNZ spring has lost it's width_pixels SST. Similar problem to what the priorty manager has done, it's width_pixel is being over-written by something else in the objects code.


    If you've already applied this fix to your hack from my "How to free up 2 universal SST's" guide, you can skip this step. Otherwise, here is a quote from the topic:



    Now, the object will work as it should do.






    Step 13 update (15/07/12) - Fix the priority for Tails' tails


    This bug I only noticed the other day (was also present in S2R). The fix was a bit more involved than I thought.

    The problem is, Tails' tail's priority has been affected. Even though Tails' priority is $100, and his tails' priority is $100. Here is an example:

    [​IMG]


    It seems that his tails are not as much as a priority anymore. Anyway, flamewing had a solution, which he uses for his hack; Sonic 2 Heroes.



    Anyway, the fix:


    First, you're going to have to use some RAM. Only a word. So, go to your list of equates. I used $FFFFF5C0 (it's free whether you use the S1 sound driver or not). Call the RAM "Tails_Tails_ptr"

    Code (ASM):
    1. Tails_Tails_ptr =       ramaddr( $FFFFF5C0 )

    Our new RAM is ready for use. First, go to "InitPlayers:" and under the line:

    Code (ASM):
    1.     move.b  #2,(Sidekick).w ; load Obj02 Tails object at $FFFFB040

    SVN users, yours will say:

    Code (ASM):
    1.     move.b  #ObjID_Tails,(Sidekick+id).w ; load Obj02 Tails object at $FFFFB040

    Insert this:

    Code (ASM):
    1.     move.w  #Tails_Tails,(Tails_Tails_ptr).w

    So you have something like this (SVN users, reference only):

    Code (ASM):
    1.     move.b  #2,(Sidekick).w ; load Obj02 Tails object at $FFFFB040
    2.     move.w  #Tails_Tails,(Tails_Tails_ptr).w
    3.     move.w  (MainCharacter+x_pos).w,(Sidekick+x_pos).w
    4.     move.w  (MainCharacter+y_pos).w,(Sidekick+y_pos).w
    5.     subi.w  #$20,(Sidekick+x_pos).w
    6.     addi.w  #4,(Sidekick+y_pos).w
    7.     move.b  #8,(Tails_Dust).w ; load Obj08 Tails' spindash dust/splash object at $FFFFD140


    Do the same at label "InitPlayers_TailsAlone:", so you have something like this (SVN users, reference only):

    Code (ASM):
    1. InitPlayers_TailsAlone:
    2.     move.b  #2,(MainCharacter).w ; load Obj02 Tails object at $FFFFB000
    3.     move.w  #Tails_Tails,(Tails_Tails_ptr).w
    4.     move.b  #8,(Tails_Dust).w ; load Obj08 Tails' spindash dust/splash object at $FFFFD100
    5.     addi.w  #4,(MainCharacter+y_pos).w
    6.     rts
    7. ; End of function InitPlayers



    Next, go to "loc_A2F2:". This is for when you're at the cutscene at the end of the game.


    XenoWhirl users. Change this:

    Code (ASM):
    1. loc_A2F2:
    2.     moveq   #$E,d0
    3.     move.b  #2,(a1) ; load Tails object
    4.     move.b  #$81,$2A(a1)
    5.     move.b  #5,(Object_RAM+$80).w ; load Obj05 (Tails' tails) at $FFFFB080
    6.     move.w  a1,(Object_RAM+$80+parent).w
    7.     rts

    to this:

    Code (ASM):
    1. loc_A2F2:
    2.     moveq   #$E,d0
    3.     move.b  #2,(a1) ; load Tails object
    4.     move.b  #$81,$2A(a1)
    5.     move.w  #Object_RAM+$80,(Tails_Tails_ptr).w
    6.     rts




    SVN users, change this:

    Code (ASM):
    1. loc_A2F2:
    2.     moveq   #$E,d0
    3.     move.b  #ObjID_Tails,id(a1) ; load Tails object
    4.     move.b  #$81,obj_control(a1)
    5.     move.b  #ObjID_TailsTails,(Tails_Tails_Cutscene+id).w ; load Obj05 (Tails' tails) at $FFFFB080
    6.     move.w  a1,(Tails_Tails_Cutscene+parent).w
    7.     rts

    to this:

    Code (ASM):
    1. loc_A2F2:
    2.     moveq   #$E,d0
    3.     move.b  #ObjID_Tails,id(a1) ; load Tails object
    4.     move.b  #$81,obj_control(a1)
    5.     move.w  #Tails_Tails_Cutscene,(Tails_Tails_ptr).w ; Tails' tails at $FFFFB080
    6.     rts





    Next, go to "Obj02_Init_Continued:", and change the last few lines. Change this:

    Code (ASM):
    1.     move.b  #5,(Tails_Tails).w ; load Obj05 (Tails' Tails) at $FFFFD000
    2.     move.w  a0,(Tails_Tails+parent).w ; set its parent object to this

    SVN users, yours will say:

    Code (ASM):
    1.     move.b  #ObjID_TailsTails,(Tails_Tails+id).w ; load Obj05 (Tails' Tails) at $FFFFD000
    2.     move.w  a0,(Tails_Tails+parent).w ; set its parent object to this

    And change it to this:

    Code (ASM):
    1.     movea.w (Tails_Tails_ptr).w,a1
    2.     move.b  #5,0(a1) ; load Obj05 (Tails' Tails) at $FFFFD000
    3.     move.w  a0,parent(a1) ; set its parent object to this

    And change it to this:

    Code (ASM):
    1.     movea.w  (Tails_Tails_ptr).w,a1
    2.     move.b  #ObjID_TailsTails,id(a1) ; load Obj05 (Tails' Tails)
    3.     move.w  a0,parent(a1) ; set its parent object to this



    One more step! Go to "Obj02:" See the command jmp Obj02_States(pc,d1.w) (jmp Obj02_Index(pc,d1.w))? Change the "jmp" to a "jsr".


    And directly underneath it, add this:

    Code (ASM):
    1.     movea.w (Tails_Tails_ptr).w,a1
    2.     tst.b   routine(a1)
    3.     beq.s   +
    4.     jmp (DisplaySprite2).l
    5. +
    6.     rts

    So you have something like this (SVN users, reference only):

    Code (ASM):
    1. Obj02:
    2.     ; a0=character
    3.     cmpi.w  #2,(Player_mode).w
    4.     bne.s   +
    5.     move.w  (Camera_Min_X_pos).w,(Tails_Min_X_pos).w
    6.     move.w  (Camera_Max_X_pos).w,(Tails_Max_X_pos).w
    7.     move.w  (Camera_Max_Y_pos_now).w,(Tails_Max_Y_pos).w
    8. +
    9.     moveq   #0,d0
    10.     move.b  routine(a0),d0
    11.     move.w  Obj02_States(pc,d0.w),d1
    12.     jsr Obj02_States(pc,d1.w)
    13.     movea.w (Tails_Tails_ptr).w,a1
    14.     tst.b   routine(a1)
    15.     beq.s   +
    16.     jmp (DisplaySprite2).l
    17. +
    18.     rts



    Done! Tails' tails should now work with the right priority! Tails will now queue his tails for drawing right after himself. Without this, the tails will be queued for drawing after all objects between Tails and the tails that have the same priority. And that's why before, Tails' tails had less priority.



    [​IMG]






    Step 14 - One extra fix for SVN users


    There seems to be a bug with following this guide with the SVN disassembly. If you're a XenoWhirl's user, you're safe and can ignore this step. SVN user? Read on.


    Tip: There is an alternative fix pointed out by ShadowsofYesterday with his post here and it's up to you on which fix you take. Either is good.

    Seems that if you follow this guide as an SVN user, there may be a glitch with the Special Stages. Sonic and Tails act like they're being hit by the bombs object over and over, making it impossible to get any emeralds in the special stages. The reason? inertia is being used for something else in the special stages. This doesn't seem to be the case in Xenowhirl's dis.


    So, in your SST table, you're better off switching inertia with something else. invulnerable_time seems to be the best to swap it with. So change them so you end up with this:


    Code (ASM):
    1. invulnerable_time = $20 ; and $21 ; time remaining until you stop blinking
    Code (ASM):
    1. inertia =       $30 ; and $31 ; directionless representation of speed... not updated in the air
    The reason being is because Xeno and SVN disassemblies slightly alter in equates.






    Step 15 - Improve ObjectMove and ObjectMoveandFall (optional, therefore, spoilered)


    Want to speed up your hack even more? This step actually has nothing to do with the priority manager, but it wasn't worth making a new topic of it's own. And as this topic is about speeding up Sonic 2, I might as well post it here.


    In S2 and S3K, there's a subroutine called "ObjectMove" and another subroutine called "ObjectMoveandFall". ObjectMove allows objects to move about and "ObjectMoveandFall" does the same, except it applies gravity to it.


    S3K has the same subroutines except the code is a bit shorter and therefor, it's faster. It may not look much faster, but again, with all the objects on screen, it has to apply it for every frame.


    [​IMG]


    Just like before, in every single frame, the game has to "ObjectMoveandFall" each scattered ring, "ObjectMove" each and every single bubble, and the two sharks, "ObjectMoveandFall" Sonic and Tails, and "ObjectMove" the rotating platforms on the side. It has to happen to these objects every single frame. So, with S3K's shorter code, it can do all this quicker, and again, get on with other things.


    To do this, it's really simple.



    Go to "ObjectMoveAndFall:" and change this:

    Code (ASM):
    1. ObjectMoveAndFall:
    2.     move.l  x_pos(a0),d2    ; load x position
    3.     move.l  y_pos(a0),d3    ; load y position
    4.     move.w  x_vel(a0),d0    ; load x speed
    5.     ext.l   d0
    6.     asl.l   #8,d0   ; shift velocity to line up with the middle 16 bits of the 32-bit position
    7.     add.l   d0,d2   ; add x speed to x position ; note this affects the subpixel position objoff_A(a0) = 2+x_pos(a0)
    8.     move.w  y_vel(a0),d0    ; load y speed
    9.     addi.w  #$38,y_vel(a0)  ; increase vertical speed (apply gravity)
    10.     ext.l   d0
    11.     asl.l   #8,d0   ; shift velocity to line up with the middle 16 bits of the 32-bit position
    12.     add.l   d0,d3   ; add old y speed to y position ; note this affects the subpixel position objoff_E(a0) = 2+y_pos(a0)
    13.     move.l  d2,x_pos(a0)    ; store new x position
    14.     move.l  d3,y_pos(a0)    ; store new y position
    15.     rts
    16. ; End of function ObjectMoveAndFall

    To this:

    Code (ASM):
    1. ObjectMoveAndFall:
    2.     move.w  x_vel(a0),d0
    3.     ext.l   d0
    4.     lsl.l   #8,d0
    5.     add.l   d0,x_pos(a0)
    6.     move.w  y_vel(a0),d0
    7.     addi.w  #$38,y_vel(a0) ; apply gravity
    8.     ext.l   d0
    9.     lsl.l   #8,d0
    10.     add.l   d0,y_pos(a0)
    11.     rts
    12. ; End of function ObjectMoveAndFall


    Next, go to "ObjectMove:" and change this:

    Code (ASM):
    1. ObjectMove:
    2.     move.l  x_pos(a0),d2    ; load x position
    3.     move.l  y_pos(a0),d3    ; load y position
    4.     move.w  x_vel(a0),d0    ; load horizontal speed
    5.     ext.l   d0
    6.     asl.l   #8,d0   ; shift velocity to line up with the middle 16 bits of the 32-bit position
    7.     add.l   d0,d2   ; add to x-axis position    ; note this affects the subpixel position objoff_A(a0) = 2+x_pos(a0)
    8.     move.w  y_vel(a0),d0    ; load vertical speed
    9.     ext.l   d0
    10.     asl.l   #8,d0   ; shift velocity to line up with the middle 16 bits of the 32-bit position
    11.     add.l   d0,d3   ; add to y-axis position    ; note this affects the subpixel position objoff_E(a0) = 2+y_pos(a0)
    12.     move.l  d2,x_pos(a0)    ; update x-axis position
    13.     move.l  d3,y_pos(a0)    ; update y-axis position
    14.     rts
    15. ; End of function ObjectMove

    to this:

    Code (ASM):
    1. ObjectMove:
    2.     move.w  x_vel(a0),d0
    3.     ext.l   d0
    4.     lsl.l   #8,d0
    5.     add.l   d0,x_pos(a0)
    6.     move.w  y_vel(a0),d0
    7.     ext.l   d0
    8.     lsl.l   #8,d0
    9.     add.l   d0,y_pos(a0)
    10.     rts
    11. ; End of function ObjectMove

    That's that. For any object that has to move, it will now do this coding a lot quicker each frame.






    Step 16 - Finish


    That's it guys! All done! You have now successfully ported Sonic 3 and Knuckles' Priority Manager into Sonic 2. And if you followed step 14, you've improved (optimised) ObjectMove(andFall). Your hack should now be experiencing less lag! And just in time for the hacking contest, hey?


    Please be aware, that this will not elimate all lag from your hack. It just helps to get rid of some of it. To get rid of more lag, you need to do more; like Object Managers and etc.


    To demonstrate how things may of improved, here are two videos to compare the Priority Managers (and the ObjectMove(andFall)).



    Demonstration of lag with S2's Priority Manager and ObjectMove(andFall)





    Demonstration of lag with S3K's Priority Manager and ObjectMove(andFall)





    ROM to download to test yourself

    To download Sonic 2 With S3K's Priority Manager and ObjectMove(andFall) already ported, then click here to download and to try for yourself!




    That's it to it. If you have any problems/questions/comments, please say.



    But may I ask? If you follow this guide, when you announce to the community that you have S3K's Priority Manager (due to following my guide), please say that you were guided by my guide. It's nice to know that I have helped to make a difference and that all this was not a waste of time. Although, this is optional.



    If anyone wants to add this guide to the SCHG, feel free.




    Many thanks, members! Enjoy your speedier hack!


    Cheers,
    redhotsonic



    WARNING: Do not use Module's suggestion below. This will make the priorities on many objects go wrong. Bug with Module's suggestion realised here.
     
  2. MoDule

    MoDule

    Tech Member
    327
    24
    18
    Procrastinating from writing bug-fix guides
    One thing:
    Code (ASM):
    1.     move.b  (a1)+,priority(a0)
    2.     move.w  priority(a0),d0
    3.     lsr.w   #1,d0
    4.     andi.w  #$380,d0
    5.     move.w  d0,priority(a0)
    I'd change it to this:
    Code (ASM):
    1.     moveq   #0,d0
    2.     move.b  (a1)+,d0
    3.     lsr.w   #1,d0
    4.     andi.w  #$380,d0
    5.     move.w  d0,priority(a0)
    On the bosses, a lot of them use the compound sprite system, where a single object holds multiple sprite definitions. These tend to collide with a lot of other SST variables. Just an FYI for those interested.
     
  3. dsrb

    dsrb

    Member
    3,149
    0
    16
    The crowd, ever united, continued to bellow its aged but no less passionate refrain:

    ‘redhotsonic for Tech Member'
     
  4. redhotsonic

    redhotsonic

    Also known as RHS Tech Member
    1,587
    10
    18
    United Kingdom
    YouTuber
    WARNING: Do not use Module's suggestion above. This will make the priorities on many objects go wrong. Bug with Module's suggestion realised here


    Spoiled this part of the post as now the code doesn't work:

    Any improvements are welcome. This will make it ever-so slightly faster, but it won't be noticeable in game-play (as this code only runs when creating the object). But anything that makes the code faster is a plus in my books.






    Exactly. Same with the Special Stage. But in the special stage, the objects I had to change all used the same priority (which was 0), so I had no problems there. Exactly the same as MCZ boss, they were all the same priorities.


    MTZ boss, there was a universal SST free (word), so I was able to fix that without losing any of the original priorities.


    It's just a shame about the ARZ, CNZ, and OOZ boss. They were all used so all the sub-objects priorities had to be the same as the boss. I tried my best to figure out a real fix, but to my knowledge, there isn't one. The fix I did show works though, and I saw no difference whatsoever, so it will have to do.
     
  5. Tets

    Tets

    one rude dude Oldbie
    904
    70
    28
    Posting my issue here as per your request. I followed this guide using the latest Sonic 2 disassembly available on the repository and now I'm running into boss freezes. Almost all bosses now freeze the game immediately, before they appear on screen. The exceptions are CPZ and HTZ, which seem to play nicely until they are defeated, and the game freezes after the screen unlocks.

    I'm all but certain the core of the problem lies in how equates are declared in the more recent disassemblies, which are a fair bit different from the 2007 version. For reference, I also followed the free 2 universal SSTs guide, both parts, so presumably any differences between disassemblies could be causing problems with that as well. Either way, I can't wrap my head around what's going wrong here. It's infuriating, to say the least.
     
  6. redhotsonic

    redhotsonic

    Also known as RHS Tech Member
    1,587
    10
    18
    United Kingdom
    YouTuber

    I stared at this bit for ages.... then it came to me


    EGG PRISON!



    Do me a favour in your hack. Enable debug, then change Sonic into an egg prison, then paste it in the game, as soon as you paste it in, does it freeze?




    If so, you haven't followed Step 3 in the priority guide correctly. Remember, in step 3, I did say that the Egg Prison is slightly different to the rest of the objects in that certain step.



    Here's a quote of the Egg Prison for that step:

     
  7. Tets

    Tets

    one rude dude Oldbie
    904
    70
    28
    Fuck me, I was way off. Never would have thought about that. Everything is working just as it should now.
     
  8. Hitaxas

    Hitaxas

    Retro 80's themed Twitch streamer ( on hiatus) Member
    Upon following this guide I ran into some issue, which I expected.

    I originally had $21 set as double_jump, for certain purposes I am sure people can figure out. However, Since inertia was to be changed to $20, I moved double_jump to $2F. I of course also changed width_pixels to $14.

    This issue? Sonic's collision with the ground was now a bit messed up, he would walk slow as hell, the resetonfloor routine wouldn't clear his double_jump flag, sometimes when collecting a ring that spilled from getting hurt the game would crash and tell me there was an issue with the displaysprite routine at line cmpi.w #$7E,(a1) (using Flamewing's debugger routine)

    So I went and moved inertia to $1E, which seems to fix Sonic's ground collision. However, the ring issue still remains.

    Edit: I've ported this into other hacks of mine, and come up with the same, or more issues. =/
     
  9. redhotsonic

    redhotsonic

    Also known as RHS Tech Member
    1,587
    10
    18
    United Kingdom
    YouTuber
    Glad to hear it.








    You can't move his inertia to $1E; that's being used by anim_frame_duration; unless you moved that elsewhere. Doesn't Sonic animate funny since moving his inertia there?


    Also, $2F is part of the move_lock SST, unless you've made move_lock into a byte like I did a while ago, then it's okay.




    What you're better off doing, is setting your double_jump to $1F ($1E is anim_frame_duration, but Sonic treats this as a byte, so $1F is unused). Then, $20 and $21 is free, so you can stick inertia there. Unless you've accidently set something else at $20 or $21, Sonic should be okay. Width_pixels is then free to go to $14, making $18 and $19 Priority.

    If there's still problems, there may be some un-equated SST's in your ASM file.




    As for this freeze/crash; does it only happen when you collect scattered rings? Or can it happen with normal rings?
     
  10. Hitaxas

    Hitaxas

    Retro 80's themed Twitch streamer ( on hiatus) Member
    I get the same issue I mentioned before when changing double_jump to $1E and inertia to $20.


    anim_frame_duration has been at $23 for SSD since I have no clue when, so that wouldn't be conflicting, right?


    The freeze/crash is with scattered rings only. Everything else was working fine.
     
  11. redhotsonic

    redhotsonic

    Also known as RHS Tech Member
    1,587
    10
    18
    United Kingdom
    YouTuber
    Can you post a list of your SST equates please?





    Have you followed step 4 correctly? Go to "Obj_37_sub_4:" and tell me, what is it moving to priority? Is it still a byte? It should be a word and look similar to this:

    Code (ASM):
    1. Obj_37_sub_4:
    2.     addq.b  #2,routine(a0)
    3.     move.b  #0,collision_flags(a0)
    4.     move.w  #$80,priority(a0)
    5.     bsr.w   sub_11FC2
     
  12. Hitaxas

    Hitaxas

    Retro 80's themed Twitch streamer ( on hiatus) Member
    It would seem that is the only priority that I changed to move.w but fail to change it from #1 to #$80. It's all fixed now I believe.
     
  13. RetroKoH

    RetroKoH

    Member
    1,662
    22
    18
    Project Sonic 8x16
    redhotsonic... We are continuing the push for your Tech Membership.
    I'm gonna try this out, and port this into my Sonic 2 Rev C hack.

    I have you a page... its not complete, but includes all necessary items.
    PLEASE look through and make sure I've made no mistakes... I will finish it and include the optional items, and fix any errors as needed later.

    http://info.sonicretro.org/SCHG_How-to:Port_S3K_Priority_Manager_into_Sonic_2

    Enjoy! God damn that was a pain in the ass....
     
  14. Tets

    Tets

    one rude dude Oldbie
    904
    70
    28
    Not sure if this is another case of my doing something wrong, but the control window in the Silver Sonic fight in DEZ appears in front of the player character. I've double- and triple-checked the priority values and they're exactly what they should be ($100 for Sonic/Tails and $180 for ObjAF).
     
  15. redhotsonic

    redhotsonic

    Also known as RHS Tech Member
    1,587
    10
    18
    United Kingdom
    YouTuber

    Looks good to me. I guess you couldn't colour code them like I did in the forums? Some instructions for labels need their own line too, like here for example:


    This just needs neatening up a bit. Otherwise, when they read "Under each of these labels, you should see this line:", they may think it's still for SVN only.







    Those priorities are correct. But I just looked at DEZ myself and I do not suffer this problem. Have a look to see if these values are correct in yours (taken from SVN):

    Code (ASM):
    1. ; off_377B4:
    2. ObjAF_SubObjData:
    3.     subObjData ObjAF_Obj98_MapUnc_39E68,make_art_tile(ArtTile_ArtNem_CNZBonusSpike,1,0),$84,5,4,$98

    Code (ASM):
    1. ; off_39DCE:
    2. ObjAF_SubObjData2:
    3.     subObjData ObjAF_Obj98_MapUnc_39E68,make_art_tile(ArtTile_ArtNem_SilverSonic,1,0),4,4,$10,$1A
    4. ; off_39DD8:
    5. ObjAF_SubObjData3:
    6.     subObjData ObjAF_MapUnc_3A08C,make_art_tile(ArtTile_ArtNem_DEZWindow,0,0),4,6,$10,0
     
  16. RetroKoH

    RetroKoH

    Member
    1,662
    22
    18
    Project Sonic 8x16
    I've fucked around with it a little bit. I'm going to give the optional item its own page, and will create the SST page today as well. Steps 1 and 12 will link to this new page instead of having excerpts put in... it just feels like the page is WAY too long.

    I'll try to tidy it up a little more in the future... but with the color, it should be easy enough to follow. Lemme know if you got any issues with it that you'd like me to fix up. I will try to look into including images on the wiki... as I think almost all of the guides (Not just the ones I published on the wiki) could benefit by having the visuals.

    Also, I made another page: http://info.sonicretro.org/SCHG_How-to:Improve_ObjectMove_subroutines
    I'm gonna try to fix shit up this evening if I have time. Also I still gotta put together the SST guide from ages ago. Haven't forgotten. Just really backlogged...
     
  17. Esrael

    Esrael

    Neto Tech Member
    304
    257
    63
    Brazil, São Paulo, Guarulhos
    Neto Assembler Editor / Sonic 2 Delta / Neto MD-DOS
    About processor cycles / speed up I had a question.
    For the time I don't want changing all object, but using a table will make any differences?


    This Code
    Code (ASM):
    1.  
    2. ;===============================================================================
    3. ; Sub Rotina DisplaySprite
    4. ; [ Início ]
    5. ;===============================================================================
    6. DisplaySprite: ; Offset_0x01647A:
    7.               ; lea     ($FFFFAC00).w, A1                    
    8.               ; move.w  $0018(A0), D0
    9.               ; lsr.w   #$01, D0                              
    10.               ; andi.w  #$0380, D0                                
    11.               ; adda.w  D0, A1
    12. ;-------------------------------------------------------------------------------
    13.                 moveq   #$00, D0                               ; Neto - Sonic 2D    
    14.                 move.b  $0018(A0), D0                          ; Neto - Sonic 2D  
    15.                 add.w   D0, D0                                 ; Neto - Sonic 2D  
    16.                 add.w   D0, D0                                 ; Neto - Sonic 2D  
    17.                 move.l  DisplayTable(PC, D0), A1               ; Neto - Sonic 2D  
    18. ;-------------------------------------------------------------------------------              
    19.                 cmpi.w  #$007E, (A1)
    20.                 bcc.s   Exit_DisplaySprite                     ; Offset_0x016496
    21.                 addq.w  #$02, (A1)
    22.                 adda.w  (A1), A1
    23.                 move.w  A0, (A1)
    24. Exit_DisplaySprite: ; Offset_0x016496:
    25.                 rts      
    26. ;===============================================================================
    27. ; Sub Rotina DisplaySprite
    28. ; [ Término ]
    29. ;===============================================================================  
    30.  
    31. DisplayTable:
    32.                 dc.l   $FFFFAC00, $FFFFAC80, $FFFFAD00, $FFFFAD80
    33.                 dc.l   $FFFFAE00, $FFFFAE80, $FFFFAF00, $FFFFAF80
    34.                 dc.l   $FFFFAC00, $FFFFAC80, $FFFFAD00, $FFFFAD80
    35.                 dc.l   $FFFFAE00, $FFFFAE80, $FFFFAF00, $FFFFAF80  
    36.  
    37.  
    vs original

    Code (ASM):
    1.  
    2. ;===============================================================================
    3. ; Sub Rotina DisplaySprite
    4. ; [ Início ]
    5. ;===============================================================================
    6. DisplaySprite: ; Offset_0x01647A:
    7.                 lea     ($FFFFAC00).w, A1                    
    8.                 move.w  $0018(A0), D0
    9.                 lsr.w   #$01, D0                              
    10.                 andi.w  #$0380, D0                                
    11.                 adda.w  D0, A1          
    12.                 cmpi.w  #$007E, (A1)
    13.                 bcc.s   Exit_DisplaySprite                     ; Offset_0x016496
    14.                 addq.w  #$02, (A1)
    15.                 adda.w  (A1), A1
    16.                 move.w  A0, (A1)
    17. Exit_DisplaySprite: ; Offset_0x016496:
    18.                 rts      
    19. ;===============================================================================
    20. ; Sub Rotina DisplaySprite
    21. ; [ Término ]
    22. ;===============================================================================  
    23.  
    Another question? where we can find fast code execution tips? Ex:

    lsl #$02, D0 ; one line

    same results as

    add.w D0, D0
    add.w D0, D0
    Two lines but less processor cycles.

    Sorry for posting here, but since this port is for speeding up. I want to know, more tips for improving Speed with assembler.
    Sometimes less code lines does not mean fast execution.
     
  18. redhotsonic

    redhotsonic

    Also known as RHS Tech Member
    1,587
    10
    18
    United Kingdom
    YouTuber



    I would have thought it's faster than the original Sonic 2 way, but it wouldn't be by much. But then this way, obviously uses more space (because you've inserted a table). You've used more space but a little bit of time saving. This is the type of thing where you ask yourself, "Speed or Size?"




    If you're asking if it's faster then the S3K way, then it's definitely not. Your way, it still have to convert the byte into a word every single frame for every object (like normal Sonic2). And like said, the S3K way, it's already a word, and can just get on with things.





    I do not know, but I would like to know this myself.
     
  19. Esrael

    Esrael

    Neto Tech Member
    304
    257
    63
    Brazil, São Paulo, Guarulhos
    Neto Assembler Editor / Sonic 2 Delta / Neto MD-DOS

    Thanks for reply.

    I do know which your method is more faster, but I am trying to improve without doing a lot of change.
     
  20. flamewing

    flamewing

    Emerald Hunter Tech Member
    1,161
    65
    28
    France
    Sonic Classic Heroes; Sonic 2 Special Stage Editor; Sonic 3&K Heroes (on hold)
    Here it goes the speed analysis of the 3 versions:
    The common code runs in 38 cycles (9 read cycles/0 write cycles) if branch taken, 68 cycles (13 read cycles/2 write cycles) if not; this should be added to all the following figures for the total function time.

    The original S2 code runs in 44 cycles (9 read cycles/0 write cycles); Esrael's version is slightly faster at 42 cycles (10 read cycles/0 write cycles); the S3&K version presented by RedHotSonic* clocks 24 cycles (5 read cycles/0 write cycles), being the fastest. By pre-computing the multiplication by 4 when the priority is being assigned, Esrael's version can be go down by 8 cycles (2 read cycles/0 write cycles), down to a final speed of 34 cycles (8 read cycles/0 write cycles), being still slower than S3&K's.

    Here is the breakdown:

    Code (ASM):
    1. ; S2's version:
    2. DisplaySprite:
    3.         lea     (Sprite_Table_Input).w,a1       ;  8 (2/0)
    4.         move.w  priority(a0),d0                 ; 12 (3/0)
    5.         lsr.w   #1,d0                                   ;  8 (1/0)
    6.         andi.w  #$380,d0                                ;  8 (2/0)
    7.         adda.w  d0,a1                                   ;  8 (1/0)
    8. ; Total for S2's version:                         44 (9/0)
    9.  
    10. ; S3&K's version:
    11. DisplaySprite:
    12.         lea     (Sprite_Table_Input).w,a1       ;  8 (2/0)
    13.         adda.w  priority(a0),a1                 ; 16 (3/0)
    14. ; Total for S3&K's version:                       24 (5/0)
    15.  
    16. ; Esrael's version:
    17. DisplaySprite:
    18.         moveq   #0,d0                                   ;  4 (1/0)
    19.         move.b  priority(a0),d0                 ; 12 (3/0)
    20.         add.w   d0,d0                                   ;  4 (1/0)
    21.         add.w   d0,d0                                   ;  4 (1/0)
    22.         move.l  DisplayTable(pc,d0),a1  ; 18 (4/0)
    23. ; Total for Esrael's version:             42 (10/0)
    24.  
    25. ; Common to all:
    26.         cmpi.w  #$7E,(a1)                               ; 12 (3/0)
    27.         bcc.s   return_16510                    ; 10 (2/0) if taken, 8 (1/0) if not
    28.         addq.w  #2,(a1)                                 ; 12 (2/1)
    29.         adda.w  (a1),a1                                 ; 12 (2/0)
    30.         move.w  a0,(a1)                                 ;  8 (1/1)
    31. return_16510:
    32.         rts                                                             ; 16 (4/0)
    33. ; Total for common code:                          38 (9/0) if branch taken, 68 (13/2) if not
    * The actual version in S3&K has priority promotion code: if an object does not fit on its given priority slot, it is successively tries to promote to a lesser priority (higher value). This slows it down slightly if implemented; how much depends on how many sprites are on-screen of any given priority, but it probably runs at the best speed in most scenarios. For reference, here is the version I use in my hack:
    Code (ASM):
    1. DisplaySprite:
    2.         lea     (Sprite_Table_Input).w,a1
    3.         adda.w  priority(a0),a1
    4.  
    5. DisplaySprite_part2:
    6.         cmpi.w  #$7E,(a1)
    7.         bhs.s   DisplaySprite_part3
    8.         addq.w  #2,(a1)
    9.         adda.w  (a1),a1
    10.         move.w  a0,(a1)
    11.  
    12. return_16510:
    13.         rts
    14.        
    15. DisplaySprite_part3:
    16.         cmpa.w  #Sprite_Table_Input+7*$80,a1
    17.         beq.s   return_16510
    18.         adda.w  #$80,a1
    19.         bra.s   DisplaySprite_part2
    20. ; End of function DisplaySprite
    If you search the net for the m68000 user's manual, you can see it has a list of instruction speeds; this can be quite handy for evaluating code speed.

    Edit: Fixed as discussed below.