Actions

SCHG How-to

Add Spin Dash to Sonic 1/Part 2

From Sonic Retro

Revision as of 17:17, 13 January 2008 by SOTI (talk | contribs)

(Original guide written by Puto)

Here's a Sonic 1 + Spindash 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 spindash 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 spindash while on a seesaw (SLZ), Strange Things(TM) will happen. So let's fix this.

Problem 1: The Monitor Bug

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

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 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 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 cmp.b #$1F,$1C(a1) ; is Sonic spin-dashing? beq loc_A25C ; if yes, branch</asm>


Here's a ROM after this step was carried out. Great, let's try it. It should be working and... oh dear.
This isn't right.

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.
These are the first lines:

<asm>Sonic_Spindash: tst.b $39(a0) ; already spindashing? bne.s loc2_1AC8E ; if set, branch</asm>

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

<asm> move.b #$1F,$1C(a0)</asm>

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

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

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 bcs.w Sound_D0toDF ; sound $D0</asm>

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

<asm> cmp.b #$DF,d7 ble Sound_D1toDF</asm>

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

<asm>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 (...)</asm>

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:

<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 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 (...)</asm>

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:

<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 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 (...)</asm>

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

<asm>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 (...)</asm>

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

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

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:

<asm>SoundD1: incbin sound\soundD1.bin even</asm>

So the surrounding code should look like this:

<asm>SoundCF: incbin sound\soundCF.bin even SoundD0: incbin sound\soundD0.bin even SoundD1: incbin sound\soundD1.bin even SegaPCM: incbin sound\segapcm.bin even</asm>

And finally, add SoundD1 to the index:

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

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

<asm>Sonic_Spindash: tst.b $39(a0) ; already spindashing? 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 spindash 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 spindash 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 spindash 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 spindash 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,($FFFFD11C).w ; clear $D11C (unused?) 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 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 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>

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

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

This problem occurs when you're spindashing on a seesaw, and then you get thrown up by the seesaw.
You keep your spindash 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:

<asm> clr.b $39(a0)</asm>

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


Problem 4: Spindash Dust

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

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

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

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


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:


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

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:

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

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

The outcome should be something like this:

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

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

This will cause Object ID 05, which was previously unused, to now point to the spindash 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:

<asm> move.b #5,$FFFFD1C0.w</asm>

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:


<asm> move.b #2,($FFFFD11C).w</asm>

to this:

<asm> move.b #2,($FFFFD1DC).w ; Set the spindash dust animation to $2.</asm>


Also, remove this line:

<asm> bcs.s loc2_1AC84 ; ??? branch if carry</asm>


And again, in loc2_1ACF4, change this line:

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

to this:

<asm> move.b #0,($FFFFD1DC).w ; clear spindash dust animation.</asm>

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>


For reference, here's the complete Sonic_Spindash routine:

<asm>Sonic_Spindash: tst.b $39(a0) ; already spindashing? 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 spindash 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 spindash 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 spindash 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 spindash 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 spindash 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 spindash 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 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>


Now, build the ROM, and the result is... oops, something's missing. Right, we forgot to add the dust art.
So download the spindash 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:

<asm>Art_Dust incbin artunc\spindust.bin</asm>


Now compile it and try a spindash:

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:

<asm>PLC_Main: dc.w 4 dc.l Nem_Lamp ; lamppost dc.w $F400</asm>

Change them to:

<asm>PLC_Main: dc.w 4 dc.l Nem_Lamp ; lamppost dc.w $D800</asm>

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:

<asm> move.w #$7A0,2(a0)</asm>

Change it to:

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

The Obj79_Main routine should then look like this:

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

Similarly, change this line in Obj79_HitLamp:

<asm> move.w #$7A0,2(a1)</asm>

to this:

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

Therefore making Obj79_HitLamp look like this:

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


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


SpindashGuide Pic4.png


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