Actions

SCHG How-to

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

From Sonic Retro

(Meant to do this 2 months ago. Still, better late than never I guess)
 
(Got rid of extra newlines around ASM tags)
Line 8: Line 8:
 
Firstly, let's modify the actual queue routines to use this RAM space. So go to DMA_68KtoVRAM and replace everything from there till the end of Process_DMA with:
 
Firstly, let's modify the actual queue routines to use this RAM space. So go to DMA_68KtoVRAM and replace everything from there till the end of Process_DMA with:
  
<asm>
+
<asm>; ---------------------------------------------------------------------------
; ---------------------------------------------------------------------------
 
 
; Subroutine for queueing VDP commands (seems to only queue transfers to VRAM),
 
; Subroutine for queueing VDP commands (seems to only queue transfers to VRAM),
 
; to be issued the next time ProcessDMAQueue is called.
 
; to be issued the next time ProcessDMAQueue is called.
Line 97: Line 96:
 
move.l #$FFFFC800,($FFFFC8FC).w
 
move.l #$FFFFC800,($FFFFC8FC).w
 
rts
 
rts
; End of function ProcessDMAQueue
+
; End of function ProcessDMAQueue</asm>
</asm>
 
  
 
Next, let's modify Sonic's PLC routine to make use of this routine. Go to LoadSonicDynPLC and replace the entire routine with the ''Sonic 2'' equivalent, slightly modified to make use of ''Sonic 1''<nowiki>'</nowiki>s PLC format:
 
Next, let's modify Sonic's PLC routine to make use of this routine. Go to LoadSonicDynPLC and replace the entire routine with the ''Sonic 2'' equivalent, slightly modified to make use of ''Sonic 1''<nowiki>'</nowiki>s PLC format:
  
<asm>
+
<asm>LoadSonicDynPLC: ; XREF: Obj01_Control; et al
LoadSonicDynPLC: ; XREF: Obj01_Control; et al
 
 
moveq #0,d0
 
moveq #0,d0
 
move.b $1A(a0),d0 ; load frame number
 
move.b $1A(a0),d0 ; load frame number
Line 139: Line 136:
 
locret_13C96:
 
locret_13C96:
 
rts
 
rts
; End of function LoadSonicDynPLC
+
; End of function LoadSonicDynPLC</asm>
</asm>
 
  
 
Third, we have to make the dust object use the new name for the DMA routine. Really simple, just go to loc_1DF0A and replace
 
Third, we have to make the dust object use the new name for the DMA routine. Really simple, just go to loc_1DF0A and replace
  
<asm>
+
<asm> jsr (DMA_68KtoVRAM).l</asm>
jsr (DMA_68KtoVRAM).l
 
</asm>
 
  
 
with
 
with
  
<asm>
+
<asm> jsr (QueueDMATransfer).l</asm>
jsr (QueueDMATransfer).l
 
</asm>
 
  
 
Finally, we have to make the game actually use these routines. Firstly, go to loc_CD4 and replace
 
Finally, we have to make the game actually use these routines. Firstly, go to loc_CD4 and replace
  
<asm>
+
<asm> tst.b ($FFFFF767).w
tst.b ($FFFFF767).w
 
 
beq.s loc_D50
 
beq.s loc_D50
 
lea ($C00004).l,a5
 
lea ($C00004).l,a5
Line 166: Line 157:
 
move.w #$83,($FFFFF640).w
 
move.w #$83,($FFFFF640).w
 
move.w ($FFFFF640).w,(a5)
 
move.w ($FFFFF640).w,(a5)
move.b #0,($FFFFF767).w
+
move.b #0,($FFFFF767).w</asm>
</asm>
 
  
 
with
 
with
  
<asm>
+
<asm> jsr (ProcessDMAQueue).l</asm>
jsr (ProcessDMAQueue).l
 
</asm>
 
  
 
Make similar changes at loc_DAE, loc_EEE and loc_FAE. Secondly, go to loc_D50 and remove these two lines:
 
Make similar changes at loc_DAE, loc_EEE and loc_FAE. Secondly, go to loc_D50 and remove these two lines:
  
<asm>
+
<asm> move #$83,($FFFFF640).w
move #$83,($FFFFF640).w
+
jsr Process_DMA</asm>
jsr Process_DMA
 
</asm>
 
  
 
Thirdly, go to Level_ClrVars3 and after the
 
Thirdly, go to Level_ClrVars3 and after the
  
