Actions

SCHG How-to

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

From Sonic Retro

m
m (Text replacement - "</asm>" to "</syntaxhighlight>")
Line 51: Line 51:
 
rts
 
rts
 
; ===========================================================================
 
; ===========================================================================
</asm>
+
</syntaxhighlight>
  
 
Now, to actually copy the Spin Dash subroutine. In Sonic 2, find the label '''loc_1AC3E:'''. This is the subroutine that we are going to copy, so select from that line all the way down to where you see '''; End of subroutine loc_1AC3E''', and copy the code. In Sonic 1, find '''; End of function Sonic_JumpHeight''' and paste the block of code from Sonic 2 beneath it.
 
Now, to actually copy the Spin Dash subroutine. In Sonic 2, find the label '''loc_1AC3E:'''. This is the subroutine that we are going to copy, so select from that line all the way down to where you see '''; End of subroutine loc_1AC3E''', and copy the code. In Sonic 1, find '''; End of function Sonic_JumpHeight''' and paste the block of code from Sonic 2 beneath it.
Line 69: Line 69:
  
 
loc_1ACD0:
 
loc_1ACD0:
</asm>
+
</syntaxhighlight>
 
* remove this too since it is super sonic's dash speeds:
 
* remove this too since it is super sonic's dash speeds:
 
<asm>
 
<asm>
Line 82: Line 82:
 
dc.w  $E80 ; 7
 
dc.w  $E80 ; 7
 
dc.w  $F00 ; 8
 
dc.w  $F00 ; 8
</asm>
+
</syntaxhighlight>
  
 
Done! You should now have a block of code that looks like the following in between where it says '''; End of function Sonic_JumpHeight''' and '''; Subroutine to slow Sonic walking up a slope''' in Sonic 1.
 
Done! You should now have a block of code that looks like the following in between where it says '''; End of function Sonic_JumpHeight''' and '''; Subroutine to slow Sonic walking up a slope''' in Sonic 1.
Line 198: Line 198:
 
rts
 
rts
 
; End of subroutine Sonic_SpinDash
 
; End of subroutine Sonic_SpinDash
</asm>
+
</syntaxhighlight>
  
 
Now, let's test it! Build your ROM and open it in your emulator.
 
Now, let's test it! Build your ROM and open it in your emulator.
Line 219: Line 219:
 
move.w #$BE,d0 ; changed from #$E0
 
move.w #$BE,d0 ; changed from #$E0
 
jsr (PlaySound_Special).l
 
jsr (PlaySound_Special).l
</asm>
+
</syntaxhighlight>
  
 
Now, find '''move.w #$900,$1C(a0)''' lower in the code. This puts the animation #9 and a flag to reset it to the first frame into Sonic's memory. For now, let's just comment out this line by placing a ; in front of the code, like so:
 
Now, find '''move.w #$900,$1C(a0)''' lower in the code. This puts the animation #9 and a flag to reset it to the first frame into Sonic's memory. For now, let's just comment out this line by placing a ; in front of the code, like so:
Line 231: Line 231:
 
move.w #$BE,d0 ; changed from #$E0
 
move.w #$BE,d0 ; changed from #$E0
 
jsr (PlaySound_Special).l
 
jsr (PlaySound_Special).l
</asm>
+
</syntaxhighlight>
  
 
Save and build your ROM! Let's see how it does...
 
Save and build your ROM! Let's see how it does...
Line 260: Line 260:
 
rts
 
rts
 
; End of subroutine Sonic_SpinDash
 
