Actions

SCHG How-to

Add Spin Dash to Sonic 1 (GitHub)

From Sonic Retro

(Original guide by DikinBaus)
(Based on the original guide by Lightning)

In this guide, we'll be porting the Spin Dash from Sonic 2 into Sonic 1. It is designed to work with the latest version of the Sonic 1 GitHub disassembly.

This tutorial was made possible thanks to the references, guides, and information found at Hacking CulT, The Sonic 2 Beta Page, Sonic Retro, and Sonicology. It was inspired by the guide at The Glowing Bridge that details porting the simpler Spin Dash from the Sonic 2 early beta, as well as Lightning's original guide that expands upon the former.

This newly-revamped guide is based off of the original version of the guide that was targeted for the legacy Hivebrain 2005 disassembly of Sonic 1. Thanks to Kramlat for making some additional updates to the original guide before this one. Further updates to the guide have been made by DikinBaus (the original author of this guide) and JGMR.

This guide is written by their respective authors. Sonic and all related characters and graphics that appear on this page are property of SEGA and are used without permission for no profit.

Before you begin

This guide is made for the AS version of the Sonic 1 GitHub disassembly. If your hack was built off the ASM68K version of the GitHub disassembly, you have to port what you were working on to AS. Just staying on the ASM68K version will not allow you to follow on with this guide.

Requirements

  • Sonic 1 split disassembly
  • Sonic 2 split disassembly (optional; useful for reference purposes and for extracting the Spin Dash sprites)
  • A plain text editor of your choice
  • A tile/sprite/mappings editor capable of editing Genesis format tiles/sprites
  • A graphics editor of your choice
  • A Sega Genesis emulator of your choice (or on real hardware with a flashcart)

In making this tutorial, I used:

Other disassemblies of Sonic 1 (and optionally Sonic 2) can be used, but you'll have to work out the differences on your own (it shouldn't be too hard to figure it out).

Porting the Spin Dash

Now that you've gotten the required tools and disassemblies of your choice, it's time to start porting over the Spin Dash into Sonic 1!

Adding new flags

Before we can start, we'll need to do some prerequisites before we can port over our code. For the purposes of this tutorial, I'll assume you do it that way too. Open Constants.asm and search for stick_to_convex:. There you will define a new RAM equate by adding the following lines underneath it:

spindash_flag:	equ $39	; spin dash flag
spindash_counter:	equ $3A	; spin dash counter

This should preferably be put in between the stick_to_convex: and standonobject: variables. We could skip this, but then we'll have to replace all spindash_flag and spindash_counter in the code for $39 and $3A respectively, and that's not what we want.

Next, open Variables.asm and above v_shieldobj = v_objspace+object_size*6, add this in:

v_spindust	= v_objspace+object_size*4	; object variable space for the spin dash dust ($40 bytes)

The code should look like this:

v_spindust	= v_objspace+object_size*4	; object variable space for the spin dash dust ($40 bytes)
v_shieldobj	= v_objspace+object_size*6	; object variable space for the shield ($40 bytes)
v_starsobj1	= v_objspace+object_size*8	; object variable space for the invincibility stars #1 ($40 bytes)
v_starsobj2	= v_objspace+object_size*9	; object variable space for the invincibility stars #2 ($40 bytes)
v_starsobj3	= v_objspace+object_size*10	; object variable space for the invincibility stars #3 ($40 bytes)
v_starsobj4	= v_objspace+object_size*11	; object variable space for the invincibility stars #4 ($40 bytes)

Note that we have added a new variable called v_spindust, which is stored at RAM address $FFFFD100. This is what we'll be using in the following guide later on.

Directly porting the object code

Open sonic.asm in your text editor, and search for Sonic_MdNormal: (line 7048). You'll find a series of branches and jumps to subroutines that manage Sonic's actions in different situations. If we take a look at the same routine in Sonic 2's code (which can be found under Obj01_MdNormal: in the Xenowhirl and GitHub disassemblies), you'll notice that there are nine branches instead of eight in Sonic 1's. That's because Sonic 2 has another branch to the code that handles the Spin Dash. Since we're going to be adding the Spin Dash into Sonic 1, we need to add a call to the Spin Dash function that we're going to port over. First, make a copy of the line bsr.w Sonic_Jump and change it to bsr.w Sonic_SpinDash, as shown:

