You can read up my guide from the Sonic Retro wiki found here. But if for some odd reason you want to read the guide from this topic which has formatting errors (due to the board keep getting updates), you may do so by opening up the spoiler:

WARNING:

Please back-up your disassembly before attempting to use this guide. I will not be held responsible for anything that goes wrong.

SSRG members! You've heard this guide first!

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, 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.

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.

It's so easy. First, go to Sonic's SST's (this will be in s2constants.asm for SVN users) and you'll see

inertia = $14 ; and $15 ; directionless representation of speed... not updated in the air

Change it to

inertia = $20 ; and $21 ; directionless representation of speed... not updated in the air

For Sonic and Tails, $20 and $21 is free, so move it there. Why? Simple. $14 is a "convention followed by most objects". I think $15 is still only used by Sonic and Tails, but that doesn't matter. Now, instead of the original $1F, $20 and $21 being free for Sonic and Tails, it's now $15 and $1F that is free for them ($14 is NOT free for Sonic and Tails! About to explain why!)

Next, go to:

width_pixels = $19

and change to

width_pixels = $14

width_pixels can be moved to $14. Which means, $19 is now free, and it's universal! How about that? Told you it was piss easy! I am using $19 as part of priority (in S3K's priority manager, the priority is a word, so I use $18 and $19). And that's the reason why $14 isn't free for Sonic and Tails, because it's being used for width_pixel. Yes, you're going to lose a SST from them but it's better to gain a universal SST, right?

So now, you have $19 free universally.

Click to expand...

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:

move.w priority(a0),d0
lsr.w #1,d0

andi.w #$380,d0

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.

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?

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:

move.w priority(a0),d0
lsr.w #1,d0

andi.w #$380,d0

move.w d0,priority(a0)

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

loc_112A4:
addq.b #2,routine(a0)

moveq #0,d0

move.b subtype(a0),d0

move.w d0,d1

lsl.w #3,d0

lea dword_111E6(pc),a1

lea (a1,d0.w),a1

move.b (a1),mapping_frame(a0)

move.l (a1)+,mappings(a0)

move.w (a1)+,art_tile(a0)

bsr.w Adjust2PArtPointer

ori.b #4,render_flags(a0)

move.b (a1)+,width_pixels(a0)

move.b (a1)+,priority(a0)

move.w priority(a0),d0

lsr.w #1,d0

andi.w #$380,d0

move.w d0,priority(a0)

lea byte_1128E(pc),a1

move.b (a1,d1.w),d1

beq.s BranchTo_MarkObjGone

move.b d1,y_radius(a0)

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):

loc_1131A:
addq.b #2,routine(a0)

move.b subtype(a0),d0

andi.w #$F,d0

lsl.w #3,d0

lea dword_11302(pc),a1

lea (a1,d0.w),a1

move.b (a1),mapping_frame(a0)

move.l (a1)+,mappings(a0)

move.w (a1)+,art_tile(a0)

bsr.w Adjust2PArtPointer

ori.b #4,render_flags(a0)

move.b (a1)+,width_pixels(a0)

move.b (a1)+,priority(a0)

move.w priority(a0),d0

lsr.w #1,d0

andi.w #$380,d0

move.w d0,priority(a0)

move.b subtype(a0),d0

andi.w #$F0,d0

lsr.b #4,d0

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):

loc_3F228:
_move.b 0(a0),0(a1) ; load obj

move.w x_pos(a0),x_pos(a1)

move.w y_pos(a0),y_pos(a1)

move.w y_pos(a0),objoff_30(a1)

move.l #Obj3E_MapUnc_3F436,mappings(a1)

move.w #$2680,art_tile(a1)

move.b #$84,render_flags(a1)

moveq #0,d0

move.b (a2)+,d0

sub.w d0,y_pos(a1)

move.w y_pos(a1),objoff_30(a1)

move.b (a2)+,routine(a1)

move.b (a2)+,width_pixels(a1)

move.b (a2)+,priority(a1)

move.w priority(a1),d0

lsr.w #1,d0

andi.w #$380,d0

move.w d0,priority(a1)

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:

LoadSubObject_Part3:
move.l (a1)+,mappings(a0)

move.w (a1)+,art_tile(a0)

jsr Adjust2PArtPointer

move.b (a1)+,d0

or.b d0,render_flags(a0)

move.b (a1)+,priority(a0)

move.b (a1)+,width_pixels(a0)

move.b (a1),collision_flags(a0)

addq.b #2,routine(a0)

rts

to this:

LoadSubObject_Part3:
move.l (a1)+,mappings(a0)

move.w (a1)+,art_tile(a0)

jsr Adjust2PArtPointer

