Bottom Boundary Fixes - RetroKoH/S1Fixed GitHub Wiki

(Original guides by RetroKoH and Mercury)
Source: See commits
Original Commit: 20994df

On every frame, the game checks to see if Sonic's y-position crosses the bottom screen boundary. If it does, then Sonic dies. As you proceed through the levels, a mechanic referred to as Dynamic Level Events (DLEs) will alter this lower boundary in real time, allowing different parts of the level to have different heights.

A fairly well-known bug related to this mechanic exists in Sonic 1 where going down the S-Tunnels in Green Hill Zone Act 1 too quickly results in a death once you hit the bottom of the screen. This happens because the DLE sets the bottom boundary to go lower, but said boundary doesn't advance quickly enough.

There are a couple of different solutions to this problem. You can either edit the DLEs to lower the boundary earlier (which is what ReadySonic and Sonic Origins both do), or you can alter the manner in which the bottom boundary kills Sonic. S1Fixed does the latter, but I will show you how to do both.

Editing DLEs

(Credit: Mercury)
It might be undesirable to have Sonic dip under the screen in the tunnels. If that's the case, this should fix that for you. In _inc\DynamicLevelEvents.asm, at DLE_GHZ1, add these lines:

	move.w	#$300,(v_limitbtm1).w ; set lower y-boundary
+	move.w	#$300,(v_limitbtm2).w ; set lower y-boundary
	cmpi.w	#$1780,(v_screenposx).w ; has the camera reached $1780 on x-axis?
	bcs.s	locret_6E08	; if not, branch
	move.w	#$400,(v_limitbtm1).w ; set lower y-boundary
+	move.w	#$400,(v_limitbtm2).w ; set lower y-boundary

This sets "v_limitbtm2" both times "v_limitbtm1" is also set, forcing the boundary to change instantaneously rather than adjusting slowly. Because of how much higher Sonic is than the lower boundary when this triggers, it's not noticeable.

Altering Bottom Boundary Behavior

(Credit: RetroKoH)
Ultimately, the problem lies in the fact that Sonic dies when he crosses the actual level boundary (v_limitbtm2, which is moving downward) and not the target level boundary (v_limitbtm1). We could just have it check the target bottom boundary instead, as ReadySonic does, but this leaves us susceptible to a sudden death bug in GHZ3, one that is also noticeable in Sonic 3K if you try using debug mode to get into lower routes. The best way to remove any instance of sudden death is to run a comparison between the target and actual boundaries, and use the more appropriate value to compare against Sonic's position.

.chkbottom:
	move.w	(v_limitbtm2).w,d0	; d0 = actual bottom boundary
+	cmp.w	(v_limitbtm1).w,d0	; is the target bottom boundary lower than the actual one?
+	bcc.s	.notlower		; if not, branch
+	move.w	(v_limitbtm1).w,d0	; d0 = target bottom boundary

+.notlower:
	addi.w	#224,d0
	cmp.w	obY(a0),d0		; has Sonic touched the	bottom boundary?
	blt.s	.bottom			; if yes, branch
	rts

If the target boundary is lower than the actual boundary, then this means the actual boundary is scrolling downward and shouldn't kill Sonic. Otherwise, it either means that the boundary is equal to its target, or it is moving upward to meet its target. In both instances, we can just use the actual bottom boundary to kill Sonic.