; End of subroutine Sonic_SpinDash
</asm>
+
</syntaxhighlight>
  
 
Save, build the ROM, and test it. You should find that the camera does reset itself when you start to Spin Dash, but if you take the double-S-tube in GHZ1, you still barely outrun the screen vertically and get killed. To fix this, find the code at the label '''Boundary_Bottom:'''. First, add a label just before the '''rts''' at the end of the block of code, named '''Boundary_Bottom_locret:'''. Then, at the beginning of '''Boundary_Bottom:''', you're going to compare two values in memory. If ($FFFFF726).w < ($FFFFF72E).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 '''Boundary_Bottom:'''.
 
Save, build the ROM, and test it. You should find that the camera does reset itself when you start to Spin Dash, but if you take the double-S-tube in GHZ1, you still barely outrun the screen vertically and get killed. To fix this, find the code at the label '''Boundary_Bottom:'''. First, add a label just before the '''rts''' at the end of the block of code, named '''Boundary_Bottom_locret:'''. Then, at the beginning of '''Boundary_Bottom:''', you're going to compare two values in memory. If ($FFFFF726).w < ($FFFFF72E).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 '''Boundary_Bottom:'''.
Line 269: Line 269:
 
cmp.w d0,d1
 
cmp.w d0,d1
 
blt.s Boundary_Bottom_locret
 
blt.s Boundary_Bottom_locret
</asm>
+
</syntaxhighlight>
  
 
You should end up with something like this:
 
You should end up with something like this:
Line 290: Line 290:
 
rts
 
rts
 
; ===========================================================================
 
; ===========================================================================
</asm>
+
</syntaxhighlight>
  
 
These fixes can also be found {{LinkRetro|topic=8593|title=here}}.
 
These fixes can also be found {{LinkRetro|topic=8593|title=here}}.
Line 302: Line 302:
 
move.b #$1A,$1C(a0)
 
move.b #$1A,$1C(a0)
 
move.w #$78,$30(a0)
 
move.w #$78,$30(a0)
</asm>
+
</syntaxhighlight>
  
 
Save, build, and test. If all is well, those bugs should be gone! Now you 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.
 
Save, build, and test. If all is well, those bugs should be gone! Now you 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.
Line 351: Line 351:
 
dc.w SonPLC_SpinDash5-SonicDynPLC ;5C
 
dc.w SonPLC_SpinDash5-SonicDynPLC ;5C
 
dc.w SonPLC_SpinDash6-SonicDynPLC ;5D
 
dc.w SonPLC_SpinDash6-SonicDynPLC ;5D
</asm>
+
</syntaxhighlight>
  
 
Note that the hex numbers commented to the side are indeces. 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. These will be important later because they are the frame numbers that will be used in your animation.
 
Note that the hex numbers commented to the side are indeces. 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. These will be important later because they are the frame numbers that will be used in your animation.
Line 364: Line 364:
 
SonPLC_SpinDash5: dc.b 1, $F5, $50 ; 01 F 550
 
SonPLC_SpinDash5: dc.b 1, $F5, $50 ; 01 F 550
 
SonPLC_SpinDash6: dc.b 1, $F5, $60 ; 01 F 560
 
SonPLC_SpinDash6: dc.b 1, $F5, $60 ; 01 F 560
</asm>
+
</syntaxhighlight>
  
 
These lines will be built in the ROM as the hex strings found commented beside each one. The format for mappings in Sonic 1 is NN(STTT), where NN is how many times (STTT) is repeated after it. S is one less than the number of tiles to be loaded in order, and TTT is the tile index from which the game should start loading. In each of your PLCs, NN is 01, meaning there is only one instance of (STTT) after it. Also, in each of your PLCs, S is $F, which means that $10 tiles will be loaded for each. The remaining TTT is the number of the first tile in the pattern (the first row of Spin Dash tiles you copied starts at tile number $510, the second at $520, etc.).
 
These lines will be built in the ROM as the hex strings found commented beside each one. The format for mappings in Sonic 1 is NN(STTT), where NN is how many times (STTT) is repeated after it. S is one less than the number of tiles to be loaded in order, and TTT is the tile index from which the game should start loading. In each of your PLCs, NN is 01, meaning there is only one instance of (STTT) after it. Also, in each of your PLCs, S is $F, which means that $10 tiles will be loaded for each. The remaining TTT is the number of the first tile in the pattern (the first row of Spin Dash tiles you copied starts at tile number $510, the second at $520, etc.).
Line 378: Line 378:
 
dc.w byte_spdh3-Map_Sonic, byte_spdh4-Map_Sonic
 
dc.w byte_spdh3-Map_Sonic, byte_spdh4-Map_Sonic
 
dc.w byte_spdh5-Map_Sonic, byte_spdh6-Map_Sonic
 
dc.w byte_spdh5-Map_Sonic, byte_spdh6-Map_Sonic
</asm>
+
</syntaxhighlight>
  
 
Then, at the bottom of the file, just before the even, add:
 
Then, at the bottom of the file, just before the even, add:
Line 395: Line 395:
 
byte_spdh6: dc.b 1 ; Spin Dash 6
 
byte_spdh6: dc.b 1 ; Spin Dash 6
 
dc.b $F8, $F, 0, 0, $F4
 
dc.b $F8, $F, 0, 0, $F4
</asm>
+
</syntaxhighlight>
  
 
You might notice that these are all identical to each other, and are also very similar to the mappings for the jumping/rolling frames. The format of these mappings is structurally similar to the format of the PLCs: the first byte defines how many mapping pieces there are following it. The format is NN(YYSSFFTTXX), where NN is the number of (YYSSFFTTXX) following, YY is the Y position of the piece, SS is the size and shape (in this case, $F is a 4x4 tile square), FF is the control for certain flip and palette changes (0 in this case means not to touch it), TT is the first tile in VRAM to start reading (in this case 0 is the first tile), and XX is the X position of the piece. The Y position of these mappings, $F8, is slightly greater than that of the jumping/rolling animation, $F0. This is so Sonic doesn't appear to be hovering slightly above the ground while charging the Spin Dash.
 
You might notice that these are all identical to each other, and are also very similar to the mappings for the jumping/rolling frames. The format of these mappings is structurally similar to the format of the PLCs: the first byte defines how many mapping pieces there are following it. The format is NN(YYSSFFTTXX), where NN is the number of (YYSSFFTTXX) following, YY is the Y position of the piece, SS is the size and shape (in this case, $F is a 4x4 tile square), FF is the control for certain flip and palette changes (0 in this case means not to touch it), TT is the first tile in VRAM to start reading (in this case 0 is the first tile), and XX is the X position of the piece. The Y position of these mappings, $F8, is slightly greater than that of the jumping/rolling animation, $F0. This is so Sonic doesn't appear to be hovering slightly above the ground while charging the Spin Dash.
Line 409: Line 409:
 
<asm>
 
<asm>
 
dc.w SonAni_SpinDash-SonicAniData ;1F
 
dc.w SonAni_SpinDash-SonicAniData ;1F
</asm>
+
</syntaxhighlight>
  
 
The ''';1F''' comment after it is again an index in the table. If you were to enumerate all of the lines in this table, starting at zero, this new line would be the $1Fth line. Notice that at index 2 is the rolling animation that we are currently using, and at index 9 is the warping one that it used before we fixed it!
 
The ''';1F''' comment after it is again an index in the table. If you were to enumerate all of the lines in this table, starting at zero, this new line would be the $1Fth line. Notice that at index 2 is the rolling animation that we are currently using, and at index 9 is the warping one that it used before we fixed it!
Line 417: Line 417:
 
<asm>
 
<asm>
 
SonAni_SpinDash: dc.b 0, $58, $59, $58, $5A, $58, $5B, $58, $5C, $58, $5D, $FF
 
SonAni_SpinDash: dc.b 0, $58, $59, $58, $5A, $58, $5B, $58, $5C, $58, $5D, $FF
</asm>
+
</syntaxhighlight>
  
 
The first byte in this animation script tells the game to use the fastest speed in this animation. Each subsequent byte until the $FF tells the game which animation frame to use. Notice that these numbers ($58, $59, etc...) are the indeces of your Spin Dash PLCs. The $FF 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.
 
The first byte in this animation script tells the game to use the fastest speed in this animation. Each subsequent byte until the $FF tells the game which animation frame to use. Notice that these numbers ($58, $59, etc...) are the indeces of your Spin Dash PLCs. The $FF 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.
Line 427: Line 427:
 
<asm>
 
<asm>
 
move.b #2,$1C(a0) ; changed from #9
 
move.b #2,$1C(a0) ; changed from #9
</asm>
+
</syntaxhighlight>
  
 
to use your new animation found at $1F:
 
to use your new animation found at $1F:
Line 433: Line 433:
 
<asm>
 
<asm>
 
move.b #$1F,$1C(a0) ; changed from #9
 
move.b #$1F,$1C(a0) ; changed from #9
</asm>
+
</syntaxhighlight>
  
 
Then, find the line you commented out before,
 
Then, find the line you commented out before,
Line 439: Line 439:
 
<asm>
 
<asm>
 
; move.w #$900,$1C(a0)
 
; move.w #$900,$1C(a0)
</asm>
+
</syntaxhighlight>
  
 
uncomment it, and change the value to $1F00.
 
uncomment it, and change the value to $1F00.
Line 445: Line 445:
 
<asm>
 
<asm>
 
move.w #$1F00,$1C(a0) ; changed from #$900
 
move.w #$1F00,$1C(a0) ; changed from #$900
</asm>
+
</syntaxhighlight>
  
 
Finally, save your code, build your ROM, and try it out.
 
Finally, save your code, build your ROM, and try it out.
Line 460: Line 460:
 
cmpi.b #$1F,$1C(a0) ; is Sonic Spin Dashing?
 
cmpi.b #$1F,$1C(a0) ; is Sonic Spin Dashing?
 
beq.w loc_1AF40 ; if yes, branch
 
beq.w loc_1AF40 ; if yes, branch
</asm>
+
</syntaxhighlight>
  
 
You should end up with something like this:
 
You should end up with something like this:
Line 472: Line 472:
 
cmpi.b #2,$1C(a0) ; is Sonic rolling?
 
cmpi.b #2,$1C(a0) ; is Sonic rolling?
 
bne.w Touch_ChkHurt ; if not, branch
 
bne.w Touch_ChkHurt ; if not, branch
</asm>
+
</syntaxhighlight>
  
 
This code adds a check to see if the animation is #$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 is #2 present, which concealed this bug before, since we were using that animation.
 
This code adds a check to see if the animation is #$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 is #2 present, which concealed this bug before, since we were using that animation.
Line 601: Line 601:
 
rts
 
rts
 
; End of subroutine Sonic_SpinDash
 
; End of subroutine Sonic_SpinDash
</asm>
+
</syntaxhighlight>
  
 
Note: while not necessary, the '''loc_''', '''locret_''', etc. labels from the Sonic 2 disassembly were replaced with '''loc2_''', '''locret2_''', etc. to avoid potential conflict.
 
Note: while not necessary, the '''loc_''', '''locret_''', etc. labels from the Sonic 2 disassembly were replaced with '''loc2_''', '''locret2_''', etc. to avoid potential conflict.
Line 610: Line 610:
 
This shouldn't be completly necessary, but since we're using GitHub disassembly, we're going to do it in GitHub's way. Open Variables.asm and scroll down to the end. There you will define a new RAM equate by adding this line:
 
This shouldn't be completly necessary, but since we're using GitHub disassembly, we're going to do it in GitHub's way. Open Variables.asm and scroll down to the end. There you will define a new RAM equate by adding this line:
  
<asm>f_spindash = $39</asm>
+
<asm>f_spindash = $39</syntaxhighlight>
  
 
You can skip this, but you will have to replace all '''f_spindash''' in the code for '''$39''', and that's not what we want.
 
You can skip this, but you will have to replace all '''f_spindash''' in the code for '''$39''', and that's not what we want.
Line 637: Line 637:
  
 
''';==========================================================================='''
 
''';==========================================================================='''
</asm>
+
</syntaxhighlight>
  
 
Now, to actually copy the Spin Dash subroutine. In Sonic 2, find the label loc_1AC3E. This is the subroutine that we are going to copy, so select from that line all the way down to where you see ; End of subroutine loc_1AC3E, and copy the code. In Sonic 1, create a new file within _incObj folder named Sonic Spindash.asm, open it and paste the code inside of it.
 
Now, to actually copy the Spin Dash subroutine. In Sonic 2, find the label loc_1AC3E. This is the subroutine that we are going to copy, so select from that line all the way down to where you see ; End of subroutine loc_1AC3E, and copy the code. In Sonic 1, create a new file within _incObj folder named Sonic Spindash.asm, open it and paste the code inside of it.
Line 657: Line 657:
 
<asm> tst.b ($FFFFFE19).w  
 
<asm> tst.b ($FFFFFE19).w  
 
beq.s loc_1ACD0  
 
beq.s loc_1ACD0  
move.w word_1AD1E(pc,d0.w),$14(a0)</asm>
+
move.w word_1AD1E(pc,d0.w),$14(a0)</syntaxhighlight>
  
<asm>loc_1ACD0: all of it</asm>
+
<asm>loc_1ACD0: all of it</syntaxhighlight>
  
 
remove this too since it is super sonic's dash speeds:
 
remove this too since it is super sonic's dash speeds:
Line 672: Line 672:
 
dc.w $E00 ; 6  
 
dc.w $E00 ; 6  
 
dc.w $E80 ; 7  
 
dc.w $E80 ; 7  
dc.w $F00 ; 8 </asm>
+
dc.w $F00 ; 8 </syntaxhighlight>
  
 
Done! Save the file and you should have something like this:
 
Done! Save the file and you should have something like this:
Line 787: Line 787:
  
 
;End of subroutine Sonic_SpinDash
 
;End of subroutine Sonic_SpinDash
</asm>
+
</syntaxhighlight>
  
 
Before building any ROM, you have to tell the program to actually load the new file. So go back to sonic.asm and go a bit lower from where you were, you should see this:
 
Before building any ROM, you have to tell the program to actually load the new file. So go back to sonic.asm and go a bit lower from where you were, you should see this:
Line 816: Line 816:
 
move.w #$BE,d0 ; changed from #$E0  
 
move.w #$BE,d0 ; changed from #$E0  
 
jsr (PlaySound_Special).l
 
jsr (PlaySound_Special).l
</asm>
+
</syntaxhighlight>
  
 
Now, find '''move.w #$900,obAnim(a0)''' lower in the code. This puts the animation '''#9''' and a flag to reset it to the first frame into Sonic's memory. For now, let's just comment out this line by placing a ; in front of the code, like so:
 
Now, find '''move.w #$900,obAnim(a0)''' lower in the code. This puts the animation '''#9''' and a flag to reset it to the first frame into Sonic's memory. For now, let's just comment out this line by placing a ; in front of the code, like so:
Line 826: Line 826:
 
move.w #$BE,d0 ; changed from #$E0  
 
move.w #$BE,d0 ; changed from #$E0  
 
jsr (PlaySound_Special).l
 
jsr (PlaySound_Special).l
</asm>
+
</syntaxhighlight>
  
 
Save and build your ROM! Let's see how it does...
 
Save and build your ROM! Let's see how it does...
Line 846: Line 846:
 
===Fixing bugs===
 
===Fixing bugs===
 
First of all, let's fix the camera issue. Go to the end of the Spin Dash subroutine, and add this line:
 
First of all, let's fix the camera issue. Go to the end of the Spin Dash subroutine, and add this line:
<asm> move.w #$60,(v_lookshift).w</asm>
+
<asm> move.w #$60,(v_lookshift).w</syntaxhighlight>
  
 
just above the '''rts'''. This will reset the vertical change in the camera's position. You should have something like this:
 
just above the '''rts'''. This will reset the vertical change in the camera's position. You should have something like this:
Line 857: Line 857:
  
 
''';End of subroutine Sonic_SpinDash'''
 
