Horizontal Scrolling Fixes - RetroKoH/S1Fixed GitHub Wiki

(Original guide by MarkeyJester)
Source: Sonic Retro thread
Commit: 8c15757

Here's a fix for a bug that you cannot cause very easily in stock Sonic 1, but if one were to code something like a cut-scene involving the screen moving to the right ahead of Sonic, and then letting the screen move back to Sonic, then they would probably see this bug in action.

The mechanic that has the screen following Sonic ensures that the screen does not move more than $10 (16) pixels in any direction at a time. This is to allow the drawing routine to draw the lines of 16x16 blocks correctly without skipping any lines. This means that if Sonic is away from the screen's "central box" position by less than 16 pixels, then the screen's "central box" will move directly to Sonic, if, however, Sonic is further than that, the screen will move at a maximum of 16 pixels in the direction it is suppose to be moving to. The routine ScrollVertical handles this for moving the screen up and down, ScrollHoriz on the other hand (or should I say MoveScreenHoriz) handles this for the screen moving right, but not when moving left.

Open _inc/DeformLayers.asm and observe this code for right movement:

SH_AheadOfMid:
	cmpi.w	#16,d0		; is Sonic within 16px of middle area?
	blo.s	SH_Ahead16	; if yes, branch
	move.w	#16,d0		; set to 16 if greater

SH_Ahead16:
	add.w	(v_screenposx).w,d0
	cmp.w	(v_limitright2).w,d0
	blt.s	SH_SetScreen
	move.w	(v_limitright2).w,d0

The first three lines serve as a restriction of sorts, preventing the screen from moving right by more than 16 pixels.
Meanwhile, if we look at the code for left movement, no such restriction exists. Let's add that here:

SH_BehindMid:
+	cmpi.w	#$FFF0,d0	; has the screen moved more than 16 pixels left?
+	bcc.s	SH_Behind16	; if not, branch
+	move.w	#$FFF0,d0	; set the maximum move distance to 16 pixels left

+SH_Behind16:
	add.w	(v_screenposx).w,d0
	cmp.w	(v_limitleft2).w,d0
	bgt.s	SH_SetScreen
	move.w	(v_limitleft2).w,d0
	bra.s	SH_SetScreen
; End of function MoveScreenHoriz

There is another bug related to this which is also related to the good old "screen wrapping vertically" bug in Sonic 2 and up, but this is to do with horizontal rather than vertical. Let's look at MoveScreenHoriz. Pay close attention to the two conditional branches:

MoveScreenHoriz:
	move.w	(v_player+obX).w,d0
	sub.w	(v_screenposx).w,d0	; Sonic's distance from left edge of screen
	subi.w	#144,d0			; is distance less than 144px?
	bcs.s	SH_BehindMid		; if yes, branch
	subi.w	#16,d0			; is distance more than 160px?
	bcc.s	SH_AheadOfMid		; if yes, branch
	clr.w	(v_scrshiftx).w
	rts	

bcc and bcs are unsigned branches. The problem here is that if Sonic is behind the screen (I.e. to the left), the result will be negative (I.e. a value from 8000 to FFFF), but with the unsigned branches it will obviously treat 8000 to FFFF as positive and higher than 7FFF, thus it believes it has to move right rather than left, the fix is also simple, change the unsigned branch conditions with signed branch conditions bmi and bpl, respectively:

MoveScreenHoriz:
	move.w	(v_player+obX).w,d0
	sub.w	(v_screenposx).w,d0	; Sonic's distance from left edge of screen
	subi.w	#144,d0			; is distance less than 144px?
!	bmi.s	SH_BehindMid		; if yes, branch
	subi.w	#16,d0			; is distance more than 160px?
!	bpl.s	SH_AheadOfMid		; if yes, branch
	clr.w	(v_scrshiftx).w
	rts	

The cc to pl actually shouldn't matter, but it's best to be safe.