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

    Powered by a malfunctioning Motorola 68000 Tech Member
    1,040
    1,053
    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

    Powered by a malfunctioning Motorola 68000 Tech Member
    1,040
    1,053
    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

    Powered by a malfunctioning Motorola 68000 Tech Member
    1,040
    1,053
    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

    Powered by a malfunctioning Motorola 68000 Tech Member
    1,040
    1,053
    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 8
    • Like Like x 1
    • List
  7. BenoitRen

    BenoitRen

    Member
    141
    58
    28
    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).