''';End of subroutine Sonic_SpinDash'''
</asm>
+
</syntaxhighlight>
  
 
Save, build the ROM, and test it. You should find that the camera does reset itself when you start to Spin Dash, but if you take the double-S-tube in GHZ1, you still barely outrun the screen vertically and get killed. To fix this, open the file '''_incObj\Sonic LevelBound.asm''' and find the code at the label '''@bottom'''. 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''':
 
Save, build the ROM, and test it. You should find that the camera does reset itself when you start to Spin Dash, but if you take the double-S-tube in GHZ1, you still barely outrun the screen vertically and get killed. To fix this, open the file '''_incObj\Sonic LevelBound.asm''' and find the code at the label '''@bottom'''. 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''':
Line 865: Line 865:
 
cmp.w d0,d1 ; screen still scrolling down?  
 
cmp.w d0,d1 ; screen still scrolling down?  
 
blt.s @dontkill; if so, don't kill Sonic  
 
blt.s @dontkill; if so, don't kill Sonic  
</asm>
+
</syntaxhighlight>
  
 
You should end up with something like this:
 
You should end up with something like this:
Line 885: Line 885:
 
rts
 
rts
 
''';==========================================================================='''
 
''';==========================================================================='''
</asm>
+
</syntaxhighlight>
 
These fixes can also be found {{LinkRetro|topic=8593|title=here}}.
 
These fixes can also be found {{LinkRetro|topic=8593|title=here}}.
  
Line 895: Line 895:
 
move.b #id_Hurt,obAnim(a0)  
 
move.b #id_Hurt,obAnim(a0)  
 
move.w #$120,$30(a0)
 
move.w #$120,$30(a0)
</asm>
+
</syntaxhighlight>
  
 
Save, build, and test. If all is well, those bugs should be gone! Now you 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.
 
Save, build, and test. If all is well, those bugs should be gone! Now you 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.
Line 931: Line 931:
 
dc.w SonPLC_SpinDash5-SonicDynPLC ;5C
 
dc.w SonPLC_SpinDash5-SonicDynPLC ;5C
 
dc.w SonPLC_SpinDash6-SonicDynPLC ;5D
 
dc.w SonPLC_SpinDash6-SonicDynPLC ;5D
</asm>
+
</syntaxhighlight>
  
 
Note that the hex numbers commented to the side are indeces. 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. These will be important later because they are the frame numbers that will be used in your animation.
 
Note that the hex numbers commented to the side are indeces. 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. These will be important later because they are the frame numbers that will be used in your animation.
Line 943: Line 943:
 
SonPLC_SpinDash5: dc.b 1, $F5, $50 ; 01 F 550  
 
SonPLC_SpinDash5: dc.b 1, $F5, $50 ; 01 F 550  
 
SonPLC_SpinDash6: dc.b 1, $F5, $60 ; 01 F 560
 
SonPLC_SpinDash6: dc.b 1, $F5, $60 ; 01 F 560
</asm>
+
</syntaxhighlight>
  
 
These lines will be built in the ROM as the hex strings found commented beside each one. The format for mappings in Sonic 1 is NN(STTT), where NN is how many times (STTT) is repeated after it. S is one less than the number of tiles to be loaded in order, and TTT is the tile index from which the game should start loading. In each of your PLCs, NN is 01, meaning there is only one instance of (STTT) after it. Also, in each of your PLCs, S is $F, which means that $10 tiles will be loaded for each. The remaining TTT is the number of the first tile in the pattern (the first row of Spin Dash tiles you copied starts at tile number $510, the second at $520, etc.).
 
These lines will be built in the ROM as the hex strings found commented beside each one. The format for mappings in Sonic 1 is NN(STTT), where NN is how many times (STTT) is repeated after it. S is one less than the number of tiles to be loaded in order, and TTT is the tile index from which the game should start loading. In each of your PLCs, NN is 01, meaning there is only one instance of (STTT) after it. Also, in each of your PLCs, S is $F, which means that $10 tiles will be loaded for each. The remaining TTT is the number of the first tile in the pattern (the first row of Spin Dash tiles you copied starts at tile number $510, the second at $520, etc.).
Line 958: Line 958:
 
ptr_MS_Spindash4: dc.w MS_Spindash4-Map_Sonic
 
ptr_MS_Spindash4: dc.w MS_Spindash4-Map_Sonic
 
ptr_MS_Spindash5: dc.w MS_Spindash5-Map_Sonic
 
ptr_MS_Spindash5: dc.w MS_Spindash5-Map_Sonic
ptr_MS_Spindash6: dc.w MS_Spindash6-Map_Sonic</asm>
+
ptr_MS_Spindash6: dc.w MS_Spindash6-Map_Sonic</syntaxhighlight>
  
 
Then, at the end of the second table, just before the '''even''', add:
 
Then, at the end of the second table, just before the '''even''', add:
Line 973: Line 973:
 
dc.b $F8, $F, 0, 0, $F4  
 
dc.b $F8, $F, 0, 0, $F4  
 
MS_Spindash6: dc.b 1 ; Spindash 6
 
MS_Spindash6: dc.b 1 ; Spindash 6
dc.b $F8, $F, 0, 0, $F4 </asm>
+
dc.b $F8, $F, 0, 0, $F4 </syntaxhighlight>
  
 
You might notice that these are all identical to each other, and are also very similar to the mappings for the jumping/rolling frames. The format of these mappings is structurally similar to the format of the PLCs: the first byte defines how many mapping pieces there are following it. The format is NN(YYSSFFTTXX), where NN is the number of (YYSSFFTTXX) following, YY is the Y position of the piece, SS is the size and shape (in this case, $F is a 4x4 tile square), FF is the control for certain flip and palette changes (0 in this case means not to touch it), TT is the first tile in VRAM to start reading (in this case 0 is the first tile), and XX is the X position of the piece. The Y position of these mappings, $F8, is slightly greater than that of the jumping/rolling animation, $F0. This is so Sonic doesn't appear to be hovering slightly above the ground while charging the Spin Dash.
 
You might notice that these are all identical to each other, and are also very similar to the mappings for the jumping/rolling frames. The format of these mappings is structurally similar to the format of the PLCs: the first byte defines how many mapping pieces there are following it. The format is NN(YYSSFFTTXX), where NN is the number of (YYSSFFTTXX) following, YY is the Y position of the piece, SS is the size and shape (in this case, $F is a 4x4 tile square), FF is the control for certain flip and palette changes (0 in this case means not to touch it), TT is the first tile in VRAM to start reading (in this case 0 is the first tile), and XX is the X position of the piece. The Y position of these mappings, $F8, is slightly greater than that of the jumping/rolling animation, $F0. This is so Sonic doesn't appear to be hovering slightly above the ground while charging the Spin Dash.
Line 986: Line 986:
 
fr_Spindash4: equ (ptr_MS_Spindash4-Map_Sonic)/2 ; $5B
 
fr_Spindash4: equ (ptr_MS_Spindash4-Map_Sonic)/2 ; $5B
 
fr_Spindash5: equ (ptr_MS_Spindash5-Map_Sonic)/2 ; $5C
 
fr_Spindash5: equ (ptr_MS_Spindash5-Map_Sonic)/2 ; $5C
fr_Spindash6: equ (ptr_MS_Spindash6-Map_Sonic)/2 ; $5D</asm>
+
fr_Spindash6: equ (ptr_MS_Spindash6-Map_Sonic)/2 ; $5D</syntaxhighlight>
  
 
For more information on mappings in Sonic 1, visit [[Sonicology]].
 
For more information on mappings in Sonic 1, visit [[Sonicology]].
Line 993: Line 993:
 
You're almost done adding in the Spin Dash graphics! Now you will add the animation sequence containing the list of frames to be used. Open the file _anim/Sonic.asm in your text editor. Yet again, we will be adding a line to the bottom of the table in the beginning, just before the line  
 
You're almost done adding in the Spin Dash graphics! Now you will add the animation sequence containing the list of frames to be used. Open the file _anim/Sonic.asm in your text editor. Yet again, we will be adding a line to the bottom of the table in the beginning, just before the line  
  
<asm>SonAni_Walk: dc.b $FF, fr_walk13, fr_walk14, fr_walk15, fr_walk16, fr_walk11, fr_walk12, afEnd</asm>
+
<asm>SonAni_Walk: dc.b $FF, fr_walk13, fr_walk14, fr_walk15, fr_walk16, fr_walk11, fr_walk12, afEnd</syntaxhighlight>
  
 
you will add
 
you will add
  
<asm>ptr_Spindash: dc.w SonAni_SpinDash-Ani_Sonic ;1F</asm>
+
<asm>ptr_Spindash: dc.w SonAni_SpinDash-Ani_Sonic ;1F</syntaxhighlight>
  
 
The ;1F comment after it is again an index in the table. If you were to enumerate all of the lines in this table, starting at zero, this new line would be the $1Fth line. Notice that at index 2 is the rolling animation that we are currently using, and at index 9 is the warping one that it used before we fixed it!
 
The ;1F comment after it is again an index in the table. If you were to enumerate all of the lines in this table, starting at zero, this new line would be the $1Fth line. Notice that at index 2 is the rolling animation that we are currently using, and at index 9 is the warping one that it used before we fixed it!
Line 1,004: Line 1,004:
  
 
<asm>SonAni_SpinDash: dc.b 0, fr_Spindash1, fr_spindash2, fr_spindash1, fr_spindash3, fr_spindash1, fr_spindash4, fr_spindash1, fr_spindash5, fr_spindash1, fr_spindash6, afEnd
 
<asm>SonAni_SpinDash: dc.b 0, fr_Spindash1, fr_spindash2, fr_spindash1, fr_spindash3, fr_spindash1, fr_spindash4, fr_spindash1, fr_spindash5, fr_spindash1, fr_spindash6, afEnd
even</asm>
+
even</syntaxhighlight>
  
 
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. Notice that you could have use numbers ($58, $59, etc...) as they are the indeces of your Spin Dash PLCs. 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.
 
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. Notice that you could have use numbers ($58, $59, etc...) as they are the indeces of your Spin Dash PLCs. 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.
Line 1,010: Line 1,010:
 