Sonic_MdNormal:
		bsr.w	Sonic_SpinDash	; <-- add this line!
		bsr.w	Sonic_Jump
		...

Now, to actually port the Spin Dash subroutine. For this, we'll need to make a new ASM file. So create a new file within the _incObj folder and name it "Sonic SpinDash.asm".

To save you some time in actually porting over the code, I have provided the completed code here below for your convenience, which you should be able to paste it into your Sonic SpinDash.asm file:

; ---------------------------------------------------------------------------
; Subroutine to check for starting to charge a spindash
; ---------------------------------------------------------------------------

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


Sonic_SpinDash:
		tst.b	spindash_flag(a0)
		bne.s	Sonic_UpdateSpindash
		cmpi.b	#id_Duck,obAnim(a0)
		bne.s	return_1AC8C
		move.b	(v_jpadpress2).w,d0
		andi.b	#btnB|btnC|btnA,d0
		beq.w	return_1AC8C
		move.b	#id_Roll,obAnim(a0)
		move.w	#sfx_Roll,d0
		jsr	(PlaySound_Special).l
		addq.l	#4,sp
		move.b	#1,spindash_flag(a0)
		move.w	#0,spindash_counter(a0)
		cmpi.b	#12,obSubtype(a0)	; if he's drowning, branch to not make dust
		blo.s	+
		move.b	#2,(v_spindust+obAnim).w
+
		bsr.w	Sonic_LevelBound
		bsr.w	Sonic_AnglePos

return_1AC8C:
		rts
; End of function Sonic_SpinDash


; ---------------------------------------------------------------------------
; Subroutine to update an already-charging spindash
; ---------------------------------------------------------------------------

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


Sonic_UpdateSpindash:
		move.b	(v_jpadhold2).w,d0 
		btst	#bitDn,d0
		bne.w	Sonic_ChargingSpindash

		; unleash the charged spindash and start rolling quickly:
		move.b	#$E,obHeight(a0)
		move.b	#7,obWidth(a0)
		move.b	#id_Roll,obAnim(a0)
		addq.w	#5,obY(a0)	; add the difference between Sonic's rolling and standing heights
		move.b	#0,spindash_flag(a0)
		moveq	#0,d0
		move.b	spindash_counter(a0),d0
		add.w	d0,d0
		move.w	SpindashSpeeds(pc,d0.w),obInertia(a0)

		; Determine how long to lag the camera for.
		; Notably, the faster Sonic goes, the less the camera lags.
		; This is seemingly to prevent Sonic from going off-screen.
		move.w	obInertia(a0),d0
		subi.w	#$800,d0 ; $800 is the lowest spin dash speed
		add.w	d0,d0
		andi.w	#$1F00,d0 ; This line is not necessary, as none of the removed bits are ever set in the first place
		neg.w	d0
		addi.w	#$2000,d0
		move.w	d0,($FFFFEED0).w

		btst	#0,obStatus(a0)
		beq.s	+
		neg.w	obInertia(a0)
+
		bset	#2,obStatus(a0)
		move.b	#0,(v_spindust+obAnim).w 
		move.w	#sfx_Teleport,d0	; spindash zoom sound
		jsr	(PlaySound_Special).l 
		bra.s	Sonic_Spindash_ResetScr
; ===========================================================================
SpindashSpeeds:
		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
; ===========================================================================

Sonic_ChargingSpindash:			; If still charging the dash...
		tst.w	spindash_counter(a0)
		beq.s	+
		move.w	spindash_counter(a0),d0
		lsr.w	#5,d0
		sub.w	d0,spindash_counter(a0)
		bcc.s	+
		move.w	#0,spindash_counter(a0)
+
		move.b	(v_jpadpress2).w,d0 
		andi.b	#btnB|btnC|btnA,d0
		beq.w	Sonic_Spindash_ResetScr
		move.w	#(id_Roll<<8)|(id_Walk<<0),obAnim(a0)
		move.w	#sfx_Roll,d0
		jsr	(PlaySound_Special).l
		addi.w	#$200,spindash_counter(a0)
		cmpi.w	#$800,spindash_counter(a0)
		blo.s	Sonic_Spindash_ResetScr
		move.w	#$800,spindash_counter(a0)