<asm>
+
<asm> move.w #$8ADF,($FFFFF624).w
move.w #$8ADF,($FFFFF624).w
+
move.w ($FFFFF624).w,(a6)</asm>
move.w ($FFFFF624).w,(a6)
 
</asm>
 
  
 
add
 
add
  
<asm>
+
<asm> clr.w ($FFFFC800).w
clr.w ($FFFFC800).w
+
move.l #$FFFFC800,($FFFFC8FC).w</asm>
move.l #$FFFFC800,($FFFFC8FC).w
 
</asm>
 
  
 
Finally, go to loc_47D4, and after
 
Finally, go to loc_47D4, and after
  
<asm>
+
<asm> jsr Hud_Base</asm>
jsr Hud_Base
 
</asm>
 
  
 
add
 
add
  
<asm>
+
<asm> clr.w ($FFFFC800).w
clr.w ($FFFFC800).w
+
move.l #$FFFFC800,($FFFFC8FC).w</asm>
move.l #$FFFFC800,($FFFFC8FC).w
 
</asm>
 
  
 
After this, the game will use the DMA queue for Sonic's art reloading, and we'll have $200 bytes of free RAM from $FFFFC900 to $FFFFCAFF.
 
After this, the game will use the DMA queue for Sonic's art reloading, and we'll have $200 bytes of free RAM from $FFFFC900 to $FFFFCAFF.
Line 216: Line 194:
 
First, go to loc_71BC8 and change it from
 
First, go to loc_71BC8 and change it from
  
<asm>
+
<asm>loc_71BC8:
loc_71BC8:
 
 
lea $40(a6),a5
 
lea $40(a6),a5
 
tst.b (a5)
 
tst.b (a5)
 
bpl.s loc_71BD4
 
bpl.s loc_71BD4
jsr sub_71C4E(pc)
+
jsr sub_71C4E(pc)</asm>
</asm>
 
  
 
to
 
to
  
<asm>
+
<asm>loc_71BC8:
loc_71BC8:
 
 
tst.b ($FFFFC901).w
 
tst.b ($FFFFC901).w
 
beq.s @cont
 
beq.s @cont
Line 236: Line 211:
 
tst.b (a5)
 
tst.b (a5)
 
bpl.s loc_71BD4
 
bpl.s loc_71BD4
jsr sub_71C4E(pc)
+
jsr sub_71C4E(pc)</asm>
</asm>
 
  
 
Then go to Sound_D1toDF and change if from
 
Then go to Sound_D1toDF and change if from
  
<asm>
+
<asm>Sound_D1toDF:
Sound_D1toDF:
 
 
tst.b $27(a6)
 
tst.b $27(a6)
 
bne.w loc_722C6
 
bne.w loc_722C6
Line 251: Line 224:
 
movea.l (Go_SoundIndex).l,a0
 
movea.l (Go_SoundIndex).l,a0
 
sub.b #$A1,d7
 
sub.b #$A1,d7
bra SoundEffects_Common
+
bra SoundEffects_Common</asm>
</asm>
 
  
 
to
 
to
  
<asm>
+
<asm>Sound_D1toDF:
Sound_D1toDF:
 
 
tst.b $27(a6)
 
tst.b $27(a6)
 
bne.w loc_722C6
 
bne.w loc_722C6
Line 287: Line 258:
 
movea.l (Go_SoundIndex).l,a0
 
movea.l (Go_SoundIndex).l,a0
 
sub.b #$A1,d7
 
sub.b #$A1,d7
bra SoundEffects_Common
+
bra SoundEffects_Common</asm>
</asm>
 
  
 
After this, go to Sound_A0toCF and after
 
After this, go to Sound_A0toCF and after
  
<asm>
+
<asm> tst.b $24(a6)
tst.b $24(a6)
+
bne.w loc_722C6</asm>
bne.w loc_722C6
 
</asm>
 
  
 
add
 
add
  
<asm>
+
<asm> clr.b ($FFFFC900).w</asm>
clr.b ($FFFFC900).w
 
</asm>
 
  
 
Then go to loc_7226E and replace
 
Then go to loc_7226E and replace
  
<asm>
+
<asm> movea.l dword_722EC(pc,d3.w),a5</asm>
movea.l dword_722EC(pc,d3.w),a5
 
</asm>
 
  
 
with
 
with
  
