Actions

SCHG How-to

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:

LevelSelectS1Glitch.png

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.

Alternative fixes

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.

SCHG How-To Guide: Sonic the Hedgehog (16-bit)
Fixing Bugs
Fix Demo Playback | Fix a Race Condition with Pattern Load Cues | Fix the SEGA Sound | Display the Press Start Button Text | Fix the Level Select Menu | Fix the Hidden Points Bug | Fix Accidental Deletion of Scattered Rings | Fix Ring Timers | Fix the Walk-Jump Bug | Correct Drowning Bugs | Fix the Death Boundary Bug | Fix the Camera Follow Bug | Fix Song Restoration Bugs | Fix the HUD Blinking | Fix the Level Select Graphics Bug
Changing Design Choices
Change Spike Behavior | Fix Special Stage Jumping Physics | Improve the Fade In\Fade Out Progression Routines | Fix Scattered Rings' Underwater Physics | Remove the Speed Cap | Port the REV01 Background Effects | Port Sonic 2's Level Art Loader | Retain Rings Between Acts | Add Sonic 2 (Simon Wai Prototype) Level Select | Improve ObjectMove Subroutines
Adding Features
Add Spin Dash ( Part 1 / Part 2 / Part 3 / Part 4 ) | Add Eggman Monitor
Sound Features
Expand Music Index From $94 to $9F | Extend Music Slots | Play Different Songs Per Act | Expand Music Index to Start at $00 | Port Sonic 2 Final Sound Driver | Port Sonic 3's Sound Driver
Extending the Game
Load Chunks From ROM | Add Extra Characters | Make an Alternative Title Screen | Use Dynamic Tilesets | Make GHZ Load Alternate Art | Add a New Zone | Set Up the Goggle Monitor | Add New Moves | Add a Dynamic Collision System | Dynamic Special Stage Walls System | Extend Sprite Mappings and Art Limit | Enigma Credits
Miscellaneous
Convert the Hivebrain 2005 Disassembly to ASM68K
Split Disassembly Guides
Set Up a Split Disassembly | Basic Level Editing | Basic Art Editing | Basic ASM Editing (Spin Dash)