Now go to the very end of the file and add this line:
 
Now go to the very end of the file and add this line:
  
<asm>id_Spindash: equ (ptr_Spindash-Ani_Sonic)/2 ; $1F</asm>
+
<asm>id_Spindash: equ (ptr_Spindash-Ani_Sonic)/2 ; $1F</syntaxhighlight>
  
 
===Changing the animation again===
 
===Changing the animation again===
 
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 change the line you changed before,
 
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 change the line you changed before,
  
<asm> move.b #id_roll,obAnim(a0) ; changed from #9</asm>
+
<asm> move.b #id_roll,obAnim(a0) ; changed from #9</syntaxhighlight>
  
 
to use your new animation found at $1F:
 
to use your new animation found at $1F:
  
<asm> move.b #id_Spindash,obAnim(a0) ; changed from #9</asm>
+
<asm> move.b #id_Spindash,obAnim(a0) ; changed from #9</syntaxhighlight>
  
 
Then, find the line you commented out before,
 
Then, find the line you commented out before,
  
<asm>; move.w #$900,obAnim(a0)</asm>
+
<asm>; move.w #$900,obAnim(a0)</syntaxhighlight>
  
 
uncomment it, and change the value to $1F00.
 
uncomment it, and change the value to $1F00.
  
<asm> move.w #$1F00,obAnim(a0) ; changed from #$900</asm>
+
<asm> move.w #$1F00,obAnim(a0) ; changed from #$900</syntaxhighlight>
  
 
Finally, save your code, build your ROM, and try it out.
 
Finally, save your code, build your ROM, and try it out.
Line 1,038: Line 1,038:
 
This is a relatively easy bug to fix. Go back to sub ReactToItem.asm  and search for the routine React_Enemy. Just above the line  
 
This is a relatively easy bug to fix. Go back to sub ReactToItem.asm  and search for the routine React_Enemy. Just above the line  
  
<asm> cmpi.b #id_Roll,obAnim(a0) ; is Sonic rolling/jumping?</asm>
+
<asm> cmpi.b #id_Roll,obAnim(a0) ; is Sonic rolling/jumping?</syntaxhighlight>
  
 
add the following two lines:
 
add the following two lines:
  
 
<asm> cmpi.b #id_Spindash,obAnim(a0) ; is Sonic Spin Dashing?  
 
<asm> cmpi.b #id_Spindash,obAnim(a0) ; is Sonic Spin Dashing?  
beq.w @donthurtsonic ; if yes, branch </asm>
+
beq.w @donthurtsonic ; if yes, branch </syntaxhighlight>
  
 
You should end up with something like this:
 
You should end up with something like this:
Line 1,053: Line 1,053:
 
beq.w @donthurtsonic ; if yes, branch  
 
beq.w @donthurtsonic ; if yes, branch  
 
cmpi.b #id_Roll,obAnim(a0) ; is Sonic rolling/jumping?
 
cmpi.b #id_Roll,obAnim(a0) ; is Sonic rolling/jumping?
bne.w React_ChkHurt ; if not, branch</asm>
+
bne.w React_ChkHurt ; if not, branch</syntaxhighlight>
  
 
This code adds a check to see if the animation is #$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 is #2 present, which concealed this bug before, since we were using that animation.
 
This code adds a check to see if the animation is #$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 is #2 present, which concealed this bug before, since we were using that animation.
Line 1,181: Line 1,181:
 
rts
 
rts
 
; End of subroutine Sonic_SpinDash
 
; End of subroutine Sonic_SpinDash
</asm>
+
</syntaxhighlight>
  
 
Note: while not necessary, the '''loc_''', '''locret_''', etc. labels from the Sonic 2 disassembly were replaced with '''loc2_''', '''locret2_''', etc. to avoid potential conflict.
 
Note: while not necessary, the '''loc_''', '''locret_''', etc. labels from the Sonic 2 disassembly were replaced with '''loc2_''', '''locret2_''', etc. to avoid potential conflict.

Revision as of 21:30, 20 December 2015

(Original guide by Lightning)
updated by kram1024 to remove dependency on super sonic flag

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.

This document is property of Lightning. Sonic and all related characters and graphics that appear on this page are property of Sega and are used without permission for no profit.

What you need

  • Sonic 1 split disassembly
  • Sonic 2 split disassembly
  • A plain text editor of your choice
  • A tile editor capable of editing Genesis format tiles
  • A Sega Genesis emulator (or hardware with a copier)

In making this tutorial, I used:


2005 Hivebrain's disassembly

Directly copying the object code

Open sonic1.asm from the Sonic 1 disassembly in your text editor, and search for the phrase Obj01: (line 23466 in Hivebrain's disassembly). This should bring you to the Sonic object's code, which is what you're interested in editing right now. Now, open s2.asm from the Sonic 2 disassembly in your text editor, and search for Object_Sonic: (line 35720 in Aurochs' disassembly). This will bring you again to Sonic's object code. Having been based on the same code from Sonic 1, it shows many similarities that you should be able to spot. Thanks to the similar code, our job in porting the Spin Dash should be pretty easy!

Find or scroll to the location Obj01_MdNormal: in Sonic 1's source, and loc_1A2B8: in Sonic 2's source. Below this label in each is a series of branches and jumps to subroutines that handle Sonic's actions in different situations. If you look carefully, you'll notice that there are nine of these branches in Sonic 2, but only eight in Sonic 1. That's because Sonic 2 has another branch to the code that handles the Spin Dash. You're going to add Spin Dash to Sonic 1, so the first thing to do is add a call to the Spin Dash function that you're going to copy. Don't worry about the label names from Sonic 2, since you're going to name the subroutine Sonic_SpinDash when we copy it over.

In Sonic 1's code, make a copy of the line bsr.w Sonic_Jump and change it to branch to Sonic_SpinDash, as shown:

<asm>

---------------------------------------------------------------------------
Modes for controlling Sonic
---------------------------------------------------------------------------

Obj01_MdNormal: ; XREF: Obj01_Modes bsr.w Sonic_SpinDash ; add this line! bsr.w Sonic_Jump bsr.w Sonic_SlopeResist bsr.w Sonic_Move bsr.w Sonic_Roll bsr.w Sonic_LevelBound jsr SpeedToPos bsr.w Sonic_AnglePos bsr.w Sonic_SlopeRepel rts

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

</syntaxhighlight>

Now, to actually copy the Spin Dash subroutine. In Sonic 2, find the label loc_1AC3E:. This is the subroutine that we are going to copy, so select from that line all the way down to where you see ; End of subroutine loc_1AC3E, and copy the code. In Sonic 1, find ; End of function Sonic_JumpHeight and paste the block of code from Sonic 2 beneath it.