<asm>
+
<asm> lea dword_722EC(pc),a5
lea dword_722EC(pc),a5
+
movea.l (a5,d3.w),a5</asm>
movea.l (a5,d3.w),a5
 
</asm>
 
  
 
Finally, go to loc_72276 and change
 
Finally, go to loc_72276 and change
  
<asm>
+
<asm> move.w (a1)+,8(a5)
move.w (a1)+,8(a5)
+
move.b #1,$E(a5)</asm>
move.b #1,$E(a5)
 
</asm>
 
  
 
to
 
to
  
<asm>
+
<asm> move.w (a1)+,8(a5)
move.w (a1)+,8(a5)
 
 
tst.b ($FFFFC900).w ; is the spindash sound playing?
 
tst.b ($FFFFC900).w ; is the spindash sound playing?
 
beq.s @cont ; if not, branch
 
beq.s @cont ; if not, branch
Line 335: Line 294:
 
 
 
@cont:
 
@cont:
move.b #1,$E(a5)
+
move.b #1,$E(a5)</asm>
</asm>
 
  
 
and now the spindash sound revs.
 
and now the spindash sound revs.
Line 343: Line 301:
 
''Sonic 2''<nowiki>'</nowiki>s spindash implements a horizontal scroll delay, while the ported one doesn't (yet). The basic idea is to check a certain flag, and if it's non-zero, base scrolling on Sonic's X position a certain number of frames ago rather than his current X position. So go to ScrollHoriz2 and change
 
''Sonic 2''<nowiki>'</nowiki>s spindash implements a horizontal scroll delay, while the ported one doesn't (yet). The basic idea is to check a certain flag, and if it's non-zero, base scrolling on Sonic's X position a certain number of frames ago rather than his current X position. So go to ScrollHoriz2 and change
  
<asm>
+
<asm>ScrollHoriz2:
ScrollHoriz2:
 
 
move.w ($FFFFD008).w,d0
 
move.w ($FFFFD008).w,d0
sub.w ($FFFFF700).w,d0
+
sub.w ($FFFFF700).w,d0</asm>
</asm>
 
  
 
to
 
to
  
<asm>
+
<asm>ScrollHoriz2: ; XREF: ScrollHoriz
ScrollHoriz2: ; XREF: ScrollHoriz
 
 
move.w ($FFFFEED0).w,d1
 
move.w ($FFFFEED0).w,d1
 
beq.s @cont1
 
beq.s @cont1
Line 372: Line 327:
 
 
 
@cont2:
 
@cont2:
sub.w ($FFFFF700).w,d0
+
sub.w ($FFFFF700).w,d0</asm>
</asm>
 
  
 
== Vertical scroll delay ==
 
== Vertical scroll delay ==
 
In stock ''Sonic 1'', the camera scrolls vertically as soon as Sonic looks up or ducks. This can be a bit annoying for the spindash, however, so let's implement the delay present in ''Sonic 2'' by using $FFFFC903 as our delay counter. Firstly, to fix a bit of incorrect code, go to loc2_1AD78 and change
 
In stock ''Sonic 1'', the camera scrolls vertically as soon as Sonic looks up or ducks. This can be a bit annoying for the spindash, however, so let's implement the delay present in ''Sonic 2'' by using $FFFFC903 as our delay counter. Firstly, to fix a bit of incorrect code, go to loc2_1AD78 and change
  
<asm>
+
<asm>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
Line 393: Line 346:
 
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
+
rts</asm>
</asm>
 
  
 
to
 
to
  
<asm>
+
<asm>loc2_1AD78:
loc2_1AD78:
 
 
addq.l #4,sp ; increase stack ptr
 
addq.l #4,sp ; increase stack ptr
 
cmpi.w #$60,($FFFFF73E).w
 
cmpi.w #$60,($FFFFF73E).w
Line 413: Line 364:
 
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
+
rts</asm>
</asm>
 
  
 
Then go to Sonic_LookUp and change
 
Then go to Sonic_LookUp and change
  
<asm>
+
<asm>Sonic_LookUp:
Sonic_LookUp:
 
 
btst #0,($FFFFF602).w ; is up being pressed?
 
btst #0,($FFFFF602).w ; is up being pressed?
 
beq.s Sonic_Duck ; if not, branch
 
beq.s Sonic_Duck ; if not, branch
Line 446: Line 395:
  
 
loc_12FBE:
 