move.b (a1)+,d0

or.b d0,render_flags(a0)

move.b (a1)+,priority(a0)

move.w priority(a0),d0

lsr.w #1,d0

andi.w #$380,d0

move.w d0,priority(a0)

move.b (a1)+,width_pixels(a0)

move.b (a1),collision_flags(a0)

addq.b #2,routine(a0)

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:

move.b #0,priority(a0)

Change it to this:

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:

move.b #1,priority(a0)

to this:

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 of become after the original calculations.

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

move.b #2,priority(a0)

to this:

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:

move.b #2,priority(a

Replace with:

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:

_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:

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:

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:

move.b priority(a0),priority(a1)
subi.b #1,priority(a1)

to this:

move.w priority(a0),priority(a1)
subi.w #$80,priority(a1)

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

loc_34864:
move.w #$400,objoff_32(a0)

move.b #$40,angle(a0)

move.b #1,($FFFFF7DE).w

clr.b collision_property(a0)

clr.b respawn_index(a0)

bsr.w loc_349C8

movea.l #Object_RAM+$180,a1

move.b #$63,(a1) ; load obj63 (shadow) at $FFFFB180

move.w x_pos(a0),x_pos(a1)

move.w y_pos(a0),y_pos(a1)

addi.w #$18,y_pos(a1)

move.l #Obj63_MapUnc_34492,mappings(a1)

move.w #$623C,art_tile(a1)

move.b #4,render_flags(a1)

move.w #$200,priority(a1)

move.l a0,objoff_38(a1)

movea.l #Object_RAM+$1C0,a1

move.b #$88,(a1) ; load obj88

move.w x_pos(a0),x_pos(a1)

move.w y_pos(a0),y_pos(a1)

move.l #Obj88_MapUnc_34DA8,mappings(a1)

move.w #$4316,art_tile(a1)

move.b #4,render_flags(a1)

move.w priority(a0),priority(a1)

subi.w #$80,priority(a1)

move.l a0,objoff_38(a1)

movea.l a1,a0

move.b #1,($FFFFF7DF).w

clr.b respawn_index(a0)

movea.l objoff_38(a0),a0 ; load 0bj address

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:

move.b priority(a1),d0
subq.b #1,d0

move.b d0,priority(a0)

to this:

move.w priority(a1),d0
subi.w #$80,d0

move.w d0,priority(a0)

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

move.b priority(a0),d4
subq.b #1,d4

to this:

move.w priority(a0),d4
subi.w #$80,d4

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

move.b d4,priority(a1)

to this:

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:

cmpi.b #2,priority(a0)

Change them to this:

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:

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:

move.w #$200,priority(a1)

Code:

move.w #$100,priority(a1)

You might as well get rid of them too.

Make sure you do NOT delete this line:

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:

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):

JmpTo37_DisplaySprite
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:

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 of 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:

move.w #$180,priority(a0)

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

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):

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:

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):

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):

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:

move.w #$180,priority(a0)

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

move.w #$200,priority(a0)

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

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):

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:

move.w #$180,priority(a0)

to this:

move.w #$180,anim_frame_duration(a0)

Still at this location, change:

move.w #$300,priority(a1)

to this:

move.w #$300,anim_frame_duration(a1)

Make sure you do NOT edit/change this line:

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:

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):

loc_32B34:
move.b #4,mapping_frame(a0)

move.w #$100,anim_frame_duration(a0)

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):

JmpTo40_DisplaySprite
jmp DisplaySprite

Change it to this:

JmpTo40_DisplaySprite
move.w anim_frame_duration(a0),d0

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:"

RunObjectDisplayOnly:
moveq #0,d0

move.b (a0),d0 ; get the object's ID

beq.s + ; if it's obj00, skip it

tst.b render_flags(a0) ; should we render it?

bpl.s + ; if not, skip it

bsr.w DisplaySprite

+

lea next_object(a0),a0 ; load 0bj address

dbf d7,RunObjectDisplayOnly

rts

; 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:

RunObjectDisplayOnly:
moveq #0,d0 ; Clear d0 quickly

move.b (a0),d0 ; get the object's ID

beq.s ++ ; if it's obj00, skip it

tst.b render_flags(a0) ; should we render it?

bpl.s ++ ; if not, skip it

move.w priority(a0),d0 ; move object's priority to d0

btst #6,render_flags(a0) ; is the compound sprites flag set?

beq.s + ; if not, branch

move.w #$200,d0 ; move $200 to d0

+

bsr.w DisplaySprite3 ; Display the object with whatever is set at d0

+

lea next_object(a0),a0 ; load 0bj address

dbf d7,RunObjectDisplayOnly

rts

; 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:

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:

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:

