Make sense, although I do have another scrolling bug; for whatever reason, going backwards in certain levels (Death Egg or even Hidden Palace if you restored it) causes parts of the background to disappear. Even stranger, it doesn't happen to HPZ in the prototypes.
It's been a long time since I looked into that, but I believe that the solution was to make the background repeat all the way to the right end of the layout file. Levels like Mystic Cave Zone do this, presumably for its single-player variant which uses dynamic background loading. If I remember right, the Sonic 1 level layout format avoids the need for this for some reason. I'll look into this issue when I get a chance. EDIT: Oooooh, I get it: Sonic 1's layout loading code was modified in Sonic 2's prototypes to specifically repeat the layouts to fill the full width of the level. This means that the game automatically repeated the game's backgrounds, and whatever tool Alex Field used to convert Hidden Palace Zone's layout failed to recreate this behaviour, likely because it was emulating Sonic 1's behaviour instead.
I do remember seeing that (mainly when converting HPZ to the final format; trying to extend its background is a nightmare); there's also one last bug, the worst of them all. ... Sonic's arm not appearing fast enough on the title screen.
That appears to be a weird prototype leftover in Sonic's animation script: at the end of 'byte_1368E', there's a reference to sprite 8, which is just an incomplete duplicate of sprite $12, which Sonic is set to when his arm spawns. Because this duplicate sprite is here, it creates an extra frame before Sonic's arm appears where he's using the sprite that should be used after the arms appears. Not only that, but because this earlier frame is incomplete, Sonic ends up missing *both* of his arms during this frame. EDIT: Speaking of buggy sprites: sprite 5 appears to be one pixel too high, causing Sonic's lower half to be visibly cut-off for a few frames.
Not sure what they were going to do there, unless they were going to make a sprite with both the body AND the face. Also, I might've already mentioned this, but the Obj0E never sets Tails' priority during the intro, meaning a few pixels of his hair (fur?) gets covered by Sonic (yet the priority IS set when skipping it...).
Here's a blink-and-you-miss-it bug: the Egg Prison sprite is incomplete: sprites 1 and 2 have their 'total sprite pieces' value set too low, causing part of the sprite to not appear: Vanilla: Fixed: To fix this, hex-edit the file 'mappings/sprite/obj3E.bin' so that bytes $47 and $89 are 08 instead of 07. Issues like this suggest that the mappings were written in assembly (likely macros): they weren't machine-produced binary files like they are in the disassemblies. Other indicators of this are Obj63's mappings, whose frames aren't sorted from first to last, and the unfinished mappings of Marble Zone's sideway spike pistons in Sonic 1, which have a similar bug to this one.
They were indeed written in assembly, whether by hand or by a program; the Sonic 2 Nick Arcade prototype's symbol lists contain names for both the mapping table (suffixed with "pat", as in "pattern") and the mappings themselves (suffixed with "sp" and then the frame number). I've considered requesting that the Sonic 2 disassembly shift to ASM mappings for this reason.
I wouldn't really consider this a bug? The program is just converting the data that is in the file. I guess I could add a hack for LevelConverter with S2NA layouts.
Quick question: has anyone managed to figure out the issue caused by this bug and a fix for it? When Sonic spin-dashes off a see-saw in Hill Top Zone, the spindash isn't cleared, and he spin-dashes when on-ground. So, logically, you should be able to clear the spindash flag before Sonic springs up. First, I tried finding a proper fix here, which did not work. Then, I tried reproducing the same bug in Sonic 1 and tinkered around with its see-saw object to see if I could fit it, but no dice.
The best part about that bug is that something similar still happens in Ice Cap Zone, so Sega never fixed it. I've seen many hacks (of both Sonic 1 and Sonic 2) which fix it, though. Puto even fixed it in my hack.
Indeed quite many Sonic 1 hacks fix this issue. But the way it's handled is by clearing the spindash in mid-air, even though Sonic 2 retains your spindash if you perform it while a platform collapses. So it feels like a feature rather than a bug since you don't lose the spindash, and the way the SCHG guide documents it fixes the see-saw bug but doesn't fix the bug as mentioned earlier/feature Sonic 2 has.
I fixed it in my Fine2uned hack by adding 'clr.b spindash_flag(a2)' to the start of 'Obj14_LaunchCharacter'.
Here is a change to something that some may consider a bug, but is actually more like an oversight. When Sonic jumps and lands from the jump while holding down, for one frame, he is in his walking animation. This is something that also happens in Sonic 1, Sonic 3 and Sonic & Knuckles. Edit: This can and should be applied to other characters in your hack if you apply this to Sonic. As a visual example: https://twitter.com/SonicPhysics/status/1385881293406318596 This is quite easy to fix/change. Navigate to Code (Text): Sonic_ResetOnFloor: As you can see, in all of the code for Sonic to reset on the floor, he is always set to walk. The code for him to roll on the ground is only initiated after this reset, and therefore he is briefly put into the walk animation. To resolve this, we need to add a new supplementary subroutine. Under the aforementioned label, under the branch to Code (Text): Sonic_ResetOnFloor_Part3 And before the line that sets his walk animation add this directional input check Code (Text): btst #1,(Ctrl_1_Held_Logical).w ; is down being pressed? bne.w Sonic_ResetOnFloor_ContRolling ; if yes, branch After this, head to the bottom of Code (Text): Sonic_ResetOnFloor_Part3 And add Code (Text): bra.w return_1B11E Next, we need to set up a roll status check for this subroutine. Remove this line from Sonic_ResetOnFloor_Part3 Code (Text): move.b #0,flip_turned(a0) Then, after the line that moves 0 to flips_remaining, and before the line that moves 0 to Sonic_Look_Delay_Counter, paste this small bit of code Code (Text): btst #2,status(a0) ; is status set to rolling? beq.s return_1B11E move.b #0,flip_turned(a0) Sonic_ResetOnFloor_Part3 should now look like this Code (Text): Sonic_ResetOnFloor_Part3: bclr #1,status(a0) ; clear in air status bclr #5,status(a0) ; clear pushing status bclr #4,status(a0) ; clear rolljump status move.b #0,jumping(a0) ; clear jumping flag move.w #0,(Chain_Bonus_counter).w move.b #0,flip_angle(a0) move.b #0,flips_remaining(a0) btst #2,status(a0) ; is status set to rolling? beq.s return_1B11E move.b #0,flip_turned(a0) move.w #0,(Sonic_Look_delay_counter).w cmpi.b #$14,anim(a0) bne.w return_1B11E move.b #0,anim(a0) bra.w return_1B11E Now, after the end of Sonic_ResetOnFloor_Part3, and before the label for return_1B11E, add this chunk of code Code (Text): ;===================================================================================================================== ; (Hitaxas) Fix for awful uncurling frame when a character lands on the ground from a jump ; or if they walked off an object or edge of a floor while holding down. ; this "bug" is present in S1-S3K. ;===================================================================================================================== Sonic_ResetOnFloor_ContRolling: jsr Obj01_DoRoll bra.w Sonic_ResetOnFloor_Part3 The final results should look like so: Edit: Cleaned up code to remove redundancy, and fixed a bug where the roll SFX would play even if landing while holding down from a momentumless jump. Edit 2: I am going to completely rewrite this whole post soonish. I have realized there are a lot of optimizations that could be made.
Here is another 1-frame crash when killing a boss: in the HTZ boss, if you deal the eighth hit on Eggman right as the flamethrower finishes and he is about to hover for a bit, the game will crash. In order to fix it, find this: Code (Text): loc_2FDAA: bsr.w loc_300A4 bsr.w loc_2FEDE lea (Ani_obj52).l,a1 bsr.w AnimateBoss jmpto (DisplaySprite).l, JmpTo36_DisplaySprite and change it to this: Code (Text): loc_2FDAA: bsr.w loc_2FEDE lea (Ani_obj52).l,a1 bsr.w AnimateBoss bsr.w loc_300A4 jmpto (DisplaySprite).l, JmpTo36_DisplaySprite The issue here is that loc_300A4 checks for hits and sets boss_routine to 8 if you beat the boss. But AnimateBoss can also increment boss_routine by 2 when the animation of the flamethrower finishes. This causes Obj52_Mobile to select an invalid entry on off_2FD0E to branch to, which crashes.
Have any of you noticed how the Game/Time Over text flashes for 1 frame when it stops moving? The cause is this. In loc_13FCC the game checks if the text has reached its destination but the code that it branches to when the text stops moving is missing a call to DisplaySprite, resulting in the text disappearing for 1 frame and then reappearing after the routine counter is incremented. The fix is simple. Go to loc_13FE2 and replace the rts below there with this: Code (Text): bra.w DisplaySprite This bug is in Sonic 1 and S3K as well.
Ported it to Tails, and eliminated some redundancy while I was at it. Code (Text): Tails_ResetOnFloor: tst.b pinball_mode(a0) bne.s Tails_ResetOnFloor_Part3 btst #1,(Ctrl_1_Held_Logical).w ; is down being pressed? bne.w Tails_ResetOnFloor_ContRolling ; if yes, branch move.b #AniIDSonAni_Walk,anim(a0) ; loc_1CB5C: Tails_ResetOnFloor_Part2: btst #2,status(a0) beq.s Tails_ResetOnFloor_Part3 bclr #2,status(a0) move.b #$F,y_radius(a0) ; this slightly increases Tails' collision height to standing move.b #9,x_radius(a0) move.b #AniIDSonAni_Walk,anim(a0) ; use running/walking/standing animation subq.w #1,y_pos(a0) ; move Tails up 1 pixel so the increased height doesn't push him slightly into the ground ; loc_1CB80: Tails_ResetOnFloor_Part3: bclr #1,status(a0) Tails_ResetOnFloor_Part4: bclr #5,status(a0) bclr #4,status(a0) move.b #0,jumping(a0) move.w #0,(Chain_Bonus_counter).w move.b #0,flip_angle(a0) move.b #0,flips_remaining(a0) move.w #0,(Tails_Look_delay_counter).w if fixBugs btst #2,status(a0) ; is status set to rolling? beq.s return_1CBC4 move.b #0,flip_turned(a0) endif cmpi.b #AniIDSonAni_Hang2,anim(a0) bne.s return_1CBC4 move.b #AniIDSonAni_Walk,anim(a0) return_1CBC4: rts ; End of subroutine Tails_ResetOnFloor Tails_ResetOnFloor_ContRolling: move.b #AniIDSonAni_Roll,anim(a0) ; play rolling animation btst #2,status(a0) ; was status set to rolling? beq.s Tails_ResetOnFloor_Part3 ; if it was, branch bset #2,status(a0) ; otherwise, set roll status move.b #$E,y_radius(a0) ; adjust character's y_radius move.b #7,x_radius(a0) ; same for x_radius addq.w #5,$C(a0) ; move character 5 pixels down, so they aren't floating move.w #SndID_Roll,d0 jsr (PlaySound).l ; play rolling sound - required because this is not part of the normal roll code bra.s Tails_ResetOnFloor_Part4 It affects every character object, and will need to be ported to each of them.
I've done so as well, I should have mentioned it could be applied to Tails as well. I implemented this for both Knuckles and Blaze as well in my hack. There is one more thing that I believe I had overlooked prior. You might notice if you jump straight up without any horizontal momentum, hold down and land... The roll SFX will play even though the character is now ducking. You can easily resolve this by checking for if the character is actually moving using x_vel. Code (Text): Tails_ResetOnFloor_ContRolling: move.b #AniIDSonAni_Roll,anim(a0) ; play rolling animation btst #2,status(a0) ; was status set to rolling? beq.s Tails_ResetOnFloor_Part3 ; if it was, branch bset #2,status(a0) ; otherwise, set roll status move.b #$E,y_radius(a0) ; adjust character's y_radius move.b #7,x_radius(a0) ; same for x_radius addq.w #5,y_pos(a0) ; move character 5 pixels down, so they aren't floating tst.w x_vel(a0) ; is Tails moving? beq.w Tails_ResetOnFloorNotContRolling ; if not, branch move.w #SndID_Roll,d0 jsr (PlaySound).l ; play rolling sound - required because this is not part of the normal roll code Tails_ResetOnFloorNotContRolling: bra.s Tails_ResetOnFloor_Part3 I also noticed one slight issue with your edit, and I have adjusted this to fix that issue. By branching to Tails_ResetOnFloor_Part4 at the end of this subroutine, you are skipping the clearing of the inair status bit. Doing this will cause the character to remain in the roll animation if you are holding down while landing from a momentumless jump. I have edited the initial post to reflect the cleanup. Everything should work correctly.
There is one small problem with this. By jumping straight to DisplaySprite, the path swappers will not be able to delete themselves (which is done in MarkObjGone3), leading to a memory leak as they fill up the Dynamic Object RAM. Instead of DisplaySprite, it should be a jump to MarkObjGone (which is exactly what the Simon Wai prototype did). If you’ve spent any length of time accessing debug mode in Sonic 2, you may well have noticed a small (and in my case, annoying) inconsistency between the sound tests on the Options Screen and the Level Select, specifically, the behavior of the A button when the current index is $71-7F. On the Level Select, it index wraps (e.g., $7A to $0A), but on the options screen, it resets to $00. So what exactly is going on? Let's look at the code for handling the A Button on each screen. Code (Text): OptionScreen_Controls: ... btst #button_A,d0 beq.s + addi.b #$10,d2 ; There is something missing here... cmp.b d3,d2 bls.s + moveq #0,d2 Code (Text): LevSelControls_CheckLR: ... btst #button_A,d1 beq.s + addi.b #$10,d0 andi.b #$7F,d0 ; It's this! Both add $10 to the index, but the level select ANDs the new value with 7F, changing $80-8F to $00-0F. The options menu on the other hand does not, and the index will subsequently be reset to $00 if it is greater than $7F (a check that prevents the options from going to invalid values). Fixing this is simple; just AND the sound index in the same way the level select does: Code (Text): OptionScreen_Controls: ... btst #button_A,d0 beq.s + addi.b #$10,d2 andi.b #$7F,d2 cmp.b d3,d2 bls.s + moveq #0,d2 (Do not touch the cmp, bls, or moveq; they are still required to prevent the other options from going to invalid values.)
Previously, I had posted some design choice changes to modify Sonic's ResetOnFloor function to prevent him from uncurling when landing from a jump and holding down while moving. I have taken everything a step further here, optimized the code and added a bit more to the changes. Now Sonic will no longer uncurl in this situation, but will also no longer display a single walking frame if landing from a standstill. In addition to this, Sonic can now instantly duck or look up if holding up or down when landing from said standstill jump. This allows the player to peelout or spindash the moment they land on the floor. In all, it resolves these issues that have been pointed out in these tweets: https://twitter.com/SonicPhysics/status/1385881293406318596 https://twitter.com/SonicPhysics/status/1385341602244222980 https://twitter.com/JSevion/status/1385472712781479937 If you want to apply the changes to your own Sonic 2 hack, here is the process. I was going to make this a bit more in-depth... but I figure this is more or less sufficient enough. Anyhow: Go to Code (Text): Sonic_ResetOnFloor Replace Code (Text): move.b #AniIDSonAni_Walk,anim(a0) with this chunk: Code (Text): ;===================================================================================================================== ; if up is being held while landing from a standstill jump, play look up animation. ; allows for activating peelout immediately ; however, to do so properly requires a frame perfect input... (enjoy, speedrunners!) ;===================================================================================================================== btst #button_up,(Ctrl_1_Held_Logical).w ; is up being pressed? beq.s .CheckIfDucking ; if not, branch move.b #7,anim(a0) ; use look up animation bra.w Sonic_ResetOnFloor_Part2 ;===================================================================================================================== ; if down is being held while landing from a standstill jump, play ducking animation. ; allows for activating spindash immediately ; however, to do so properly requires a frame perfect input... (enjoy, speedrunners!) ;===================================================================================================================== .CheckIfDucking: btst #button_down,(Ctrl_1_Held_Logical).w ; is down being pressed? beq.s .ReturnToWalkRunDash ; if not, branch tst.w inertia(a0) ; is character moving horizontally? bne.s .ContinueRolling ; if so, branch tst.w y_vel(a0) ; is character moving vertically? bne.s .ContinueRolling ; if so, branch bclr #2,status(a0) ; clear rolling status move.b #$13,y_radius(a0) ; this increases Sonic's collision height to standing move.b #9,x_radius(a0) ; adjust Sonic's collision width to standing jsr Sonic_Duck bra.w Adjust_Y_Pos .ContinueRolling: jsr Obj01_DoRoll ; make character roll bra.w Sonic_ResetOnFloor_Part3 ; still need to clear some flags, etc. .ReturnToWalkRunDash: move.b #AniIDSonAni_Walk,anim(a0) Now proceed to Code (Text): Sonic_ResetOnFloor_Part2 And replace everything up until the label for Code (Text): Sonic_ResetOnFloor_Part3 with this chunk: Code (Text): ;===================================================================================================================== ; this code is called by the code that handles player standing on objects, like platforms or bridges ; some routines outside of Tails' code can call Sonic_ResetOnFloor_Part2 ; when they mean to call Tails_ResetOnFloor_Part2, so fix that here ;===================================================================================================================== ; loc_1B0AC: Sonic_ResetOnFloor_Part2: cmpi.l #Obj_Tails,id(a0) ; is this object ID Tails? beq.w Tails_ResetOnFloor_Part2 ; if so, branch to the Tails version of this code btst #2,status(a0) ; is rolling status set? beq.w Sonic_ResetOnFloor_Part3 ; if so, branch bclr #2,status(a0) ; clear rolling status move.b #$13,y_radius(a0) ; this increases Sonic's collision height to standing move.b #9,x_radius(a0) ; adjust Sonic's collision width to standing tst.w x_vel(a0) ; is character moving? bne.s .ReturnToWalkRunDash ; if so, branch .ReturnToIdle: move.b #$5,anim(a0) ; use standing/idle animation bra.s Adjust_Y_Pos .ReturnToWalkRunDash: move.b #AniIDSonAni_Walk,anim,anim(a0) ; use running/walking/standing animation Adjust_Y_Pos: subq.w #5,y_pos(a0) ; move Sonic up 5 pixels so the increased height doesn't push him into the ground Finally, you need to replace Code (Text): Sonic_ResetOnFloor_Part3 with this chunk here: Code (Text): ;===================================================================================================================== ; clear flags, and signify a true reset on floor ;===================================================================================================================== ; loc_1B0DA: Sonic_ResetOnFloor_Part3: bclr #1,status(a0) ; clear in air status bclr #5,status(a0) ; clear pushing status bclr #4,status(a0) ; clear rolljump status move.b #0,jumping(a0) ; clear jumping flag move.w #0,(Chain_Bonus_counter).w move.b #0,flip_angle(a0) move.b #0,flips_remaining(a0) ;===================================================================================================================== ; if rolling status was already set, we want to branch away from the end of this code ; this prevents an issue where holding down would cause the character to duck rather than roll in most cases ;===================================================================================================================== btst #2,status(a0) ; is status set to rolling? beq.s return_1B11E ;===================================================================================================================== ; clear a few more flags ;===================================================================================================================== move.b #0,flip_turned(a0) move.w #0,(Sonic_Look_delay_counter).w cmpi.b #$14,anim(a0) bne.s return_1B11E move.b #AniIDSonAni_Walk,anim(a0) bra.s return_1B11E ;===================================================================================================================== ; The end ;===================================================================================================================== If there are more optimizations that could be done to this, I would love to know about them. I feel like I did a decent job compared to the previous post. Also; I have applied these changes to Sonic, Tails and Knuckles in a personal fork of Sonic 2 Community's Cut. Which can be viewed here: https://github.com/Hitaxas/Sonic-2-Communitys-Cut Additionally, I have applied some more bug fixes from this thread to it. Along with some other things, and will update it once in a while to fix more bugs, or add more features. So use it as you wish, or contribute to the bug fixes. Edit: Changed some .w branches to .s Edit 2: Forgot to account for vertical movement in the duck/roll code, which caused a rare case where sometimes the character would duck when landing on a slope while holding down, instead of rolling like they should.