loc_12FBE:
subq.w #2,($FFFFF73E).w ; move screen back to default
+
subq.w #2,($FFFFF73E).w ; move screen back to default</asm>
</asm>
 
  
 
to
 
to
  
<asm>
+
<asm>Sonic_LookUp:
Sonic_LookUp:
 
 
btst #0,($FFFFF602).w ; is up being pressed?
 
btst #0,($FFFFF602).w ; is up being pressed?
 
beq.s Sonic_Duck ; if not, branch
 
beq.s Sonic_Duck ; if not, branch
Line 490: Line 437:
  
 
loc_12FBE:
 
loc_12FBE:
subq.w #2,($FFFFF73E).w ; move screen back to default
+
subq.w #2,($FFFFF73E).w ; move screen back to default</asm>
</asm>
 
  
 
Finally, to make the screen bias gradually reset during rolling, go to loc_131CC and change
 
Finally, to make the screen bias gradually reset during rolling, go to loc_131CC and change
  
<asm>
+
<asm>loc_131CC:
loc_131CC:
 
 
move.b $26(a0),d0
 
move.b $26(a0),d0
jsr (CalcSine).l
+
jsr (CalcSine).l</asm>
</asm>
 
  
 
to
 
to
  
<asm>
+
<asm>loc_131CC:
loc_131CC:
 
 
cmp.w #$60,($FFFFF73E).w
 
cmp.w #$60,($FFFFF73E).w
 
beq.s @cont2
 
beq.s @cont2
Line 515: Line 458:
 
@cont2:
 
@cont2:
 
move.b $26(a0),d0
 
move.b $26(a0),d0
jsr (CalcSine).l
+
jsr (CalcSine).l</asm>
</asm>
 
  
 
And that's it! Let me know if I made any mistakes, or if there's anything still not covered. [[Media:s1spindashrom.zip|Here's]] a ROM after all these steps have been done, and [[Media:s1spindashasm.zip|here's]] the source code (with Puto's SEGA sound fix implemented instead of Esrael's for ASM68k compatibility).
 
And that's it! Let me know if I made any mistakes, or if there's anything still not covered. [[Media:s1spindashrom.zip|Here's]] a ROM after all these steps have been done, and [[Media:s1spindashasm.zip|here's]] the source code (with Puto's SEGA sound fix implemented instead of Esrael's for ASM68k compatibility).
  
 
[[Category:SCHG How-tos|Add Spindash to Sonic 1/Part 2]]
 
[[Category:SCHG How-tos|Add Spindash to Sonic 1/Part 2]]

Revision as of 03:02, 23 August 2008

(Original guide written by shobiz)

While the end result of following Lightning's and Puto's guides is a near-perfect spindash in Sonic 1, there are still a few things not right about it, so let's fix those.

Converting Sonic's art buffer to a DMA queue

This isn't exactly a bug per se, but the method used to port over the DMA queue to Sonic 1 in Puto's guide is slightly hack-ish. In stock Sonic 1, Sonic's sprites are copied over into a RAM buffer at $FFFFC800 before being DMAed to VRAM. This buffer occupies $300 bytes, so if we convert it to a DMA queue, besides getting a proper structure we also save $200 bytes of RAM (some of which will be used later on in this guide).

Firstly, let's modify the actual queue routines to use this RAM space. So go to DMA_68KtoVRAM and replace everything from there till the end of Process_DMA with:

<asm>; ---------------------------------------------------------------------------

Subroutine for queueing VDP commands (seems to only queue transfers to VRAM),
to be issued the next time ProcessDMAQueue is called.
Can be called a maximum of 18 times before the buffer needs to be cleared
by issuing the commands (this subroutine DOES check for overflow)
---------------------------------------------------------------------------
||||||||||||||| S U B R O U T I N E |||||||||||||||||||||||||||||||||||||||
sub_144E
DMA_68KtoVRAM: QueueCopyToVRAM: QueueVDPCommand: Add_To_DMA_Queue:

QueueDMATransfer: movea.l ($FFFFC8FC).w,a1 cmpa.w #$C8FC,a1 beq.s QueueDMATransfer_Done ; return if there's no more room in the buffer

; piece together some VDP commands and store them for later... move.w #$9300,d0 ; command to specify DMA transfer length & $00FF move.b d3,d0 move.w d0,(a1)+ ; store command

move.w #$9400,d0 ; command to specify DMA transfer length & $FF00 lsr.w #8,d3 move.b d3,d0 move.w d0,(a1)+ ; store command

