Fix the Level Select graphics bug
From Sonic Retro
(Original guide by MarkeyJester)
If you use Sonic 1's Level Select very often, you might have noticed this:
As you can see, a strip of Green Hill Zone's background has appeared in white/yellow. This doesn't happen very often and is a rare glitch, it requires the level select to be entered at very specific times, so 90% of the time, you will never cross it.
The fix is the epitome of simplicity; you find the routine "LevelSelect:"
LevelSelect: move.b #4,($FFFFF62A).w bsr.w DelayProgram
And change the 4, to a 2. It couldn't be simpler.
Explaining the issue
Why the bug occurs however, is somewhat difficult to explain.
Sonic 1's level drawing software is designed to render the level graphics in an optimal way, both of which are designed to keep performance up, and memory down. The performance part is done by drawing only single strips of a levels mappings on the outside of the screen, as it scrolls onto the screen and into display. Sonic 1's backgrounds have a series of strips, in Green Hill Zone for example, you'll have one vertical strip for the sky and top mountains, and another strip for the bottom mountains/waterfalls, and sea. Each section of the background draws its strip outside the screen when the it moves 10 (16 decimal) pixels to the left or right. This ensures each background renders a strip at the correct time.
This however, is what is responsible for the mess that occurs rarely in the level select. The scroll routines will reposition the background strips correctly, and if any section moves more than 10 (16 decimal) pixels to the left (which it will), then it'll set the correct draw flag. This is done inside the main routine loop, the actual drawing however, is done inside various v-blank routines. The value "4" that you changed above, is the v-blank routine index value:
off_B6E: dc.w loc_B88-off_B6E, loc_C32-off_B6E dc.w loc_C44-off_B6E, loc_C5E-off_B6E dc.w loc_C6E-off_B6E, loc_DA6-off_B6E dc.w loc_E72-off_B6E, loc_F8A-off_B6E dc.w loc_C64-off_B6E, loc_F9A-off_B6E dc.w loc_C36-off_B6E, loc_FA6-off_B6E dc.w loc_E72-off_B6E
Where "loc_C44" is routine 4. Inside the routine you'll find:
loc_C44: bsr.w sub_106E bsr.w sub_6886 bsr.w sub_1642
"sub_106E" is the routine responsible for updating the palette, scrolling and sprites. "sub_1642" is responsible for Nemesis decompression PLC. And "sub_6886" is part of the draw code (more specifically to draw only the background, never the foreground).
So what's happening here is; the scroll routine scrolls a section 10 (16 decimal) pixels left, and sets the draw flag(s), start button is checked to see if the level should start or if level select should be run. The level select routine is run, which clears the background mappings, and then the level select routine allows the v-blank routine to run, which then allows the draw code to read the draw flag(s) and draw a strip, even though it doesn't need to.
The background sections usually have to be at positions of 0010 pixels (starting from 00C0, going up 00D0, 00E0, 00F0, etc).
We changed the routine number to 2 "loc_C32", which doesn't run the draw code at all, hence doesn't draw the graphical strip.
The main fix shown above, is the simplest, but doesn't "fix" per-se, it more avoids the issue. Another way of fixing the issue is done by going to routine "Title_ChkLevSel:"
Title_ChkLevSel: tst.b ($FFFFFFE0).w ; check if level select code is on beq.w PlayLevel ; if not, play level btst #6,($FFFFF604).w ; check if A is pressed beq.w PlayLevel ; if not, play level
And under that, placing:
move.b #4,($FFFFF62A).w bsr.w DelayProgram
To allow the draw code to read the flag(s), do its drawing before the background is cleared, and clear the flag(s).
Another alternative (perhaps the most correct given the situation) is by simply clearing the flag(s) directly, again under the code above at "Title_ChkLevSel:":
moveq #$00,d0 ; clear d0 move.b d0,($FFFFFF32).w ; clear background strip 1 draw flags move.b d0,($FFFFFF34).w ; clear background strip 2 draw flags move.b d0,($FFFFFF30).w ; clear foreground strip draw flags
Please note, REV01 background will have more strips, thus more flags to be cleared.