JmpTo_DisplaySprite

to this:

JmpTo_DisplaySpriteSpecial

Example (SVN users, reference only):

loc_7536:
move.b d3,objoff_F(a0)

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):

JmpTo_DisplaySprite
jmp DisplaySprite.l

Directly underneath it, add this:

JmpTo_DisplaySpriteSpecial
move.w #0,d0

jmp DisplaySprite3.l

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

JmpTo_DisplaySprite
jmp DisplaySprite.l

JmpTo_DisplaySpriteSpecial

move.w #0,d0

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):

loc_357B2:
tst.b ($FFFFDBA0).w

bne.w return_357D0

tst.b ($FFFFDBA6).w

bne.s return_357D0

move.b ($FFFFFE0F).w,d0

andi.b #7,d0

cmpi.b #6,d0

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):

loc_357B2:
tst.b ($FFFFDBA0).w

bne.w return_357D0

tst.b ($FFFFDBA6).w

bne.s return_357D0

move.b ($FFFFFE0F).w,d0

andi.b #7,d0

cmpi.b #6,d0

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:

JmpTo_DisplaySpriteSpecial2:
move.w #0,d0

jmp Displaysprite3.l

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

JmpTo_DisplaySpriteSpecial2:
move.w #0,d0

jmp Displaysprite3.l

loc_35F76:

add.w d0,d0

move.w d0,d1

add.w d0,d0

add.w d1,d0

move.w word_35F92(pc,d0.w),(Normal_palette_line4+$16).w

move.w word_35F92+2(pc,d0.w),(Normal_palette_line4+$18).w

move.w word_35F92+4(pc,d0.w),(Normal_palette_line4+$1A).w

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

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.

The problem like Module mentioned, was that part of it's height is involved and over-writes the new width_pixels SST. You cannot change the height's location of this object, otherwise the object will never work. But I realised in this object, that it's priority is used for something different as well. Instead, it's priority is moved to d0 then jumps to DisplaySprite3. So, I made width_pixels do the same thing, and now the object is fine. It will have to keep moving the byte to width_pixels every frame, but it's not going to make any lag. Anyway, the fix:

Go to "loc_2ABFA:"("Obj85_Init:") and you'll see this (SVN users, reference only):

loc_2ABFA:
bsr.w JmpTo49_Adjust2PArtPointer

move.b #4,render_flags(a0)

bset #6,render_flags(a0)

move.b #1,objoff_B(a0)

tst.b subtype(a0)

beq.s loc_2AC54

addq.b #2,routine(a0)

move.b #$20,objoff_E(a0)

move.b #$18,width_pixels(a0)

move.w x_pos(a0),objoff_2E(a0)

move.w y_pos(a0),objoff_34(a0)

move.w x_pos(a0),d2

move.w y_pos(a0),d3

addi.w #0,d3

move.b #1,objoff_F(a0)

lea $10(a0),a2

move.w d2,(a2)+

move.w d3,(a2)+

move.w #2,(a2)+

bra.w loc_2AE56

Cut out the line "move.b #$18,width_pixels(a0)" so it's not there anymore. We're going to move it.

Next, go to "Obj85:" and you'll see this (SVN users, reference only):

Obj85:
moveq #0,d0

move.b routine(a0),d0

move.w off_2ABCE(pc,d0.w),d1

jsr off_2ABCE(pc,d1.w)

move.w #$200,d0

tst.w (Two_player_mode).w

beq.s loc_2ABA0

bra.w JmpTo4_DisplaySprite3

Just before the "move.w #$200,d0", paste the width_pixel line there. So, you end up with this (SVN users, reference only):

Obj85:
moveq #0,d0

move.b routine(a0),d0

move.w off_2ABCE(pc,d0.w),d1

jsr off_2ABCE(pc,d1.w)

move.b #$18,width_pixels(a0) ; Now moved here instead of being at loc_2ABFA

move.w #$200,d0

tst.w (Two_player_mode).w

beq.s loc_2ABA0

bra.w JmpTo4_DisplaySprite3

Click to expand...

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:

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"

Tails_Tails_ptr = ramaddr( $FFFFF5C0 )

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

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:

movea.w (Tails_Tails_ptr).w,a1
tst.b routine(a1)

beq.s +

jmp (DisplaySprite2).l

+

rts

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

Obj02:
; a0=character

cmpi.w #2,(Player_mode).w

bne.s +

move.w (Camera_Min_X_pos).w,(Tails_Min_X_pos).w

move.w (Camera_Max_X_pos).w,(Tails_Max_X_pos).w

move.w (Camera_Max_Y_pos_now).w,(Tails_Max_Y_pos).w

+

