don't click here

Some changes/fixes for Sonic 1

Discussion in 'Engineering & Reverse Engineering' started by RetroKoH, Sep 4, 2012.

  1. Unlimited Trees

    Unlimited Trees

    we Do a Little Mischief, Mischief... Member
    95
    73
    18
    When
    UNITRES, Project Time, etc.
    i wish i knew about this when i still made rom hacks....... it was so so annoying how much ihad to worry about getting suddenly stopped on slopes, i never knew why it happened and it made designing levels so so annoying... anyways this fix is cool as hell LOL !
     
  2. Devon

    Devon

    I'm a loser, baby, so why don't you kill me? Tech Member
    1,248
    1,419
    93
    your mom
    There is an oversight with invisible solid block collision. At the top of its main routine, it checks if it's offscreen:
    Code (Text):
    1. Invis_Solid:    ; Routine 2
    2.         bsr.w    ChkObjectVisible
    3.         bne.s    .chkdel
    There is a problem though. If we go to the routine:
    Code (Text):
    1. ; ---------------------------------------------------------------------------
    2. ; Subroutine to    check if an object is off screen
    3.  
    4. ; output:
    5. ;    d0 = flag set if object is off screen
    6. ; ---------------------------------------------------------------------------
    7.  
    8. ; ||||||||||||||| S U B    R O U T    I N E |||||||||||||||||||||||||||||||||||||||
    9.  
    10.  
    11. ChkObjectVisible:
    12.         move.w    obX(a0),d0    ; get object x-position
    13.         sub.w    (v_screenposx).w,d0 ; subtract screen x-position
    14.         bmi.s    .offscreen
    15.         cmpi.w    #320,d0        ; is object on the screen?
    16.         bge.s    .offscreen    ; if not, branch
    17.  
    18.         move.w    obY(a0),d1    ; get object y-position
    19.         sub.w    (v_screenposy).w,d1 ; subtract screen y-position
    20.         bmi.s    .offscreen
    21.         cmpi.w    #224,d1        ; is object on the screen?
    22.         bge.s    .offscreen    ; if not, branch
    23.  
    24.         moveq    #0,d0        ; set flag to 0
    25.         rts  
    26.  
    27. .offscreen:
    28.         moveq    #1,d0        ; set flag to 1
    29.         rts  
    30. ; End of function ChkObjectVisible

    It doesn't take into account the size of the hitbox, just the position. Now, it's not very common at all to run into this issue, but you can easily see the effect if you duck the camera down in this area:

    [​IMG]

    To fix this, add this routine that accounts for the hitbox:
    Code (Text):
    1. ; ---------------------------------------------------------------------------
    2. ; Subroutine to    check if an object is off screen
    3. ; Takes both width and height into account
    4.  
    5. ; output:
    6. ;    d0 = flag set if object is off screen
    7. ; ---------------------------------------------------------------------------
    8.  
    9. ; ||||||||||||||| S U B    R O U T    I N E |||||||||||||||||||||||||||||||||||||||
    10.  
    11.  
    12. ChkSizedObjVisible:
    13.         moveq    #0,d1                ; Get object's width
    14.         move.b    obActWid(a0),d1
    15.         move.w    obX(a0),d0            ; Get object's X position
    16.         sub.w    (v_screenposx).w,d0        ; Get object's X position on screen
    17.         add.w    d1,d0                ; Is the right side of the object on screen?
    18.         bmi.s    .offscreen2            ; If not, branch
    19.         add.w    d1,d1                ; Is the left side of the object on screen?
    20.         sub.w    d1,d0
    21.         cmpi.w    #320,d0
    22.         bge.s    .offscreen2            ; If not, branch
    23.  
    24.         moveq    #0,d1                ; Get object's height
    25.         move.b    obHeight(a0),d1
    26.         move.w    obY(a0),d0            ; Get object's Y position
    27.         sub.w    (v_screenposy).w,d0        ; Get object's Y position on screen
    28.         add.w    d1,d0                ; Is the bottom side of the object on screen?
    29.         bmi.s    .offscreen2            ; If not, branch
    30.         add.w    d1,d1                ; Is the top side of the object on screen?
    31.         sub.w    d1,d0
    32.         cmpi.w    #224,d1
    33.         bge.s    .offscreen2            ; If not, branch
    34.  
    35.         moveq    #0,d0                ; Visible
    36.         rts
    37.  
    38. .offscreen2:
    39.         moveq    #1,d0                ; Not visible
    40.         rts    

    And change the call to ChkObjectVisible to ChkSizedObjVisible instead, and tada!

    [​IMG]
     
    • Like Like x 5
    • Informative Informative x 1
    • List
  3. Devon

    Devon

    I'm a loser, baby, so why don't you kill me? Tech Member
    1,248
    1,419
    93
    your mom
    Small bug that's probably really insignificant in the grand scheme of things, but I thought it would be worth documenting.

    The plasma balls in Final Zone's movement code when it first gets spawned has a small bug in it. If we go to loc_1A9C0 in "_incObj/86 FZ Plasma Ball Launcher.asm", we can see this:
    Code (Text):
    1.         move.w    obX(a0),d0
    2.         sub.w    $30(a0),d0
    3.         bcc.s    loc_1A9E6
    4.         clr.w    obVelX(a0)
    5.         add.w    d0,obX(a0)    ; <-- BUG

    The first 2 lines get the distance between the plasma ball's current X position and its target X position. If it has moved past that target X position, that distance value should be negative. The branch after checks if the result underflowed (has gone negative), and if not, it skips over the rest. If it HAS, then it stops its movement and attempts to align it to the target X position by nudging it by the amount that it has moved past.

    The bug in question is that it uses an addition to do that alignment. When you add a negative number, it's a subtraction. In this case, it's basically moving the plasma ball MORE to the left, instead of pushing it towards the right to its actual target X position.

    To fix that, just change the add to a sub. Now, if you're okay with the balls spreading out a bit less like this, you can stop here.

    [​IMG]

    In fact, this is the same behavior as the 2013 Taxman remake, because it doesn't have that bug in it.

    [​IMG]

    But, if you want them to actually spread out further, then go to Obj86_Loop and change
    Code (Text):
    1.         muls.w    #-$4F,d1

    to
    Code (Text):
    1.         muls.w    #-$59,d1

    and you'll get this again.

    [​IMG]

    If you want to have the plasma balls spread out further without adding this bug, then in "SBZ/FZEggman.txt", in the FZEGGMAN_SETUP_PLASMAATTACK case, change the subtraction of 0x4F0000 to 0x590000.
     
    Last edited: Aug 20, 2023
    • Like Like x 2
    • Informative Informative x 2
    • List
  4. Devon

    Devon

    I'm a loser, baby, so why don't you kill me? Tech Member
    1,248
    1,419
    93
    your mom
    The trap doors in Scrap Brain Zone have a small bug where if you are off screen, but not enough to despawn them, their sprite will wrap over and appear at the edge of the screen.

    For example, here's this trap door

    [​IMG]

    And if I move a bit to the left, but not have it despawned yet...

    [​IMG]

    The reason for this is that it has a pretty ridiculously high sprite width set on initialization.

    Code (Text):
    1.         move.b    #$80,obActWid(a0)

    The trap door sprite is $80 pixels wide, but the value set here is supposed to be half of that. The fix is simple: change the $80 to a $40.

    It should be noted that the sound for when it moves is only ever played when the object is on screen. Due to the larger width, the sound could play even with it was just slightly off screen, but with this change, it will no longer play if the object is not off screen at all. If you want to retain that behavior, then you'll have to manually check the camera's position.
     
    Last edited: Sep 12, 2023
  5. So... wonder why you can't die at all in debug mode? It turns out the culprit lies in the line of code pointed with the arrow:
    Code (Text):
    1.  
    2. HIVEBRAIN 2005:
    3. HurtSonic:
    4.         tst.b    ($FFFFFE2C).w    ; does Sonic have a shield?
    5.         bne.s    Hurt_Shield    ; if yes, branch
    6.         tst.w    ($FFFFFE20).w    ; does Sonic have any rings?
    7.         beq.w    Hurt_NoRings    ; if not, branch   <--- This line
    8.         jsr    SingleObjLoad
    9.         bne.s    Hurt_Shield
    10.         move.b    #$37,0(a1)    ; load bouncing    multi rings object
    11.         move.w    8(a0),8(a1)
    12.         move.w    $C(a0),$C(a1)
    Code (Text):
    1.  
    2. GITHUB:
    3. HurtSonic:
    4.         tst.b    (v_shield).w    ; does Sonic have a shield?
    5.         bne.s    @hasshield    ; if yes, branch
    6.         tst.w    (v_rings).w    ; does Sonic have any rings?
    7.         beq.w    @norings    ; if not, branch <--- This line
    The code for that particular routine just has a check for debug mode and branching to the shield injury routine:
    Code (Text):
    1.  
    2. HIVEBRAIN 2005:
    3. Hurt_NoRings:
    4.         tst.w    ($FFFFFFFA).w    ; is debug mode    cheat on?
    5.         bne.w    Hurt_Shield    ; if yes, branch
    6. ; End of function HurtSonic
    Code (Text):
    1.  
    2. GITHUB:
    3. @norings:
    4.         tst.w    (f_debugmode).w    ; is debug mode    cheat on?
    5.         bne.w    @hasshield    ; if yes, branch
    So in HurtSonic, if you replace the pointed branch to Hurt_NoRings/@norings with KillSonic, the lack of rings properly kills Sonic regardless of being in debug mode or not. It's odd that no revision of Sonic 1 ever noticed this, much less any rom hacks of the game.
     
  6. Devon

    Devon

    I'm a loser, baby, so why don't you kill me? Tech Member
    1,248
    1,419
    93
    your mom
    While this is fine and dandy, I don't think it's a bug. Having the debug mode disable dying makes sense, at least to me it does.
     
    • Agree Agree x 9
    • Like Like x 1
    • List
  7. BenoitRen

    BenoitRen

    Tech Member
    409
    183
    43
    Same thing in Sonic CD. The function playdieset() is responsible for setting the necessary values if Sonic has to die. The first few lines check if edit mode is on, and if so, it immediately bails:
    Code (Text):
    1. if (editmode.w)
    2.   return -1;
    But that's not all. The PC port also has an actual debug mode. In playdamage(), if you have no rings, you get damaged instead of dying if debug mode is on:
    Code (Text):
    1. if (debugflag.w)
    2. {
    3.   playdamagechk(pActwk, pColliAct);
    4.   return -1;
    5. }
    6. return playdieset(pActwk);
     
  8. Yeah I'm guessing it'd be more of a consistency thing for how S2 and S3K handle deaths while in debug mode, ain't worth losin' sleep over it for sure
     
  9. saxman

    saxman

    Oldbie Tech Member
    As I recall, it is a cheat on its own. In the Japanese version, you can enable four different cheats with four different combinations of U D L R (press C zero times, two times, four times, and six times). In the American version, it just enables everything.

    But I do think it's be nice if he could actually die if for no other reason than to restart the level, which is a legitimate reason to *not* have invulnerability. I like how they handled cheating death in the later games.
     
  10. I've noticed that with this code tweak applied, the player can't go into debug mode to recover from a death, which definitely adds to their idea of cheating death in said mode (although not exact but oh well).
     
  11. OrionNavattan

    OrionNavattan

    Tech Member
    166
    164
    43
    Oregon
    The background scrolling code for GHZ calculates the VScroll values in a way that doesn't make sense. The code below is from Revision 1, but Revision 0 is similar.

    Code (ASM):
    1. ; code from Hivebrain 2022
    2.        ; calculate Y position
    3.        lea   (v_hscroll_buffer).w,a1
    4.        move.w   (v_camera_y_pos).w,d0           ; get camera pos
    5.        andi.w   #$7FF,d0               ; maximum $7FF
    6.        lsr.w   #5,d0                   ; divide by $20
    7.        neg.w   d0
    8.        addi.w   #$20,d0
    9.        bpl.s   .limitY                   ; branch if v_camera_y_pos is between 0 and $400
    10.        moveq   #0,d0                   ; use 0 if greater
    11.    .limitY:
    12.        move.w   d0,d4                     ; used later to determine where to start writing values for highest row of clouds
    13.        move.w   d0,(v_bg_y_pos_vsram).w           ; update bg y pos

    The VScroll value varies inversely in relation to the camera y-pos: the maximum VScroll value of $20 is applied if the y-pos is 0, eventually falling to 0 once the camera reaches $400. This has the effect of the background scrolling in the "wrong" direction in relation to the camera, moving down as the camera rises, and up as the camera falls. While this may have been a deliberate design choice, I don't think it looks right. The following code makes the background scroll in the "correct" direction:

    Code (ASM):
    1. ; calculate Y position
    2.        lea   (v_hscroll_buffer).w,a1
    3.        move.w   (v_camera_y_pos).w,d0           ; get camera pos
    4.        andi.w   #$7FF,d0               ; maximum $7FF
    5.        lsr.w   #5,d0                   ; divide by $20
    6.        cmpi.w   #$20,d0
    7.        bls.s   .limitY                   ; branch if v_camera_y_pos is between 0 and $400
    8.        moveq   #$20,d0                   ; use $20 if greater
    9.    .limitY:
    10.        move.w   d0,d4
    11.        move.w   d0,(v_bg_y_pos_vsram).w           ; update bg y pos
     
    Last edited: Oct 15, 2023
  12. Devon

    Devon

    I'm a loser, baby, so why don't you kill me? Tech Member
    1,248
    1,419
    93
    your mom
    Avoiding CalcAngle When Performing Collision in the Air

    The way Sonic handles collision while he is airborne involves calculating the general direction that he is moving, and then jumping to the appropriate code for handling collision for that specific direction. If he is moving downwards, he would check collision with the walls and the floor. If he is moving left or right, he would check collision with the wall he is moving towards and the ceiling and floor. If he is moving upwards, he would check collision with the walls and ceiling. The way it checks that direction is to take the angle he is moving at by using CalcAngle with his X and Y speeds as the parameters, and the output angle value determines the direction he is moving.

    [​IMG]

    That works and all, but we can definitely use a less intensive calculation to handle this.

    What we can do is actually just directly check the speed values ourselves. What we can then deduce from this is that we can basically determine Sonic's direction depending on if he is moving more horizontally than vertically and the direction of the larger speed value. We know this, because the angle values that separate the different directions are all angles where the absolute values of X and Y are the same.

    Here is a chart showing the new logic with the conditions that should be checked:

    [​IMG]

    Here's an example implementation.

    Replace:
    Code (Text):
    1. Sonic_Floor:
    2.                 move.w  obVelX(a0),d1
    3.                 move.w  obVelY(a0),d2
    4.                 jsr     (CalcAngle).l
    5.                 move.b  d0,(v_unused3).w
    6.                 subi.b  #$20,d0
    7.                 move.b  d0,(v_unused4).w
    8.                 andi.b  #$C0,d0
    9.                 move.b  d0,(v_unused5).w
    10.                 cmpi.b  #$40,d0
    11.                 beq.w   loc_13680
    12.                 cmpi.b  #$80,d0
    13.                 beq.w   loc_136E2
    14.                 cmpi.b  #$C0,d0
    15.                 beq.w   loc_1373E

    with:
    Code (Text):
    1. Sonic_Floor:
    2.                 move.w  obVelX(a0),d0                   ; Get X speed
    3.                 move.w  obVelY(a0),d1                   ; Get Y speed
    4.                 bpl.s   SonAirCol_PosY                  ; If it's positive, branch
    5.  
    6.                 cmp.w   d0,d1                           ; Are we moving towards the left?
    7.                 bgt.w   loc_13680                       ; If so, branch
    8.  
    9.                 neg.w   d0                              ; Are we moving towards the right?
    10.                 cmp.w   d0,d1
    11.                 bge.w   loc_1373E                       ; If so, branch
    12.  
    13.                 bra.w   loc_136E2                       ; We are moving upwards
    14.  
    15. SonAirCol_PosY:
    16.                 cmp.w   d0,d1                           ; Are we moving towards the right?
    17.                 blt.w   loc_1373E                       ; If so, branch
    18.  
    19.                 neg.w   d0                              ; Are we moving towards the left?
    20.                 cmp.w   d0,d1
    21.                 ble.w   loc_13680                       ; If so, branch
     
    Last edited: Dec 22, 2023
  13. Devon

    Devon

    I'm a loser, baby, so why don't you kill me? Tech Member
    1,248
    1,419
    93
    your mom
    Full Position Alignment With Block Collision

    The way stage block collision works is that a function is called to determine the distance away or inside a block. Typically, if an object is inside an block, their speed is set to 0 and their position is aligned against the block. However, only the integer half of the position value tends to be affected, but not the fraction half.

    For example, let's say your Y position is 38.75, and you are inside a block 6 pixels deep downwards. The alignment would subtract your Y position by 6 pixels upwards, and your final Y position would end up being 32.75. In most cases, not clearing out the fraction part doesn't really seem to affect anything, but can still creep in some edge cases when it comes to movement on the ground, and mess up the collision.

    So, if you so desire to do a full alignment where the fraction part is cleared out (in the previous example, the final Y position ends up just being 32), it's rather easy: just search for the calls for the various collision detection functions, and in the part where the position value is aligned, also clear out the fraction part.

    For example:
    Code (Text):
    1. loc_13680:
    2.         bsr.w    Sonic_HitWall
    3.         tst.w    d1
    4.         bpl.s    loc_1369A
    5.         sub.w    d1,obX(a0)
    6.         clr.w    obX+2(a0)            ; <-- Add this
    7.         move.w   #0,obVelX(a0)
    8.         move.w   obVelY(a0),obInertia(a0)
    9.         rts    

    The functions to look for are Sonic_HitFloor, Sonic_HitWall, Sonic_DontRunOnWalls, sub_14EB4, ObjFloorDist, ObjFloorDist2, ObjHitWallRight, ObjHitCeiling, ObjHitWallLeft, and Sonic_WalkSpeed. Also FindFloor and FindWall calls throughout Sonic_AnglePos.

    Also, it might be worth capping Sonic's top ground speed at something like 15.75 ($FC0) in both directions and replace the rolling speed cap (that is only done to the X speed) to prevent further collision errors.
     
    Last edited: Dec 22, 2023
  14. Devon

    Devon

    I'm a loser, baby, so why don't you kill me? Tech Member
    1,248
    1,419
    93
    your mom
    Here's a fun little oddity. The rolling speed cap only applies horizontally, but not vertically. If you don't want that, and instead would rather have it be consistent with the regular ground movement speed cap, just go to Sonic_RollSpeed, and change:
    Code (Text):
    1.                 move.b  obAngle(a0),d0
    2.                 jsr     (CalcSine).l
    3.                 muls.w  obInertia(a0),d0
    4.                 asr.l   #8,d0
    5.                 move.w  d0,obVelY(a0)
    6.                 muls.w  obInertia(a0),d1
    7.                 asr.l   #8,d1
    8.                 cmpi.w  #$1000,d1
    9.                 ble.s   loc_131F0
    10.                 move.w  #$1000,d1
    11.  
    12. loc_131F0:
    13.                 cmpi.w  #-$1000,d1
    14.                 bge.s   loc_131FA
    15.                 move.w  #-$1000,d1
    16.  
    17. loc_131FA:
    18.                 move.w  d1,obVelX(a0)

    to:
    Code (Text):
    1.                 move.b  obAngle(a0),d0
    2.                 jsr     (CalcSine).l
    3.                 move.w  obInertia(a0),d2
    4.                 cmpi.w  #$1000,d2
    5.                 ble.s   loc_131F0
    6.                 move.w  #$1000,d2
    7.  
    8. loc_131F0:
    9.                 cmpi.w  #-$1000,d2
    10.                 bge.s   loc_131FA
    11.                 move.w  #-$1000,d2
    12.  
    13. loc_131FA:
    14.                 muls.w  d2,d0
    15.                 asr.l   #8,d0
    16.                 move.w  d0,obVelY(a0)
    17.                 muls.w  d2,d1
    18.                 asr.l   #8,d1
    19.                 move.w  d1,obVelX(a0)
     
  15. MarkeyJester

    MarkeyJester

    Original, No substitute Resident Jester
    2,202
    432
    63
    Japan
    This is a slight tangent from the norm, but...


    ...is anyone documenting these changes segregated from the thread? Could we get some of them put on the wiki? Specifically here.

    Perhaps as a solution, we could put links on that wiki page to the fixes to specific posts of this thread, rather than creating a brand new wiki-page? Temporary measure of course. as the only issue I can see is if someone happens to have a conscientious mental breakdown or some form of insecurity, and goes back and edits their posts to replace the fixes (which happens from time to time), but it's better than nothing... at least as a temporary measure?

    If no-one wants to do it, then I'll try and find time to do a crude copy/paste set of pages maybe.
     
  16. DeltaWooloo

    DeltaWooloo

    Be a boss-man and come to my big and tall man shop Member
    I'll be glad to help out as well, especially with the Sonic 2 thread as well. Both threads have a lot of great guides and such that'll benefit being sourced to a much wider audience with SCHG being a prime example to ROM hacking.


    I also want to rework some of the guides to be a little more user friendly and compatible with newer disassemblies as well, like the Sonic 2 level select port to Sonic 1 that's very dated nowadays and is hard for a newbie to get a good grasp at how everything works.
     
  17. Devon

    Devon

    I'm a loser, baby, so why don't you kill me? Tech Member
    1,248
    1,419
    93
    your mom
    How about another guide?

    Properly Removing the Roll Jump Movement Lock

    So, some of you of course know about how when you jump after rolling, your movement is locked. Removing that lock is as simple as removing this line that's in Sonic_Jump:
    Code (Text):
    1.         bset    #4,obStatus(a0)

    However, there's one issue that needs to be addressed. You see, when you hold left or right in the air when your control isn't locked, there's a speed cap that gets applied. When the lock is active, moving left or right is prevented, which in turn, prevents that speed cap from kicking in. With that, you can jump and keep your momentum, if you are rolling down a hill, for instance:

    [​IMG]

    But, when we remove the lock and allow the speed cap to kick in...

    [​IMG] [​IMG]

    Yeaaaah, that's not ideal. Luckily, there's already a guide that makes it so that if you are moving faster than the maximum speed, it doesn't cap it (referred to as "removing the speed cap"). Apply those changes, and you'll be able to maintain your momentum and also be able to control yourself in the air.
     
    Last edited: Jan 25, 2024
    • Informative Informative x 4
    • List
  18. Kilo

    Kilo

    That inbetween sprite from S&K's title screen Member
    307
    309
    63
    Canada
    This is a fix specifically for old Hivebrain version Project Sonic 1: Two-Eight. If you look at Sonic_Loops (Which also handles Sonic's interaction with the S-Tubes in Green Hill) you'll see:
    Code (Text):
    1.         cmp.b    #$75,d1                    ; MJ: is the chunk 75 (Top top left S Bend)
    2.         beq.w    Obj01_ChkRoll                ; MJ: if so, branch
    3.         cmp.b    #$76,d1                    ; MJ: is the chunk 76 (Top top right S Bend)
    4.         beq.w    Obj01_ChkRoll                ; MJ: if so, branch
    5.         cmp.b    #$77,d1                    ; MJ: is the chunk 77 (Top bottom left S Bend)
    6.         beq.w    Obj01_ChkRoll                ; MJ: if so, branch
    7.         cmp.b    #$78,d1                    ; MJ: is the chunk 78 (Top bottom right S Bend)
    8.         beq.w    Obj01_ChkRoll                ; MJ: if so, branch
    9.         cmp.b    #$79,d1                    ; MJ: is the chunk 79 (Bottom top left S Bend)
    10.         beq.w    Obj01_ChkRoll                ; MJ: if so, branch
    11.         cmp.b    #$7A,d1                    ; MJ: is the chunk 7A (Bottom top right S Bend)
    12.         beq.w    Obj01_ChkRoll                ; MJ: if so, branch
    13.         cmp.b    #$7B,d1                    ; MJ: is the chunk 7B (Bottom bottom left S Bend)
    14.         beq.w    Obj01_ChkRoll                ; MJ: if so, branch
    15.         cmp.b    #$7C,d1                    ; MJ: is the chunk 7C (Bottom bottom right S Bend)
    16.         beq.w    Obj01_ChkRoll                ; MJ: if so, branch
    Now, MarkeyJester, great guy, but what the hell went on here. Let's clean this up. We'll do 2 approaches.

    The first approach is just porting over the code from the P128 branch of the GitHub disassembly, this method is more modular, as it uses a table of chunks which you can edit if you move around the S-Tube IDs or make more S-Tubes. Just replace the above code with:
    Code (Text):
    1.         lea    STunnel_Chunks_End(pc),a2            ; MJ: lead list of S-Tunnel chunks
    2.         moveq    #(STunnel_Chunks_End-STunnel_Chunks)-1,d2    ; MJ: get size of list
    3.  
    4. @loop:
    5.         cmp.b    -(a2),d1    ; MJ: is the chunk an S-Tunnel chunk?
    6.         dbeq    d2,@loop    ; MJ: check for each listed S-Tunnel chunk
    7.         beq.w    Obj01_ChkRoll    ; MJ: if so, branch
    Then, just before the " ; End of function Sonic_Loops" comment, insert this table:
    Code (Text):
    1. STunnel_Chunks:        ; MJ: list of S-Tunnel chunks
    2.         dc.b    $75,$76,$77,$78
    3.         dc.b    $79,$7A,$7B,$7C
    4. STunnel_Chunks_End:
    As I said this method is great because you can edit the table to any length with any chunk IDs. But it is flawed in that in execution, it's essentially the same as the original code, just condensed, it still loops through and checks each individual ID. And things could get nasty if you make every chunk an S-Tube chunk (Why would you do that). This is where the second approach comes in.

    If you know that your S-Tube chunks are only going to be within a specific ID range, then we only need to do 2 checks to see if we're within the S-Tube chunk range. Which can be achieved by replacing the original code with this:
    Code (Text):
    1.         cmp.b    #$75,d1
    2.         blo.s    @NotInRange        ; If the chunk ID is below our range, don't roll.
    3.         cmp.b    #$7C,d1
    4.         bhi.s    @NotInRange        ; If the chunk ID is above our range, don't roll.
    5.         bra.w    Obj01_ChkRoll    ; Otherwise, if the chunk is in range, roll!
    6.  
    7. @NotInRange:
    Now there is a third option that triumphs both of these, and this is the method Sonic 2 used, that is to make a dedicated force spin trigger object, this takes a lot more work which is out of the scope of this guide, though. But do look into it if the way P128 handles S-Tubes bothers you like it does to me.
     
  19. Kilo

    Kilo

    That inbetween sprite from S&K's title screen Member
    307
    309
    63
    Canada
    So the steps to change the assembler options kind of bugged me for a while now, it's very hacky. And it wasn't compatible out of the box with the 2005 disasm since it has a single instance of vdp_control_port which is a very minor thing that takes a second to fix. But for the sake of convenience, here's a version that is 100% compatible with ASM68k and works with Hive 2005 by using the VDP control port's raw address. It allows you to skip changing the assembler options and declaring VDP_Command_Buffer and VDP_Command_Buffer_Slot as I put those in the file too.
     

    Attached Files:

    Last edited: Feb 5, 2024
  20. Kilo

    Kilo

    That inbetween sprite from S&K's title screen Member
    307
    309
    63
    Canada
    Cleaning Up the Sound Driver if You've Fixed the Sega Chant
    This guide only effects Hivebrain 2005 users who are still using the default sound driver, as newer disassemblies don't face this issue.
    It's pretty infamous that when hacking with this disassembly you'll quickly run into a bug that causes the Sega chant to make a glitching noise towards the end of playback. A fix has been available for a long time now, and has been everyone's go to solution. What this fix does is use the 68k to play the chant rather than the Z80, because the Z80 sound driver expects the Sega chant sample to be at a specific ROM location and at a specific size. Not to mention it's Kosinski compressed, and the combination of these factors lead to this monstrosity when it comes time to include the Z80 driver:
    Code (Text):
    1. Kos_Z80:    incbin    sound\z80_1.bin
    2.         dc.w ((SegaPCM&$FF)<<8)+((SegaPCM&$FF00)>>8)
    3.         dc.b $21
    4.         dc.w (((EndOfRom-SegaPCM)&$FF)<<8)+(((EndOfRom-SegaPCM)&$FF00)>>8)
    5.         incbin    sound\z80_2.bin
    6.         even
    But with the fix linked above, it doesn't need to be like this, as it's effectively dead code. So let's clean it up.

    You'll need Puto's sound driver disassembly which can be found here.

    First, if you plan on integrating this disassembly into your hack's disassembly then it's not a bad idea to delete all these equates since we won't be needing them.
    Code (Text):
    1. SEGA_Size    equ    6978h        ; The size of the SEGA sound
    2. SEGA_Location    equ    9688h        ; The location within the bank of the SEGA sound, 8000h-based
    3. SEGA_Pitch    equ    0Bh        ; The pitch of the SEGA sound
    Note that you do need SEGA_Bank and SEGA_Bank_Minor I tried deleting that and got my ears deleted instead.

    Then at Wait_for_DAC_Request you can delete
    Code (Text):
    1.     cp    6            ; is the value=6 (playing sample 87)?
    2.     jr    nc,Play_SegaPCM        ; If sample>=87, branch to Play_SegaPCM
    And finally you can delete the entire function Play_SegaPCM
    Code (Text):
    1. ;
    2. ; Subroutine - Play_SegaPCM
    3. ;
    4. ; This subroutine plays the "SEGA" sound.
    5. ;
    6. Play_SegaPCM:  
    7.     ld    de,SEGA_Location    ; Load the location of the SEGA sound (80h-based relative pointer to $78000 in the main ROM) to address de.
    8.     ld    hl,SEGA_Size        ; Load the size of the SEGA sound to register hl.
    9.     ld    c,2Ah            ; c = 2Ah
    10. PlaySegaPCM_Loop:  
    11.     ld    a,(de)            ; Load the contents of whatever de points at to register a
    12.     ld    (ix+0),c        ; 4000h = c
    13.     ld    (ix+1),a        ; 4001h = a
    14.     ld    b,SEGA_Pitch        ; b = 0Bh (Pitch of the SEGA sample)
    15. loop_CA:  
    16.     djnz    loop_CA            ; Decrement b; jump to loop_CA if not 0. (Read the loop_8E comment for more info).
    17.     inc    de            ; Increment de
    18.     dec    hl            ; Decrement hl
    19.     ld    a,l            ; a = l
    20.     or    h            ; a = a or h
    21.     jp    nz,PlaySegaPCM_Loop    ; If a!=0, jump to PlaySegaPCM_Loop
    22.     jp    Load_Sample        ; Otherwise, if you finished playing the SEGA sound, jump back to Load_Sample
    Build and you'll have a cleaned up sound driver! Bring smpsbuilt_compressed.bin to your hack's main disassembly, rename it to z80 if you'd like or DAC or whatever, and replace kos_z80's nasty hack with a simple incbin.