Improve the fade in\fade out progression routines in Sonic 1
From Sonic Retro
(Original guide by MarkeyJester) Github Adaptation By CK15 Extra Part by Shadow05
The palette fading routines in Sonic 1 and Sonic 2 (and possibly to a degree Sonic 3 & Knuckles) known as "Pal_FadeTo" and "Pal_FadeFrom", are not 100% true to their name. Here is a screenshot of Sonic 1's title screen 6 frames during fade in:
From Sonic Team's point of view, it may not be incorrect and is possibly intentional, however, from a logical point of view, this is incorrect fading, what happens here is it fades the blue parts in first, then fades the green parts in, and then finally fades the red parts in, the same goes for fading out, red first, then green, finally blue. Strictly speaking for a the nearest possible perfect fade, all colours need to be fading in and out simultaneously (At the correct timing of course), so for example the colour 06E4 should fade in the pattern of:
0020 | 0040 | 0060 | 0080 | 02A0 | 04C2 | 06E4 |
And fade out in the pattern of:
04C2 | 02A0 | 0080 | 0060 | 0040 | 0020 | 0000 |
Hivebrain
To make the above function for Sonic 1, you'll need to go to the routine "Pal_FadeTo" and replace everything from there down to "; End of function Pal_DecColor" with this:
Pal_FadeTo:
move.w #$3F,($FFFFF626).w
Pal_FadeTo2:
moveq #0,d0
lea ($FFFFFB00).w,a0
move.b ($FFFFF626).w,d0
adda.w d0,a0
moveq #0,d1
move.b ($FFFFF627).w,d0
Pal_ToBlack:
move.w d1,(a0)+
dbf d0,Pal_ToBlack ; fill pallet with $000 (black)
moveq #$0E,d4 ; MJ: prepare maximum colour check
moveq #$00,d6 ; MJ: clear d6
loc_1DCE:
bsr.w RunPLC_RAM
move.b #$12,($FFFFF62A).w
bsr.w DelayProgram
bchg #$00,d6 ; MJ: change delay counter
beq loc_1DCE ; MJ: if null, delay a frame
bsr.s Pal_FadeIn
subq.b #$02,d4 ; MJ: decrease colour check
bne loc_1DCE ; MJ: if it has not reached null, branch
move.b #$12,($FFFFF62A).w ; MJ: wait for V-blank again (so colours transfer)
bra DelayProgram ; MJ: ''
; End of function Pal_FadeTo
; ---------------------------------------------------------------------------
; Pallet fade-in subroutine
; ---------------------------------------------------------------------------
; ||||||||||||||| S U B R O U T I N E |||||||||||||||||||||||||||||||||||||||
Pal_FadeIn: ; XREF: Pal_FadeTo
moveq #0,d0
lea ($FFFFFB00).w,a0
lea ($FFFFFB80).w,a1
move.b ($FFFFF626).w,d0
adda.w d0,a0
adda.w d0,a1
move.b ($FFFFF627).w,d0
loc_1DFA:
bsr.s Pal_AddColor
dbf d0,loc_1DFA
cmpi.b #1,($FFFFFE10).w
bne.s locret_1E24
moveq #0,d0
lea ($FFFFFA80).w,a0
lea ($FFFFFA00).w,a1
move.b ($FFFFF626).w,d0
adda.w d0,a0
adda.w d0,a1
move.b ($FFFFF627).w,d0
loc_1E1E:
bsr.s Pal_AddColor
dbf d0,loc_1E1E
locret_1E24:
rts
; End of function Pal_FadeIn
; ||||||||||||||| S U B R O U T I N E |||||||||||||||||||||||||||||||||||||||
Pal_AddColor: ; XREF: Pal_FadeIn
move.b (a1),d5 ; MJ: load blue
move.w (a1)+,d1 ; MJ: load green and red
move.b d1,d2 ; MJ: load red
lsr.b #$04,d1 ; MJ: get only green
andi.b #$0E,d2 ; MJ: get only red
move.w (a0),d3 ; MJ: load current colour in buffer
cmp.b d5,d4 ; MJ: is it time for blue to fade?
bhi FCI_NoBlue ; MJ: if not, branch
addi.w #$0200,d3 ; MJ: increase blue
FCI_NoBlue:
cmp.b d1,d4 ; MJ: is it time for green to fade?
bhi FCI_NoGreen ; MJ: if not, branch
addi.b #$20,d3 ; MJ: increase green
FCI_NoGreen:
cmp.b d2,d4 ; MJ: is it time for red to fade?
bhi FCI_NoRed ; MJ: if not, branch
addq.b #$02,d3 ; MJ: increase red
FCI_NoRed:
move.w d3,(a0)+ ; MJ: save colour
rts ; MJ: return
; End of function Pal_AddColor
; ||||||||||||||| S U B R O U T I N E |||||||||||||||||||||||||||||||||||||||
Pal_FadeFrom:
move.w #$3F,($FFFFF626).w
moveq #$07,d4 ; MJ: set repeat times
moveq #$00,d6 ; MJ: clear d6
loc_1E5C:
bsr.w RunPLC_RAM
move.b #$12,($FFFFF62A).w
bsr.w DelayProgram
bchg #$00,d6 ; MJ: change delay counter
beq loc_1E5C ; MJ: if null, delay a frame
bsr.s Pal_FadeOut
dbf d4,loc_1E5C
rts
; End of function Pal_FadeFrom
; ---------------------------------------------------------------------------
; Pallet fade-out subroutine
; ---------------------------------------------------------------------------
; ||||||||||||||| S U B R O U T I N E |||||||||||||||||||||||||||||||||||||||
Pal_FadeOut: ; XREF: Pal_FadeFrom
moveq #0,d0
lea ($FFFFFB00).w,a0
move.b ($FFFFF626).w,d0
adda.w d0,a0
move.b ($FFFFF627).w,d0
loc_1E82:
bsr.s Pal_DecColor
dbf d0,loc_1E82
moveq #0,d0
lea ($FFFFFA80).w,a0
move.b ($FFFFF626).w,d0
adda.w d0,a0
move.b ($FFFFF627).w,d0
loc_1E98:
bsr.s Pal_DecColor
dbf d0,loc_1E98
rts
; End of function Pal_FadeOut
; ||||||||||||||| S U B R O U T I N E |||||||||||||||||||||||||||||||||||||||
Pal_DecColor: ; XREF: Pal_FadeOut
move.w (a0),d5 ; MJ: load colour
move.w d5,d1 ; MJ: copy to d1
move.b d1,d2 ; MJ: load green and red
move.b d1,d3 ; MJ: load red
andi.w #$0E00,d1 ; MJ: get only blue
beq FCO_NoBlue ; MJ: if blue is finished, branch
subi.w #$0200,d5 ; MJ: decrease blue
FCO_NoBlue:
andi.w #$00E0,d2 ; MJ: get only green (needs to be word)
beq FCO_NoGreen ; MJ: if green is finished, branch
subi.b #$20,d5 ; MJ: decrease green
FCO_NoGreen:
andi.b #$0E,d3 ; MJ: get only red
beq FCO_NoRed ; MJ: if red is finished, branch
subq.b #$02,d5 ; MJ: decrease red
FCO_NoRed:
move.w d5,(a0)+ ; MJ: save new colour
rts ; MJ: return
; End of function Pal_DecColor
Github
To make the above function for Sonic 1 GitHub, you'll need to go to the routine "PaletteFadeIn" and replace everything from there down to "; End of function FadeOut_DecColor" with this:
PaletteFadeIn:
move.w #$3F,($FFFFF626).w
PalFadeIn_Alt:
moveq #0,d0
lea ($FFFFFB00).w,a0
move.b ($FFFFF626).w,d0
adda.w d0,a0
moveq #0,d1
move.b ($FFFFF627).w,d0
Pal_ToBlack:
move.w d1,(a0)+
dbf d0,Pal_ToBlack ; fill pallet with $000 (black)
moveq #$0E,d4 ; MJ: prepare maximum colour check
moveq #$00,d6 ; MJ: clear d6
loc_1DCE:
bsr.w RunPLC
move.b #$12,($FFFFF62A).w
bsr.w WaitforVBla
bchg #$00,d6 ; MJ: change delay counter
beq loc_1DCE ; MJ: if null, delay a frame
bsr.s Pal_FadeIn
subq.b #$02,d4 ; MJ: decrease colour check
bne loc_1DCE ; MJ: if it has not reached null, branch
move.b #$12,($FFFFF62A).w ; MJ: wait for V-blank again (so colours transfer)
bra WaitforVBla ; MJ: ''
; End of function Pal_FadeTo
; ---------------------------------------------------------------------------
; Pallet fade-in subroutine
; ---------------------------------------------------------------------------
; ||||||||||||||| S U B R O U T I N E |||||||||||||||||||||||||||||||||||||||
Pal_FadeIn: ; XREF: Pal_FadeTo
moveq #0,d0
lea ($FFFFFB00).w,a0
lea ($FFFFFB80).w,a1
move.b ($FFFFF626).w,d0
adda.w d0,a0
adda.w d0,a1
move.b ($FFFFF627).w,d0
loc_1DFA:
bsr.s Pal_AddColor
dbf d0,loc_1DFA
cmpi.b #1,($FFFFFE10).w
bne.s locret_1E24
moveq #0,d0
lea ($FFFFFA80).w,a0
lea ($FFFFFA00).w,a1
move.b ($FFFFF626).w,d0
adda.w d0,a0
adda.w d0,a1
move.b ($FFFFF627).w,d0
loc_1E1E:
bsr.s Pal_AddColor
dbf d0,loc_1E1E
locret_1E24:
rts
; End of function Pal_FadeIn
; ||||||||||||||| S U B R O U T I N E |||||||||||||||||||||||||||||||||||||||
Pal_AddColor: ; XREF: Pal_FadeIn
move.b (a1),d5 ; MJ: load blue
move.w (a1)+,d1 ; MJ: load green and red
move.b d1,d2 ; MJ: load red
lsr.b #$04,d1 ; MJ: get only green
andi.b #$0E,d2 ; MJ: get only red
move.w (a0),d3 ; MJ: load current colour in buffer
cmp.b d5,d4 ; MJ: is it time for blue to fade?
bhi FCI_NoBlue ; MJ: if not, branch
addi.w #$0200,d3 ; MJ: increase blue
FCI_NoBlue:
cmp.b d1,d4 ; MJ: is it time for green to fade?
bhi FCI_NoGreen ; MJ: if not, branch
addi.b #$20,d3 ; MJ: increase green
FCI_NoGreen:
cmp.b d2,d4 ; MJ: is it time for red to fade?
bhi FCI_NoRed ; MJ: if not, branch
addq.b #$02,d3 ; MJ: increase red
FCI_NoRed:
move.w d3,(a0)+ ; MJ: save colour
rts ; MJ: return
; End of function Pal_AddColor
; ||||||||||||||| S U B R O U T I N E |||||||||||||||||||||||||||||||||||||||
PaletteFadeOut:
move.w #$3F,($FFFFF626).w
moveq #$07,d4 ; MJ: set repeat times
moveq #$00,d6 ; MJ: clear d6
loc_1E5C:
bsr.w RunPLC
move.b #$12,($FFFFF62A).w
bsr.w WaitforVBla
bchg #$00,d6 ; MJ: change delay counter
beq loc_1E5C ; MJ: if null, delay a frame
bsr.s FadeOut_ToBlack
dbf d4,loc_1E5C
rts
; End of function Pal_FadeFrom
; ---------------------------------------------------------------------------
; Pallet fade-out subroutine
; ---------------------------------------------------------------------------
; ||||||||||||||| S U B R O U T I N E |||||||||||||||||||||||||||||||||||||||
FadeOut_ToBlack: ; XREF: Pal_FadeFrom
moveq #0,d0
lea ($FFFFFB00).w,a0
move.b ($FFFFF626).w,d0
adda.w d0,a0
move.b ($FFFFF627).w,d0
loc_1E82:
bsr.s Pal_DecColor
dbf d0,loc_1E82
moveq #0,d0
lea ($FFFFFA80).w,a0
move.b ($FFFFF626).w,d0
adda.w d0,a0
move.b ($FFFFF627).w,d0
loc_1E98:
bsr.s Pal_DecColor
dbf d0,loc_1E98
rts
; End of function Pal_FadeOut
; ||||||||||||||| S U B R O U T I N E |||||||||||||||||||||||||||||||||||||||
Pal_DecColor: ; XREF: Pal_FadeOut
move.w (a0),d5 ; MJ: load colour
move.w d5,d1 ; MJ: copy to d1
move.b d1,d2 ; MJ: load green and red
move.b d1,d3 ; MJ: load red
andi.w #$0E00,d1 ; MJ: get only blue
beq FCO_NoBlue ; MJ: if blue is finished, branch
subi.w #$0200,d5 ; MJ: decrease blue
FCO_NoBlue:
andi.w #$00E0,d2 ; MJ: get only green (needs to be word)
beq FCO_NoGreen ; MJ: if green is finished, branch
subi.b #$20,d5 ; MJ: decrease green
FCO_NoGreen:
andi.b #$0E,d3 ; MJ: get only red
beq FCO_NoRed ; MJ: if red is finished, branch
subq.b #$02,d5 ; MJ: decrease red
FCO_NoRed:
move.w d5,(a0)+ ; MJ: save new colour
rts ; MJ: return
; End of function Pal_DecColor
This will ensure that the colour fading scheme fades normally and smoothly, here is the new fading routine in action, 6 frames after fading in:
A little more
In addition if you want the Sega Logo to fade from black goto SegaScreen:
bsr.w Pal_FadeFrom
and replace it with this. (if you have Hivebrain)
bsr.w Pal_MakeFlash
If you are using GitHub, replace
bsr.w PaletteFadeFrom
with
bsr.w PaletteWhiteOut
Now the Sega Screen will fade from black.
HOWEVER if you followed this guide you will notice that the Segascreen doesn't clear the title screen art!, to fix it add this after the bsr.w ClearPLC
bsr.w ClearScreen
|Improve the fade in\fade out progression routines in Sonic 1]]