Sonic_Spindash_ResetScr:
		addq.l	#4,sp
		cmpi.w	#(224/2)-16,($FFFFEED8).w
		beq.s	loc_1AD8C
		bhs.s	+
		addq.w	#4,($FFFFEED8).w
+		subq.w	#2,($FFFFEED8).w

loc_1AD8C:
		bsr.w	Sonic_LevelBound
		bsr.w	Sonic_AnglePos
		move.w	#$60,(v_lookshift).w
		rts
; End of function Sonic_UpdateSpindash

As you can see, this is the exact same subroutine of the Spin Dash as found in the latest version of the Sonic 2 GitHub disassembly, but ported over into Sonic 1. To summarize what we have changed in our new code in comparison to the original, here is a list of some of the changes that we have made to our Spin Dash code:

  • Removed a section of code relating to "fixBugs" (an assembler option that doesn't exist in Sonic 1).
  • Changed every single variable names and labels from its Sonic 2 disassembly nomenclature to the ones used in the Sonic 1 GitHub disassembly (or an equivalent one), as well as removing some comments relating to equivalent subroutine names for older Sonic 2 disassemblies.
    • This also includes renaming Sonic_CheckSpindash to Sonic_SpinDash (which is the label that we're using for our Spin Dash subroutine).
  • Fixed incorrect animations and sound effects with the ones that are already present in Sonic 1.
    • For context, the unmodified code from Sonic 2 presents us with some problems: the Spin Dash animation in Sonic 2 becomes an unused warping animation while the music and sound effects fade away when performing a Spin Dash. This is what happens when we leave the original IDs for the animations and sound effects as-is. But because our code was based off of the GitHub version of the Sonic 2 disassembly when we ported it, it will instead give us an assembly error that will notify us with the undefined variable names. This was temporarily fixed by providing the rolling animation and sound effects as a substitute.
  • Removed some leftover remnants of the code related to Super Sonic as Super Sonic doesn't exist in Sonic 1.
  • Fixed an issue where the camera will stay low when Spin Dashing after ducking.

Now it's time to put our Spin Dash code into our main code. Go to locret_13302: (line 7121), and scroll down until you see include "_incObj/Sonic JumpHeight.asm". In between the includes for "_incObj/Sonic JumpHeight.asm" and "_incObj/Sonic SlopeResist.asm", insert include "_incObj/Sonic SpinDash.asm". You should have this:

		include	"_incObj/Sonic Jump.asm"
		include	"_incObj/Sonic JumpHeight.asm"
		include	"_incObj/Sonic SpinDash.asm"	; <-- add this line!
		include	"_incObj/Sonic SlopeResist.asm"

Now, let's test it! Save your changes, and then build the ROM. Then, test it out in the emulator, and...

First attempt

Spin Dashing with rolling animation

Success! The charging sound works fine, and the animation looks similar to the Spin Dash found in Sonic CD. Not bad! Now, while this is all good and all, there are still a few bugs that we have to resolve.

The most obvious one is that if you get hit while charging, you'll still be in a Spin Dash-charging state when you land. If you let go of the down button while bouncing backwards, Sonic will release his Spin Dash right away when he hits the ground (which depends on the way you got hit, e.g. forwards or backwards). Now, this might be awesome in of itself and should certainly kill whatever badnik hurt you in the first place, but it isn't what should happen.

Aside from all of that, the physics of the Spin Dash are clearly in place, however. This is a good start!

Fixing bugs/errors

First of all, let's fix the boundary issue. As mentioned earlier in this guide, we have already fixed the camera bug in our code that we have provided. As such, you'll find that the camera does reset itself when you start to Spin Dash, however if you take the double-S-tube in GHZ1, you still barely outrun the screen vertically and get killed. So to fix this, go into the _incObj folder and open the file "Sonic LevelBound.asm". Find the code at the label .bottom:. Here's what you should see:

.bottom:
		cmpi.w	#(id_SBZ<<8)+1,(v_zone).w ; is level SBZ2 ?
		bne.w	KillSonic	; if not, kill Sonic
		cmpi.w	#$2000,(v_player+obX).w
		blo.w	KillSonic
		clr.b	(v_lastlamp).w	; clear	lamppost counter
		move.w	#1,(f_restart).w ; restart the level
		move.w	#(id_LZ<<8)+3,(v_zone).w ; set level to SBZ3 (LZ4)
		rts

First, add a label just before the rts at the end of the block of code, named .dontkill. Then, at the beginning of .bottom:, you're going to compare two values in memory. If (v_limitbtm1).w < (v_limitbtm2).w, then the screen is still scrolling down and you don't want to die. In this case, it should skip the part below that jumps to the routine KillSonic. You should add the following lines of code below .bottom::

		move.w	(v_limitbtm1).w,d0 
		move.w	(v_limitbtm2).w,d1 
		cmp.w	d0,d1		; screen still scrolling down? 
		blt.s	.dontkill	; if so, don't kill Sonic

You should end up with something like this:

.bottom:
		move.w	(v_limitbtm1).w,d0 
		move.w	(v_limitbtm2).w,d1 
		cmp.w	d0,d1		; screen still scrolling down? 
		blt.s	.dontkill	; if so, don't kill Sonic
		cmpi.w	#(id_SBZ<<8)+1,(v_zone).w ; is level SBZ2 ?
		bne.w	KillSonic	; if not, kill Sonic
		cmpi.w	#$2000,(v_player+obX).w
		blo.w	KillSonic
		clr.b	(v_lastlamp).w	; clear	lamppost counter
		move.w	#1,(f_restart).w ; restart the level
		move.w	#(id_LZ<<8)+3,(v_zone).w ; set level to SBZ3 (LZ4)
		rts	
; ===========================================================================

.dontkill: 
		rts
There is a
Sonic Retro
forum thread
that talks about this specific bug as well as a fix for it.

Now, you'll fix the problem where Sonic is still in his Spin Dashing state after he gets injured. Open sub ReactToItem.asm located in _incObj and find the subroutine HurtSonic. This is the code that handles hurting Sonic, if you couldn't guess. You're going to have it clear the flag in memory that stores whether or not Sonic is Spin Dashing. Scroll to the label .isleft and add the line move.b #0,spindash_flag(a0) directly below it. You should end up with:

.isleft:
		move.b	#0,spindash_flag(a0)	; clear Spin Dash flag 
		move.w	#0,obInertia(a0)
		move.b	#id_Hurt,obAnim(a0)
		move.w	#120,flashtime(a0)	; set temp invincible time to 2 seconds

Save, build, and test. See if you can try to outrun the camera in the S-tube in GHZ1 and try to see if you're not in a Spin Dashing state after getting hurt. If all goes well, those pesky bugs should be gone! We now have a functioning Spin Dash in Sonic 1! However, it doesn't look like the Spin Dash from Sonic 2. In the next sections, we will rectify this.

Adding new sprites

Now, you'll need to add the Spin Dash sprites from Sonic 2 into Sonic 1. Since Sonic doesn't have a dedicated Spin Dash sprite as it's not present in Sonic 1, we need to implement the sprites for it. To do this, we'll need a tile/sprite/mappings editor of our choice. We'll be using Flex 2 for this guide.

Step 1: Basic setup

Open up sonic1.flex.json in your disassembly via Flex 2 and select the "Sonic" object under the "Common" folder. Once there, click on the "Load" button under Object and...

setting up Flex 2 part 1

Uh-oh. It looks like we have a problem. But, no matter, we can just select "Sonic 2" under Game Format, and let’s see what we got:

setting up Flex 2 part 2

Success! Our object now displays as intended!

Now create a new object by hitting the "Object" button, and it should appear above the "Common" folder. You can name this new object to anything you want. Select your newly-created object, and begin to load in your Spin Dash sprites. First, we’ll create a new folder on our disassembly for the time being to put our Sonic 2 files in. You can name this folder to anything we want.

Step 2: Extracting Spin Dash sprites

Next, we need to extract the Spin Dash sprites from Sonic 2. For this section, we'll show you two methods on how to port the Spin Dash sprites into Sonic 1. For the purposes of this tutorial, we'll start off with the quick-and-easy section first.

Method 1: The "quick" way

Get the Spin Dash sprites that I have provided here. Download it and then extract it into your newly-created folder that you'll be putting the Spin Dash sprites in. Done. You can then proceed on to Step 3 below.

Method 2: The "long" way

For those who have the time (and patience) to get the Spin Dash sprites from a Sonic 2 disassembly, then this method is for you.

First, we need to provide the Sonic 2 files. Here's what you will be able to find in your Sonic 2 disassembly as far as Sonic 2 files go:

  • Sonic's art tiles (found under "art\uncompressed" with the filename "Sonic's art.bin")
  • Sonic's mappings (found under "mappings\sprite" with the filename "Sonic.asm")
  • Sonic's DPLCs (found under "mappings\spriteDPLC" with the filename "Sonic.asm"; rename as "SonicDPLC.asm")

Note that these files are meant for the Xenowhirl and GitHub disassemblies of Sonic 2, provided that you have one for this part of the guide.

As for the palette, we'll be using Sonic's palette from our Sonic 1 disassembly. This way the sprites will still remain consistent in terms of the color palette, otherwise they will use the nearest matching color palette for our Spin Dash sprites if we import them with the Sonic 2 palette, and that’s not what we want.

Now head over to your Sonic 2 disassembly, copy the provided files, and then paste them into your new folder. To save you some time, I have provided the Sonic 2 files here for download. Once you have downloaded the archive containing the files, extract them to the folder that you will be putting the Sonic 2 files in.

Now load those files into Flex 2, select "Sonic 2" under Game Format, and then press the "Load" button near Object. Here's what we should get:

setting up Flex 2 part 3

Success! The basic setup is complete!

Now comes the long part, extracting the Spin Dash sprites. Head over to the "Mappings" tab and press the "Export Spritesheet" option (or press "E" (uppercase) on your keyboard) and save it. Once finished, open your exported sprite sheet in a raster graphics editor of your choice (I'll be using GIMP for this part of the guide). You should see a big list of all of the sprites that we just exported. Make sure to find where the Spin Dash sprites are located. Now you want to crop the image down to focus only on the Spin Dash sprites. Use the selection tool to select the Spin Dash sprites as shown:

selecting spin dash sprites

Now find the option to crop the image. If you're using GIMP, go to "Image" => "Crop to Selection". You should be left with this:

cropped selection showing only spin dash sprites

Now save your image file as a new image by going to "File" => "Export As...", and then save your new image in the folder that you put the Sonic 2 files in. Make sure the file extension ".png" is defined, as we'll be saving in this format. You can name your new image to anything you want; I will be using "spindash.png" as an example.

With all of that done, we can now move on to Step 3.

Step 3: Importing Spin Dash sprites

Head over to Flex 2, select the "Project" tab, click on the "Sonic" object under the "Common" folder, load it, and then head back to the "Mappings" tab. Click on the Import Spritesheet button (or pressing "s" (lowercase) on your keyboard) and select the Spin Dash sprite you have saved. You should see a screen that looks like this:

importing spin dash sprites in Flex 2 part 1

Now click on Detect Sprites, and you should see all of the Spin Dash sprites being highlighted. Then click on Import 6 Sprites. This will take you to the "Import Sprite" screen for each individual sprite, as shown below:

importing spin dash sprites in Flex 2 part 2

To save us some time in importing each and every single sprite, We'll click on Import All to import all of our Spin Dash sprites. Once that's finished, we should see our Spin Dash sprites that we added once you scroll down to the bottom under the "Sprites" section (or by scrolling the horizontal scroll bar to the very end of the sprite list).

Next, we'll adjust the positioning of them. To do that, we'll select each individual Spin Dash sprite one by one from our selection and then hitting "Ctrl+A" (or by clicking and dragging the mouse cursor) to select the whole sprite as shown:

selecting spin dash sprites via Flex 2

Then, we'll start moving the sprites down by 9 pixels and to the right by 5 pixels. This will ensure that our newly-added Spin Dash sprites match up with the original Sonic 2 sprites in terms of the positioning. Repeat this step for every single Spin Dash sprite.

To help you with this process, there is a very handy indicator at the top-right hand corner of the editing window that shows you the positioning of the sprites when you have selected the whole sprite (or even individual sprite pieces), as shown here (highlighted in red):

Flex 2 hud indicator

Make sure to double-check the positioning of your newly-added sprites by selecting one sprite or another that has the same positioning as the one you've done several times. You can also scroll the horizontal scroll bar to easily see the position of the sprites. Go ahead and save your files by going to the "Project" tab, clicking on the "Sonic" object under the "Common" folder, and then hitting the "Save" button under Object. Make sure to switch the Game Format to "Sonic 1" before saving.

At this point, you’re now ready to build your ROM. BUT WAIT! Don't go for that "build.bat" option yet, we've only just scratched the surface!

Adding animations

We have not added the proper animations for our ported Spin Dash yet! So for this step, we need to create new animations for our Spin Dash sprites that we have imported. Go to Sonic.asm (or Sonic (without frame IDs).asm) in the _anim folder and locate ptr_Float4:. You'll see this:

ptr_Float4:	dc.w SonAni_Float4-Ani_Sonic

Add this underneath:

ptr_Spindash:	dc.w SonAni_Spindash-Ani_Sonic ;1F

Notice that the hex number commented on the side is an index in the table. If you were to number each line in the list you just added to in hex, starting at zero, those would be the numbers for those lines. As such, the ;1F comment after it indicates that this new line would become the $1Fth line. Notice that at index 2 is the rolling animation that we are currently using.

Once that's done, locate SonAni_Float4:. You'll see this:

SonAni_Float4:	dc.b 3,	fr_Float1, afChange, id_Walk
		even

Now, you will again add data just after the even at the end of the second table:

SonAni_Spindash: dc.b 0, fr_Spindash1, fr_Spindash2, fr_Spindash1, fr_Spindash3, fr_Spindash1, fr_Spindash4
		dc.b fr_Spindash1, fr_Spindash5, fr_Spindash1, fr_Spindash6, afEnd
		even

The first byte in this animation script tells the game to use the fastest speed in this animation. Each subsequent word until the afEnd tells the game which animation frame to use. The afEnd at the end tells the game to loop the entire animation. Basically, this script will have the game cycle through the 1st, 2nd, 1st, 3rd, ..., 1st, 6th, 1st, 2nd, ... frames.

Notice that you could have use numbers ($58, $59, etc...) as they are the indexes of your Spin Dash PLCs. In the _anim folder of your disassembly, there is an alternative animation file that does not use frame IDs, titled Sonic (without frame IDs).asm. Here's what you should be doing when it has been done this way:

SonAni_SpinDash:
		dc.b 0, $58, $59, $58, $5A, $58, $5B, $58, $5C, $58, $5D, afEnd
		even

Paste this underneath the animation data for SonAni_Float4: within Sonic (without frame IDs).asm.

Now go to the very end of the file and add this line:

id_Spindash:	equ (ptr_Spindash-Ani_Sonic)/2	; $1F

Now to call the frame IDs for the Spin Dash animation. Go to Constants.asm and look for fr_WaterSlide: equ $57. Underneath it, insert these lines:

fr_Spindash1:	equ $58
fr_Spindash2:	equ $59
fr_Spindash3:	equ $5A
fr_Spindash4:	equ $5B
fr_Spindash5:	equ $5C
fr_Spindash6:	equ $5D

Now that you have added all the necessary information for using the new Spin Dash tiles in an animation, it's time for the last step in adding this animation: actually having the game use it! Open Sonic SpinDash.asm in your text editor (if you haven't already) and replace instances of id_Roll with id_Spindash, being very careful not to touch the other id_Roll animation under Sonic_UpdateSpindash:.

Now let's save our progress, build the ROM, and test it out in our emulator to see how it goes:

Spin Dashing with Spin Dash animation

Success! We now have a Spin Dash that looks and functions identical to Sonic 2! However, this produces a new bug, as the game’s hurt-checking routine has not been updated to use our new animation that we have just set. So if you try to charge a Spin Dash while having rings and a badnik starts attacking you, they can still hurt you in your Spin Dashing state. Sonic gets injured, when instead the enemy should be destroyed. This is what causes the Spin Dashing badnik bug.

Final bugfix

Fortunately, this is a relatively easy bug to fix. So to fix this bug once and for all, go back to sub ReactToItem under _incObj and look for React_Enemy:. Find this piece of code:

React_Enemy:
		tst.b	(v_invinc).w	; is Sonic invincible?
		bne.s	.donthurtsonic	; if yes, branch
		cmpi.b	#id_Roll,obAnim(a0) ; is Sonic rolling/jumping?
		bne.w	React_ChkHurt	; if not, branch

Just above the line cmpi.b #id_Roll,obAnim(a0) ; is Sonic rolling/jumping?, add these lines underneath:

		cmpi.b	#id_Spindash,obAnim(a0)	; is Sonic Spin Dashing? 
		beq.w	.donthurtsonic	; if yes, branch

You should end up with something like this:

React_Enemy:
		tst.b	(v_invinc).w	; is Sonic invincible?
		bne.s	.donthurtsonic	; if yes, branch
		cmpi.b	#id_Spindash,obAnim(a0)	; is Sonic Spin Dashing? 
		beq.w	.donthurtsonic	; if yes, branch
		cmpi.b	#id_Roll,obAnim(a0) ; is Sonic rolling/jumping?
		bne.w	React_ChkHurt	; if not, branch

This code adds a check to see if the animation is id_Spindash (#$1F), our new animation. If it is, it branches over the code that jumps to the routine to hurt Sonic. Notice that there is another comparison for if the animation id_Roll (#2) is present, which concealed this bug before, since we were using that animation.

Save, build, and test. Let's test our Spin Dash against a badnik:

killing an enemy while Spin Dashing

Pop! Congratulations! We've successfully ported the Spin Dash from Sonic 2 into Sonic 1, complete with the proper animations! However, it's still not fully complete though, as it's missing a few bells and whistles. We still have to add the Spin Dash smoke/dust object, as well as fixing a few more bugs. Luckily, Puto has written a wonderful guide continuing from this point that does exactly that.

Complete Spin Dash code

For your convenience, here's the full Spin Dash code after following this guide, complete with commenting:

; ---------------------------------------------------------------------------
; Subroutine to check for starting to charge a spindash
; ---------------------------------------------------------------------------

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


Sonic_SpinDash:
		tst.b	spindash_flag(a0)	; already Spin Dashing?
		bne.s	Sonic_UpdateSpindash	; if set, branch
		cmpi.b	#id_Duck,obAnim(a0)	; is anim duck?
		bne.s	return_1AC8C	; if not, return
		move.b	(v_jpadpress2).w,d0	; read controller
		andi.b	#btnB|btnC|btnA,d0	; pressing A/B/C ?
		beq.w	return_1AC8C	; if not, return
		move.b	#id_Spindash,obAnim(a0)	; set Spin Dash anim (9 in s2)
		move.w	#sfx_Roll,d0	; spin sound ($E0 in s2)
		jsr	(PlaySound_Special).l		; play spin sound
		addq.l	#4,sp		; increment stack ptr
		move.b	#1,spindash_flag(a0)	; set Spin Dash flag
		move.w	#0,spindash_counter(a0)	; set charge count to 0
		cmpi.b	#12,obSubtype(a0)	; if he's drowning, branch to not make dust
		blo.s	+
		move.b	#2,(v_spindust+obAnim).w ; set Spin Dash dust anim to 2
+
		bsr.w	Sonic_LevelBound
		bsr.w	Sonic_AnglePos

return_1AC8C:
		rts
; End of function Sonic_SpinDash


; ---------------------------------------------------------------------------
; Subroutine to update an already-charging spindash
; ---------------------------------------------------------------------------

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


Sonic_UpdateSpindash:
		move.b	(v_jpadhold2).w,d0	; read controller
		btst	#bitDn,d0			; check down button
		bne.w	Sonic_ChargingSpindash	; if set, branch

		; unleash the charged spindash and start rolling quickly:
		move.b	#$E,obHeight(a0)	; obHeight(a0) is height/2
		move.b	#7,obWidth(a0)		; obWidth(a0) is width/2
		move.b	#id_Roll,obAnim(a0)	; set animation to roll
		addq.w	#5,obY(a0)	; add the difference between Sonic's rolling and standing heights
		move.b	#0,spindash_flag(a0)	; clear Spin Dash flag
		moveq	#0,d0
		move.b	spindash_counter(a0),d0	; copy charge count
		add.w	d0,d0	; double it
		move.w	SpindashSpeeds(pc,d0.w),obInertia(a0)	; get spindash speed

		; Determine how long to lag the camera for.
		; Notably, the faster Sonic goes, the less the camera lags.
		; This is seemingly to prevent Sonic from going off-screen.
		move.w	obInertia(a0),d0	; get inertia
		subi.w	#$800,d0 ; $800 is the lowest spin dash speed
		add.w	d0,d0	; double it
		andi.w	#$1F00,d0 ; This line is not necessary, as none of the removed bits are ever set in the first place
		neg.w	d0		; negate it
		addi.w	#$2000,d0	; add $2000
		move.w	d0,($FFFFEED0).w	; move to $EED0

		btst	#0,obStatus(a0)	; is sonic facing right?
		beq.s	+		; if not, branch
		neg.w	obInertia(a0)	; negate inertia
+
		bset	#2,obStatus(a0)	; set unused (in s1) flag
		move.b	#0,(v_spindust+obAnim).w	; clear Spin Dash dust anim
		move.w	#sfx_Teleport,d0	; spindash zoom sound
		jsr	(PlaySound_Special).l	; play it!
		bra.s	Sonic_Spindash_ResetScr
; ===========================================================================
SpindashSpeeds:
		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
; ===========================================================================

Sonic_ChargingSpindash:			; If still charging the dash...
		tst.w	spindash_counter(a0)	; check charge count
		beq.s	+				; if zero, branch
		move.w	spindash_counter(a0),d0	; otherwise put it in d0
		lsr.w	#5,d0			; shift right 5 (divide it by 32)
		sub.w	d0,spindash_counter(a0)	; subtract from charge count
		bcc.s	+				; ??? branch if carry clear
		move.w	#0,spindash_counter(a0)	; set charge count to 0
+
		move.b	(v_jpadpress2).w,d0	; read controller
		andi.b	#btnB|btnC|btnA,d0	; pressing A/B/C?
		beq.w	Sonic_Spindash_ResetScr	; if not, branch
		move.w	#(id_Spindash<<8)|(id_Walk<<0),obAnim(a0)	; reset spindash animation
		move.w	#sfx_Roll,d0	; was $E0 in sonic 2
		jsr	(PlaySound_Special).l	; play charge sound
		addi.w	#$200,spindash_counter(a0)	; increase charge count
		cmpi.w	#$800,spindash_counter(a0)	; check if it's maxed
		blo.s	Sonic_Spindash_ResetScr	; if not, then branch
		move.w	#$800,spindash_counter(a0)	; reset it to max

Sonic_Spindash_ResetScr:
		addq.l	#4,sp		; increase stack ptr
		cmpi.w	#(224/2)-16,($FFFFEED8).w	; $EED8 only ever seems to be used in Spin Dash
		beq.s	loc_1AD8C
		bhs.s	+
		addq.w	#4,($FFFFEED8).w
+		subq.w	#2,($FFFFEED8).w

loc_1AD8C:
		bsr.w	Sonic_LevelBound
		bsr.w	Sonic_AnglePos
		move.w	#$60,(v_lookshift).w	; reset looking up/down
		rts
; End of function Sonic_UpdateSpindash
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 | Add Sonic 2 Level Select | Collide with Water After Being Hurt | Smooth Out Rotation in Special Stages
Adding Features
Add Spin Dash ( Part 1 (GitHub)/(Hivebrain) / Part 2 / Part 3 / Part 4 ) | Add Eggman Monitor | Add Super Sonic | Add Extended Camera | Add the Air Roll | Add 6-Button Support
Sound Features
Expand the Sound Index | Play Different Songs Per Act | Port Sonic 2 Final Sound Driver | Port Sonic 3's Sound Driver | Change The SEGA Sound | Correct PAL Music Tempo
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 (GitHub)]]