Actions

SCHG How-to

Difference between revisions of "Add Spin Dash to Sonic 1/Part 2"

From Sonic Retro

m (+cat)
 
(44 intermediate revisions by 17 users not shown)
Line 1: Line 1:
''(Original guide written by [[Puto]])''
+
{{GuideBy|Puto}}
  
[[:Image:Spindash1.zip|Here]]'s a Sonic 1 + Spindash ROM built by following [[Lightning]]'s [http://h4xopolis.net:1069/lightning/devel/spindash-tutorial/spindash.html guide.]
+
[[Media:Spindash1.zip|Here]]'s a Sonic 1 + Spin Dash ROM built by following [[User:Lightning|Lightning]]'s [[SCHG_How-to:Add_Spin Dash_to_Sonic_1/Part_1|guide]].  
 
If you try it, you will be able to easily see that there are several things that keep it from being a perfect port.<br>
 
If you try it, you will be able to easily see that there are several things that keep it from being a perfect port.<br>
For example, if you spindash next to a monitor, you won't break it. <br>
+
For example, if you Spin Dash next to a monitor, you won't break it. <br>
Also, the sound effect, while fitting, isn't ideal, there's no dust, and if you spindash while on a seesaw (SLZ), Strange Things(TM)
+
Also, the sound effect, while fitting, isn't ideal, there's no dust, and if you Spin Dash while on a seesaw (SLZ), Strange Things™
 
will happen. So let's fix this.  
 
will happen. So let's fix this.  
  
=Problem 1: The Monitor Bug=
+
==Problem 1: The Monitor Bug==
 
+
When you try to Spin Dash next to a monitor, you won't be able to do that and simply stop when releasing. It only works if you walk a little bit away from the monitor and do the Spin Dash. To fix that, go to ''loc_A1EC'' ('''Hint for GitHub Disassembly users:''' Look for file called _incObj/26 Monitor.asm). You should see something like this:
<asm>loc_A1EC: ; XREF: Obj26_Solid
+
<syntaxhighlight lang="asm">loc_A1EC: ; XREF: Obj26_Solid
 
move.w #$1A,d1
 
move.w #$1A,d1
 
move.w #$F,d2
 
move.w #$F,d2
Line 17: Line 17:
 
bmi.s loc_A20A
 
bmi.s loc_A20A
 
cmpi.b #2,$1C(a1) ; is Sonic rolling?
 
cmpi.b #2,$1C(a1) ; is Sonic rolling?
beq.s loc_A25C ; if yes, branch</asm>
+
beq.s loc_A25C ; if yes, branch</syntaxhighlight>
  
Obj26 is the monitor object. By reading this code, you can see that there is a check for the rolling animation there. This is what  
+
By reading this code, you can see that there is a check for the rolling animation there. This is what causes the monitor bug. To fix it, simply add a second check for the Spin Dash animation after this one:
causes the monitor bug. To fix it, simply add a second check for the spindash animation after this one:
 
  
<asm>loc_A1EC: ; XREF: Obj26_Solid
+
<syntaxhighlight lang="asm">loc_A1EC: ; XREF: Obj26_Solid
 
move.w #$1A,d1
 
move.w #$1A,d1
 
move.w #$F,d2
 
move.w #$F,d2
Line 31: Line 30:
 
cmpi.b #2,$1C(a1) ; is Sonic rolling?
 
cmpi.b #2,$1C(a1) ; is Sonic rolling?
 
beq.s loc_A25C ; if yes, branch
 
beq.s loc_A25C ; if yes, branch
cmp.b #$1F,$1C(a1) ; is Sonic spin-dashing?
+
cmpi.b #$1F,$1C(a1) ; is Sonic spin-dashing?
beq loc_A25C ; if yes, branch</asm>
+
beq.s loc_A25C ; if yes, branch</syntaxhighlight>
 
 
  
Here's a ROM after this step was carried out. Great, let's try it. It should be working and... oh dear.<br>
+
Great, let's try it. It should be working and... oh dear.<br>
 
This isn't right.  
 
This isn't right.  
  
 
[[Image:SpindashGuide_Pic1.png]]
 
[[Image:SpindashGuide_Pic1.png]]
  
We're spindashing, we're not supposed to use the pushing animation. To fix that, go to Sonic_Spindash, and look at what it does.<br>
+
We're Spin Dashing, we're not supposed to use the pushing animation. To fix that, go to Sonic_SpinDash, and look at what it does.<br>
 
These are the first lines:
 
These are the first lines:
  
<asm>Sonic_Spindash:
+
<syntaxhighlight lang="asm">Sonic_SpinDash:
tst.b $39(a0) ; already spindashing?
+
tst.b $39(a0) ; already Spin Dashing?
bne.s loc2_1AC8E ; if set, branch</asm>
+
bne.s loc2_1AC8E ; if set, branch</syntaxhighlight>
  
Since we know we're spindashing when $39(a0) is set, let's make sure that the spindash animation is used when that happens.<br>  
+
Since we know we're Spin Dashing when $39(a0) is set, let's make sure that the Spin Dash animation is used when that happens.<br>  
 
Therefore, go to loc2_1AC8E, and right after the label, add the following line:
 
Therefore, go to loc2_1AC8E, and right after the label, add the following line:
  
<asm> move.b #$1F,$1C(a0)</asm>
+
<syntaxhighlight lang="asm"> move.b #$1F,$1C(a0)</syntaxhighlight>
  
Build the ROM again, and the monitor bug should be permanently taken care of. [[:Image:Spindash2.zip|Here]]'s a ROM after this  
+
Build the ROM again, and the monitor bug should be permanently taken care of. [[Media:Spindash2.zip|Here]]'s a ROM after this  
 
step was carried out.
 
step was carried out.
  
=Problem 2: Spindash Sound Effect=
+
==Problem 2: Spin Dash Sound Effect==
 
 
 
Adding a sound effect to Sonic 1 isn't the most trivial of things to do. So be sure to follow these steps exactly.
 
Adding a sound effect to Sonic 1 isn't the most trivial of things to do. So be sure to follow these steps exactly.
  
Line 63: Line 60:
 
line there:
 
line there:
  
<asm> cmpi.b #$E0,d7
+
<syntaxhighlight lang="asm"> cmpi.b #$E0,d7
bcs.w Sound_D0toDF ; sound $D0-$DF</asm>
+
bcs.w Sound_D0toDF ; sound $D0-$DF</syntaxhighlight>
  
 
Since only $D0 is actually used here, it's a rather large waste to keep the rest of the slots unused, so change that $E0 to $D1:
 
Since only $D0 is actually used here, it's a rather large waste to keep the rest of the slots unused, so change that $E0 to $D1:
  
<asm> cmpi.b #$D1,d7
+
<syntaxhighlight lang="asm"> cmpi.b #$D1,d7
bcs.w Sound_D0toDF ; sound $D0</asm>
+
bcs.w Sound_D0toDF ; sound $D0</syntaxhighlight>
  
 
And after that line, let's add our own comparison:
 
And after that line, let's add our own comparison:
  
<asm> cmp.b #$DF,d7
+
<syntaxhighlight lang="asm">
ble Sound_D1toDF</asm>
+
cmpi.b #$DF,d7
 +
blo.w Sound_D1toDF ; sound $D1-$DF
 +
</syntaxhighlight>
  
 
Now, we have to actually create this routine. So go to Sound_A0toCF, and see what it does:
 
Now, we have to actually create this routine. So go to Sound_A0toCF, and see what it does:
  
<asm>Sound_A0toCF: ; XREF: Sound_ChkValue
+
<syntaxhighlight lang="asm">Sound_A0toCF: ; XREF: Sound_ChkValue
 
tst.b $27(a6)
 
tst.b $27(a6)
 
bne.w loc_722C6
 
bne.w loc_722C6
Line 114: Line 113:
 
subq.b #1,d7
 
subq.b #1,d7
 
moveq #$30,d6
 
moveq #$30,d6
(...)</asm>
+
(...)</syntaxhighlight>
  
 
As you can see, this routine has a lot of checks for special sound effects, like the ring effect and the pushing effect.<br>  
 
As you can see, this routine has a lot of checks for special sound effects, like the ring effect and the pushing effect.<br>  
 
We don't need that for our new routine, so trash all of that, and you end up with:
 
We don't need that for our new routine, so trash all of that, and you end up with:
  
<asm>Sound_D1toDF:
+
<syntaxhighlight lang="asm">Sound_D1toDF:
 
tst.b $27(a6)
 
tst.b $27(a6)
 
bne.w loc_722C6
 
bne.w loc_722C6
Line 138: Line 137:
 
subq.b #1,d7
 
subq.b #1,d7
 
moveq #$30,d6
 
moveq #$30,d6
(...)</asm>
+
(...)</syntaxhighlight>
  
 
There's a problem here: If we subtract $A0 to get the current index, then the next entry in this index will be sound $D0,  
 
There's a problem here: If we subtract $A0 to get the current index, then the next entry in this index will be sound $D0,  
 
which is handled by a different routine, forcing us to have an unused entry in an index. So, change that line to subtract $A1 instead:
 
which is handled by a different routine, forcing us to have an unused entry in an index. So, change that line to subtract $A1 instead:
  
<asm>Sound_D1toDF:
+
<syntaxhighlight lang="asm">Sound_D1toDF:
 
tst.b $27(a6)
 
tst.b $27(a6)
 
bne.w loc_722C6
 
bne.w loc_722C6
Line 162: Line 161:
 
subq.b #1,d7
 
subq.b #1,d7
 
moveq #$30,d6
 
moveq #$30,d6
(...)</asm>
+
(...)</syntaxhighlight>
  
 
Also, it can be noted that any code after the $A1 line is common to the two routines. <br>
 
Also, it can be noted that any code after the $A1 line is common to the two routines. <br>
 
So in the original routine, add a label right before "lsl.w #2,d7":
 
So in the original routine, add a label right before "lsl.w #2,d7":
  
<asm>Sound_notA7:
+
<syntaxhighlight lang="asm">Sound_notA7:
 
movea.l (Go_SoundIndex).l,a0
 
movea.l (Go_SoundIndex).l,a0
 
subi.b #$A0,d7
 
subi.b #$A0,d7
Line 181: Line 180:
 
subq.b #1,d7
 
subq.b #1,d7
 
moveq #$30,d6
 
moveq #$30,d6
(...)</asm>
+
(...)</syntaxhighlight>
  
 
And we can now greatly reduce the size of our new routine:
 
And we can now greatly reduce the size of our new routine:
  
<asm>Sound_D1toDF:
+
<syntaxhighlight lang="asm">Sound_D1toDF:
 
tst.b $27(a6)
 
tst.b $27(a6)
 
bne.w loc_722C6
 
bne.w loc_722C6
Line 194: Line 193:
 
movea.l (Go_SoundIndex).l,a0
 
movea.l (Go_SoundIndex).l,a0
 
sub.b #$A1,d7
 
sub.b #$A1,d7
bra SoundEffects_Common</asm>
+
bra SoundEffects_Common</syntaxhighlight>
  
 
So, after this is done, put the routine right above Sound_A0toCF.<br>  
 
So, after this is done, put the routine right above Sound_A0toCF.<br>  
Now, all we need is the actual ported spin-dash sound effect, which I provide [[:Image:SoundD1.zip|here.]]  
+
Now, all we need is the actual ported spin-dash sound effect, which I provide [[Media:SoundD1.zip|here]].
 
Put this file in the sound\ directory of your source code, and go to the SoundD0 label. After the even, add the following lines:
 
Put this file in the sound\ directory of your source code, and go to the SoundD0 label. After the even, add the following lines:
  
<asm>SoundD1: incbin sound\soundD1.bin
+
<syntaxhighlight lang="asm">SoundD1: incbin sound\soundD1.bin
even</asm>
+
even</syntaxhighlight>
  
 
So the surrounding code should look like this:
 
So the surrounding code should look like this:
  
<asm>SoundCF: incbin sound\soundCF.bin
+
<syntaxhighlight lang="asm">SoundCF: incbin sound\soundCF.bin
 
even
 
even
 
SoundD0: incbin sound\soundD0.bin
 
SoundD0: incbin sound\soundD0.bin
Line 212: Line 211:
 
even
 
even
 
SegaPCM: incbin sound\segapcm.bin
 
SegaPCM: incbin sound\segapcm.bin
even</asm>
+
even</syntaxhighlight>
  
 
And finally, add SoundD1 to the index:
 
And finally, add SoundD1 to the index:
  
<asm>SoundIndex: dc.l SoundA0, SoundA1, SoundA2
+
<syntaxhighlight lang="asm">SoundIndex: dc.l SoundA0, SoundA1, SoundA2
 
dc.l SoundA3, SoundA4, SoundA5
 
dc.l SoundA3, SoundA4, SoundA5
 
dc.l SoundA6, SoundA7, SoundA8
 
dc.l SoundA6, SoundA7, SoundA8
Line 232: Line 231:
 
dc.l SoundCA, SoundCB, SoundCC
 
dc.l SoundCA, SoundCB, SoundCC
 
dc.l SoundCD, SoundCE, SoundCF
 
dc.l SoundCD, SoundCE, SoundCF
dc.l SoundD1</asm>
+
dc.l SoundD1</syntaxhighlight>
  
Now, go to Sound_Spindash, and change all references to sound $BE to reference sound $D1 instead.  
+
Now, go to Sonic_SpinDash, and change all references to sound $BE to reference sound $D1 instead.  
 
For reference, here's the complete routine:
 
For reference, here's the complete routine:
  
<asm>Sonic_Spindash:
+
<syntaxhighlight lang="asm">Sonic_SpinDash:
tst.b $39(a0) ; already spindashing?
+
tst.b $39(a0) ; already Spin Dashing?
 
bne.s loc2_1AC8E ; if set, branch
 
bne.s loc2_1AC8E ; if set, branch
 
cmpi.b #8,$1C(a0) ; is anim duck
 
cmpi.b #8,$1C(a0) ; is anim duck
Line 245: Line 244:
 
andi.b #$70,d0 ; pressing A/B/C ?
 
andi.b #$70,d0 ; pressing A/B/C ?
 
beq.w locret2_1AC8C ; if not, return
 
beq.w locret2_1AC8C ; if not, return
move.b #$1F,$1C(a0) ; set spindash anim (9 in s2)
+
move.b #$1F,$1C(a0) ; set Spin Dash anim (9 in s2)
 
move.w #$D1,d0 ; spin sound ($E0 in s2)
 
move.w #$D1,d0 ; spin sound ($E0 in s2)
 
jsr (PlaySound_Special).l ; play spin sound
 
jsr (PlaySound_Special).l ; play spin sound
 
addq.l #4,sp ; increment stack ptr
 
addq.l #4,sp ; increment stack ptr
move.b #1,$39(a0) ; set spindash flag
+
move.b #1,$39(a0) ; set Spin Dash flag
 
move.w #0,$3A(a0) ; set charge count to 0
 
move.w #0,$3A(a0) ; set charge count to 0
 
cmpi.b #$C,$28(a0) ; ??? oxygen remaining?
 
cmpi.b #$C,$28(a0) ; ??? oxygen remaining?
 
bcs.s loc2_1AC84 ; ??? branch if carry
 
bcs.s loc2_1AC84 ; ??? branch if carry
 
move.b #2,($FFFFD11C).w ; ??? $D11C only seems
 
move.b #2,($FFFFD11C).w ; ??? $D11C only seems
; to be used in spindash
+
; to be used in Spin Dash
 
loc2_1AC84:
 
loc2_1AC84:
 
bsr.w Sonic_LevelBound
 
bsr.w Sonic_LevelBound
 
bsr.w Sonic_AnglePos
 
bsr.w Sonic_AnglePos
 
+
 
locret2_1AC8C:
 
locret2_1AC8C:
 
rts
 
rts
 
; ---------------------------------------------------------------------------
 
; ---------------------------------------------------------------------------
 
+
 
loc2_1AC8E:
 
loc2_1AC8E:
 
move.b #$1F,$1C(a0)
 
move.b #$1F,$1C(a0)
Line 272: Line 271:
 
move.b #2,$1C(a0) ; set animation to roll
 
move.b #2,$1C(a0) ; set animation to roll
 
addq.w #5,$C(a0) ; $C(a0) is Y coordinate
 
addq.w #5,$C(a0) ; $C(a0) is Y coordinate
move.b #0,$39(a0) ; clear spindash flag
+
move.b #0,$39(a0) ; clear Spin Dash flag
 
moveq #0,d0
 
moveq #0,d0
 
move.b $3A(a0),d0 ; copy charge count
 
move.b $3A(a0),d0 ; copy charge count
 
add.w d0,d0 ; double it
 
add.w d0,d0 ; double it
move.w spdsh_norm(pc,d0.w),$14(a0) ; get normal speed
+
move.w Dash_Speeds(pc,d0.w),$14(a0) ; get normal speed
tst.b ($FFFFFE19).w ; is sonic super?
 
beq.s loc2_1ACD0 ; if no, branch
 
move.w spdsh_super(pc,d0.w),$14(a0) ; get super speed
 
 
 
loc2_1ACD0: ; TODO: figure this out
 
 
move.w $14(a0),d0 ; get inertia
 
move.w $14(a0),d0 ; get inertia
 
subi.w #$800,d0 ; subtract $800
 
subi.w #$800,d0 ; subtract $800
Line 292: Line 286:
 
beq.s loc2_1ACF4 ; if not, branch
 
beq.s loc2_1ACF4 ; if not, branch
 
neg.w $14(a0) ; negate inertia
 
neg.w $14(a0) ; negate inertia
 
+
 
loc2_1ACF4:
 
loc2_1ACF4:
 
bset #2,$22(a0) ; set unused (in s1) flag
 
bset #2,$22(a0) ; set unused (in s1) flag
Line 299: Line 293:
 
jsr (PlaySound_Special).l ; play it!
 
jsr (PlaySound_Special).l ; play it!
 
bra.s loc2_1AD78
 
bra.s loc2_1AD78
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
+
; ===========================================================================
spdsh_norm:
+
Dash_Speeds: dc.w  $800 ; 0
dc.w  $800 ; 0
 
 
dc.w  $880 ; 1
 
dc.w  $880 ; 1
 
dc.w  $900 ; 2
 
dc.w  $900 ; 2
Line 310: Line 303:
 
dc.w  $B80 ; 7
 
dc.w  $B80 ; 7
 
dc.w  $C00 ; 8
 
dc.w  $C00 ; 8
 
+
; ===========================================================================
spdsh_super:
+
   
dc.w  $B00 ; 0
 
dc.w  $B80 ; 1
 
dc.w  $C00 ; 2
 
dc.w  $C80 ; 3
 
dc.w  $D00 ; 4
 
dc.w  $D80 ; 5
 
dc.w  $E00 ; 6
 
dc.w  $E80 ; 7
 
dc.w $F00 ; 8
 
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
 
 
 
 
loc2_1AD30: ; If still charging the dash...
 
loc2_1AD30: ; If still charging the dash...
 
tst.w $3A(a0) ; check charge count
 
tst.w $3A(a0) ; check charge count
Line 331: Line 313:
 
bcc.s loc2_1AD48 ; ??? branch if carry clear
 
bcc.s loc2_1AD48 ; ??? branch if carry clear
 
move.w #0,$3A(a0) ; set charge count to 0
 
move.w #0,$3A(a0) ; set charge count to 0
 
+
 
loc2_1AD48:
 
loc2_1AD48:
 
move.b ($FFFFF603).w,d0 ; read controller
 
move.b ($FFFFF603).w,d0 ; read controller
Line 343: Line 325:
 
bcs.s loc2_1AD78 ; if not, then branch
 
bcs.s loc2_1AD78 ; if not, then branch
 
move.w #$800,$3A(a0) ; reset it to max
 
move.w #$800,$3A(a0) ; reset it to max
 
+
 
loc2_1AD78:
 
loc2_1AD78:
 
addq.l #4,sp ; increase stack ptr
 
addq.l #4,sp ; increase stack ptr
 
cmpi.w #$60,($FFFFEED8).w ; $EED8 only ever seems
 
cmpi.w #$60,($FFFFEED8).w ; $EED8 only ever seems
beq.s loc2_1AD8C ; to be used in spindash
+
beq.s loc2_1AD8C ; to be used in Spin Dash
 
bcc.s loc2_1AD88
 
bcc.s loc2_1AD88
 
addq.w #4,($FFFFEED8).w
 
addq.w #4,($FFFFEED8).w
 
+
 
loc2_1AD88:
 
loc2_1AD88:
 
subq.w #2,($FFFFEED8).w
 
subq.w #2,($FFFFEED8).w
 
+
 
loc2_1AD8C:
 
loc2_1AD8C:
 
bsr.w Sonic_LevelBound
 
bsr.w Sonic_LevelBound
 
bsr.w Sonic_AnglePos
 
bsr.w Sonic_AnglePos
 
move.w #$60,($FFFFF73E).w ; reset looking up/down
 
move.w #$60,($FFFFF73E).w ; reset looking up/down
rts</asm>
+
rts</syntaxhighlight>
  
So, problem solved. [[:Image:Spindash5.zip|Here]]'s a rom of the output after fixing this problem.
+
So, problem solved. [[Media:Spindash5.zip|Here]]'s a rom of the output after fixing this problem.
  
=Problem 3: See-Saw Bug (AKA Sonic 2 Spindash Bug)=
+
==Problem 3: See-Saw Bug (AKA Sonic 2 Spin Dash Bug)==
 
+
This problem occurs when you're Spin Dashing on a seesaw, and then you get thrown up by the seesaw.<br>  
This problem occurs when you're spindashing on a seesaw, and then you get thrown up by the seesaw.<br>  
+
You keep your Spin Dash state, and dash off immediately once you land. This is clearly not the wanted behaviour.<br>  
You keep your spindash state, and dash off immediately once you land. This is clearly not the wanted behaviour.<br>  
 
 
To fix this, go to Obj01_MdJump and Obj01_MdJump2, and add the following line at the beginning of each of the two routines:
 
To fix this, go to Obj01_MdJump and Obj01_MdJump2, and add the following line at the beginning of each of the two routines:
  
<asm> clr.b $39(a0)</asm>
+
<syntaxhighlight lang="asm"> clr.b $39(a0)</syntaxhighlight>
  
This should fix the see-saw bug. [[:Image:Spindash6.zip|Here]]'s the rom.
+
This should fix the see-saw bug. [[Media:Spindash6.zip|Here]]'s the rom.
  
 
+
==Problem 4: Spin Dash Dust==
=Problem 4: Spindash Dust=
+
This is a big one. For starters, you need to port the Spin Dash dust object from Sonic 2.<br>  
 
 
This is a big one. For starters, you need to port the spindash dust object from Sonic 2.<br>  
 
 
For your convenience, here is the code to the ported object, paste it right before Obj01 (the sonic object):
 
For your convenience, here is the code to the ported object, paste it right before Obj01 (the sonic object):
  
<asm>Spindash_dust:
+
<syntaxhighlight lang="asm">SpinDash_dust:
Sprite_1DD20: ; DATA XREF: ROM:0001600C�o
+
Sprite_1DD20: ; DATA XREF: ROM:0001600C?o
 
moveq #0,d0
 
moveq #0,d0
 
move.b $24(a0),d0
 
move.b $24(a0),d0
Line 385: Line 364:
 
jmp off_1DD2E(pc,d1.w)
 
jmp off_1DD2E(pc,d1.w)
 
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
 
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
off_1DD2E: dc loc_1DD36-off_1DD2E; 0 ; DATA XREF: h+6DBA�o h+6DBC�o ...
+
off_1DD2E: dc loc_1DD36-off_1DD2E; 0 ; DATA XREF: h+6DBA?o h+6DBC?o ...
 
dc loc_1DD90-off_1DD2E; 1
 
dc loc_1DD90-off_1DD2E; 1
 
dc loc_1DE46-off_1DD2E; 2
 
dc loc_1DE46-off_1DD2E; 2
Line 391: Line 370:
 
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
 
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
  
loc_1DD36: ; DATA XREF: h+6DBA�o
+
loc_1DD36: ; DATA XREF: h+6DBA?o
 
addq.b #2,$24(a0)
 
addq.b #2,$24(a0)
 
move.l #MapUnc_1DF5E,4(a0)
 
move.l #MapUnc_1DF5E,4(a0)
Line 409: Line 388:
 
; move #-$6E80,$3C(a0)
 
; move #-$6E80,$3C(a0)
  
loc_1DD8C: ; CODE XREF: h+6DF6�j h+6E04�j
+
loc_1DD8C: ; CODE XREF: h+6DF6?j h+6E04?j
 
; bsr.w sub_16D6E
 
; bsr.w sub_16D6E
  
loc_1DD90: ; DATA XREF: h+6DBA�o
+
loc_1DD90: ; DATA XREF: h+6DBA?o
 
movea.w $3E(a0),a2
 
movea.w $3E(a0),a2
 
moveq #0,d0
 
moveq #0,d0
Line 420: Line 399:
 
jmp off_1DDA4(pc,d1.w)
 
jmp off_1DDA4(pc,d1.w)
 
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
 
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
off_1DDA4: dc loc_1DE28-off_1DDA4; 0 ; DATA XREF: h+6E30�o h+6E32�o ...
+
off_1DDA4: dc loc_1DE28-off_1DDA4; 0 ; DATA XREF: h+6E30?o h+6E32?o ...
 
dc loc_1DDAC-off_1DDA4; 1
 
dc loc_1DDAC-off_1DDA4; 1
 
dc loc_1DDCC-off_1DDA4; 2
 
dc loc_1DDCC-off_1DDA4; 2
Line 426: Line 405:
 
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
 
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
  
loc_1DDAC: ; DATA XREF: h+6E30�o
+
loc_1DDAC: ; DATA XREF: h+6E30?o
 
move ($FFFFF646).w,$C(a0)
 
move ($FFFFF646).w,$C(a0)
 
tst.b $1D(a0)
 
tst.b $1D(a0)
Line 436: Line 415:
 
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
 
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
  
loc_1DDCC: ; DATA XREF: h+6E30�o
+
loc_1DDCC: ; DATA XREF: h+6E30?o
 
; cmp.b #$C,$28(a2)
 
; cmp.b #$C,$28(a2)
 
; bcs.s loc_1DE3E
 
; bcs.s loc_1DE3E
Line 451: Line 430:
 
sub #4,$C(a0)
 
sub #4,$C(a0)
  
loc_1DE06: ; CODE XREF: h+6E8A�j
+
loc_1DE06: ; CODE XREF: h+6E8A?j
 
tst.b $1D(a0)
 
tst.b $1D(a0)
 
bne.s loc_1DE28
 
bne.s loc_1DE28
Line 460: Line 439:
 
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
 
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
  
loc_1DE20: ; DATA XREF: h+6E30�o
+
loc_1DE20: ; DATA XREF: h+6E30?o
loc_1DE28: ; CODE XREF: h+6E42�j h+6E56�j ...
+
loc_1DE28: ; CODE XREF: h+6E42?j h+6E56?j ...
 
lea (off_1DF38).l,a1
 
lea (off_1DF38).l,a1
 
jsr AnimateSprite
 
jsr AnimateSprite
Line 468: Line 447:
 
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
 
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
  
loc_1DE3E: ; CODE XREF: h+6E5E�j h+6E66�j ...
+
loc_1DE3E: ; CODE XREF: h+6E5E?j h+6E66?j ...
 
move.b #0,$1C(a0)
 
move.b #0,$1C(a0)
 
rts
 
rts
 
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
 
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
  
loc_1DE46: ; DATA XREF: h+6DBA�o
+
loc_1DE46: ; DATA XREF: h+6DBA?o
 
bra.w DeleteObject
 
bra.w DeleteObject
 
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
 
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Line 492: Line 471:
 
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
 
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
  
loc_1DE64: ; CODE XREF: h+6EE0�j
+
loc_1DE64: ; CODE XREF: h+6EE0?j
 
subq.b #1,$32(a0)
 
subq.b #1,$32(a0)
 
bpl.s loc_1DEE0
 
bpl.s loc_1DEE0
Line 505: Line 484:
 
sub #4,d1
 
sub #4,d1
  
loc_1DE9A: ; CODE XREF: h+6F1E�j
+
loc_1DE9A: ; CODE XREF: h+6F1E?j
 
add d1,$C(a1)
 
add d1,$C(a1)
 
move.b #0,$22(a1)
 
move.b #0,$22(a1)
Line 521: Line 500:
 
or #-$8000,2(a1)
 
or #-$8000,2(a1)
  
loc_1DEE0: ; CODE XREF: h+6EF4�j h+6F00�j ...
+
loc_1DEE0: ; CODE XREF: h+6EF4?j h+6F00?j ...
 
bsr.s loc_1DEE4
 
bsr.s loc_1DEE4
 
rts
 
rts
 
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
 
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
  
loc_1DEE4: ; CODE XREF: h+6EC0�p h+6F6C�p
+
loc_1DEE4: ; CODE XREF: h+6EC0?p h+6F6C?p
 
moveq #0,d0
 
moveq #0,d0
 
move.b $1A(a0),d0
 
move.b $1A(a0),d0
Line 540: Line 519:
 
move $3C(a0),d4
 
move $3C(a0),d4
  
loc_1DF0A: ; CODE XREF: h+6FBE�j
+
loc_1DF0A: ; CODE XREF: h+6FBE?j
 
moveq #0,d1
 
moveq #0,d1
 
move (a2)+,d1
 
move (a2)+,d1
Line 557: Line 536:
 
     rts
 
     rts
  
locret_1DF36: ; CODE XREF: h+6F7A�j h+6F90�j
+
locret_1DF36: ; CODE XREF: h+6F7A?j h+6F90?j
 
rts
 
rts
 
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
 
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
off_1DF38: dc byte_1DF40-off_1DF38; 0 ; DATA XREF: h+6EB4�o h+6FC4�o ...
+
off_1DF38: dc byte_1DF40-off_1DF38; 0 ; DATA XREF: h+6EB4?o h+6FC4?o ...
 
dc byte_1DF43-off_1DF38; 1
 
dc byte_1DF43-off_1DF38; 1
 
dc byte_1DF4F-off_1DF38; 2
 
dc byte_1DF4F-off_1DF38; 2
 
dc byte_1DF58-off_1DF38; 3
 
dc byte_1DF58-off_1DF38; 3
byte_1DF40: dc.b $1F,  0,$FF ; 0 ; DATA XREF: h+6FC4�o
+
byte_1DF40: dc.b $1F,  0,$FF ; 0 ; DATA XREF: h+6FC4?o
byte_1DF43: dc.b  3,  1,  2,  3,  4,  5,  6,  7,  8,  9,$FD,  0; 0 ; DATA XREF: h+6FC4�o
+
byte_1DF43: dc.b  3,  1,  2,  3,  4,  5,  6,  7,  8,  9,$FD,  0; 0 ; DATA XREF: h+6FC4?o
byte_1DF4F: dc.b  1, $A, $B, $C, $D, $E, $F,$10,$FF; 0 ; DATA XREF: h+6FC4�o
+
byte_1DF4F: dc.b  1, $A, $B, $C, $D, $E, $F,$10,$FF; 0 ; DATA XREF: h+6FC4?o
byte_1DF58: dc.b  3,$11,$12,$13,$14,$FC; 0 ; DATA XREF: h+6FC4�o
+
byte_1DF58: dc.b  3,$11,$12,$13,$14,$FC; 0 ; DATA XREF: h+6FC4?o
 
; -------------------------------------------------------------------------------
 
; -------------------------------------------------------------------------------
 
; Unknown Sprite Mappings
 
; Unknown Sprite Mappings
Line 702: Line 681:
 
word_1E0EC: dc 1
 
word_1E0EC: dc 1
 
dc $F0BA
 
dc $F0BA
even</asm>
+
even</syntaxhighlight>
  
 
Now, save and try to build... oops! It seems that there's a routine missing: DMA_68KtoVRAM.<br>  
 
Now, save and try to build... oops! It seems that there's a routine missing: DMA_68KtoVRAM.<br>  
Line 710: Line 689:
 
...easier than porting from the Sonic 2 Final disassembly, so let's use that).
 
...easier than porting from the Sonic 2 Final disassembly, so let's use that).
  
<asm>DMA_68KtoVRAM: ; CODE XREF: LoadSonicDynPLC+48�p
+
<syntaxhighlight lang="asm">DMA_68KtoVRAM: ; CODE XREF: LoadSonicDynPLC+48?p
; LoadTailsDynPLC+48�p ...
+
; LoadTailsDynPLC+48?p ...
 
movea.l ($FFFFDCFC).w,a1
 
movea.l ($FFFFDCFC).w,a1
 
cmpa.w #$DCFC,a1
 
cmpa.w #$DCFC,a1
Line 745: Line 724:
 
move.w #0,(a1)
 
move.w #0,(a1)
  
DMA_68KtoVRAM_NoDMA: ; CODE XREF: DMA_68KtoVRAM+8�j
+
DMA_68KtoVRAM_NoDMA: ; CODE XREF: DMA_68KtoVRAM+8?j
; DMA_68KtoVRAM+56�j
+
; DMA_68KtoVRAM+56?j
 
rts
 
rts
 
; End of function DMA_68KtoVRAM
 
; End of function DMA_68KtoVRAM
Line 754: Line 733:
  
  
Process_DMA: ; CODE XREF: ROM:00000D9C�p
+
Process_DMA: ; CODE XREF: ROM:00000D9C?p
; ROM:00000E84�p ...
+
; ROM:00000E84?p ...
 
lea ($C00004).l,a5
 
lea ($C00004).l,a5
 
lea ($FFFFDC00).w,a1
 
lea ($FFFFDC00).w,a1
  
Process_DMA_Loop: ; CODE XREF: Process_DMA+20�j
+
Process_DMA_Loop: ; CODE XREF: Process_DMA+20?j
 
move.w (a1)+,d0
 
move.w (a1)+,d0
 
beq.s Process_DMA_End
 
beq.s Process_DMA_End
Line 772: Line 751:
 
bne.s Process_DMA_Loop
 
bne.s Process_DMA_Loop
  
Process_DMA_End: ; CODE XREF: Process_DMA+C�j
+
Process_DMA_End: ; CODE XREF: Process_DMA+C?j
 
move.w #0,($FFFFDC00).w
 
move.w #0,($FFFFDC00).w
 
move.l #$FFFFDC00,($FFFFDCFC).w
 
move.l #$FFFFDC00,($FFFFDCFC).w
 
rts
 
rts
; End of function Process_DMA</asm>
+
; End of function Process_DMA</syntaxhighlight>
  
  
Line 798: Line 777:
 
Here's the routine after this conversion has been done:
 
Here's the routine after this conversion has been done:
  
 
+
<syntaxhighlight lang="asm">DMA_68KtoVRAM: ; CODE XREF: LoadSonicDynPLC+48?p
<asm>DMA_68KtoVRAM: ; CODE XREF: LoadSonicDynPLC+48�p
+
; LoadTailsDynPLC+48?p ...
; LoadTailsDynPLC+48�p ...
 
 
movea.l ($FFFFD3EE).w,a1
 
movea.l ($FFFFD3EE).w,a1
 
cmpa.w #$D3EE,a1
 
cmpa.w #$D3EE,a1
Line 834: Line 812:
 
move.w #0,(a1)
 
move.w #0,(a1)
  
DMA_68KtoVRAM_NoDMA: ; CODE XREF: DMA_68KtoVRAM+8�j
+
DMA_68KtoVRAM_NoDMA: ; CODE XREF: DMA_68KtoVRAM+8?j
; DMA_68KtoVRAM+56�j
+
; DMA_68KtoVRAM+56?j
 
rts
 
rts
 
; End of function DMA_68KtoVRAM
 
; End of function DMA_68KtoVRAM
Line 843: Line 821:
  
  
Process_DMA: ; CODE XREF: ROM:00000D9C�p
+
Process_DMA: ; CODE XREF: ROM:00000D9C?p
; ROM:00000E84�p ...
+
; ROM:00000E84?p ...
 
lea ($C00004).l,a5
 
lea ($C00004).l,a5
 
lea ($FFFFD3C2).w,a1
 
lea ($FFFFD3C2).w,a1
  
Process_DMA_Loop: ; CODE XREF: Process_DMA+20�j
+
Process_DMA_Loop: ; CODE XREF: Process_DMA+20?j
 
move.w (a1)+,d0
 
move.w (a1)+,d0
 
beq.s Process_DMA_End
 
beq.s Process_DMA_End
Line 861: Line 839:
 
bne.s Process_DMA_Loop
 
bne.s Process_DMA_Loop
  
Process_DMA_End: ; CODE XREF: Process_DMA+C�j
+
Process_DMA_End: ; CODE XREF: Process_DMA+C?j
 
move.w #0,($FFFFD3C2).w
 
move.w #0,($FFFFD3C2).w
 
move.l #$FFFFD3C2,($FFFFD3EE).w
 
move.l #$FFFFD3C2,($FFFFD3EE).w
 
rts
 
rts
; End of function Process_DMA</asm>
+
; End of function Process_DMA</syntaxhighlight>
  
 
Paste it right after ShowVDPGraphics.<br>  
 
Paste it right after ShowVDPGraphics.<br>  
Line 871: Line 849:
 
To do that, go to loc_D50, and add these two lines at the beginning:
 
To do that, go to loc_D50, and add these two lines at the beginning:
  
<asm> move #$83,($FFFFF640).w
+
<syntaxhighlight lang="asm"> move #$83,($FFFFF640).w
jsr Process_DMA</asm>
+
jsr Process_DMA</syntaxhighlight>
  
The queue should now be working. Now we need to do one more thing, call the actual spindash dust object. <br>
+
The queue should now be working. Now we need to do one more thing, call the actual Spin Dash dust object. <br>
To do that, open _inc\Object pointers.asm, and replace the first call to ObjectFall in the second line with Spindash_dust.  
+
To do that, open _inc\Object pointers.asm, and replace the first call to ObjectFall in the second line with Spin Dash_dust.  
  
 
The outcome should be something like this:
 
The outcome should be something like this:
  
<asm>; ---------------------------------------------------------------------------
+
<syntaxhighlight lang="asm">; ---------------------------------------------------------------------------
 
; Object pointers
 
; Object pointers
 
; ---------------------------------------------------------------------------
 
; ---------------------------------------------------------------------------
 
dc.l Obj01, ObjectFall, ObjectFall, ObjectFall
 
dc.l Obj01, ObjectFall, ObjectFall, ObjectFall
dc.l Spindash_dust, ObjectFall, ObjectFall, Obj08
+
dc.l SpinDash_dust, ObjectFall, ObjectFall, Obj08
 
dc.l Obj09, Obj0A, Obj0B, Obj0C
 
dc.l Obj09, Obj0A, Obj0B, Obj0C
 
dc.l Obj0D, Obj0E, Obj0F, Obj10
 
dc.l Obj0D, Obj0E, Obj0F, Obj10
Line 916: Line 894:
 
dc.l Obj81, Obj82, Obj83, Obj84
 
dc.l Obj81, Obj82, Obj83, Obj84
 
dc.l Obj85, Obj86, Obj87, Obj88
 
dc.l Obj85, Obj86, Obj87, Obj88
dc.l Obj89, Obj8A, Obj8B, Obj8C</asm>
+
dc.l Obj89, Obj8A, Obj8B, Obj8C</syntaxhighlight>
  
This will cause Object ID 05, which was previously unused, to now point to the spindash dust object.
+
This will cause Object ID 05, which was previously unused, to now point to the Spin Dash dust object.
  
 
Finally, we need to call the object itself. So go to Obj01_Main, and at the end of the routine (before Obj01_Control),  
 
Finally, we need to call the object itself. So go to Obj01_Main, and at the end of the routine (before Obj01_Control),  
 
add the following line:
 
add the following line:
  
<asm> move.b #5,$FFFFD1C0.w</asm>
+
<syntaxhighlight lang="asm"> move.b #5,$FFFFD1C0.w</syntaxhighlight>
  
 
This will load the dust object to address $FFFFD1C0, (addresses from $D000 to $D400 are part of the SST).
 
This will load the dust object to address $FFFFD1C0, (addresses from $D000 to $D400 are part of the SST).
  
Finally, we need to have the spindash routine set the dust's animation. To do that, go to Sonic_Spindash, and change this line:
+
Finally, we need to have the Spin Dash routine set the dust's animation. To do that, go to Sonic_SpinDash, and change this line:
  
  
<asm> move.b #2,($FFFFD11C).w</asm>
+
<syntaxhighlight lang="asm"> move.b #2,($FFFFD11C).w</syntaxhighlight>
  
 
to this:
 
to this:
  
<asm> move.b #2,($FFFFD1DC).w ; Set the spindash dust animation to $2.</asm>
+
<syntaxhighlight lang="asm"> move.b #2,($FFFFD1DC).w ; Set the Spin Dash dust animation to $2.</syntaxhighlight>
  
  
 
Also, remove this line:
 
Also, remove this line:
  
<asm> bcs.s loc2_1AC84 ; ??? branch if carry</asm>
+
<syntaxhighlight lang="asm"> bcs.s loc2_1AC84 ; ??? branch if carry</syntaxhighlight>
  
  
Line 945: Line 923:
 
And again, in loc2_1ACF4, change this line:
 
And again, in loc2_1ACF4, change this line:
  
<asm> move.b #0,($FFFFD11C).w ; clear $D11C (unused?)</asm>
+
<syntaxhighlight lang="asm"> move.b #0,($FFFFD11C).w ; clear $D11C (unused?)</syntaxhighlight>
  
 
to this:
 
to this:
  
<asm> move.b #0,($FFFFD1DC).w ; clear spindash dust animation.</asm>
+
<syntaxhighlight lang="asm"> move.b #0,($FFFFD1DC).w ; clear Spin Dash dust animation.</syntaxhighlight>
  
 
And finally, add the following line in loc2_1AD48, right before the jsr to PlaySound_Special:
 
And finally, add the following line in loc2_1AD48, right before the jsr to PlaySound_Special:
  
<asm> move.b #2,$FFFFD1DC.w ; Set the spindash dust animation to $2.</asm>
+
<syntaxhighlight lang="asm"> move.b #2,$FFFFD1DC.w ; Set the Spin Dash dust animation to $2.</syntaxhighlight>
  
  
For reference, here's the complete Sonic_Spindash routine:
+
For reference, here's the complete Sonic_SpinDash routine:
  
<asm>Sonic_Spindash:
+
<syntaxhighlight lang="asm">Sonic_SpinDash:
tst.b $39(a0) ; already spindashing?
+
tst.b $39(a0) ; already Spin Dashing?
 
bne.s loc2_1AC8E ; if set, branch
 
bne.s loc2_1AC8E ; if set, branch
 
cmpi.b #8,$1C(a0) ; is anim duck
 
cmpi.b #8,$1C(a0) ; is anim duck
Line 966: Line 944:
 
andi.b #$70,d0 ; pressing A/B/C ?
 
andi.b #$70,d0 ; pressing A/B/C ?
 
beq.w locret2_1AC8C ; if not, return
 
beq.w locret2_1AC8C ; if not, return
move.b #$1F,$1C(a0) ; set spindash anim (9 in s2)
+
move.b #$1F,$1C(a0) ; set Spin Dash anim (9 in s2)
 
move.w #$D1,d0 ; spin sound ($E0 in s2)
 
move.w #$D1,d0 ; spin sound ($E0 in s2)
 
jsr (PlaySound_Special).l ; play spin sound
 
jsr (PlaySound_Special).l ; play spin sound
 
addq.l #4,sp ; increment stack ptr
 
addq.l #4,sp ; increment stack ptr
move.b #1,$39(a0) ; set spindash flag
+
move.b #1,$39(a0) ; set Spin Dash flag
 
move.w #0,$3A(a0) ; set charge count to 0
 
move.w #0,$3A(a0) ; set charge count to 0
 
cmpi.b #$C,$28(a0) ; ??? oxygen remaining?
 
cmpi.b #$C,$28(a0) ; ??? oxygen remaining?
move.b #2,($FFFFD1DC).w ; Set the spindash dust animation to $2
+
move.b #2,($FFFFD1DC).w ; Set the Spin Dash dust animation to $2
  
 
loc2_1AC84:
 
loc2_1AC84:
Line 992: Line 970:
 
move.b #2,$1C(a0) ; set animation to roll
 
move.b #2,$1C(a0) ; set animation to roll
 
addq.w #5,$C(a0) ; $C(a0) is Y coordinate
 
addq.w #5,$C(a0) ; $C(a0) is Y coordinate
move.b #0,$39(a0) ; clear spindash flag
+
move.b #0,$39(a0) ; clear Spin Dash flag
 
moveq #0,d0
 
moveq #0,d0
 
move.b $3A(a0),d0 ; copy charge count
 
move.b $3A(a0),d0 ; copy charge count
Line 1,015: Line 993:
 
loc2_1ACF4:
 
loc2_1ACF4:
 
bset #2,$22(a0) ; set unused (in s1) flag
 
bset #2,$22(a0) ; set unused (in s1) flag
move.b #0,($FFFFD1DC).w ; clear spindash dust animation
+
move.b #0,($FFFFD1DC).w ; clear Spin Dash dust animation
 
move.w #$BC,d0 ; spin release sound
 
move.w #$BC,d0 ; spin release sound
 
jsr (PlaySound_Special).l ; play it!
 
jsr (PlaySound_Special).l ; play it!
Line 1,058: Line 1,036:
 
move.w #$1F00,$1C(a0) ; reset spdsh animation
 
move.w #$1F00,$1C(a0) ; reset spdsh animation
 
move.w #$D1,d0 ; was $E0 in sonic 2
 
move.w #$D1,d0 ; was $E0 in sonic 2
move.b #2,$FFFFD1DC.w ; Set the spindash dust animation to $2.
+
move.b #2,$FFFFD1DC.w ; Set the Spin Dash dust animation to $2.
 
jsr (PlaySound_Special).l ; play charge sound
 
jsr (PlaySound_Special).l ; play charge sound
 
addi.w #$200,$3A(a0) ; increase charge count
 
addi.w #$200,$3A(a0) ; increase charge count
Line 1,068: Line 1,046:
 
addq.l #4,sp ; increase stack ptr
 
addq.l #4,sp ; increase stack ptr
 
cmpi.w #$60,($FFFFEED8).w ; $EED8 only ever seems
 
cmpi.w #$60,($FFFFEED8).w ; $EED8 only ever seems
beq.s loc2_1AD8C ; to be used in spindash
+
beq.s loc2_1AD8C ; to be used in Spin Dash
 
bcc.s loc2_1AD88
 
bcc.s loc2_1AD88
 
addq.w #4,($FFFFEED8).w
 
addq.w #4,($FFFFEED8).w
Line 1,079: Line 1,057:
 
bsr.w Sonic_AnglePos
 
bsr.w Sonic_AnglePos
 
move.w #$60,($FFFFF73E).w ; reset looking up/down
 
move.w #$60,($FFFFF73E).w ; reset looking up/down
rts</asm>
+
rts</syntaxhighlight>
  
  
  
 
Now, build the ROM, and the result is... oops, something's missing. Right, we forgot to add the dust art. <br>
 
Now, build the ROM, and the result is... oops, something's missing. Right, we forgot to add the dust art. <br>
So download the spindash dust art here (put it in the artunc\ folder inside your source dir),  
+
So download the Spin Dash dust art [[Media:Spindust.zip|here]] (put it in the artunc\ folder inside your source dir),  
 
and at the end of the ROM, after SegaPCM, add the following lines:
 
and at the end of the ROM, after SegaPCM, add the following lines:
  
<asm>Art_Dust incbin artunc\spindust.bin</asm>
+
<syntaxhighlight lang="asm">Art_Dust incbin artunc\spindust.bin</syntaxhighlight>
  
  
Now compile it and try a spindash:
+
Now compile it and try a Spin Dash:
  
 
[[Image:SpindashGuide_Pic2.png]]
 
[[Image:SpindashGuide_Pic2.png]]
Line 1,105: Line 1,083:
 
So open the file _inc\Pattern load cues.asm, and search for these lines:
 
So open the file _inc\Pattern load cues.asm, and search for these lines:
  
<asm>PLC_Main: dc.w 4
+
<syntaxhighlight lang="asm">PLC_Main: dc.w 4
 
dc.l Nem_Lamp ; lamppost
 
dc.l Nem_Lamp ; lamppost
dc.w $F400</asm>
+
dc.w $F400</syntaxhighlight>
  
 
Change them to:
 
Change them to:
  
<asm>PLC_Main: dc.w 4
+
<syntaxhighlight lang="asm">PLC_Main: dc.w 4
 
dc.l Nem_Lamp ; lamppost
 
dc.l Nem_Lamp ; lamppost
dc.w $D800</asm>
+
dc.w $D800</syntaxhighlight>
  
 
This will load the lamppost art in $D800 in VRAM, instead of $F400. <br>
 
This will load the lamppost art in $D800 in VRAM, instead of $F400. <br>
Line 1,119: Line 1,097:
 
So now go to Obj79 (lamppost object) and search for this line, which should be in Obj79_Main:
 
So now go to Obj79 (lamppost object) and search for this line, which should be in Obj79_Main:
  
<asm> move.w #$7A0,2(a0)</asm>
+
<syntaxhighlight lang="asm"> move.w #$7A0,2(a0)</syntaxhighlight>
  
 
Change it to:
 
Change it to:
  
<asm> move.w #($D800/$20),2(a0)</asm>
+
<syntaxhighlight lang="asm"> move.w #($D800/$20),2(a0)</syntaxhighlight>
  
 
The Obj79_Main routine should then look like this:
 
The Obj79_Main routine should then look like this:
  
<asm>Obj79_Main: ; XREF: Obj79_Index
+
<syntaxhighlight lang="asm">Obj79_Main: ; XREF: Obj79_Index
 
addq.b #2,$24(a0)
 
addq.b #2,$24(a0)
 
move.l #Map_obj79,4(a0)
 
move.l #Map_obj79,4(a0)
Line 1,145: Line 1,123:
 
andi.b #$7F,d2
 
andi.b #$7F,d2
 
cmp.b d2,d1 ; is lamppost number higher than the number hit?
 
cmp.b d2,d1 ; is lamppost number higher than the number hit?
bcs.s Obj79_BlueLamp ; if yes, branch</asm>
+
bcs.s Obj79_BlueLamp ; if yes, branch</syntaxhighlight>
  
 
Similarly, change this line in Obj79_HitLamp:
 
Similarly, change this line in Obj79_HitLamp:
  
<asm> move.w #$7A0,2(a1)</asm>
+
<syntaxhighlight lang="asm"> move.w #$7A0,2(a1)</syntaxhighlight>
  
 
to this:
 
to this:
  
<asm> move.w #($D800/$20),2(a1)</asm>
+
<syntaxhighlight lang="asm"> move.w #($D800/$20),2(a1)</syntaxhighlight>
  
 
Therefore making Obj79_HitLamp look like this:
 
Therefore making Obj79_HitLamp look like this:
  
<asm>Obj79_HitLamp:
+
<syntaxhighlight lang="asm">Obj79_HitLamp:
  
 
move.w ($FFFFD008).w,d0
 
move.w ($FFFFD008).w,d0
Line 1,185: Line 1,163:
 
move.b #4,$18(a1)
 
move.b #4,$18(a1)
 
move.b #2,$1A(a1)
 
move.b #2,$1A(a1)
move.w #$20,$36(a1)</asm>
+
move.w #$20,$36(a1)</syntaxhighlight>
 
 
 
 
 
 
...and that's it. Compile your ROM and we should now have perfect spindash in Sonic 1. <br>
 
[[:Image:Spindash7.zip|Here]]'s a rom of the result...
 
  
 +
...and that's it. Compile your ROM and we should now have perfect Spin Dash in Sonic 1.
  
 
[[Image:SpindashGuide_Pic4.png]]
 
[[Image:SpindashGuide_Pic4.png]]
  
=Optional Step: Sega Sound=
+
[[Media:Spindash8_Final.zip|Here]]'s the final result of this thing. Source code available [[Media:SpindashGuide_EndSource.rar|here.]] Have fun :)
...But while we're at it, let's fix a problem that, while irrelevant to the spindash itself,
 
causes a great deal of pain to anyone adding code to Sonic 1: The SEGA sound.
 
 
 
Thanks to Esrael, we can now fix it properly, without having it garbled every time a line of code is added behind it.
 
 
 
So go to SoundDriverLoad, and after this line:
 
 
 
<asm> bsr.w KosDec ; decompress</asm>
 
 
 
Add this:
 
 
 
<asm> move.b #(SegaPCM/$10000),$A00019.l</asm>
 
 
 
Similarly, go to where SegaPCM is, and right before that, add the following line:
 
 
 
<asm> cnop -$6978,$10000</asm>
 
 
 
And before the "even", add this:
 
 
 
<asm>EndSegaPCM:</asm>
 
 
 
So the final result should look like this:
 
 
 
<asm> cnop -$6978,$10000
 
SegaPCM: incbin sound\segapcm.bin
 
EndSegaPCM: even</asm>
 
 
 
 
 
 
 
And the SoundDriverLoad routine should look like this:
 
 
 
<asm>SoundDriverLoad: ; XREF: GameClrRAM; TitleScreen
 
nop
 
move.w #$100,($A11100).l ; stop the Z80
 
move.w #$100,($A11200).l ; reset the Z80
 
lea (Kos_Z80).l,a0 ; load sound driver
 
lea ($A00000).l,a1
 
bsr.w KosDec ; decompress
 
move.b #(SegaPCM/$10000),$A00019.l
 
move.w #0,($A11200).l
 
nop
 
nop
 
nop
 
nop
 
move.w #$100,($A11200).l ; reset the Z80
 
move.w #0,($A11100).l ; start the Z80
 
rts </asm>
 
 
 
Finally, the compile time calculation that's done at Kos_Z80 is stupid,
 
as it relies on the SegaPCM being at the end of the ROM, which isn't the case anymore. <br>
 
So go to Kos_Z80, and replace all instances of EndOfRom with EndSegaPCM. The end result will then be this:
 
 
 
<asm>Kos_Z80: incbin sound\z80_1.bin
 
dc.w ((SegaPCM&$FF)<<8)+((SegaPCM&$FF00)>>8)
 
dc.b $21
 
dc.w (((EndSegaPCM-SegaPCM)&$FF)<<8)+(((EndSegaPCM-SegaPCM)&$FF00)>>8)
 
incbin sound\z80_2.bin
 
even</asm>
 
  
[[:Image:Spindash8_Final.zip|Here]]'s the final result of this thing. Source code available [[:Image:SpindashGuide_EndSource.rar|here.]] Have fun :)
+
Note that you should look [[SCHG_How-to:Fix the SEGA Sound|here]] for instructions on fixing the SEGA sound, and see [[SCHG How-to:Add Spin Dash to Sonic 1/Part 3|Part 3]] for some additional fixes not covered in this guide.
  
[[Category:SCHG How-tos]]
+
{{S1Howtos}}
 +
|Add Spin Dash to Sonic 1/Part 2]]

Latest revision as of 17:43, 20 November 2023

(Original guide by Puto)

Here's a Sonic 1 + Spin Dash ROM built by following Lightning's guide. If you try it, you will be able to easily see that there are several things that keep it from being a perfect port.
For example, if you Spin Dash next to a monitor, you won't break it.
Also, the sound effect, while fitting, isn't ideal, there's no dust, and if you Spin Dash while on a seesaw (SLZ), Strange Things™ will happen. So let's fix this.

Problem 1: The Monitor Bug

When you try to Spin Dash next to a monitor, you won't be able to do that and simply stop when releasing. It only works if you walk a little bit away from the monitor and do the Spin Dash. To fix that, go to loc_A1EC (Hint for GitHub Disassembly users: Look for file called _incObj/26 Monitor.asm). You should see something like this:

loc_A1EC:				; XREF: Obj26_Solid
		move.w	#$1A,d1
		move.w	#$F,d2
		bsr.w	Obj26_SolidSides
		beq.w	loc_A25C
		tst.w	$12(a1)
		bmi.s	loc_A20A
		cmpi.b	#2,$1C(a1)	; is Sonic rolling?
		beq.s	loc_A25C	; if yes, branch

By reading this code, you can see that there is a check for the rolling animation there. This is what causes the monitor bug. To fix it, simply add a second check for the Spin Dash animation after this one:

loc_A1EC:				; XREF: Obj26_Solid
		move.w	#$1A,d1
		move.w	#$F,d2
		bsr.w	Obj26_SolidSides
		beq.w	loc_A25C
		tst.w	$12(a1)
		bmi.s	loc_A20A
		cmpi.b	#2,$1C(a1)	; is Sonic rolling?
		beq.s	loc_A25C	; if yes, branch
		cmpi.b	#$1F,$1C(a1)	; is Sonic spin-dashing?
		beq.s	loc_A25C	; if yes, branch

Great, let's try it. It should be working and... oh dear.
This isn't right.

SpindashGuide Pic1.png

We're Spin Dashing, we're not supposed to use the pushing animation. To fix that, go to Sonic_SpinDash, and look at what it does.
These are the first lines:

Sonic_SpinDash:
		tst.b	$39(a0)			; already Spin Dashing?
		bne.s	loc2_1AC8E		; if set, branch

Since we know we're Spin Dashing when $39(a0) is set, let's make sure that the Spin Dash animation is used when that happens.
Therefore, go to loc2_1AC8E, and right after the label, add the following line:

		move.b	#$1F,$1C(a0)

Build the ROM again, and the monitor bug should be permanently taken care of. Here's a ROM after this step was carried out.

Problem 2: Spin Dash Sound Effect

Adding a sound effect to Sonic 1 isn't the most trivial of things to do. So be sure to follow these steps exactly.

First, we need a free slot. Since all the slots reserved for sound effects in Sonic 1 are taken, we therefore need to reserve extra slots for sound effects. The $D1-$DF area is free, so let's use that. Go to Sound_ChkValue, and you should be able to see this line there:

		cmpi.b	#$E0,d7
		bcs.w	Sound_D0toDF	; sound	$D0-$DF

Since only $D0 is actually used here, it's a rather large waste to keep the rest of the slots unused, so change that $E0 to $D1:

		cmpi.b	#$D1,d7
		bcs.w	Sound_D0toDF	; sound	$D0

And after that line, let's add our own comparison:

		cmpi.b	#$DF,d7
		blo.w	Sound_D1toDF	; sound	$D1-$DF

Now, we have to actually create this routine. So go to Sound_A0toCF, and see what it does:

Sound_A0toCF:				; XREF: Sound_ChkValue
		tst.b	$27(a6)
		bne.w	loc_722C6
		tst.b	4(a6)
		bne.w	loc_722C6
		tst.b	$24(a6)
		bne.w	loc_722C6
		cmpi.b	#$B5,d7		; is ring sound	effect played?
		bne.s	Sound_notB5	; if not, branch
		tst.b	$2B(a6)
		bne.s	loc_721EE
		move.b	#$CE,d7		; play ring sound in left speaker

loc_721EE:
		bchg	#0,$2B(a6)	; change speaker

Sound_notB5:
		cmpi.b	#$A7,d7		; is "pushing" sound played?
		bne.s	Sound_notA7	; if not, branch
		tst.b	$2C(a6)
		bne.w	locret_722C4
		move.b	#$80,$2C(a6)

Sound_notA7:
		movea.l	(Go_SoundIndex).l,a0
		subi.b	#$A0,d7
		lsl.w	#2,d7
		movea.l	(a0,d7.w),a3
		movea.l	a3,a1
		moveq	#0,d1
		move.w	(a1)+,d1
		add.l	a3,d1
		move.b	(a1)+,d5
		move.b	(a1)+,d7
		subq.b	#1,d7
		moveq	#$30,d6
		(...)

As you can see, this routine has a lot of checks for special sound effects, like the ring effect and the pushing effect.
We don't need that for our new routine, so trash all of that, and you end up with:

Sound_D1toDF:
		tst.b	$27(a6)
		bne.w	loc_722C6
		tst.b	4(a6)
		bne.w	loc_722C6
		tst.b	$24(a6)
		bne.w	loc_722C6
		movea.l	(Go_SoundIndex).l,a0
		subi.b	#$A0,d7
		lsl.w	#2,d7
		movea.l	(a0,d7.w),a3
		movea.l	a3,a1
		moveq	#0,d1
		move.w	(a1)+,d1
		add.l	a3,d1
		move.b	(a1)+,d5
		move.b	(a1)+,d7
		subq.b	#1,d7
		moveq	#$30,d6
		(...)

There's a problem here: If we subtract $A0 to get the current index, then the next entry in this index will be sound $D0, which is handled by a different routine, forcing us to have an unused entry in an index. So, change that line to subtract $A1 instead:

Sound_D1toDF:
		tst.b	$27(a6)
		bne.w	loc_722C6
		tst.b	4(a6)
		bne.w	loc_722C6
		tst.b	$24(a6)
		bne.w	loc_722C6
		movea.l	(Go_SoundIndex).l,a0
		subi.b	#$A1,d7
		lsl.w	#2,d7
		movea.l	(a0,d7.w),a3
		movea.l	a3,a1
		moveq	#0,d1
		move.w	(a1)+,d1
		add.l	a3,d1
		move.b	(a1)+,d5
		move.b	(a1)+,d7
		subq.b	#1,d7
		moveq	#$30,d6
		(...)

Also, it can be noted that any code after the $A1 line is common to the two routines.
So in the original routine, add a label right before "lsl.w #2,d7":

Sound_notA7:
		movea.l	(Go_SoundIndex).l,a0
		subi.b	#$A0,d7
SoundEffects_Common:
		lsl.w	#2,d7
		movea.l	(a0,d7.w),a3
		movea.l	a3,a1
		moveq	#0,d1
		move.w	(a1)+,d1
		add.l	a3,d1
		move.b	(a1)+,d5
		move.b	(a1)+,d7
		subq.b	#1,d7
		moveq	#$30,d6
		(...)

And we can now greatly reduce the size of our new routine:

Sound_D1toDF:
		tst.b	$27(a6)
		bne.w	loc_722C6
		tst.b	4(a6)
		bne.w	loc_722C6
		tst.b	$24(a6)
		bne.w	loc_722C6
		movea.l	(Go_SoundIndex).l,a0
		sub.b	#$A1,d7
		bra	SoundEffects_Common

So, after this is done, put the routine right above Sound_A0toCF.
Now, all we need is the actual ported spin-dash sound effect, which I provide here. Put this file in the sound\ directory of your source code, and go to the SoundD0 label. After the even, add the following lines:

SoundD1:	incbin	sound\soundD1.bin
		even

So the surrounding code should look like this:

SoundCF:	incbin	sound\soundCF.bin
		even
SoundD0:	incbin	sound\soundD0.bin
		even
SoundD1:	incbin	sound\soundD1.bin
		even		
SegaPCM:	incbin	sound\segapcm.bin
		even

And finally, add SoundD1 to the index:

SoundIndex:	dc.l SoundA0, SoundA1, SoundA2
		dc.l SoundA3, SoundA4, SoundA5
		dc.l SoundA6, SoundA7, SoundA8
		dc.l SoundA9, SoundAA, SoundAB
		dc.l SoundAC, SoundAD, SoundAE
		dc.l SoundAF, SoundB0, SoundB1
		dc.l SoundB2, SoundB3, SoundB4
		dc.l SoundB5, SoundB6, SoundB7
		dc.l SoundB8, SoundB9, SoundBA
		dc.l SoundBB, SoundBC, SoundBD
		dc.l SoundBE, SoundBF, SoundC0
		dc.l SoundC1, SoundC2, SoundC3
		dc.l SoundC4, SoundC5, SoundC6
		dc.l SoundC7, SoundC8, SoundC9
		dc.l SoundCA, SoundCB, SoundCC
		dc.l SoundCD, SoundCE, SoundCF
		dc.l SoundD1

Now, go to Sonic_SpinDash, and change all references to sound $BE to reference sound $D1 instead. For reference, here's the complete routine:

Sonic_SpinDash:
		tst.b	$39(a0)			; already Spin Dashing?
		bne.s	loc2_1AC8E		; if set, branch
		cmpi.b	#8,$1C(a0)		; is anim duck
		bne.s	locret2_1AC8C		; if not, return
		move.b	($FFFFF603).w,d0	; read controller
		andi.b	#$70,d0			; pressing A/B/C ?
		beq.w	locret2_1AC8C		; if not, return
		move.b	#$1F,$1C(a0)		; set Spin Dash anim (9 in s2)
		move.w	#$D1,d0			; spin sound ($E0 in s2)
		jsr	(PlaySound_Special).l	; play spin sound
		addq.l	#4,sp			; increment stack ptr
		move.b	#1,$39(a0)		; set Spin Dash flag
		move.w	#0,$3A(a0)		; set charge count to 0
		cmpi.b	#$C,$28(a0)		; ??? oxygen remaining?
		bcs.s	loc2_1AC84		; ??? branch if carry
		move.b	#2,($FFFFD11C).w	; ??? $D11C only seems
						; to be used in Spin Dash
loc2_1AC84:
		bsr.w	Sonic_LevelBound
		bsr.w	Sonic_AnglePos
 
locret2_1AC8C:
		rts	
; ---------------------------------------------------------------------------
 
loc2_1AC8E:
		move.b	#$1F,$1C(a0)
		move.b	($FFFFF602).w,d0	; read controller
		btst	#1,d0			; check down button
		bne.w	loc2_1AD30		; if set, branch
		move.b	#$E,$16(a0)		; $16(a0) is height/2
		move.b	#7,$17(a0)		; $17(a0) is width/2
		move.b	#2,$1C(a0)		; set animation to roll
		addq.w	#5,$C(a0)		; $C(a0) is Y coordinate
		move.b	#0,$39(a0)		; clear Spin Dash flag
		moveq	#0,d0
		move.b	$3A(a0),d0		; copy charge count
		add.w	d0,d0			; double it
		move.w	Dash_Speeds(pc,d0.w),$14(a0) ; get normal speed
		move.w	$14(a0),d0		; get inertia
		subi.w	#$800,d0		; subtract $800
		add.w	d0,d0			; double it
		andi.w	#$1F00,d0		; mask it against $1F00
		neg.w	d0			; negate it
		addi.w	#$2000,d0		; add $2000
		move.w	d0,($FFFFEED0).w	; move to $EED0
		btst	#0,$22(a0)		; is sonic facing right?
		beq.s	loc2_1ACF4		; if not, branch
		neg.w	$14(a0)			; negate inertia
 
loc2_1ACF4:
		bset	#2,$22(a0)		; set unused (in s1) flag
		move.b	#0,($FFFFD11C).w	; clear $D11C (unused?)
		move.w	#$BC,d0			; spin release sound
		jsr	(PlaySound_Special).l	; play it!
		bra.s	loc2_1AD78
; ===========================================================================
Dash_Speeds:	dc.w  $800		; 0
		dc.w  $880		; 1
		dc.w  $900		; 2
		dc.w  $980		; 3
		dc.w  $A00		; 4
		dc.w  $A80		; 5
		dc.w  $B00		; 6
		dc.w  $B80		; 7
		dc.w  $C00		; 8
; ===========================================================================
 
loc2_1AD30:				; If still charging the dash...
		tst.w	$3A(a0)		; check charge count
		beq.s	loc2_1AD48	; if zero, branch
		move.w	$3A(a0),d0	; otherwise put it in d0
		lsr.w	#5,d0		; shift right 5 (divide it by 32)
		sub.w	d0,$3A(a0)	; subtract from charge count
		bcc.s	loc2_1AD48	; ??? branch if carry clear
		move.w	#0,$3A(a0)	; set charge count to 0
 
loc2_1AD48:
		move.b	($FFFFF603).w,d0	; read controller
		andi.b	#$70,d0			; pressing A/B/C?
		beq.w	loc2_1AD78		; if not, branch
		move.w	#$1F00,$1C(a0)		; reset spdsh animation
		move.w	#$D1,d0			; was $E0 in sonic 2
		jsr	(PlaySound_Special).l	; play charge sound	
		addi.w	#$200,$3A(a0)		; increase charge count
		cmpi.w	#$800,$3A(a0)		; check if it's maxed
		bcs.s	loc2_1AD78		; if not, then branch
		move.w	#$800,$3A(a0)		; reset it to max
 
loc2_1AD78:
		addq.l	#4,sp			; increase stack ptr
		cmpi.w	#$60,($FFFFEED8).w	; $EED8 only ever seems
		beq.s	loc2_1AD8C		; to be used in Spin Dash
		bcc.s	loc2_1AD88
		addq.w	#4,($FFFFEED8).w
 
loc2_1AD88:
		subq.w	#2,($FFFFEED8).w
 
loc2_1AD8C:
		bsr.w	Sonic_LevelBound
		bsr.w	Sonic_AnglePos
		move.w	#$60,($FFFFF73E).w	; reset looking up/down
		rts

So, problem solved. Here's a rom of the output after fixing this problem.

Problem 3: See-Saw Bug (AKA Sonic 2 Spin Dash Bug)

This problem occurs when you're Spin Dashing on a seesaw, and then you get thrown up by the seesaw.
You keep your Spin Dash state, and dash off immediately once you land. This is clearly not the wanted behaviour.
To fix this, go to Obj01_MdJump and Obj01_MdJump2, and add the following line at the beginning of each of the two routines:

		clr.b	$39(a0)

This should fix the see-saw bug. Here's the rom.

Problem 4: Spin Dash Dust

This is a big one. For starters, you need to port the Spin Dash dust object from Sonic 2.
For your convenience, here is the code to the ported object, paste it right before Obj01 (the sonic object):

SpinDash_dust:
Sprite_1DD20:				; DATA XREF: ROM:0001600C?o
		moveq	#0,d0
		move.b	$24(a0),d0
		move	off_1DD2E(pc,d0.w),d1
		jmp	off_1DD2E(pc,d1.w)
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
off_1DD2E:	dc loc_1DD36-off_1DD2E; 0 ; DATA XREF: h+6DBA?o h+6DBC?o ...
		dc loc_1DD90-off_1DD2E; 1
		dc loc_1DE46-off_1DD2E; 2
		dc loc_1DE4A-off_1DD2E; 3
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ

loc_1DD36:				; DATA XREF: h+6DBA?o
		addq.b	#2,$24(a0)
		move.l	#MapUnc_1DF5E,4(a0)
		or.b	#4,1(a0)
		move.b	#1,$18(a0)
		move.b	#$10,$19(a0)
		move	#$7A0,2(a0)
		move	#-$3000,$3E(a0)
		move	#$F400,$3C(a0)
		cmp	#-$2E40,a0
		beq.s	loc_1DD8C
		move.b	#1,$34(a0)
;		cmp	#2,($FFFFFF70).w
;		beq.s	loc_1DD8C
;		move	#$48C,2(a0)
;		move	#-$4FC0,$3E(a0)
;		move	#-$6E80,$3C(a0)

loc_1DD8C:				; CODE XREF: h+6DF6?j h+6E04?j
;		bsr.w	sub_16D6E

loc_1DD90:				; DATA XREF: h+6DBA?o
		movea.w	$3E(a0),a2
		moveq	#0,d0
		move.b	$1C(a0),d0
		add	d0,d0
		move	off_1DDA4(pc,d0.w),d1
		jmp	off_1DDA4(pc,d1.w)
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
off_1DDA4:	dc loc_1DE28-off_1DDA4; 0 ; DATA XREF: h+6E30?o h+6E32?o ...
		dc loc_1DDAC-off_1DDA4; 1
		dc loc_1DDCC-off_1DDA4; 2
		dc loc_1DE20-off_1DDA4; 3
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ

loc_1DDAC:				; DATA XREF: h+6E30?o
		move	($FFFFF646).w,$C(a0)
		tst.b	$1D(a0)
		bne.s	loc_1DE28
		move	8(a2),8(a0)
		move.b	#0,$22(a0)
		and	#$7FFF,2(a0)
		bra.s	loc_1DE28
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ

loc_1DDCC:				; DATA XREF: h+6E30?o
;		cmp.b	#$C,$28(a2)
;		bcs.s	loc_1DE3E
		cmp.b	#4,$24(a2)
		bcc.s	loc_1DE3E
		tst.b	$39(a2)
		beq.s	loc_1DE3E
		move	8(a2),8(a0)
		move	$C(a2),$C(a0)
		move.b	$22(a2),$22(a0)
		and.b	#1,$22(a0)
		tst.b	$34(a0)
		beq.s	loc_1DE06
		sub	#4,$C(a0)

loc_1DE06:				; CODE XREF: h+6E8A?j
		tst.b	$1D(a0)
		bne.s	loc_1DE28
		and	#$7FFF,2(a0)
		tst	2(a2)
		bpl.s	loc_1DE28
		or	#-$8000,2(a0)
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ

loc_1DE20:				; DATA XREF: h+6E30?o
loc_1DE28:				; CODE XREF: h+6E42?j h+6E56?j ...
		lea	(off_1DF38).l,a1
		jsr	AnimateSprite
		bsr.w	loc_1DEE4
		jmp	DisplaySprite
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ

loc_1DE3E:				; CODE XREF: h+6E5E?j h+6E66?j ...
		move.b	#0,$1C(a0)
		rts	
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ

loc_1DE46:				; DATA XREF: h+6DBA?o
		bra.w	DeleteObject
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ



loc_1DE4A:
	movea.w	$3E(a0),a2
	moveq	#$10,d1
	cmp.b	#$D,$1C(a2)
	beq.s	loc_1DE64
	moveq	#$6,d1
	cmp.b	#$3,$21(a2)
	beq.s	loc_1DE64
	move.b	#2,$24(a0)
	move.b	#0,$32(a0)
	rts
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ

loc_1DE64:				; CODE XREF: h+6EE0?j
		subq.b	#1,$32(a0)
		bpl.s	loc_1DEE0
		move.b	#3,$32(a0)
		jsr	SingleObjLoad
		bne.s	loc_1DEE0
		move.b	0(a0),0(a1)
		move	8(a2),8(a1)
		move	$C(a2),$C(a1)
		tst.b	$34(a0)
		beq.s	loc_1DE9A
		sub	#4,d1

loc_1DE9A:				; CODE XREF: h+6F1E?j
		add	d1,$C(a1)
		move.b	#0,$22(a1)
		move.b	#3,$1C(a1)
		addq.b	#2,$24(a1)
		move.l	4(a0),4(a1)
		move.b	1(a0),1(a1)
		move.b	#1,$18(a1)
		move.b	#4,$19(a1)
		move	2(a0),2(a1)
		move	$3E(a0),$3E(a1)
		and	#$7FFF,2(a1)
		tst	2(a2)
		bpl.s	loc_1DEE0
		or	#-$8000,2(a1)

loc_1DEE0:				; CODE XREF: h+6EF4?j h+6F00?j ...
		bsr.s	loc_1DEE4
		rts	
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ

loc_1DEE4:				; CODE XREF: h+6EC0?p h+6F6C?p
		moveq	#0,d0
		move.b	$1A(a0),d0
		cmp.b	$30(a0),d0
		beq.w	locret_1DF36
		move.b	d0,$30(a0)
		lea	(off_1E074).l,a2
		add	d0,d0
		add	(a2,d0.w),a2
		move	(a2)+,d5
		subq	#1,d5
		bmi.w	locret_1DF36
		move $3C(a0),d4

loc_1DF0A:				; CODE XREF: h+6FBE?j
		moveq	#0,d1
		move	(a2)+,d1
		move	d1,d3
		lsr.w	#8,d3
		and	#$F0,d3	; 'ð'
		add	#$10,d3
		and	#$FFF,d1
		lsl.l	#5,d1
		add.l	#Art_Dust,d1
		move	d4,d2
		add	d3,d4
		add	d3,d4
		jsr	(DMA_68KtoVRAM).l
		dbf	d5,loc_1DF0A
    rts

locret_1DF36:				; CODE XREF: h+6F7A?j h+6F90?j
		rts	
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
off_1DF38:	dc byte_1DF40-off_1DF38; 0 ; DATA XREF: h+6EB4?o h+6FC4?o ...
		dc byte_1DF43-off_1DF38; 1
		dc byte_1DF4F-off_1DF38; 2
		dc byte_1DF58-off_1DF38; 3
byte_1DF40:	dc.b $1F,  0,$FF	; 0 ; DATA XREF: h+6FC4?o
byte_1DF43:	dc.b   3,  1,  2,  3,  4,  5,  6,  7,  8,  9,$FD,  0; 0	; DATA XREF: h+6FC4?o
byte_1DF4F:	dc.b   1, $A, $B, $C, $D, $E, $F,$10,$FF; 0 ; DATA XREF: h+6FC4?o
byte_1DF58:	dc.b   3,$11,$12,$13,$14,$FC; 0	; DATA XREF: h+6FC4?o
; -------------------------------------------------------------------------------
; Unknown Sprite Mappings
; -------------------------------------------------------------------------------
MapUnc_1DF5E:
	dc word_1DF8A-MapUnc_1DF5E; 0
	dc word_1DF8C-MapUnc_1DF5E; 1
	dc word_1DF96-MapUnc_1DF5E; 2
	dc word_1DFA0-MapUnc_1DF5E; 3
	dc word_1DFAA-MapUnc_1DF5E; 4
	dc word_1DFB4-MapUnc_1DF5E; 5
	dc word_1DFBE-MapUnc_1DF5E; 6
	dc word_1DFC8-MapUnc_1DF5E; 7
	dc word_1DFD2-MapUnc_1DF5E; 8
	dc word_1DFDC-MapUnc_1DF5E; 9
	dc word_1DFE6-MapUnc_1DF5E; 10
	dc word_1DFF0-MapUnc_1DF5E; 11
	dc word_1DFFA-MapUnc_1DF5E; 12
	dc word_1E004-MapUnc_1DF5E; 13
	dc word_1E016-MapUnc_1DF5E; 14
	dc word_1E028-MapUnc_1DF5E; 15
	dc word_1E03A-MapUnc_1DF5E; 16
	dc word_1E04C-MapUnc_1DF5E; 17
	dc word_1E056-MapUnc_1DF5E; 18
	dc word_1E060-MapUnc_1DF5E; 19
	dc word_1E06A-MapUnc_1DF5E; 20
	dc word_1DF8A-MapUnc_1DF5E; 21
word_1DF8A:	dc.b 0
word_1DF8C:	dc.b 1
	dc.b $F2, $0D, $0, 0,$F0; 0
word_1DF96:	dc.b 1
	dc.b $E2, $0F, $0, 0,$F0; 0
word_1DFA0:	dc.b 1
	dc.b $E2, $0F, $0, 0,$F0; 0
word_1DFAA:	dc.b 1
	dc.b $E2, $0F, $0, 0,$F0; 0
word_1DFB4:	dc.b 1
	dc.b $E2, $0F, $0, 0,$F0; 0
word_1DFBE:	dc.b 1
	dc.b $E2, $0F, $0, 0,$F0; 0
word_1DFC8:	dc.b 1
	dc.b $F2, $0D, $0, 0,$F0; 0
word_1DFD2:	dc.b 1
	dc.b $F2, $0D, $0, 0,$F0; 0
word_1DFDC:	dc.b 1
	dc.b $F2, $0D, $0, 0,$F0; 0
word_1DFE6:	dc.b 1
	dc.b $4, $0D, $0, 0,$E0; 0
word_1DFF0:	dc.b 1
	dc.b $4, $0D, $0, 0,$E0; 0
word_1DFFA:	dc.b 1
	dc.b $4, $0D, $0, 0,$E0; 0
word_1E004:	dc.b 2
	dc.b $F4, $01, $0, 0,$E8; 0
	dc.b $4, $0D, $0, 2,$E0; 4
word_1E016:	dc.b 2
	dc.b $F4, $05, $0, 0,$E8; 0
	dc.b $4, $0D, $0, 4,$E0; 4
word_1E028:	dc.b 2
	dc.b $F4, $09, $0, 0,$E0; 0
	dc.b $4, $0D, $0, 6,$E0; 4
word_1E03A:	dc.b 2
	dc.b $F4, $09, $0, 0,$E0; 0
	dc.b $4, $0D, $0, 6,$E0; 4
word_1E04C:	dc.b 1
	dc.b $F8, $05, $0, 0,$F8; 0
word_1E056:	dc.b 1
	dc.b $F8, $05, $0, 4,$F8; 0
word_1E060:	dc.b 1
	dc.b $F8, $05, $0, 8,$F8; 0
word_1E06A:	dc.b 1
	dc.b $F8, $05, $0, $C,$F8; 0
	dc.b 0
off_1E074:	dc word_1E0A0-off_1E074; 0
	dc word_1E0A2-off_1E074; 1
	dc word_1E0A6-off_1E074; 2
	dc word_1E0AA-off_1E074; 3
	dc word_1E0AE-off_1E074; 4
	dc word_1E0B2-off_1E074; 5
	dc word_1E0B6-off_1E074; 6
	dc word_1E0BA-off_1E074; 7
	dc word_1E0BE-off_1E074; 8
	dc word_1E0C2-off_1E074; 9
	dc word_1E0C6-off_1E074; 10
	dc word_1E0CA-off_1E074; 11
	dc word_1E0CE-off_1E074; 12
	dc word_1E0D2-off_1E074; 13
	dc word_1E0D8-off_1E074; 14
	dc word_1E0DE-off_1E074; 15
	dc word_1E0E4-off_1E074; 16
	dc word_1E0EA-off_1E074; 17
	dc word_1E0EA-off_1E074; 18
	dc word_1E0EA-off_1E074; 19
	dc word_1E0EA-off_1E074; 20
	dc word_1E0EC-off_1E074; 21
word_1E0A0:	dc 0
word_1E0A2:	dc 1
	dc $7000
word_1E0A6:	dc 1
	dc $F008
word_1E0AA:	dc 1
	dc $F018
word_1E0AE:	dc 1
	dc $F028
word_1E0B2:	dc 1
	dc $F038
word_1E0B6:	dc 1
	dc $F048
word_1E0BA:	dc 1
	dc $7058
word_1E0BE:	dc 1
	dc $7060
word_1E0C2:	dc 1
	dc $7068
word_1E0C6:	dc 1
	dc $7070
word_1E0CA:	dc 1
	dc $7078
word_1E0CE:	dc 1
	dc $7080
word_1E0D2:	dc 2
	dc $1088
	dc $708A
word_1E0D8:	dc 2
	dc $3092
	dc $7096
word_1E0DE:	dc 2
	dc $509E
	dc $70A4
word_1E0E4:	dc 2
	dc $50AC
	dc $70B2
word_1E0EA:	dc 0
word_1E0EC:	dc 1
	dc $F0BA
	even

Now, save and try to build... oops! It seems that there's a routine missing: DMA_68KtoVRAM.
This routine handles the DMA queue present in Sonic 2, but that queue does not exist in Sonic 1.
This queue is used in Sonic 2 to handle the dynamic art for Sonic, Tails, and the dust.
Here's the code to the DMA queue routines in the Sonic 2 Nick Arcade prototype (porting it from the NA disassembly is somewhat... ...easier than porting from the Sonic 2 Final disassembly, so let's use that).

DMA_68KtoVRAM:				; CODE XREF: LoadSonicDynPLC+48?p
					; LoadTailsDynPLC+48?p ...
		movea.l	($FFFFDCFC).w,a1
		cmpa.w	#$DCFC,a1
		beq.s	DMA_68KtoVRAM_NoDMA
		move.w	#$9300,d0
		move.b	d3,d0
		move.w	d0,(a1)+
		move.w	#$9400,d0
		lsr.w	#8,d3
		move.b	d3,d0
		move.w	d0,(a1)+
		move.w	#$9500,d0
		lsr.l	#1,d1
		move.b	d1,d0
		move.w	d0,(a1)+
		move.w	#$9600,d0
		lsr.l	#8,d1
		move.b	d1,d0
		move.w	d0,(a1)+
		move.w	#$9700,d0
		lsr.l	#8,d1
		move.b	d1,d0
		move.w	d0,(a1)+
		andi.l	#$FFFF,d2
		lsl.l	#2,d2
		lsr.w	#2,d2
		swap	d2
		ori.l	#$40000080,d2
		move.l	d2,(a1)+
		move.l	a1,($FFFFDCFC).w
		cmpa.w	#$DCFC,a1
		beq.s	DMA_68KtoVRAM_NoDMA
		move.w	#0,(a1)

DMA_68KtoVRAM_NoDMA:			; CODE XREF: DMA_68KtoVRAM+8?j
					; DMA_68KtoVRAM+56?j
		rts
; End of function DMA_68KtoVRAM


; ÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛ S U B	R O U T	I N E ÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛ


Process_DMA:				; CODE XREF: ROM:00000D9C?p
					; ROM:00000E84?p ...
		lea	($C00004).l,a5
		lea	($FFFFDC00).w,a1

Process_DMA_Loop:			; CODE XREF: Process_DMA+20?j
		move.w	(a1)+,d0
		beq.s	Process_DMA_End
		move.w	d0,(a5)
		move.w	(a1)+,(a5)
		move.w	(a1)+,(a5)
		move.w	(a1)+,(a5)
		move.w	(a1)+,(a5)
		move.w	(a1)+,(a5)
		move.w	(a1)+,(a5)
		cmpa.w	#$DCFC,a1
		bne.s	Process_DMA_Loop

Process_DMA_End:			; CODE XREF: Process_DMA+C?j
		move.w	#0,($FFFFDC00).w
		move.l	#$FFFFDC00,($FFFFDCFC).w
		rts
; End of function Process_DMA


Now, a brief explanation(sp?) of how I believe this routine works.
There's a queue that's $100 bytes long, that's stored at $DC00 in 68K RAM.
This queue is where the dynamically loaded art for Sonic, Tails, and the dust gets stored (you send the art to the queue by calling DMA_68KtoVRAM), until it's processed and sent to VRAM, which is done when the Process_DMA routine is called.
To port this to Sonic 1, we need to find an appropriate RAM location to fit it, and call Process_DMA at the right location.
So, where can we find $100 free bytes of RAM in Sonic 1 to fit this queue?

...Oops, we can't. But let's look at this from another point of view.
In Sonic 2, this queue is used to store the art for both Sonic, Tails, AND the dust.
For that reason, it needs $100 bytes. But we only need it to store the dust, and for that, we only actually need $30 bytes.
So, is there any location in RAM where we can stick a $30 bytes-long queue? Yes. We can stick it inside an unused object's SST.

I used the RAM location starting at $FFFFD3C2, if you know any better location, feel free to use it instead.
So we now need to change this routine to point to this new RAM location, as well as reduce its size.
To do that, replace all references to $FFFFDC00 with $FFFFD3C2, and $FFFFDCFC with $FFFFD3EE (and similarly, do the same for the "reduced" versions, $DC00->$D3C2, $DCFC->$D3EE).

Here's the routine after this conversion has been done:

DMA_68KtoVRAM:				; CODE XREF: LoadSonicDynPLC+48?p
					; LoadTailsDynPLC+48?p ...
		movea.l	($FFFFD3EE).w,a1
		cmpa.w	#$D3EE,a1
		beq.s	DMA_68KtoVRAM_NoDMA
		move.w	#$9300,d0
		move.b	d3,d0
		move.w	d0,(a1)+
		move.w	#$9400,d0
		lsr.w	#8,d3
		move.b	d3,d0
		move.w	d0,(a1)+
		move.w	#$9500,d0
		lsr.l	#1,d1
		move.b	d1,d0
		move.w	d0,(a1)+
		move.w	#$9600,d0
		lsr.l	#8,d1
		move.b	d1,d0
		move.w	d0,(a1)+
		move.w	#$9700,d0
		lsr.l	#8,d1
		move.b	d1,d0
		move.w	d0,(a1)+
		andi.l	#$FFFF,d2
		lsl.l	#2,d2
		lsr.w	#2,d2
		swap	d2
		ori.l	#$40000080,d2
		move.l	d2,(a1)+
		move.l	a1,($FFFFD3EE).w
		cmpa.w	#$D3EE,a1
		beq.s	DMA_68KtoVRAM_NoDMA
		move.w	#0,(a1)

DMA_68KtoVRAM_NoDMA:			; CODE XREF: DMA_68KtoVRAM+8?j
					; DMA_68KtoVRAM+56?j
		rts
; End of function DMA_68KtoVRAM


; ÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛ S U B	R O U T	I N E ÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛ


Process_DMA:				; CODE XREF: ROM:00000D9C?p
					; ROM:00000E84?p ...
		lea	($C00004).l,a5
		lea	($FFFFD3C2).w,a1

Process_DMA_Loop:			; CODE XREF: Process_DMA+20?j
		move.w	(a1)+,d0
		beq.s	Process_DMA_End
		move.w	d0,(a5)
		move.w	(a1)+,(a5)
		move.w	(a1)+,(a5)
		move.w	(a1)+,(a5)
		move.w	(a1)+,(a5)
		move.w	(a1)+,(a5)
		move.w	(a1)+,(a5)
		cmpa.w	#$D3EE,a1
		bne.s	Process_DMA_Loop

Process_DMA_End:			; CODE XREF: Process_DMA+C?j
		move.w	#0,($FFFFD3C2).w
		move.l	#$FFFFD3C2,($FFFFD3EE).w
		rts
; End of function Process_DMA

Paste it right after ShowVDPGraphics.
Now all that's left to get the queue up and running is to call Process_DMA in the right location.
To do that, go to loc_D50, and add these two lines at the beginning:

		move	#$83,($FFFFF640).w
		jsr	Process_DMA

The queue should now be working. Now we need to do one more thing, call the actual Spin Dash dust object.
To do that, open _inc\Object pointers.asm, and replace the first call to ObjectFall in the second line with Spin Dash_dust.

The outcome should be something like this:

; ---------------------------------------------------------------------------
; Object pointers
; ---------------------------------------------------------------------------
	dc.l Obj01, ObjectFall,	ObjectFall, ObjectFall
	dc.l SpinDash_dust, ObjectFall, ObjectFall, Obj08
	dc.l Obj09, Obj0A, Obj0B, Obj0C
	dc.l Obj0D, Obj0E, Obj0F, Obj10
	dc.l Obj11, Obj12, Obj13, Obj14
	dc.l Obj15, Obj16, Obj17, Obj18
	dc.l Obj19, Obj1A, Obj1B, Obj1C
	dc.l Obj1D, Obj1E, Obj1F, Obj20
	dc.l Obj21, Obj22, Obj23, Obj24
	dc.l Obj25, Obj26, Obj27, Obj28
	dc.l Obj29, Obj2A, Obj2B, Obj2C
	dc.l Obj2D, Obj2E, Obj2F, Obj30
	dc.l Obj31, Obj32, Obj33, Obj34
	dc.l Obj35, Obj36, Obj37, Obj38
	dc.l Obj39, Obj3A, Obj3B, Obj3C
	dc.l Obj3D, Obj3E, Obj3F, Obj40
	dc.l Obj41, Obj42, Obj43, Obj44
	dc.l Obj45, Obj46, Obj47, Obj48
	dc.l Obj49, Obj4A, Obj4B, Obj4C
	dc.l Obj4D, Obj4E, Obj4F, Obj50
	dc.l Obj51, Obj52, Obj53, Obj54
	dc.l Obj55, Obj56, Obj57, Obj58
	dc.l Obj59, Obj5A, Obj5B, Obj5C
	dc.l Obj5D, Obj5E, Obj5F, Obj60
	dc.l Obj61, Obj62, Obj63, Obj64
	dc.l Obj65, Obj66, Obj67, Obj68
	dc.l Obj69, Obj6A, Obj6B, Obj6C
	dc.l Obj6D, Obj6E, Obj6F, Obj70
	dc.l Obj71, Obj72, Obj73, Obj74
	dc.l Obj75, Obj76, Obj77, Obj78
	dc.l Obj79, Obj7A, Obj7B, Obj7C
	dc.l Obj7D, Obj7E, Obj7F, Obj80
	dc.l Obj81, Obj82, Obj83, Obj84
	dc.l Obj85, Obj86, Obj87, Obj88
	dc.l Obj89, Obj8A, Obj8B, Obj8C

This will cause Object ID 05, which was previously unused, to now point to the Spin Dash dust object.

Finally, we need to call the object itself. So go to Obj01_Main, and at the end of the routine (before Obj01_Control), add the following line:

		move.b	#5,$FFFFD1C0.w

This will load the dust object to address $FFFFD1C0, (addresses from $D000 to $D400 are part of the SST).

Finally, we need to have the Spin Dash routine set the dust's animation. To do that, go to Sonic_SpinDash, and change this line:


		move.b	#2,($FFFFD11C).w

to this:

		move.b	#2,($FFFFD1DC).w	; Set the Spin Dash dust animation to $2.


Also, remove this line:

		bcs.s	loc2_1AC84		; ??? branch if carry


And again, in loc2_1ACF4, change this line:

		move.b	#0,($FFFFD11C).w	; clear $D11C (unused?)

to this:

		move.b	#0,($FFFFD1DC).w	; clear Spin Dash dust animation.

And finally, add the following line in loc2_1AD48, right before the jsr to PlaySound_Special:

		move.b	#2,$FFFFD1DC.w	; Set the Spin Dash dust animation to $2.


For reference, here's the complete Sonic_SpinDash routine:

Sonic_SpinDash:
		tst.b	$39(a0)			; already Spin Dashing?
		bne.s	loc2_1AC8E		; if set, branch
		cmpi.b	#8,$1C(a0)		; is anim duck
		bne.s	locret2_1AC8C		; if not, return
		move.b	($FFFFF603).w,d0	; read controller
		andi.b	#$70,d0			; pressing A/B/C ?
		beq.w	locret2_1AC8C		; if not, return
		move.b	#$1F,$1C(a0)		; set Spin Dash anim (9 in s2)
		move.w	#$D1,d0			; spin sound ($E0 in s2)
		jsr	(PlaySound_Special).l	; play spin sound
		addq.l	#4,sp			; increment stack ptr
		move.b	#1,$39(a0)		; set Spin Dash flag
		move.w	#0,$3A(a0)		; set charge count to 0
		cmpi.b	#$C,$28(a0)		; ??? oxygen remaining?
		move.b	#2,($FFFFD1DC).w	; Set the Spin Dash dust animation to $2

loc2_1AC84:
		bsr.w	Sonic_LevelBound
		bsr.w	Sonic_AnglePos

locret2_1AC8C:
		rts	
; ---------------------------------------------------------------------------

loc2_1AC8E:
		move.b	#$1F,$1C(a0)
		move.b	($FFFFF602).w,d0	; read controller
		btst	#1,d0			; check down button
		bne.w	loc2_1AD30		; if set, branch
		move.b	#$E,$16(a0)		; $16(a0) is height/2
		move.b	#7,$17(a0)		; $17(a0) is width/2
		move.b	#2,$1C(a0)		; set animation to roll
		addq.w	#5,$C(a0)		; $C(a0) is Y coordinate
		move.b	#0,$39(a0)		; clear Spin Dash flag
		moveq	#0,d0
		move.b	$3A(a0),d0		; copy charge count
		add.w	d0,d0			; double it
		move.w	spdsh_norm(pc,d0.w),$14(a0) ; get normal speed
		tst.b	($FFFFFE19).w		; is sonic super?
		beq.s	loc2_1ACD0		; if no, branch
		move.w	spdsh_super(pc,d0.w),$14(a0) ; get super speed

loc2_1ACD0:					; TODO: figure this out
		move.w	$14(a0),d0		; get inertia
		subi.w	#$800,d0		; subtract $800
		add.w	d0,d0			; double it
		andi.w	#$1F00,d0		; mask it against $1F00
		neg.w	d0			; negate it
		addi.w	#$2000,d0		; add $2000
		move.w	d0,($FFFFEED0).w	; move to $EED0
		btst	#0,$22(a0)		; is sonic facing right?
		beq.s	loc2_1ACF4		; if not, branch
		neg.w	$14(a0)			; negate inertia

loc2_1ACF4:
		bset	#2,$22(a0)		; set unused (in s1) flag
		move.b	#0,($FFFFD1DC).w	; clear Spin Dash dust animation
		move.w	#$BC,d0			; spin release sound
		jsr	(PlaySound_Special).l	; play it!
		bra.s	loc2_1AD78
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
spdsh_norm:
		dc.w  $800		; 0
		dc.w  $880		; 1
		dc.w  $900		; 2
		dc.w  $980		; 3
		dc.w  $A00		; 4
		dc.w  $A80		; 5
		dc.w  $B00		; 6
		dc.w  $B80		; 7
		dc.w  $C00		; 8

spdsh_super:
		dc.w  $B00		; 0
		dc.w  $B80		; 1
		dc.w  $C00		; 2
		dc.w  $C80		; 3
		dc.w  $D00		; 4
		dc.w  $D80		; 5
		dc.w  $E00		; 6
		dc.w  $E80		; 7
		dc.w  $F00		; 8
; ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ

loc2_1AD30:				; If still charging the dash...
		tst.w	$3A(a0)		; check charge count
		beq.s	loc2_1AD48	; if zero, branch
		move.w	$3A(a0),d0	; otherwise put it in d0
		lsr.w	#5,d0		; shift right 5 (divide it by 32)
		sub.w	d0,$3A(a0)	; subtract from charge count
		bcc.s	loc2_1AD48	; ??? branch if carry clear
		move.w	#0,$3A(a0)	; set charge count to 0

loc2_1AD48:
		move.b	($FFFFF603).w,d0	; read controller
		andi.b	#$70,d0			; pressing A/B/C?
		beq.w	loc2_1AD78		; if not, branch
		move.w	#$1F00,$1C(a0)		; reset spdsh animation
		move.w	#$D1,d0			; was $E0 in sonic 2
		move.b	#2,$FFFFD1DC.w	; Set the Spin Dash dust animation to $2.
		jsr	(PlaySound_Special).l	; play charge sound
		addi.w	#$200,$3A(a0)		; increase charge count
		cmpi.w	#$800,$3A(a0)		; check if it's maxed
		bcs.s	loc2_1AD78		; if not, then branch
		move.w	#$800,$3A(a0)		; reset it to max

loc2_1AD78:
		addq.l	#4,sp			; increase stack ptr
		cmpi.w	#$60,($FFFFEED8).w	; $EED8 only ever seems
		beq.s	loc2_1AD8C		; to be used in Spin Dash
		bcc.s	loc2_1AD88
		addq.w	#4,($FFFFEED8).w

loc2_1AD88:
		subq.w	#2,($FFFFEED8).w

loc2_1AD8C:
		bsr.w	Sonic_LevelBound
		bsr.w	Sonic_AnglePos
		move.w	#$60,($FFFFF73E).w	; reset looking up/down
		rts


Now, build the ROM, and the result is... oops, something's missing. Right, we forgot to add the dust art.
So download the Spin Dash dust art here (put it in the artunc\ folder inside your source dir), and at the end of the ROM, after SegaPCM, add the following lines:

Art_Dust	incbin	artunc\spindust.bin


Now compile it and try a Spin Dash:

SpindashGuide Pic2.png

Nice, it seems to be working...
...or not.

SpindashGuide Pic3.png

I'm pretty sure the lamppost wasn't meant to look like THAT.
The problem here seems to be that the dust art is overwriting the lamppost art.
To fix this, we need to move the lamppost art to a different location.
For that, we can use the VRAM location $D800, which is free.
So open the file _inc\Pattern load cues.asm, and search for these lines:

PLC_Main:	dc.w 4
		dc.l Nem_Lamp		; lamppost
		dc.w $F400

Change them to:

PLC_Main:	dc.w 4
		dc.l Nem_Lamp		; lamppost
		dc.w $D800

This will load the lamppost art in $D800 in VRAM, instead of $F400.
Now we must make the lamppost object use that art, instead of the one in $F400.
So now go to Obj79 (lamppost object) and search for this line, which should be in Obj79_Main:

		move.w	#$7A0,2(a0)

Change it to:

		move.w	#($D800/$20),2(a0)

The Obj79_Main routine should then look like this:

Obj79_Main:				; XREF: Obj79_Index
		addq.b	#2,$24(a0)
		move.l	#Map_obj79,4(a0)
		move.w	#($D800/$20),2(a0)
		move.b	#4,1(a0)
		move.b	#8,$19(a0)
		move.b	#5,$18(a0)
		lea	($FFFFFC00).w,a2
		moveq	#0,d0
		move.b	$23(a0),d0
		bclr	#7,2(a2,d0.w)
		btst	#0,2(a2,d0.w)
		bne.s	Obj79_RedLamp
		move.b	($FFFFFE30).w,d1
		andi.b	#$7F,d1
		move.b	$28(a0),d2	; get lamppost number
		andi.b	#$7F,d2
		cmp.b	d2,d1		; is lamppost number higher than the number hit?
		bcs.s	Obj79_BlueLamp	; if yes, branch

Similarly, change this line in Obj79_HitLamp:

		move.w	#$7A0,2(a1)

to this:

		move.w	#($D800/$20),2(a1)

Therefore making Obj79_HitLamp look like this:

Obj79_HitLamp:

		move.w	($FFFFD008).w,d0
		sub.w	8(a0),d0
		addq.w	#8,d0
		cmpi.w	#$10,d0
		bcc.w	locret_16F90
		move.w	($FFFFD00C).w,d0
		sub.w	$C(a0),d0
		addi.w	#$40,d0
		cmpi.w	#$68,d0
		bcc.s	locret_16F90
		move.w	#$A1,d0
		jsr	(PlaySound_Special).l ;	play lamppost sound
		addq.b	#2,$24(a0)
		jsr	SingleObjLoad
		bne.s	loc_16F76
		move.b	#$79,0(a1)	; load twirling	lamp object
		move.b	#6,$24(a1)	; use "Obj79_Twirl" routine
		move.w	8(a0),$30(a1)
		move.w	$C(a0),$32(a1)
		subi.w	#$18,$32(a1)
		move.l	#Map_obj79,4(a1)
		move.w	#($D800/$20),2(a1)
		move.b	#4,1(a1)
		move.b	#8,$19(a1)
		move.b	#4,$18(a1)
		move.b	#2,$1A(a1)
		move.w	#$20,$36(a1)

...and that's it. Compile your ROM and we should now have perfect Spin Dash in Sonic 1.

SpindashGuide Pic4.png

Here's the final result of this thing. Source code available here. Have fun :)

Note that you should look here for instructions on fixing the SEGA sound, and see Part 3 for some additional fixes not covered in this guide.

SCHG How-To Guide: Sonic the Hedgehog (16-bit)
Fixing Bugs
Fix Demo Playback | Fix a Race Condition with Pattern Load Cues | Fix the SEGA Sound | Display the Press Start Button Text | Fix the Level Select Menu | Fix the Hidden Points Bug | Fix Accidental Deletion of Scattered Rings | Fix Ring Timers | Fix the Walk-Jump Bug | Correct Drowning Bugs | Fix the Death Boundary Bug | Fix the Camera Follow Bug | Fix Song Restoration Bugs | Fix the HUD Blinking | Fix the Level Select Graphics Bug | Fix a remember sprite related bug
Changing Design Choices
Change Spike Behavior | Collide with Water After Being Hurt | Fix Special Stage Jumping Physics | Improve the Fade In\Fade Out Progression Routines | Fix Scattered Rings' Underwater Physics | Remove the Speed Cap | Port the REV01 Background Effects | Port Sonic 2's Level Art Loader | Retain Rings Between Acts | Add Sonic 2 (Simon Wai Prototype) Level Select | Improve ObjectMove Subroutines | Port Sonic 2 Level Select
Adding Features
Add Spin Dash ( Part 1 / Part 2 / Part 3 / Part 4 ) | Add Eggman Monitor | Add Super Sonic | Add the Air Roll
Sound Features
Expand the Sound Index | Play Different Songs Per Act | Port Sonic 2 Final Sound Driver | Port Sonic 3's Sound Driver | Port Flamewing's Sonic 3 & Knuckles Sound Driver | Change The SEGA Sound
Extending the Game
Load Chunks From ROM | Add Extra Characters | Make an Alternative Title Screen | Use Dynamic Tilesets | Make GHZ Load Alternate Art | Make Ending Load Alternate Art | Add a New Zone | Set Up the Goggle Monitor | Add New Moves | Add a Dynamic Collision System | Dynamic Special Stage Walls System | Extend Sprite Mappings and Art Limit | Enigma Credits | Use Dynamic Palettes
Miscellaneous
Convert the Hivebrain 2005 Disassembly to ASM68K
Split Disassembly Guides
Set Up a Split Disassembly | Basic Level Editing | Basic Art Editing | Basic ASM Editing (Spin Dash)

|Add Spin Dash to Sonic 1/Part 2]]