move.w #$9500,d0 ; command to specify source address & $0001FE lsr.l #1,d1 move.b d1,d0 move.w d0,(a1)+ ; store command

move.w #$9600,d0 ; command to specify source address & $01FE00 lsr.l #8,d1 move.b d1,d0 move.w d0,(a1)+ ; store command

move.w #$9700,d0 ; command to specify source address & $FE0000 lsr.l #8,d1 move.b d1,d0 move.w d0,(a1)+ ; store command

andi.l #$FFFF,d2 ; command to specify destination address and begin DMA lsl.l #2,d2 lsr.w #2,d2 swap d2 ori.l #$40000080,d2 ; set bits to specify VRAM transfer move.l d2,(a1)+ ; store command

move.l a1,($FFFFC8FC).w ; set the next free slot address cmpa.w #$C8FC,a1 beq.s QueueDMATransfer_Done ; return if there's no more room in the buffer move.w #0,(a1) ; put a stop token at the end of the used part of the buffer

return_14AA

QueueDMATransfer_Done: rts

End of function QueueDMATransfer


---------------------------------------------------------------------------
Subroutine for issuing all VDP commands that were queued
(by earlier calls to QueueDMATransfer)
Resets the queue when it's done
---------------------------------------------------------------------------
||||||||||||||| S U B R O U T I N E |||||||||||||||||||||||||||||||||||||||
sub_14AC
CopyToVRAM: IssueVDPCommands: Process_DMA: Process_DMA_Queue:

ProcessDMAQueue: lea ($C00004).l,a5 lea ($FFFFC800).w,a1

loc_14B6

ProcessDMAQueue_Loop: move.w (a1)+,d0 beq.s ProcessDMAQueue_Done ; branch if we reached a stop token ; issue a set of VDP commands... move.w d0,(a5) ; transfer length move.w (a1)+,(a5) ; transfer length move.w (a1)+,(a5) ; source address move.w (a1)+,(a5) ; source address move.w (a1)+,(a5) ; source address move.w (a1)+,(a5) ; destination move.w (a1)+,(a5) ; destination cmpa.w #$C8FC,a1 bne.s ProcessDMAQueue_Loop ; loop if we haven't reached the end of the buffer

loc_14CE

ProcessDMAQueue_Done: move.w #0,($FFFFC800).w move.l #$FFFFC800,($FFFFC8FC).w rts

End of function ProcessDMAQueue</asm>

Next, let's modify Sonic's PLC routine to make use of this routine. Go to LoadSonicDynPLC and replace the entire routine with the Sonic 2 equivalent, slightly modified to make use of Sonic 1's PLC format:

<asm>LoadSonicDynPLC: ; XREF: Obj01_Control; et al moveq #0,d0 move.b $1A(a0),d0 ; load frame number cmp.b ($FFFFF766).w,d0 beq.s locret_13C96 move.b d0,($FFFFF766).w lea (SonicDynPLC).l,a2 add.w d0,d0 adda.w (a2,d0.w),a2 moveq #0,d5 move.b (a2)+,d5 subq.w #1,d5 bmi.s locret_13C96 move.w #$F000,d4 move.l #Art_Sonic,d6

SPLC_ReadEntry: moveq #0,d1 move.b (a2)+,d1 lsl.w #8,d1 move.b (a2)+,d1 move.w d1,d3 lsr.w #8,d3 andi.w #$F0,d3 addi.w #$10,d3 andi.w #$FFF,d1 lsl.l #5,d1 add.l d6,d1 move.w d4,d2 add.w d3,d4 add.w d3,d4 jsr (QueueDMATransfer).l dbf d5,SPLC_ReadEntry ; repeat for number of entries

locret_13C96: rts

End of function LoadSonicDynPLC</asm>

Third, we have to make the dust object use the new name for the DMA routine. Really simple, just go to loc_1DF0A and replace

<asm> jsr (DMA_68KtoVRAM).l</asm>

with

<asm> jsr (QueueDMATransfer).l</asm>

Finally, we have to make the game actually use these routines. Firstly, go to loc_CD4 and replace

<asm> tst.b ($FFFFF767).w beq.s loc_D50 lea ($C00004).l,a5 move.l #$94019370,(a5) move.l #$96E49500,(a5) move.w #$977F,(a5) move.w #$7000,(a5) move.w #$83,($FFFFF640).w move.w ($FFFFF640).w,(a5) move.b #0,($FFFFF767).w</asm>