There are a few changes that we need to make so this code will cooperate with the rest of the Sonic 1 code, since some of the labels in each disassembly differ from each other (the same functions have different names). The following replacements should be made (use your text editor's find and replace function, if applicable):

  • loc_1AC3E with Sonic_SpinDash
  • loc_1A974 with Sonic_LevelBound
  • loc_1E234 with Sonic_AnglePos
  • play_SFX with PlaySound_Special
  • word_1AD0C with 'Dash_Speeds
  • remove all this code since it is a leftover artifact of super sonic:

<asm> tst.b ($FFFFFE19).w beq.s loc_1ACD0 move.w word_1AD1E(pc,d0.w),$14(a0)

loc_1ACD0: </syntaxhighlight>

  • remove this too since it is super sonic's dash speeds:

<asm>

word_1AD1E: 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 </syntaxhighlight>

Done! You should now have a block of code that looks like the following in between where it says ; End of function Sonic_JumpHeight and ; Subroutine to slow Sonic walking up a slope in Sonic 1.

<asm>

---------------------------------------------------------------------------
Subroutine to make Sonic perform a spindash
---------------------------------------------------------------------------
||||||||||||||| S U B R O U T I N E |||||||||||||||||||||||||||||||||||||||


Sonic_SpinDash: tst.b $39(a0) bne.s loc_1AC8E cmpi.b #8,$1C(a0) bne.s locret_1AC8C move.b ($FFFFF603).w,d0 andi.b #$70,d0 beq.w locret_1AC8C move.b #9,$1C(a0) move.w #$E0,d0 jsr (PlaySound_Special).l addq.l #4,sp move.b #1,$39(a0) move.w #0,$3A(a0) cmpi.b #$C,$28(a0) bcs.s loc_1AC84 move.b #2,($FFFFD11C).w

loc_1AC84: bsr.w Sonic_LevelBound bsr.w Sonic_AnglePos

locret_1AC8C: rts

---------------------------------------------------------------------------

loc_1AC8E: move.b ($FFFFF602).w,d0 btst #1,d0 bne.w loc_1AD30 move.b #$E,$16(a0) move.b #7,$17(a0) move.b #2,$1C(a0) addq.w #5,$C(a0) move.b #0,$39(a0) moveq #0,d0 move.b $3A(a0),d0 add.w d0,d0 move.w Dash_Speeds(pc,d0.w),$14(a0) move.w $14(a0),d0 subi.w #$800,d0 add.w d0,d0 andi.w #$1F00,d0 neg.w d0 addi.w #$2000,d0 move.w d0,($FFFFEED0).w btst #0,$22(a0) beq.s loc_1ACF4 neg.w $14(a0)

loc_1ACF4: bset #2,$22(a0) move.b #0,($FFFFD11C).w move.w #$BC,d0 jsr (PlaySound_Special).l bra.s loc_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

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

loc_1AD30: ; If still charging the dash... tst.w $3A(a0) beq.s loc_1AD48 move.w $3A(a0),d0 lsr.w #5,d0 sub.w d0,$3A(a0) bcc.s loc_1AD48 move.w #0,$3A(a0)

loc_1AD48: move.b ($FFFFF603).w,d0 andi.b #$70,d0 ; 'p' beq.w loc_1AD78 move.w #$900,$1C(a0) move.w #$E0,d0 ; 'à' jsr (PlaySound_Special).l addi.w #$200,$3A(a0) cmpi.w #$800,$3A(a0) bcs.s loc_1AD78 move.w #$800,$3A(a0)

loc_1AD78: addq.l #4,sp cmpi.w #$60,($FFFFEED8).w beq.s loc_1AD8C bcc.s loc_1AD88 addq.w #4,($FFFFEED8).w

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

loc_1AD8C: bsr.w Sonic_LevelBound bsr.w Sonic_AnglePos rts

End of subroutine Sonic_SpinDash

</syntaxhighlight>

Now, let's test it! Build your ROM and open it in your emulator.

Spin Dashing with a strange animation

Uh-oh, it seems like it's a bit rough around the edges right now. First of all, Sonic's Spin Dashing animation isn't present in Sonic 1, of course, and right now it's using the animation from the spot that the Spin Dash's animation data replaced. Also, the music and sounds seem to stop when you start charging a Spin Dash. That's because the sound ID that is the Spin Dash sound in Sonic 2 has a different purpose in Sonic 1. The physics of the Spin Dash are clearly in place, however. This is a good start!

Changing the animations and sounds

Now, you're going to make a few simple changes to the Spin Dash subroutine so it uses suitable animations and sounds that are already present in Sonic 1. First, find any instances of the line move.w #$E0,d0 followed by jsr (PlaySound_Special).l. This code loads the sound ID #$E0 into the data register d0 and jumps to the subroutine that is responsible for playing that sound. #$E0 is not a suitable sound for a Spin Dash in Sonic 1, as mentioned earlier--it silences the music. Replace the #$E0 with #$BE in each occurance. #$BE is the sound ID of a normal spin in Sonic 1. You can check it in the sound test for yourself!

Now, go up to the line containing the code move.b #9,$1C(a0), and replace the #9 with #2. This code copies the animation number to be used when Sonic starts charging his Spin Dash. #9 is the ID of the animation used for Spin Dash in Sonic 2; however, in Sonic 1, it's the ID of an unused animation presumably for warping. Animation #2 is the animation used for jumping and rolling in Sonic 1. You should have something like this:

<asm> move.b ($FFFFF603).w,d0 andi.b #$70,d0 beq.w locret_1AC8C move.b #2,$1C(a0) ; changed from #9 move.w #$BE,d0 ; changed from #$E0 jsr (PlaySound_Special).l </syntaxhighlight>

Now, find move.w #$900,$1C(a0) lower in the code. This puts the animation #9 and a flag to reset it to the first frame into Sonic's memory. For now, let's just comment out this line by placing a ; in front of the code, like so:

<asm> loc_1AD48: move.b ($FFFFF603).w,d0 andi.b #$70,d0 ; 'p' beq.w loc_1AD78 ; move.w #$900,$1C(a0) move.w #$BE,d0 ; changed from #$E0 jsr (PlaySound_Special).l </syntaxhighlight>

Save and build your ROM! Let's see how it does...

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! However, there are a few bugs present.

First of all, collect a few rings, then start charging a Spin Dash somewhere that an enemy can shoot you or injure you somehow. 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. Now, this might be awesome in itself and certainly should kill whatever badnik hurt you in the first place, but it isn't what should happen.

Second, move somewhere where the camera has room to move downward. If you hold the down button to duck, then start charging the Spin Dash, the camera stays low until you are no longer rolling.

Spin Dashing with low camera

Another problem that results from this is that if you Spin Dash before an area where your vertical position radically changes (for instance, the double-S-tube in GHZ1), the camera takes too long to catch up to you.

camera lagging behind

Fixing bugs

First of all, let's fix the camera issue. Go to the end of the Spin Dash subroutine, and put move.w #$60,($FFFFF73E).w just above the rts. This will reset the vertical change in the camera's position. You should have something like this:

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

End of subroutine Sonic_SpinDash

</syntaxhighlight>

Save, build the ROM, and test it. You should find that the camera does reset itself when you start to Spin Dash, but if you take the double-S-tube in GHZ1, you still barely outrun the screen vertically and get killed. To fix this, find the code at the label Boundary_Bottom:. First, add a label just before the rts at the end of the block of code, named Boundary_Bottom_locret:. Then, at the beginning of Boundary_Bottom:, you're going to compare two values in memory. If ($FFFFF726).w < ($FFFFF72E).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 Boundary_Bottom:.

<asm> move.w ($FFFFF726).w,d0 move.w ($FFFFF72E).w,d1 cmp.w d0,d1 blt.s Boundary_Bottom_locret </syntaxhighlight>

You should end up with something like this:

<asm> Boundary_Bottom: move.w ($FFFFF726).w,d0 move.w ($FFFFF72E).w,d1 cmp.w d0,d1 ; screen still scrolling down? blt.s Boundary_Bottom_locret ; if so, don't kill Sonic cmpi.w #$501,($FFFFFE10).w ; is level SBZ2 ? bne.w KillSonic ; if not, kill Sonic cmpi.w #$2000,($FFFFD008).w bcs.w KillSonic clr.b ($FFFFFE30).w ; clear lamppost counter move.w #1,($FFFFFE02).w ; restart the level move.w #$103,($FFFFFE10).w ; set level to SBZ3 (LZ4)

Boundary_Bottom_locret: rts

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

</syntaxhighlight>

These fixes can also be found
Sonic Retro
here
.

Now, you'll fix the problem where Sonic is still in his Spin Dashing state after he gets injured. 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 Hurt_ChkSpikes: and add the line move.b #0,$39(a0) directly below it. You should end up with:

<asm> Hurt_ChkSpikes: move.b #0,$39(a0) ; clear Spin Dash flag move.w #0,$14(a0) move.b #$1A,$1C(a0) move.w #$78,$30(a0) </syntaxhighlight>

Save, build, and test. If all is well, those bugs should be gone! Now you 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 tiles

Now, you'll need to add the Spin Dash tiles from Sonic 2's tiles to Sonic 1's; However, you'll need to pad the file artunc/sonic.bin to get room for the new tiles first. To do this, you have to create a batch file (.bat) with following code in the folder artunc:

fsutil file createnew padding.bin 16384
 :: Create a file called "padding.bin" with 16KB

ren sonic.bin sonic_old.bin
 :: Rename the original "sonic.bin" to "sonic_old.bin"

copy /b sonic_old.bin+padding.bin sonic.bin
 :: Copy the 2 files into a new "sonic.bin"

Run it and you will find your sonic.bin 16KB bigger. If anything went wrong, use the backup file named as sonic_old.bin


Now, in your tile editor, open the padded artunc/sonic.bin and Sonic 2's art/uncompressed/Sonic's art.bin. These are the tiles you're after:

copying sonic's Spin Dash tiles

Copy them from the Sonic 2 tiles to the end of the Sonic 1 tiles. Do note that it shouldn't matter if you use the default palette of your tile editor, as long as the one you're using for Sonic 2's tiles matches the one you're using for Sonic 1's tiles.

Save the Sonic 1 file artunc/sonic.bin and close your tile editor. You may rebuild the ROM, but there shouldn't be any noticeable changes, since the game isn't coded to actually do anything with the tiles we put there yet.

Making PLCs for the tiles

Now, you'll add the pattern load cues to tell the game to load the tiles you just added. Open _inc/Sonic dynamic pattern load cues.asm in your text editor. At the bottom of the big table of offsets, and just before the label SonPLC_Blank:, add the following lines:

<asm> dc.w SonPLC_SpinDash1-SonicDynPLC ;58 dc.w SonPLC_SpinDash2-SonicDynPLC ;59 dc.w SonPLC_SpinDash3-SonicDynPLC ;5A dc.w SonPLC_SpinDash4-SonicDynPLC ;5B dc.w SonPLC_SpinDash5-SonicDynPLC ;5C dc.w SonPLC_SpinDash6-SonicDynPLC ;5D </syntaxhighlight>

Note that the hex numbers commented to the side are indeces. 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. These will be important later because they are the frame numbers that will be used in your animation.

Now, at the very bottom of the file, but just before the even, insert the following lines:

<asm> SonPLC_SpinDash1: dc.b 1, $F5, $10 ; 01 F 510 SonPLC_SpinDash2: dc.b 1, $F5, $20 ; 01 F 520 SonPLC_SpinDash3: dc.b 1, $F5, $30 ; 01 F 530 SonPLC_SpinDash4: dc.b 1, $F5, $40 ; 01 F 540 SonPLC_SpinDash5: dc.b 1, $F5, $50 ; 01 F 550 SonPLC_SpinDash6: dc.b 1, $F5, $60 ; 01 F 560 </syntaxhighlight>

These lines will be built in the ROM as the hex strings found commented beside each one. The format for mappings in Sonic 1 is NN(STTT), where NN is how many times (STTT) is repeated after it. S is one less than the number of tiles to be loaded in order, and TTT is the tile index from which the game should start loading. In each of your PLCs, NN is 01, meaning there is only one instance of (STTT) after it. Also, in each of your PLCs, S is $F, which means that $10 tiles will be loaded for each. The remaining TTT is the number of the first tile in the pattern (the first row of Spin Dash tiles you copied starts at tile number $510, the second at $520, etc.).

For more information about pattern load cues in Sonic 1, visit Sonicology.

Mappings

Now you will make the mappings for the Spin Dash, which define the shape and size of the area to be used in placing the tiles. Open the file _maps/Sonic.asm in your text editor. Again, you're going to add some entries in the big table at the beginning of the file, much like you did in editing the PLCs. Just above the line byte_21292: dc.b 0, add the following lines:

<asm> dc.w byte_spdh1-Map_Sonic, byte_spdh2-Map_Sonic dc.w byte_spdh3-Map_Sonic, byte_spdh4-Map_Sonic dc.w byte_spdh5-Map_Sonic, byte_spdh6-Map_Sonic </syntaxhighlight>

Then, at the bottom of the file, just before the even, add:

<asm> byte_spdh1: dc.b 1 ; Spin Dash 1 dc.b $F8, $F, 0, 0, $F4 byte_spdh2: dc.b 1 ; Spin Dash 2 dc.b $F8, $F, 0, 0, $F4 byte_spdh3: dc.b 1 ; Spin Dash 3 dc.b $F8, $F, 0, 0, $F4 byte_spdh4: dc.b 1 ; Spin Dash 4 dc.b $F8, $F, 0, 0, $F4 byte_spdh5: dc.b 1 ; Spin Dash 5 dc.b $F8, $F, 0, 0, $F4 byte_spdh6: dc.b 1 ; Spin Dash 6 dc.b $F8, $F, 0, 0, $F4 </syntaxhighlight>

You might notice that these are all identical to each other, and are also very similar to the mappings for the jumping/rolling frames. The format of these mappings is structurally similar to the format of the PLCs: the first byte defines how many mapping pieces there are following it. The format is NN(YYSSFFTTXX), where NN is the number of (YYSSFFTTXX) following, YY is the Y position of the piece, SS is the size and shape (in this case, $F is a 4x4 tile square), FF is the control for certain flip and palette changes (0 in this case means not to touch it), TT is the first tile in VRAM to start reading (in this case 0 is the first tile), and XX is the X position of the piece. The Y position of these mappings, $F8, is slightly greater than that of the jumping/rolling animation, $F0. This is so Sonic doesn't appear to be hovering slightly above the ground while charging the Spin Dash.

These mappings use 4x4 pieces to hold 16 tiles. You might have noticed while you were copying the Spin Dash tiles before that each frame of the animation had a row of 16 tiles, one after the other. With these mappings, those tiles will be loaded in order in four columns of four, starting with the top left and working toward the bottom right. Try arranging the tiles yourself in your tile editor in a scratch file if you want to understand how they are arranged. Just don't save over any files we're using!

For more information on mappings in Sonic 1, visit Sonicology.

Animation sequence

You're almost done adding in the Spin Dash graphics! Now you will add the animation sequence containing the list of frames to be used. Open the file _anim/Sonic.asm in your text editor. Yet again, we will be adding a line to the bottom of the table in the beginning, just before the line 'SonAni_Walk: dc.b $FF, 8, 9, $A, $B, 6, 7, $FF.

<asm> dc.w SonAni_SpinDash-SonicAniData ;1F </syntaxhighlight>

The ;1F comment after it is again an index in the table. If you were to enumerate all of the lines in this table, starting at zero, this new line would be the $1Fth line. Notice that at index 2 is the rolling animation that we are currently using, and at index 9 is the warping one that it used before we fixed it!

Now, you will again add data just before the even at the end of the file:

<asm> SonAni_SpinDash: dc.b 0, $58, $59, $58, $5A, $58, $5B, $58, $5C, $58, $5D, $FF </syntaxhighlight>

The first byte in this animation script tells the game to use the fastest speed in this animation. Each subsequent byte until the $FF tells the game which animation frame to use. Notice that these numbers ($58, $59, etc...) are the indeces of your Spin Dash PLCs. The $FF 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.

Changing the animation again

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 sonic1.asm in your text editor (if you haven't already) and find the Sonic_SpinDash: code you ported before. Change the line you changed before,

<asm> move.b #2,$1C(a0) ; changed from #9 </syntaxhighlight>

to use your new animation found at $1F:

<asm> move.b #$1F,$1C(a0) ; changed from #9 </syntaxhighlight>

Then, find the line you commented out before,

<asm> ; move.w #$900,$1C(a0) </syntaxhighlight>

uncomment it, and change the value to $1F00.

<asm> move.w #$1F00,$1C(a0) ; changed from #$900 </syntaxhighlight>

Finally, save your code, build your ROM, and try it out.

Spin Dashing with Spin Dash animation

Success! However, there's a new bug that occurs now that the game is using this new animation: while charging a Spin Dash, let an enemy touch you. Sonic gets injured, when instead the enemy should be destroyed.

Another fix

This is a relatively easy bug to fix. Find the routine Touch_Enemy:. Just above the line cmpi.b #2,$1C(a0) ; is Sonic rolling?, add the following two lines:

<asm> cmpi.b #$1F,$1C(a0) ; is Sonic Spin Dashing? beq.w loc_1AF40 ; if yes, branch </syntaxhighlight>

You should end up with something like this:

<asm> Touch_Enemy: ; XREF: Touch_ChkValue tst.b ($FFFFFE2D).w ; is Sonic invincible? bne.s loc_1AF40 ; if yes, branch cmpi.b #$1F,$1C(a0) ; is Sonic Spin Dashing? beq.w loc_1AF40 ; if yes, branch cmpi.b #2,$1C(a0) ; is Sonic rolling? bne.w Touch_ChkHurt ; if not, branch </syntaxhighlight>

This code adds a check to see if the animation is #$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 is #2 present, which concealed this bug before, since we were using that animation.

Save, build, and test.

killing an enemy while Spin Dashing

Pop! Congratulations, you've successfully ported the Spin Dash from Sonic 2 into Sonic 1, complete with animations. The only thing remaining to add is the Spin Dash smoke object, and fix a few more bugs.


Appendix

The Sonic_SpinDash routine in its entirety, with comments:

<asm>

---------------------------------------------------------------------------
Subroutine to make Sonic perform a spindash
---------------------------------------------------------------------------
||||||||||||||| S U B R O U T I N E |||||||||||||||||||||||||||||||||||||||


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 #$BE,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 is used for ; the smoke/dust object loc2_1AC84: bsr.w Sonic_LevelBound bsr.w Sonic_AnglePos

locret2_1AC8C: rts

---------------------------------------------------------------------------

loc2_1AC8E: 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 (smoke) 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 #$BE,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

End of subroutine Sonic_SpinDash

</syntaxhighlight>

Note: while not necessary, the loc_, locret_, etc. labels from the Sonic 2 disassembly were replaced with loc2_, locret2_, etc. to avoid potential conflict.


GitHub disassembly

Previous preparation

This shouldn't be completly necessary, but since we're using GitHub disassembly, we're going to do it in GitHub's way. Open Variables.asm and scroll down to the end. There you will define a new RAM equate by adding this line:

<asm>f_spindash = $39</syntaxhighlight>

You can skip this, but you will have to replace all f_spindash in the code for $39, and that's not what we want.

Directly copying the object code

Open sonic1.asm from the Sonic 1 disassembly in your text editor, and search for the phrase Object 01 (line 6734 in GitHub disassembly). This should bring you to the Sonic object's code, which is what you're interested in editing right now. Now, open s2.asm from the Sonic 2 disassembly in your text editor, and search for Object_Sonic (line 35720 in Aurochs' disassembly). This will bring you again to Sonic's object code. Having been based on the same code from Sonic 1, it shows many similarities that you should be able to spot. Thanks to the similar code, our job in porting the Spin Dash should be pretty easy!

Find or scroll to the location Sonic_MdNormal in Sonic 1's source, and loc_1A2B8 in Sonic 2's source. Below this label in each is a series of branches and jumps to subroutines that handle Sonic's actions in different situations. If you look carefully, you'll notice that there are nine of these branches in Sonic 2, but only eight in Sonic 1. That's because Sonic 2 has another branch to the code that handles the Spin Dash. You're going to add Spin Dash to Sonic 1, so the first thing to do is add a call to the Spin Dash function that you're going to copy. Don't worry about the label names from Sonic 2, since you're going to name the subroutine Sonic_SpinDash when we copy it over.

In Sonic 1's code, make a copy of the line bsr.w Sonic_Jump and change it to branch to Sonic_SpinDash, as shown:

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

Modes for controlling Sonic
---------------------------------------------------------------------------

Sonic_MdNormal: ; XREF: Sonic_Modes bsr.w Sonic_SpinDash ; add this line! bsr.w Sonic_Jump bsr.w Sonic_SlopeResist bsr.w Sonic_Move bsr.w Sonic_Roll bsr.w Sonic_LevelBound jsr SpeedToPos bsr.w Sonic_AnglePos bsr.w Sonic_SlopeRepel rts

;=========================================================================== </syntaxhighlight>

Now, to actually copy the Spin Dash subroutine. In Sonic 2, find the label loc_1AC3E. This is the subroutine that we are going to copy, so select from that line all the way down to where you see ; End of subroutine loc_1AC3E, and copy the code. In Sonic 1, create a new file within _incObj folder named Sonic Spindash.asm, open it and paste the code inside of it.

There are a few changes that we need to make so this code will cooperate with the rest of the Sonic 1 code, since some of the labels in each disassembly differ from each other (the same functions have different names). The following replacements should be made (use your text editor's find and replace function, if applicable):

loc_1AC3E with Sonic_SpinDash loc_1A974 with Sonic_LevelBound loc_1E234 with Sonic_AnglePos play_SFX with PlaySound_Special word_1AD0C with Dash_Speeds $39 with f_spindash (NOT #$39) $1C with obAnim (NOT #$1C) $14 with obInertia (NOT #$14) Also, if you open _anim\Sonic.asm and scroll to the end, you will see animation's IDs. In Sonic 2's code, the numbers at the right will be used and so in Sonic 1's HiveBrain's disassembly; but in GitHub's one, the names at left are used, so for completion's sake, check every time that a number is moved into obAnim to replace it with its correct name /example: move.b #2,obAnim(a0) --> move.b #id_roll,obAnim(a0)/.

Remove all this code since it is a leftover artifact of super sonic:

<asm> tst.b ($FFFFFE19).w beq.s loc_1ACD0 move.w word_1AD1E(pc,d0.w),$14(a0)</syntaxhighlight>

<asm>loc_1ACD0: all of it</syntaxhighlight>

remove this too since it is super sonic's dash speeds:

<asm>word_1AD1E: 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 </syntaxhighlight>

Done! Save the file and you should have something like this: <asm>;---------------------------------------------------------------------------

Subroutine to make Sonic perform a spindash
---------------------------------------------------------------------------
||||||||||||||| S U B R O U T I N E |||||||||||||||||||||||||||||||||||||||

Sonic_SpinDash: tst.b f_spindash(a0) bne.s loc_1AC8E cmpi.b #id_duck,ob_anim(a0) bne.s locret_1AC8C move.b (v_jpadpress2).w,d0 andi.b #$70,d0 beq.w locret_1AC8C move.b #9,obAnim(a0) move.w #$E0,d0 jsr (PlaySound_Special).l addq.l #4,sp move.b #1,f_spindash(a0) move.w #0,$3A(a0) cmpi.b #$C,$28(a0) bcs.s loc_1AC84 move.b #2,($FFFFD11C).w

loc_1AC84: bsr.w Sonic_LevelBound bsr.w Sonic_AnglePos

locret_1AC8C: rts

---------------------------------------------------------------------------

loc_1AC8E: move.b (v_jpadhold2).w,d0 btst #1,d0 bne.w loc_1AD30 move.b #$E,$16(a0) move.b #7,$17(a0) move.b #2,obAnim(a0) addq.w #5,$C(a0) move.b #0,f_spindash(a0) moveq #0,d0 move.b $3A(a0),d0 add.w d0,d0 move.w Dash_Speeds(pc,d0.w),$14(a0) move.w obInertia(a0),d0 subi.w #$800,d0 add.w d0,d0 andi.w #$1F00,d0 neg.w d0 addi.w #$2000,d0 move.w d0,($FFFFEED0).w btst #0,$22(a0) beq.s loc_1ACF4 neg.w $14(a0)

loc_1ACF4: bset #2,$22(a0) move.b #0,($FFFFD11C).w move.w #$BC,d0 jsr (PlaySound_Special).l bra.s loc_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

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

loc_1AD30: ; If still charging the dash... tst.w $3A(a0) beq.s loc_1AD48 move.w $3A(a0),d0 lsr.w #5,d0 sub.w d0,$3A(a0) bcc.s loc_1AD48 move.w #0,$3A(a0)

loc_1AD48: move.b (v_jpadpress2).w,d0 andi.b #$70,d0 ; 'p' beq.w loc_1AD78 move.w #$900,$1C(a0) move.w #$E0,d0 ; 'à' jsr (PlaySound_Special).l addi.w #$200,$3A(a0) cmpi.w #$800,$3A(a0) bcs.s loc_1AD78 move.w #$800,$3A(a0)

loc_1AD78: addq.l #4,sp cmpi.w #$60,($FFFFEED8).w beq.s loc_1AD8C bcc.s loc_1AD88 addq.w #4,($FFFFEED8).w

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

loc_1AD8C: bsr.w Sonic_LevelBound bsr.w Sonic_AnglePos rts

End of subroutine Sonic_SpinDash

</syntaxhighlight>

Before building any ROM, you have to tell the program to actually load the new file. So go back to sonic.asm and go a bit lower from where you were, you should see this:

include "_incObj\Sonic Move.asm" include "_incObj\Sonic RollSpeed.asm" include "_incObj\Sonic JumpDirection.asm"

You just have to add this line either before or after that;

include "_incObj\Sonic Spindash.asm"

Save the file again. Now, let's test it! Build your ROM and open it in your emulator.

Spin Dashing with a strange animation

Uh-oh, it seems like it's a bit rough around the edges right now. First of all, Sonic's Spin Dashing animation isn't present in Sonic 1, of course, and right now it's using the animation from the spot that the Spin Dash's animation data replaced. Also, the music and sounds seem to stop when you start charging a Spin Dash. That's because the sound ID that is the Spin Dash sound in Sonic 2 has a different purpose in Sonic 1. The physics of the Spin Dash are clearly in place, however. This is a good start!

Changing the animations and sounds

Now, you're going to make a few simple changes to the Spin Dash subroutine so it uses suitable animations and sounds that are already present in Sonic 1. First, find any instances of the line move.w #$E0,d0 followed by jsr (PlaySound_Special).l. This code loads the sound ID #$E0 into the data register d0 and jumps to the subroutine that is responsible for playing that sound. #$E0 is not a suitable sound for a Spin Dash in Sonic 1, as mentioned earlier--it silences the music. Replace the #$E0 with #$BE in each occurance. #$BE is the sound ID of a normal spin in Sonic 1. You can check it in the sound test for yourself!

Now, go up to the line containing the code move.b #9,obAnim(a0), and replace the #9 with #id_roll. This code copies the animation number to be used when Sonic starts charging his Spin Dash. #9 is the ID of the animation used for Spin Dash in Sonic 2; however, in Sonic 1, it's the ID of an unused animation presumably for warping. Animation #2 (rolling animation's ID) is the animation used for jumping and rolling in Sonic 1. You should have something like this:

<asm> move.b (v_jpadpress2).w,d0 andi.b #$70,d0 beq.w locret_1AC8C move.b #id_roll,obAnim(a0) ; changed from #9 move.w #$BE,d0 ; changed from #$E0 jsr (PlaySound_Special).l </syntaxhighlight>

Now, find move.w #$900,obAnim(a0) lower in the code. This puts the animation #9 and a flag to reset it to the first frame into Sonic's memory. For now, let's just comment out this line by placing a ; in front of the code, like so:

<asm>loc_1AD48: move.b (v_jpadpress2).w,d0 andi.b #$70,d0 ; 'p' beq.w loc_1AD78

move.w #$900,$1C(a0)

move.w #$BE,d0 ; changed from #$E0 jsr (PlaySound_Special).l </syntaxhighlight>

Save and build your ROM! Let's see how it does...

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! However, there are a few bugs present.

First of all, collect a few rings, then start charging a Spin Dash somewhere that an enemy can shoot you or injure you somehow. 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. Now, this might be awesome in itself and certainly should kill whatever badnik hurt you in the first place, but it isn't what should happen.

Second, move somewhere where the camera has room to move downward. If you hold the down button to duck, then start charging the Spin Dash, the camera stays low until you are no longer rolling.

Spin Dashing with low camera

Another problem that results from this is that if you Spin Dash before an area where your vertical position radically changes (for instance, the double-S-tube in GHZ1), the camera takes too long to catch up to you.

camera lagging behind

Fixing bugs

First of all, let's fix the camera issue. Go to the end of the Spin Dash subroutine, and add this line: <asm> move.w #$60,(v_lookshift).w</syntaxhighlight>

just above the rts. This will reset the vertical change in the camera's position. You should have something like this:

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

;End of subroutine Sonic_SpinDash </syntaxhighlight>

Save, build the ROM, and test it. You should find that the camera does reset itself when you start to Spin Dash, but if you take the double-S-tube in GHZ1, you still barely outrun the screen vertically and get killed. To fix this, open the file _incObj\Sonic LevelBound.asm and find the code at the label @bottom. 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:

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

You should end up with something like this:

<asm>@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 bcs.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 ;=========================================================================== </syntaxhighlight>

These fixes can also be found
Sonic Retro
here
.

Now, you'll fix the problem where Sonic is still in his Spin Dashing state after he gets injured. Open _incObj\sub ReactToItem.asm 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,(a0) directly below it. You should end up with:

<asm>@isleft: move.b #0,f_spindash(a0) ; clear Spin Dash flag move.w #0,obInertia(a0) move.b #id_Hurt,obAnim(a0) move.w #$120,$30(a0) </syntaxhighlight>

Save, build, and test. If all is well, those bugs should be gone! Now you 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 tiles

Now, you'll need to add the Spin Dash tiles from Sonic 2's tiles to Sonic 1's; However, you'll need to pad the file artunc/sonic.bin to get room for the new tiles first. To do this, you have to create a batch file (.bat) with following code in the folder artunc:

fsutil file createnew padding.bin 16384

:: Create a file called "padding.bin" with 16KB

ren sonic.bin sonic_old.bin

:: Rename the original "sonic.bin" to "sonic_old.bin"

copy /b sonic_old.bin+padding.bin sonic.bin

:: Copy the 2 files into a new "sonic.bin"

Run it and you will find your sonic.bin 16KB bigger. If anything went wrong, use the backup file named as sonic_old.bin.


Now, in your tile editor, open the padded artunc/sonic.bin and Sonic 2's art/uncompressed/Sonic's art.bin. These are the tiles you're after:

copying sonic's Spin Dash tiles

Copy them from the Sonic 2 tiles to the end of the Sonic 1 tiles. Do note that it shouldn't matter if you use the default palette of your tile editor, as long as the one you're using for Sonic 2's tiles matches the one you're using for Sonic 1's tiles.

Save the Sonic 1 file artunc/sonic.bin and close your tile editor. You may rebuild the ROM, but there shouldn't be any noticeable changes, since the game isn't coded to actually do anything with the tiles we put there yet.

Making PLCs for the tiles

Now, you'll add the pattern load cues to tell the game to load the tiles you just added. Open _maps/Sonic -Dynamic Gfx Script.asm in your text editor. At the bottom of the big table of offsets, and just before the label SonPLC_Null, add the following lines:

<asm> dc.w SonPLC_SpinDash1-SonicDynPLC ;58 dc.w SonPLC_SpinDash2-SonicDynPLC ;59 dc.w SonPLC_SpinDash3-SonicDynPLC ;5A dc.w SonPLC_SpinDash4-SonicDynPLC ;5B dc.w SonPLC_SpinDash5-SonicDynPLC ;5C dc.w SonPLC_SpinDash6-SonicDynPLC ;5D </syntaxhighlight>

Note that the hex numbers commented to the side are indeces. 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. These will be important later because they are the frame numbers that will be used in your animation.

Now, at the very bottom of the file, but just before the even, insert the following lines:

<asm>SonPLC_SpinDash1: dc.b 1, $F5, $10 ; 01 F 510 SonPLC_SpinDash2: dc.b 1, $F5, $20 ; 01 F 520 SonPLC_SpinDash3: dc.b 1, $F5, $30 ; 01 F 530 SonPLC_SpinDash4: dc.b 1, $F5, $40 ; 01 F 540 SonPLC_SpinDash5: dc.b 1, $F5, $50 ; 01 F 550 SonPLC_SpinDash6: dc.b 1, $F5, $60 ; 01 F 560 </syntaxhighlight>

These lines will be built in the ROM as the hex strings found commented beside each one. The format for mappings in Sonic 1 is NN(STTT), where NN is how many times (STTT) is repeated after it. S is one less than the number of tiles to be loaded in order, and TTT is the tile index from which the game should start loading. In each of your PLCs, NN is 01, meaning there is only one instance of (STTT) after it. Also, in each of your PLCs, S is $F, which means that $10 tiles will be loaded for each. The remaining TTT is the number of the first tile in the pattern (the first row of Spin Dash tiles you copied starts at tile number $510, the second at $520, etc.).

For more information about pattern load cues in Sonic 1, visit Sonicology.

Mappings

Now you will make the mappings for the Spin Dash, which define the shape and size of the area to be used in placing the tiles. Open the file _maps/Sonic.asm in your text editor. Again, you're going to add some entries in the big table at the beginning of the file, much like you did in editing the PLCs. Just above the line MS_Null: dc.b 0, add the following lines:

<asm>ptr_MS_Spindash1: dc.w MS_Spindash1-Map_Sonic ptr_MS_Spindash2: dc.w MS_Spindash2-Map_Sonic ptr_MS_Spindash3: dc.w MS_Spindash3-Map_Sonic ptr_MS_Spindash4: dc.w MS_Spindash4-Map_Sonic ptr_MS_Spindash5: dc.w MS_Spindash5-Map_Sonic ptr_MS_Spindash6: dc.w MS_Spindash6-Map_Sonic</syntaxhighlight>

Then, at the end of the second table, just before the even, add:

<asm>MS_Spindash1: dc.b 1 ; Spindash 1 dc.b $F8, $F, 0, 0, $F4 MS_Spindash2: dc.b 1 ; Spindash 2 dc.b $F8, $F, 0, 0, $F4 MS_Spindash3: dc.b 1 ; Spindash 3 dc.b $F8, $F, 0, 0, $F4 MS_Spindash4: dc.b 1 ; Spindash 4 dc.b $F8, $F, 0, 0, $F4 MS_Spindash5: dc.b 1 ; Spindash 5 dc.b $F8, $F, 0, 0, $F4 MS_Spindash6: dc.b 1 ; Spindash 6 dc.b $F8, $F, 0, 0, $F4 </syntaxhighlight>

You might notice that these are all identical to each other, and are also very similar to the mappings for the jumping/rolling frames. The format of these mappings is structurally similar to the format of the PLCs: the first byte defines how many mapping pieces there are following it. The format is NN(YYSSFFTTXX), where NN is the number of (YYSSFFTTXX) following, YY is the Y position of the piece, SS is the size and shape (in this case, $F is a 4x4 tile square), FF is the control for certain flip and palette changes (0 in this case means not to touch it), TT is the first tile in VRAM to start reading (in this case 0 is the first tile), and XX is the X position of the piece. The Y position of these mappings, $F8, is slightly greater than that of the jumping/rolling animation, $F0. This is so Sonic doesn't appear to be hovering slightly above the ground while charging the Spin Dash.

These mappings use 4x4 pieces to hold 16 tiles. You might have noticed while you were copying the Spin Dash tiles before that each frame of the animation had a row of 16 tiles, one after the other. With these mappings, those tiles will be loaded in order in four columns of four, starting with the top left and working toward the bottom right. Try arranging the tiles yourself in your tile editor in a scratch file if you want to understand how they are arranged. Just don't save over any files we're using!

Then scroll down to the end of the file add theese lines:

<asm>fr_Spindash1: equ (ptr_MS_Spindash1-Map_Sonic)/2 ; $58 fr_Spindash2: equ (ptr_MS_Spindash2-Map_Sonic)/2 ; $59 fr_Spindash3: equ (ptr_MS_Spindash3-Map_Sonic)/2 ; $5A fr_Spindash4: equ (ptr_MS_Spindash4-Map_Sonic)/2 ; $5B fr_Spindash5: equ (ptr_MS_Spindash5-Map_Sonic)/2 ; $5C fr_Spindash6: equ (ptr_MS_Spindash6-Map_Sonic)/2 ; $5D</syntaxhighlight>

For more information on mappings in Sonic 1, visit Sonicology.

Animation sequence

You're almost done adding in the Spin Dash graphics! Now you will add the animation sequence containing the list of frames to be used. Open the file _anim/Sonic.asm in your text editor. Yet again, we will be adding a line to the bottom of the table in the beginning, just before the line

<asm>SonAni_Walk: dc.b $FF, fr_walk13, fr_walk14, fr_walk15, fr_walk16, fr_walk11, fr_walk12, afEnd</syntaxhighlight>

you will add

<asm>ptr_Spindash: dc.w SonAni_SpinDash-Ani_Sonic ;1F</syntaxhighlight>

The ;1F comment after it is again an index in the table. If you were to enumerate all of the lines in this table, starting at zero, this new line would be the $1Fth line. Notice that at index 2 is the rolling animation that we are currently using, and at index 9 is the warping one that it used before we fixed it!

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

<asm>SonAni_SpinDash: dc.b 0, fr_Spindash1, fr_spindash2, fr_spindash1, fr_spindash3, fr_spindash1, fr_spindash4, fr_spindash1, fr_spindash5, fr_spindash1, fr_spindash6, afEnd even</syntaxhighlight>

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. Notice that you could have use numbers ($58, $59, etc...) as they are the indeces of your Spin Dash PLCs. 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.

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

<asm>id_Spindash: equ (ptr_Spindash-Ani_Sonic)/2 ; $1F</syntaxhighlight>

Changing the animation again

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 change the line you changed before,

<asm> move.b #id_roll,obAnim(a0) ; changed from #9</syntaxhighlight>

to use your new animation found at $1F:

<asm> move.b #id_Spindash,obAnim(a0) ; changed from #9</syntaxhighlight>

Then, find the line you commented out before,

<asm>; move.w #$900,obAnim(a0)</syntaxhighlight>

uncomment it, and change the value to $1F00.

<asm> move.w #$1F00,obAnim(a0) ; changed from #$900</syntaxhighlight>

Finally, save your code, build your ROM, and try it out.

Spin Dashing with Spin Dash animation

Success! However, there's a new bug that occurs now that the game is using this new animation: while charging a Spin Dash, let an enemy touch you. Sonic gets injured, when instead the enemy should be destroyed.

Another fix

This is a relatively easy bug to fix. Go back to sub ReactToItem.asm and search for the routine React_Enemy. Just above the line

<asm> cmpi.b #id_Roll,obAnim(a0) ; is Sonic rolling/jumping?</syntaxhighlight>

add the following two lines:

<asm> cmpi.b #id_Spindash,obAnim(a0) ; is Sonic Spin Dashing? beq.w @donthurtsonic ; if yes, branch </syntaxhighlight>

You should end up with something like this:

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

This code adds a check to see if the animation is #$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 is #2 present, which concealed this bug before, since we were using that animation.

Save, build, and test.

killing an enemy while Spin Dashing

Pop! Congratulations, you've successfully ported the Spin Dash from Sonic 2 into Sonic 1, complete with animations. The only thing remaining to add is the Spin Dash smoke object, and fix a few more bugs.

Appendix

The Sonic_SpinDash routine in its entirety, with comments:

<asm>

---------------------------------------------------------------------------
Subroutine to make Sonic perform a spindash
---------------------------------------------------------------------------
||||||||||||||| S U B R O U T I N E |||||||||||||||||||||||||||||||||||||||


Sonic_SpinDash: tst.b f_spindash(a0) ; already Spin Dashing? bne.s loc2_1AC8E ; if set, branch cmpi.b #id_duck,obAnim(a0) ; is anim duck bne.s locret2_1AC8C ; if not, return move.b (v_jpadpress2).w,d0 ; read controller andi.b #$70,d0 ; pressing A/B/C ? beq.w locret2_1AC8C ; if not, return move.b #id_spindash,obAnim(a0) ; set Spin Dash anim (9 in s2) move.w #$BE,d0 ; spin sound ($E0 in s2) jsr (PlaySound_Special).l ; play spin sound addq.l #4,sp ; increment stack ptr move.b #1,f_spindash(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 is used for ; the smoke/dust object loc2_1AC84: bsr.w Sonic_LevelBound bsr.w Sonic_AnglePos

locret2_1AC8C: rts

---------------------------------------------------------------------------

loc2_1AC8E: move.b (v_jpadhold2).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 #id_roll,obAnim(a0) ; set animation to roll addq.w #5,$C(a0) ; $C(a0) is Y coordinate move.b #0,f_spindash(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),obInertia(a0) ; get normal speed move.w obInertia(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 obInertia(a0) ; negate inertia

loc2_1ACF4: bset #2,$22(a0) ; set unused (in s1) flag move.b #0,($FFFFD11C).w ; clear $D11C (smoke) 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 (v_jpadpress2).w,d0 ; read controller andi.b #$70,d0 ; pressing A/B/C? beq.w loc2_1AD78 ; if not, branch move.w #$1F00,obAnim(a0) ; reset spdsh animation move.w #$BE,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,(v_lookshift).w ; reset looking up/down rts

End of subroutine Sonic_SpinDash

</syntaxhighlight>

Note: while not necessary, the loc_, locret_, etc. labels from the Sonic 2 disassembly were replaced with loc2_, locret2_, etc. to avoid potential conflict.

Pu7o's continuation

Pu7o has written a wonderful guide that picks up from this point.


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)