moveq #0,d0

move.b routine(a0),d0

move.w Obj02_States(pc,d0.w),d1

jsr Obj02_States(pc,d1.w)

movea.w (Tails_Tails_ptr).w,a1

tst.b routine(a1)

beq.s +

jmp (DisplaySprite2).l

+

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.

Step 14 - Improve ObjectMove and ObjectMoveandFall (optional)

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.

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:

ObjectMoveAndFall:
move.l x_pos(a0),d2 ; load x position

move.l y_pos(a0),d3 ; load y position

move.w x_vel(a0),d0 ; load x speed

ext.l d0

asl.l #8,d0 ; shift velocity to line up with the middle 16 bits of the 32-bit position

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

Step 15 - 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)

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.

Also Is there a way to free up a 4th SST? I've read your post on freeing the 3rd SST, and I've fixed the CNZ Manuel Spring objects collision, but I have a feeling that there is a hole in the programming that we could free one more. I've gotten rid of all the rehashed/ unused Sonic 1 objects and now I have some extra ram space.

Hello, all! There is an update to the priority guide. 2 extra steps! These steps are actually bug fixes caused by the new priority manager, and you may want to implement these if you've followed my guide.

Thanks to flamewing for the help and advice on these fixes.

Step 10 update - Fix the priority for when the main character is dead

In my priority guide, for step 10, I explained that when you're dead, all the objects stop moving. And they all jump to "DisplaySprite:" via "RunObjectDisplayOnly:". Because of this, some objects that use priority for different reason will cause the game to crash. The way I showed you was that we had to make all objects use the same priority, so that it wouldn't freeze. Most of the time, this isn't a problem, but then, you'll may notice things like this:

This is because, when you die, all objects jump to the "RunObjectDisplayOnly:". As they're all using the same priority, a newer one will overlap. Take the EHZ boss. His drill gets displayed first, then his wheels (so they overlap), then the cockpit (so they overlap the rest), then finally Eggman (which overlaps all). See what I mean?

To fix this, it's simple, and I should have thought of it before (Sonic 2 Recreation has different coding and doesn't suffer this). Anyway, go to "RunObjectDisplayOnly:", and change from this:

RunObjectDisplayOnly:
moveq #0,d0

move.b (a0),d0 ; get the object's ID

beq.s + ; if it's obj00, skip it

tst.b render_flags(a0) ; should we render it?

bpl.s + ; if not, skip it

move.w #$200,d0

bsr.w DisplaySprite3

+

lea next_object(a0),a0 ; load 0bj address

dbf d7,RunObjectDisplayOnly

rts

; End of function RunObjectDisplayOnly

to this:

RunObjectDisplayOnly:
moveq #0,d0 ; Clear d0 quickly

move.b (a0),d0 ; get the object's ID

beq.s ++ ; if it's obj00, skip it

tst.b render_flags(a0) ; should we render it?

bpl.s ++ ; if not, skip it

move.w priority(a0),d0 ; move object's priority to d0

btst #6,render_flags(a0) ; is the compound sprites flag set?

beq.s + ; if not, branch

move.w #$200,d0 ; move $200 to d0

+

bsr.w DisplaySprite3 ; Display the object with whatever is set at d0

+

lea next_object(a0),a0 ; load 0bj address

dbf d7,RunObjectDisplayOnly

rts

; End of function RunObjectDisplayOnly

So now, 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.

That's it. Done, you'll now get stuff like this:

It was that simple.

Step 13 - 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:

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"

Tails_Tails_ptr = ramaddr( $FFFFF5C0 )

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

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:

movea.w (Tails_Tails_ptr).w,a1
tst.b routine(a1)

beq.s +

jmp (DisplaySprite2).l

+

rts

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

Obj02:
; a0=character

cmpi.w #2,(Player_mode).w

bne.s +

move.w (Camera_Min_X_pos).w,(Tails_Min_X_pos).w

move.w (Camera_Max_X_pos).w,(Tails_Max_X_pos).w

move.w (Camera_Max_Y_pos_now).w,(Tails_Max_Y_pos).w

+

moveq #0,d0

move.b routine(a0),d0

move.w Obj02_States(pc,d0.w),d1

jsr Obj02_States(pc,d1.w)

movea.w (Tails_Tails_ptr).w,a1

tst.b routine(a1)

beq.s +

jmp (DisplaySprite2).l

+

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.

I'll update my first post containing the guide with this information. Any bugs/troubles, reply here.

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 post and go on with your day. SVN user? Read on.

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:

invulnerable_time = $20 ; and $21 ; time remaining until you stop blinking

Code:

inertia = $30 ; and $31 ; directionless representation of speed... not updated in the air