don't click here

Some changes/fixes for Sonic 1

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

  1. Kilo

    Kilo

    Deathly afraid of the YM2612 Tech Member
    1,094
    1,066
    93
    Canada
    Sonic 1 Source Code Recreation + Source Code Wiki Page
    Yeah the roll jump lock was definitely set in place due to the air speed cap. But since we have guides to remove the cap, we can also remove the lock.
     
  2. DigitalDuck

    DigitalDuck

    Arriving four years late. Member
    5,418
    492
    63
    Lincs, UK
    TurBoa, S1RL
    2010 is not ancient and we were very much aware of the air speed cap back then.

    EDIT: In fact it's even brought up in the thread.

    Just to add more to this: removing the air speed cap is a much easier fix than implementing a roll jump lock. For this to be the case, you're telling me the guy who wrote the physics engine for the game (Yuji Naka himself, no less) doesn't know how conditional branches work.

    The much more likely scenario is that nobody during Sonic 1's development even noticed the speed cap reduced your speed if you were above the cap, because there are very few scenarios where that can even happen in Sonic 1. Despite my initial leanings towards it being air cap related in that thread, I've actually dived into the code since then and I doubt the roll jump lock has anything to do with the air speed cap.
     
    Last edited: May 3, 2024
  3. Devon

    Devon

    La mer va embrassé moi et délivré moi lakay. Tech Member
    1,429
    1,745
    93
    your mom
    2010 was 14 years ago. It's closer to the creation of this website than it is to today.
    I thought rolling down hills and half pipes to pick up speed was basically one of the selling points of the game? Spring Yard Zone act 3 is a prime example, where it begins with a massive downward slope for you to roll down, which makes the air speed cap issue very apparent if you jumped after rolling down it.
    Yuji Naka is not above quick and dirty hacks, and his code is literred with other sorts of issues. His engine was clever, for sure, but it wasn't perfect. The lock isn't even remotely complex, and I'd even argue it was a temporary fix that was made permanent, because...
    The air speed cap kicks in if you press left or right while in the air, because the functions for handling that check Sonic's speed. The lock simply skips over that. Can't wreck the momentum of the jump if you don't let those functions mess with it. Quick and easy fix that gets the job done.
     
    Last edited: May 3, 2024
    • Agree Agree x 3
    • Like Like x 1
    • List
  4. Kilo

    Kilo

    Deathly afraid of the YM2612 Tech Member
    1,094
    1,066
    93
    Canada
    Sonic 1 Source Code Recreation + Source Code Wiki Page
    Yes? Sonic 1 is littered with instances of Naka forgetting if he's counting from 1 or 0. Using the wrong registers. Object memory spamming with things like bridges and swings. There's a reason we got a billion fixes for this game. In fact one of them is a bug stemming from the incorrect use of a conditional branch.
    I hate this idea that Naka is some genius that was unmatched with the Genesis when he just knew how to use premade trigonometry functions.
     
    Last edited: May 3, 2024
  5. DigitalDuck

    DigitalDuck

    Arriving four years late. Member
    5,418
    492
    63
    Lincs, UK
    TurBoa, S1RL
    SYZ3 is... basically the only scenario you'll see this in in Sonic 1. And if you roll down the slope and don't jump at the bottom (which I guarantee most people did the first time they played)... your air speed is still capped as soon as you leave the slope at the bottom!

    The one situation you're likely to encounter it and it doesn't fix it. Great fix.

    It's not that simple. The roll lock needs to be set when jumping from a rolling state; it needs to be cleared when landing on ground, and when landing on objects (which is handled separately), and when interacting with various different objects. And every object you touch has the potential to add more bugs.

    Saying "if speed > cap don't accelerate" is much quicker and easier, especially since if you don't mind Sonic's top speed not being exactly 6, you can simply change where you do the branch. Granted, it has to be done twice (hi Sonic 2).

    The roll jump lock does nothing to fix the ground speed cap issue, which can happen in more situations and is even more obvious when it does. You'd think there'd be something to patch that one too... or are you going to claim that the inability to accelerate on flat ground while rolling is a "quick fix" for that one too?

    Nobody made this claim.

    Yes, there's a ton of bugs. Bugs happen. But when you have a bug in code you've written, and you're made aware of the bug, you usually have a decent idea of how to fix it, especially when it's something this simple. You wouldn't go into multiple different areas of the code and probably introduce even more bugs when you could go into the portion of the code that is very obviously causing this behaviour and fix it there with a smaller change.

    The roll jump lock is a deliberate design decision.
     
  6. Devon

    Devon

    La mer va embrassé moi et délivré moi lakay. Tech Member
    1,429
    1,745
    93
    your mom
    Except that SolidObject and its cohorts also call Sonic_ResetOnFloor, which is shared with the ground collision code, and that's where the flag is cleared. So no, it's not handled separately in this case. It really is as simple as setting the flag when jumping from rolling, checking during the air control routine, and then clearing it in Sonic_ResetOnFloor, and that's how it is in Sonic 1.

    Here's what I think.

    I would say the normal ground and air controls is moreso designed for the platforming side, while the rolling state is designed for the faster paced sections, where you are making the most of the physics engine. The normal ground and rolling states use their own routines, but the air and jump states share their functions with each other. They couldn't have just checked the rolling flag, because that is set when jumping, so it would lock the controls when doing a normal jump. So, they put in an extra flag to specifically indicate a jump from rolling.

    I would argue that if the pit that Sonic fell in was larger and more spacious, it would probably have been addressed. But, in my opinion, doing a jump exposes the issue more blatantly. Now, I am not arguing that locking the controls was a good fix. In fact, it's a pretty shitty one, especially since you said it doesn't even work when rolling off a cliff, but I do think it's possible they just simply forgot about that case, or didn't think it was as bad as the jumping case. I don't think it's really unheard of for programmers to sometimes think of gross hacks over a proper fix (even if the proper fix is more simple).
     
    Last edited: May 3, 2024
  7. MarkeyJester

    MarkeyJester

    Original, No substitute Resident Jester
    2,250
    510
    93
    Japan
    I'm not really getting involved with the classification of Bug vs Intention, but I would like to address the whole Yuji Naka thing.

    I think it's important to segregate your biases of the programmer and his work. I don't really like Naka much, but I won't let it shroud my judgement.

    You guys could (as a hobby) write an engine to be perfect; then as you add more game elements, it begins to disturb your optimum engine, then your choice is; do you rewrite the engine from scratch with the changes in mind, or do you just put in a flag and risk it being a bit dirty? These game developers are under time restraints, they likely don't have the privilege of such a choice. Career, competitive business, and money are all at stake here, it's no joke.

    Incorrect conditional branching (signed vs unsigned) is not an indication of a programmer's competence, especially if the differences only occur in extreme circumstances you're never likely to see. Mistakes happen while under pressure too, in the real world you have to be somewhat industrious and messy, this is why industrious people succeed. Obsess over the code too much and you'll lose the worm to someone else.

    The point I'm making; it's easy to criticise the professionals when you do the hobby equivalent with unlimited time, resources, and freedom, and no-one else over-watching. The state of the code can be used to debate whether bug or intention, not problem with that, but not to debate competence. You need other factors to debate competence, not just the state of the code.
     
  8. Brainulator

    Brainulator

    Regular garden-variety member Member
    My dad has told me multiple times that in many cases, "it doesn't have to be good, it just has to be". Also, perfect is the enemy of good enough. For all of the mistakes made and questionable choices, the code runs as it's supposed to and with minimal game-breaking errors.
     
  9. Devon

    Devon

    La mer va embrassé moi et délivré moi lakay. Tech Member
    1,429
    1,745
    93
    your mom
    For the record, when I said Yuji Naka wasn't above dirty hacks, it wasn't necessarily meant to be an insult to him, but rather a point that he is human and can make mistakes, just like everyone else. Especially with deadlines and planning getting in the way, I really doubt Yuji Naka put in the effort to make sure the physics engine didn't have any hacks or flaws in its logic. I think at the very least he wanted the engine to at least function without glaring issues, which is why I find the roll jump lock being a hack to prevent weird/limited movement while in the air when jumping after rolling at a high speed to be believable. I agree it isn't a great fix at all, but it is entirely possible that was just what was quickly decided on in a flash and was left as is. It really isn't entirely fair to judge the code as if Yuji Naka had all the time in the world to make sure everything was perfect in some way, which is what I meant when I said he wasn't above dirty and quick hacks.

    However, to say that he was incompetent is just not right. Even with the flaws, the engine he developed was clever and was very much helpful for establishing Sonic's own identity from the rest of the mascot platformers of the time. It did its job well. We here have the benefit of hindsight and more free time. We are, in a sense, spoiled. We will never truly know what it was like in Yuji Naka's shoes, all we can do is make educated guesses. For all I know, I can be very wrong, and perhaps it really was a design choice, but at the moment, I do stand by what I said, and I do believe that just because a certain solution might seem more simple/obvious, doesn't necessarily mean that that was what came to Yuji Naka's mind at the time he was programming Sonic 1. IMO, the only reason we really see the solution of not accelerating if past top speed as a better and more simple solution is because that was what was done in Sonic 2, so we have that to refer to... but that's just it, it's only my opinion.
     
    Last edited: May 5, 2024
  10. RetroKoH

    RetroKoH

    Member
    1,728
    107
    43
    S1Fixed: A successor to ReadySonic
    Minor question to anyone in the know, regarding a recent edit to the wiki: https://info.sonicretro.org/SCHG_How-to:Display_the_Press_Start_Button_text

    A user by the name of YouTails has apparently reported that this PSB object fix causes a minor visual bug in which the Title Screen emblem tilemap is shifted upward. I want to point out that this is likely incorrect (though someone can correct me if it is a minute difference of 1px that I'm not noticing).

    I also want to point out that many of us who modify stuff extensively know that sometimes the positioning and/or movement of this emblem can get messed up badly by accident, and I'm assuming that's likely the case w/ YouTails' observation.

    I'd like to get confirmation on this before editing the wiki.
     
  11. Kilo

    Kilo

    Deathly afraid of the YM2612 Tech Member
    1,094
    1,066
    93
    Canada
    Sonic 1 Source Code Recreation + Source Code Wiki Page
    As crazy as it is, it's true. Here's an image from the guide overlayed with a vanilla ROM.
    upload_2024-6-3_11-16-18.png
    Although it's not the emblem that's moved up it's the background. During the title screen the emblem has no vertical scrolling applied to it.
     
  12. RetroKoH

    RetroKoH

    Member
    1,728
    107
    43
    S1Fixed: A successor to ReadySonic
    Oh wow... that's insane. I never picked up on that.
     
  13. Brainulator

    Brainulator

    Regular garden-variety member Member
    I have no idea how that would even happen. Furthermore, I compared a vanilla REV01 ROM with a hex-edited version in Exodus, and no relevant differences could be found.
     
  14. Devon

    Devon

    La mer va embrassé moi et délivré moi lakay. Tech Member
    1,429
    1,745
    93
    your mom
    Yeah, uh, I followed the guide and I didn't get that result.

    [​IMG]

    Though, it's a bit messed up, because the current GitHub disassembly already has the fix bundled in, which is activated by enabling FixBugs, so the added code is redundant. I went ahead and updated the page to account for this, while remaking the Hivebrain 2005 version.
     
    Last edited: Jun 4, 2024
  15. Kilo

    Kilo

    Deathly afraid of the YM2612 Tech Member
    1,094
    1,066
    93
    Canada
    Sonic 1 Source Code Recreation + Source Code Wiki Page
    Glad the Hivebrain version was added back... I get that it's old but wiping it does more harm than good. Still annoyed that it got wiped from the Spindash guide.
     
  16. Devon

    Devon

    La mer va embrassé moi et délivré moi lakay. Tech Member
    1,429
    1,745
    93
    your mom
    Okay, I believe I found out what was going on. The screenshot on the wiki is in REV00, which is what the 2005 disassembly is based on, but the GitHub disassembly defaults to REV01, and the Y position of the background for Green Hill Zone was changed between revisions in the deformation code.

    REV00:
    Code (ASM):
    1.                 move.w  (v_screenposy).w,d0
    2.                 andi.w  #$7FF,d0
    3.                 lsr.w   #5,d0
    4.                 neg.w   d0
    5.                 addi.w  #$26,d0

    REV01:
    Code (ASM):
    1.                 move.w  (v_screenposy).w,d0
    2.                 andi.w  #$7FF,d0
    3.                 lsr.w   #5,d0
    4.                 neg.w   d0
    5.                 addi.w  #$20,d0
    6.                 bpl.s   .limitY
    7.                 moveq   #0,d0
    8.  
    9.         .limitY:

    So yeah, the guide is not at fault, just some confusion. I removed the "bug" part of the guide, and instead put in a disclaimer for the screenshot.
     
    • Like Like x 3
    • Informative Informative x 3
    • List
  17. Brainulator

    Brainulator

    Regular garden-variety member Member
    At the same time, I feel the real problem is that often, there ends up being redundancy between the two sections, so I could see how merging them might be beneficial.

    Are the credits on the top intended for people following the guide who would have to give credit to those using the guide? Otherwise, I would think that the page history would be enough to indicate who wrote what.
     
  18. nineko

    nineko

    I am the Holy Cat Tech Member
    6,369
    524
    93
    italy
    Those credits should be taken carefully in general, I seem to remember that I'm credited for a guide I didn't even write, so it's possible that more of them are inaccurate.
     
  19. RetroKoH

    RetroKoH

    Member
    1,728
    107
    43
    S1Fixed: A successor to ReadySonic
    S3K Priority Manager in Sonic 1 (GitHub disassembly)

    Writing this from my phone. I will make this look more appealing to read later. So, I did this once about a decade ago, but struggled with fixing a particular bug with the Caterkiller. Good news, it's actually a simple problem and I figured it out! So here's your guide:

    Step 1: Free up an SST slot for obPriority.
    For this we must go to Constants.asm. This can be tricky to figure out, but here is what I did. I moved obInertia from $14 (+$15) to $20 (+$21). Those are already marked as obColType and obColProp, respectively, though these are only used by enemy objects, whereas Inertia is used only by Sonic and Caterkiller... the latter of which we will address shortly. With $14 and $15 now free, we can move obActWid (the SST after obPriority) to one of those two slots. I chose $14. With that, your SSTs should look similar to this:
    Code (Text):
    1. ; Object variables (Rearranged for S3K Priority Manager -- RetroKoH)
    2. obID:            equ 0            ; object ID number
    3. obRender:        equ 1            ; bitfield for x/y flip, display mode
    4. obGfx:            equ 2            ; palette line & VRAM setting (2 bytes)
    5. obMap:            equ 4            ; mappings address (4 bytes)
    6. obX:            equ 8            ; x-axis position (2-4 bytes)
    7. obScreenY:        equ $A            ; y-axis position for screen-fixed items (2 bytes)
    8. obY:            equ $C            ; y-axis position (2-4 bytes)
    9. obVelX:            equ $10            ; x-axis velocity (2 bytes)
    10. obVelY:            equ $12            ; y-axis velocity (2 bytes)
    11. obActWid:        equ $14            ; action width (formerly obInertia)
    12.                             ; $15 is now free
    13. obHeight:        equ $16            ; height/2
    14. obWidth:        equ $17            ; width/2
    15. obPriority:        equ $18            ; sprite stack priority (2 bytes)
    16. obFrame:        equ $1A            ; current frame displayed
    17. obAniFrame:        equ $1B            ; current frame in animation script
    18. obAnim:            equ $1C            ; current animation
    19. obPrevAni:        equ $1D            ; previous animation
    20. obTimeFrame:    equ $1E            ; time to next frame
    21. obDelayAni:        equ $1F            ; time to delay animation
    22. obInertia:        equ $20            ; potential speed (2 bytes) -- Exclusive to players
    23. obColType:        equ $20            ; collision response type
    24. obColProp:        equ $21            ; collision extra property
    25. obStatus:        equ $22            ; orientation or mode
    Step 2: Follow this SCHG Guide
    Yeah. Sounds lazy but that's all there is to it. We are just changing instructions to handle words instead of bytes. Sonic 2 uses the same manager as Sonic 1, so handling the various cases of obPriority should be easy enough if you follow that guide. If there any questions, I'll edit this and address them.

    Step 3: Caterkiller Fix
    To the best of my knowledge, this is the only object in the entire game that has a problem with what we did. If you build and run the game, you will notice that you cannot interact with Caterkiller at all. That's because we moved obInertia to obColType/Prop, and it uses BOTH SSTs. To solve this, I gave Caterkiller a new SST variable called cat_inertia, and set it to $12. $12 is obVelY, but despite moving, Caterkiller never uses these two bytes. From there, replace every instance of obInertia in Caterkiller's file with cat_inertia.

    With that, you now have S3K Priority in Sonic 1! Devon recently shared with me a way to optimize this even further, so I'll try it out and update you all soon.
     
  20. RetroKoH

    RetroKoH

    Member
    1,728
    107
    43
    S1Fixed: A successor to ReadySonic
    So, piggybacking off of a pink fade I cooked up for Sonic 2 Pink Edition a while back, I was working on some different colored fading effects, and in the midst of this I decided to try my hand at an optimized Fade in technique for the standard blue effect, with the hopes that this method would make other color effects much easier to implement. It only required edits to FadeIn_AddColour at the end of the section of code. With that, here it is:

    In summation, the old function would increment the blue nybble of a copy of the current color before comparing, and if it went over target, would undo its change before going to the next color component and trying again with the green nybble, again undoing if needed and incrementing the red nybble. This means a bunch of unnecessary incrementing and reloading once the palette is nearly finished loading and is just fading in the red component(s). My iteration removes some of the redundant workload by having the "target palette" table instead work as a countdown mechanism to tell the function whether a component should be incremented. As a palette entry is faded in, its corresponding nybble on the duplicate palette table is counted down. This eliminates the need for cumbersome comparisons and extra incrementations.

    Code (Text):
    1.  
    2. ; ||||||||||||||| S U B R O U T I N E |||||||||||||||||||||||||||||||||||||||
    3. ; This modified function decrements values from _dry_dup while adding to _dry.
    4. ; if _dry_dup entry == $0000, then the fade should be complete for that palette entry.
    5. ; if a specific component in _dry_dup's current value == $0, then we don't need to fade that color.
    6.  
    7. FadeIn_AddColour:
    8.         tst.w    (a1)            ; Does this colour entry need to be faded in?
    9.         beq.s    .next           ; if not, branch and jump to the next palette entry
    10.  
    11. .checkcolors:
    12.     ; This part will look familiar if you've ever used MarkeyJester's "proper" fade in code.
    13.     ; Special thanks to MarkeyJester for this bit.
    14.         move.b    (a1),d5        ; d5 = target blue
    15.         move.b    1(a1),d1       ; d1 = GR byte
    16.         move.b    d1,d2          ; copy to d2
    17.         lsr.b     #4,d1          ; d1 = target green
    18.         andi.b    #$E,d2         ; d2 = target red
    19. ; Check components (These calls can be swapped around easily to apply various colour effects!)
    20.         tst.b    d5              ; Does blue need adding (yes if d5 is non-zero)
    21.         bne.s    .addblue        ; if yes, branch and add blue to current colour
    22.         tst.b    d1              ; Does green need adding (yes if d5 is non-zero)
    23.         bne.s    .addgreen       ; if yes, branch and add green to current colour
    24.         bra.s    .addred                  
    25.  
    26. .addblue:
    27.         addq.b    #2,(a0)        ; increase blue value
    28.         subq.b    #2,(a1)        ; decrease target blue value
    29.         bra.s    .next           ; advance color values and exit  
    30. ; ===========================================================================
    31.  
    32. .addgreen:
    33.         addi.b    #$20,1(a0)     ; increase green value
    34.         subi.b    #$20,1(a1)     ; decrease target green value
    35.         bra.s    .next           ; advance color values and exit  
    36. ; ===========================================================================
    37.  
    38. .addred:
    39.         addq.b    #2,1(a0)       ; increase red value
    40.         subq.b    #2,1(a1)       ; decrease target red value
    41. ; ===========================================================================
    42.  
    43. .next:
    44.         addq.w    #2,a0          ; next colour
    45.         addq.w    #2,a1          ; next target colour
    46.         rts
    47. ; End of function FadeIn_AddColour
    48.  

    I've seen no issues whatsoever with this method so far. If you find any, lemme know. Otherwise I'm gonna cook up sth similar for fading out.
     
    Last edited: Jun 17, 2024