with

<asm> jsr (ProcessDMAQueue).l</asm>

Make similar changes at loc_DAE, loc_EEE and loc_FAE. Secondly, go to loc_D50 and remove these two lines:

<asm> move #$83,($FFFFF640).w jsr Process_DMA</asm>

Thirdly, go to Level_ClrVars3 and after the

<asm> move.w #$8ADF,($FFFFF624).w move.w ($FFFFF624).w,(a6)</asm>

add

<asm> clr.w ($FFFFC800).w move.l #$FFFFC800,($FFFFC8FC).w</asm>

Finally, go to loc_47D4, and after

<asm> jsr Hud_Base</asm>

add

<asm> clr.w ($FFFFC800).w move.l #$FFFFC800,($FFFFC8FC).w</asm>

After this, the game will use the DMA queue for Sonic's art reloading, and we'll have $200 bytes of free RAM from $FFFFC900 to $FFFFCAFF.

Making the spindash sound rev

This has always annoyed me a lot, but thanks to Xenowhirl's Sonic 2 sound driver disassembly and Tweaker's music guide, we can fix it. We need to use three RAM variables, so we can utilise our newly freed RAM. $FFFFC900 is a flag which is 1 if the spindash sound was the last one played and 0 if it was not. $FFFFC901 is a timer, and $FFFFC902 is the pitch increase value.

First, go to loc_71BC8 and change it from

<asm>loc_71BC8: lea $40(a6),a5 tst.b (a5) bpl.s loc_71BD4 jsr sub_71C4E(pc)</asm>

to

<asm>loc_71BC8: tst.b ($FFFFC901).w beq.s @cont subq.b #1,($FFFFC901).w

@cont: lea $40(a6),a5 tst.b (a5) bpl.s loc_71BD4 jsr sub_71C4E(pc)</asm>

Then go to Sound_D1toDF and change if from

<asm>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</asm>

to

<asm>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 clr.b ($FFFFC900).w cmp.b #$D1,d7 ; is this the spindash sound? bne.s @cont3 ; if not, branch move.w d0,-(sp) move.b ($FFFFC902).w,d0 ; store extra frequency tst.b ($FFFFC901).w ; is the spindash timer active? bne.s @cont1 ; if it is, branch move.b #-1,d0 ; otherwise, reset frequency (becomes 0 on next line)

@cont1: addq.b #1,d0 cmp.b #$C,d0 ; has the limit been reached? bcc.s @cont2 ; if it has, branch move.b d0,($FFFFC902).w ; otherwise, set new frequency

@cont2: move.b #1,($FFFFC900).w ; set flag move.b #60,($FFFFC901).w ; set timer move.w (sp)+,d0

@cont3: movea.l (Go_SoundIndex).l,a0 sub.b #$A1,d7 bra SoundEffects_Common</asm>

After this, go to Sound_A0toCF and after

<asm> tst.b $24(a6) bne.w loc_722C6</asm>

add

<asm> clr.b ($FFFFC900).w</asm>

Then go to loc_7226E and replace

<asm> movea.l dword_722EC(pc,d3.w),a5</asm>

with

<asm> lea dword_722EC(pc),a5 movea.l (a5,d3.w),a5</asm>

Finally, go to loc_72276 and change

<asm> move.w (a1)+,8(a5) move.b #1,$E(a5)</asm>

to

<asm> move.w (a1)+,8(a5) tst.b ($FFFFC900).w ; is the spindash sound playing? beq.s @cont ; if not, branch move.w d0,-(sp) move.b ($FFFFC902).w,d0 add.b d0,8(a5) move.w (sp)+,d0

@cont: move.b #1,$E(a5)</asm>

and now the spindash sound revs.

Horizontal scroll delay

Sonic 2's spindash implements a horizontal scroll delay, while the ported one doesn't (yet). The basic idea is to check a certain flag, and if it's non-zero, base scrolling on Sonic's X position a certain number of frames ago rather than his current X position. So go to ScrollHoriz2 and change

<asm>ScrollHoriz2: move.w ($FFFFD008).w,d0 sub.w ($FFFFF700).w,d0</asm>

to

<asm>ScrollHoriz2: ; XREF: ScrollHoriz move.w ($FFFFEED0).w,d1 beq.s @cont1 sub.w #$100,d1 move.w d1,($FFFFEED0).w moveq #0,d1 move.b ($FFFFEED0).w,d1 lsl.b #2,d1 addq.b #4,d1 move.w ($FFFFF7A8).w,d0 sub.b d1,d0 lea ($FFFFCB00).w,a1 move.w (a1,d0.w),d0 and.w #$3FFF,d0 bra.s @cont2

@cont1: move.w ($FFFFD008).w,d0

@cont2: sub.w ($FFFFF700).w,d0</asm>

Vertical scroll delay

In stock Sonic 1, the camera scrolls vertically as soon as Sonic looks up or ducks. This can be a bit annoying for the spindash, however, so let's implement the delay present in Sonic 2 by using $FFFFC903 as our delay counter. Firstly, to fix a bit of incorrect code, go to loc2_1AD78 and change

<asm>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 spindash 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</asm>

to

<asm>loc2_1AD78: addq.l #4,sp ; increase stack ptr cmpi.w #$60,($FFFFF73E).w beq.s loc2_1AD8C bcc.s loc2_1AD88 addq.w #4,($FFFFF73E).w

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

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

Then go to Sonic_LookUp and change

<asm>Sonic_LookUp: btst #0,($FFFFF602).w ; is up being pressed? beq.s Sonic_Duck ; if not, branch move.b #7,$1C(a0) ; use "looking up" animation cmpi.w #$C8,($FFFFF73E).w beq.s loc_12FC2 addq.w #2,($FFFFF73E).w bra.s loc_12FC2

===========================================================================

Sonic_Duck: btst #1,($FFFFF602).w ; is down being pressed? beq.s Obj01_ResetScr ; if not, branch move.b #8,$1C(a0) ; use "ducking" animation cmpi.w #8,($FFFFF73E).w beq.s loc_12FC2 subq.w #2,($FFFFF73E).w bra.s loc_12FC2

===========================================================================

Obj01_ResetScr: cmpi.w #$60,($FFFFF73E).w ; is screen in its default position? beq.s loc_12FC2 ; if yes, branch bcc.s loc_12FBE addq.w #4,($FFFFF73E).w ; move screen back to default

loc_12FBE: subq.w #2,($FFFFF73E).w ; move screen back to default</asm>

to

<asm>Sonic_LookUp: btst #0,($FFFFF602).w ; is up being pressed? beq.s Sonic_Duck ; if not, branch move.b #7,$1C(a0) ; use "looking up" animation addq.b #1,($FFFFC903).w cmp.b #$78,($FFFFC903).w bcs.s Obj01_ResetScr_Part2 move.b #$78,($FFFFC903).w cmpi.w #$C8,($FFFFF73E).w beq.s loc_12FC2 addq.w #2,($FFFFF73E).w bra.s loc_12FC2

===========================================================================

Sonic_Duck: btst #1,($FFFFF602).w ; is down being pressed? beq.s Obj01_ResetScr ; if not, branch move.b #8,$1C(a0) ; use "ducking" animation addq.b #1,($FFFFC903).w cmpi.b #$78,($FFFFC903).w bcs.s Obj01_ResetScr_Part2 move.b #$78,($FFFFC903).w cmpi.w #8,($FFFFF73E).w beq.s loc_12FC2 subq.w #2,($FFFFF73E).w bra.s loc_12FC2

===========================================================================

Obj01_ResetScr: move.b #0,($FFFFC903).w

Obj01_ResetScr_Part2: cmpi.w #$60,($FFFFF73E).w ; is screen in its default position? beq.s loc_12FC2 ; if yes, branch bcc.s loc_12FBE addq.w #4,($FFFFF73E).w ; move screen back to default

loc_12FBE: subq.w #2,($FFFFF73E).w ; move screen back to default</asm>

Finally, to make the screen bias gradually reset during rolling, go to loc_131CC and change

<asm>loc_131CC: move.b $26(a0),d0 jsr (CalcSine).l</asm>

to

<asm>loc_131CC: cmp.w #$60,($FFFFF73E).w beq.s @cont2 bcc.s @cont1 addq.w #4,($FFFFF73E).w

@cont1: subq.w #2,($FFFFF73E).w

@cont2: move.b $26(a0),d0 jsr (CalcSine).l</asm>

And that's it! Let me know if I made any mistakes, or if there's anything still not covered. Here's a ROM after all these steps have been done, and here's the source code (with Puto's SEGA sound fix implemented instead of Esrael's for ASM68k compatibility).