diff --git a/src/d_main.c b/src/d_main.c
index 52f1d2997fabc05e65858dd36b7da4776ae152f7..ca40fcb24783aece322647b61d619e2da06beeb2 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -227,7 +227,14 @@ static void D_Display(void)
 		SCR_SetMode(); // change video mode
 
 	if (vid.recalc)
+	{
 		SCR_Recalc(); // NOTE! setsizeneeded is set by SCR_Recalc()
+#ifdef HWRENDER
+		// Shoot! The screen texture was flushed!
+		if ((rendermode == render_opengl) && (gamestate == GS_INTERMISSION))
+			usebuffer = false;
+#endif
+	}
 
 	// change the view size if needed
 	if (setsizeneeded)
@@ -415,6 +422,7 @@ static void D_Display(void)
 				if (rendermode == render_soft)
 				{
 					VID_BlitLinearScreen(screens[0], screens[1], vid.width*vid.bpp, vid.height, vid.width*vid.bpp, vid.rowbytes);
+					Y_ConsiderScreenBuffer();
 					usebuffer = true;
 				}
 				lastdraw = false;
diff --git a/src/dehacked.c b/src/dehacked.c
index aaeffd63565b5f58d424db1caf45bf3433979d94..2f89fb675073053cc3461fbd1271203006646cfb 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -2280,12 +2280,59 @@ static void readmenu(MYFILE *f, INT32 num)
 				menupres[num].bgcolor = get_number(word2);
 				titlechanged = true;
 			}
-			else if (fastcmp(word, "HIDETITLEPICS") || fastcmp(word, "HIDEPICS"))
+			else if (fastcmp(word, "HIDETITLEPICS") || fastcmp(word, "HIDEPICS") || fastcmp(word, "TITLEPICSHIDE"))
 			{
 				// true by default, except MM_MAIN
 				menupres[num].hidetitlepics = (boolean)(value || word2[0] == 'T' || word2[0] == 'Y');
 				titlechanged = true;
 			}
+			else if (fastcmp(word, "TITLEPICSMODE"))
+			{
+				if (fastcmp(word2, "USER"))
+					menupres[num].ttmode = TTMODE_USER;
+				else if (fastcmp(word2, "ALACROIX"))
+					menupres[num].ttmode = TTMODE_ALACROIX;
+				else if (fastcmp(word2, "HIDE") || fastcmp(word2, "HIDDEN") || fastcmp(word2, "NONE"))
+				{
+					menupres[num].ttmode = TTMODE_USER;
+					menupres[num].ttname[0] = 0;
+					menupres[num].hidetitlepics = true;
+				}
+				else // if (fastcmp(word2, "OLD") || fastcmp(word2, "SSNTAILS"))
+					menupres[num].ttmode = TTMODE_OLD;
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "TITLEPICSSCALE"))
+			{
+				// Don't handle Alacroix special case here; see Maincfg section.
+				menupres[num].ttscale = max(1, min(8, (UINT8)get_number(word2)));
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "TITLEPICSNAME"))
+			{
+				strncpy(menupres[num].ttname, word2, 9);
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "TITLEPICSX"))
+			{
+				menupres[num].ttx = (INT16)get_number(word2);
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "TITLEPICSY"))
+			{
+				menupres[num].tty = (INT16)get_number(word2);
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "TITLEPICSLOOP"))
+			{
+				menupres[num].ttloop = (INT16)get_number(word2);
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "TITLEPICSTICS"))
+			{
+				menupres[num].tttics = (UINT16)get_number(word2);
+				titlechanged = true;
+			}
 			else if (fastcmp(word, "TITLESCROLLSPEED") || fastcmp(word, "TITLESCROLLXSPEED")
 				|| fastcmp(word, "SCROLLSPEED") || fastcmp(word, "SCROLLXSPEED"))
 			{
@@ -2499,6 +2546,7 @@ static actionpointer_t actionpointers[] =
 	{{A_ThrownRing},             "A_THROWNRING"},
 	{{A_SetSolidSteam},          "A_SETSOLIDSTEAM"},
 	{{A_UnsetSolidSteam},        "A_UNSETSOLIDSTEAM"},
+	{{A_SignSpin},               "S_SIGNSPIN"},
 	{{A_SignPlayer},             "A_SIGNPLAYER"},
 	{{A_OverlayThink},           "A_OVERLAYTHINK"},
 	{{A_JetChase},               "A_JETCHASE"},
@@ -3688,11 +3736,78 @@ static void readmaincfg(MYFILE *f)
 				titlemap = (INT16)value;
 				titlechanged = true;
 			}
-			else if (fastcmp(word, "HIDETITLEPICS"))
+			else if (fastcmp(word, "HIDETITLEPICS") || fastcmp(word, "TITLEPICSHIDE"))
 			{
 				hidetitlepics = (boolean)(value || word2[0] == 'T' || word2[0] == 'Y');
 				titlechanged = true;
 			}
+			else if (fastcmp(word, "TITLEPICSMODE"))
+			{
+				if (fastcmp(word2, "USER"))
+					ttmode = TTMODE_USER;
+				else if (fastcmp(word2, "ALACROIX"))
+					ttmode = TTMODE_ALACROIX;
+				else if (fastcmp(word2, "HIDE") || fastcmp(word2, "HIDDEN") || fastcmp(word2, "NONE"))
+				{
+					ttmode = TTMODE_USER;
+					ttname[0] = 0;
+					hidetitlepics = true;
+				}
+				else // if (fastcmp(word2, "OLD") || fastcmp(word2, "SSNTAILS"))
+					ttmode = TTMODE_OLD;
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "TITLEPICSSCALE"))
+			{
+				ttscale = max(1, min(8, (UINT8)get_number(word2)));
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "TITLEPICSSCALESAVAILABLE"))
+			{
+				// SPECIAL CASE for Alacroix: Comma-separated list of resolutions that are available
+				// for gfx loading.
+				ttavailable[0] = ttavailable[1] = ttavailable[2] = ttavailable[3] =\
+					ttavailable[4] = ttavailable[5] = false;
+
+				if (strstr(word2, "1") != NULL)
+					ttavailable[0] = true;
+				if (strstr(word2, "2") != NULL)
+					ttavailable[1] = true;
+				if (strstr(word2, "3") != NULL)
+					ttavailable[2] = true;
+				if (strstr(word2, "4") != NULL)
+					ttavailable[3] = true;
+				if (strstr(word2, "5") != NULL)
+					ttavailable[4] = true;
+				if (strstr(word2, "6") != NULL)
+					ttavailable[5] = true;
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "TITLEPICSNAME"))
+			{
+				strncpy(ttname, word2, 9);
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "TITLEPICSX"))
+			{
+				ttx = (INT16)get_number(word2);
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "TITLEPICSY"))
+			{
+				tty = (INT16)get_number(word2);
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "TITLEPICSLOOP"))
+			{
+				ttloop = (INT16)get_number(word2);
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "TITLEPICSTICS"))
+			{
+				tttics = (UINT16)get_number(word2);
+				titlechanged = true;
+			}
 			else if (fastcmp(word, "TITLESCROLLSPEED") || fastcmp(word, "TITLESCROLLXSPEED"))
 			{
 				titlescrollxspeed = get_number(word2);
@@ -5593,59 +5708,18 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_BUBBLES4",
 
 	// Level End Sign
-	"S_SIGN1",
-	"S_SIGN2",
-	"S_SIGN3",
-	"S_SIGN4",
-	"S_SIGN5",
-	"S_SIGN6",
-	"S_SIGN7",
-	"S_SIGN8",
-	"S_SIGN9",
-	"S_SIGN10",
-	"S_SIGN11",
-	"S_SIGN12",
-	"S_SIGN13",
-	"S_SIGN14",
-	"S_SIGN15",
-	"S_SIGN16",
-	"S_SIGN17",
-	"S_SIGN18",
-	"S_SIGN19",
-	"S_SIGN20",
-	"S_SIGN21",
-	"S_SIGN22",
-	"S_SIGN23",
-	"S_SIGN24",
-	"S_SIGN25",
-	"S_SIGN26",
-	"S_SIGN27",
-	"S_SIGN28",
-	"S_SIGN29",
-	"S_SIGN30",
-	"S_SIGN31",
-	"S_SIGN32",
-	"S_SIGN33",
-	"S_SIGN34",
-	"S_SIGN35",
-	"S_SIGN36",
-	"S_SIGN37",
-	"S_SIGN38",
-	"S_SIGN39",
-	"S_SIGN40",
-	"S_SIGN41",
-	"S_SIGN42",
-	"S_SIGN43",
-	"S_SIGN44",
-	"S_SIGN45",
-	"S_SIGN46",
-	"S_SIGN47",
-	"S_SIGN48",
-	"S_SIGN49",
-	"S_SIGN50",
-	"S_SIGN51",
-	"S_SIGN52", // Eggman
-	"S_SIGN53",
+	"S_SIGN",
+	"S_SIGNSPIN1",
+	"S_SIGNSPIN2",
+	"S_SIGNSPIN3",
+	"S_SIGNSPIN4",
+	"S_SIGNSPIN5",
+	"S_SIGNSPIN6",
+	"S_SIGNPLAYER",
+	"S_SIGNSLOW",
+	"S_SIGNSTOP",
+	"S_SIGNBOARD",
+	"S_EGGMANSIGN",
 
 	// Spike Ball
 	"S_SPIKEBALL1",
diff --git a/src/f_finale.c b/src/f_finale.c
index eb1415042bc2a10efa6f0798db80b53a93db101d..635e5f99e745b0b20561038b535570c881e73883 100644
--- a/src/f_finale.c
+++ b/src/f_finale.c
@@ -70,7 +70,6 @@ mobj_t *titlemapcameraref = NULL;
 // menu presentation state
 char curbgname[9];
 SINT8 curfadevalue;
-boolean curhidepics;
 INT32 curbgcolor;
 INT32 curbgxspeed;
 INT32 curbgyspeed;
@@ -81,6 +80,28 @@ static UINT8  curDemo = 0;
 static UINT32 demoDelayLeft;
 static UINT32 demoIdleLeft;
 
+// customizable title screen graphics
+
+ttmode_enum ttmode = TTMODE_OLD;
+UINT8 ttscale = 1; // FRACUNIT / ttscale
+// ttmode user vars
+char ttname[9];
+INT16 ttx = 0;
+INT16 tty = 0;
+INT16 ttloop = -1;
+UINT16 tttics = 1;
+
+boolean curhidepics;
+ttmode_enum curttmode;
+UINT8 curttscale;
+// ttmode user vars
+char curttname[9];
+INT16 curttx;
+INT16 curtty;
+INT16 curttloop;
+UINT16 curtttics;
+
+// ttmode old
 static patch_t *ttbanner; // white banner with "robo blast" and "2"
 static patch_t *ttwing; // wing background
 static patch_t *ttsonic; // "SONIC"
@@ -97,6 +118,78 @@ static patch_t *ttspop5;
 static patch_t *ttspop6;
 static patch_t *ttspop7;
 
+// ttmode alacroix
+static SINT8 testttscale = 0;
+static SINT8 activettscale = 0;
+boolean ttavailable[6];
+boolean ttloaded[6];
+
+static patch_t *ttribb[6][TTMAX_ALACROIX];
+static patch_t *ttsont[6][TTMAX_ALACROIX];
+static patch_t *ttrobo[6][TTMAX_ALACROIX];
+static patch_t *tttwot[6][TTMAX_ALACROIX];
+static patch_t *ttembl[6][TTMAX_ALACROIX];
+static patch_t *ttrbtx[6][TTMAX_ALACROIX];
+static patch_t *ttsoib[6][TTMAX_ALACROIX];
+static patch_t *ttsoif[6][TTMAX_ALACROIX];
+static patch_t *ttsoba[6][TTMAX_ALACROIX];
+static patch_t *ttsobk[6][TTMAX_ALACROIX];
+static patch_t *ttsodh[6][TTMAX_ALACROIX];
+static patch_t *tttaib[6][TTMAX_ALACROIX];
+static patch_t *tttaif[6][TTMAX_ALACROIX];
+static patch_t *tttaba[6][TTMAX_ALACROIX];
+static patch_t *tttabk[6][TTMAX_ALACROIX];
+static patch_t *tttabt[6][TTMAX_ALACROIX];
+static patch_t *tttaft[6][TTMAX_ALACROIX];
+static patch_t *ttknib[6][TTMAX_ALACROIX];
+static patch_t *ttknif[6][TTMAX_ALACROIX];
+static patch_t *ttknba[6][TTMAX_ALACROIX];
+static patch_t *ttknbk[6][TTMAX_ALACROIX];
+static patch_t *ttkndh[6][TTMAX_ALACROIX];
+
+#define TTEMBL (ttembl[activettscale-1])
+#define TTRIBB (ttribb[activettscale-1])
+#define TTSONT (ttsont[activettscale-1])
+#define TTROBO (ttrobo[activettscale-1])
+#define TTTWOT (tttwot[activettscale-1])
+#define TTRBTX (ttrbtx[activettscale-1])
+#define TTSOIB (ttsoib[activettscale-1])
+#define TTSOIF (ttsoif[activettscale-1])
+#define TTSOBA (ttsoba[activettscale-1])
+#define TTSOBK (ttsobk[activettscale-1])
+#define TTSODH (ttsodh[activettscale-1])
+#define TTTAIB (tttaib[activettscale-1])
+#define TTTAIF (tttaif[activettscale-1])
+#define TTTABA (tttaba[activettscale-1])
+#define TTTABK (tttabk[activettscale-1])
+#define TTTABT (tttabt[activettscale-1])
+#define TTTAFT (tttaft[activettscale-1])
+#define TTKNIB (ttknib[activettscale-1])
+#define TTKNIF (ttknif[activettscale-1])
+#define TTKNBA (ttknba[activettscale-1])
+#define TTKNBK (ttknbk[activettscale-1])
+#define TTKNDH (ttkndh[activettscale-1])
+
+static boolean sonic_blink = false;
+static boolean sonic_blink_twice = false;
+static boolean sonic_blinked_already = false;
+static INT32 sonic_idle_start = 0;
+static INT32 sonic_idle_end = 0;
+static boolean tails_blink = false;
+static boolean tails_blink_twice = false;
+static boolean tails_blinked_already = false;
+static INT32 tails_idle_start = 0;
+static INT32 tails_idle_end = 0;
+static boolean knux_blink = false;
+static boolean knux_blink_twice = false;
+static boolean knux_blinked_already = false;
+static INT32 knux_idle_start = 0;
+static INT32 knux_idle_end = 0;
+
+// ttmode user
+static patch_t *ttuser[TTMAX_USER];
+static INT32 ttuser_count = 0;
+
 static boolean goodending;
 static patch_t *endbrdr[2]; // border - blue, white, pink - where have i seen those colours before?
 static patch_t *endbgsp[3]; // nebula, sun, planet
@@ -836,7 +929,7 @@ void F_IntroDrawer(void)
 			V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31);
 			V_DrawScaledPatch(0, 0, 0, radar);
 			W_UnlockCachedPatch(radar);
-			V_DrawString(8, 128, 0, cutscene_disptext);
+			V_DrawString(8, 128, V_ALLOWLOWERCASE, cutscene_disptext);
 
 			F_WipeEndScreen();
 			F_RunWipe(99,true);
@@ -849,7 +942,7 @@ void F_IntroDrawer(void)
 			V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31);
 			V_DrawScaledPatch(0, 0, 0, grass);
 			W_UnlockCachedPatch(grass);
-			V_DrawString(8, 128, 0, cutscene_disptext);
+			V_DrawString(8, 128, V_ALLOWLOWERCASE, cutscene_disptext);
 
 			F_WipeEndScreen();
 			F_RunWipe(99,true);
@@ -862,7 +955,7 @@ void F_IntroDrawer(void)
 			V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31);
 			V_DrawSmallScaledPatch(0, 0, 0, confront);
 			W_UnlockCachedPatch(confront);
-			V_DrawString(8, 128, 0, cutscene_disptext);
+			V_DrawString(8, 128, V_ALLOWLOWERCASE, cutscene_disptext);
 
 			F_WipeEndScreen();
 			F_RunWipe(99,true);
@@ -875,7 +968,7 @@ void F_IntroDrawer(void)
 			V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31);
 			V_DrawSmallScaledPatch(0, 0, 0, sdo);
 			W_UnlockCachedPatch(sdo);
-			V_DrawString(224, 8, 0, cutscene_disptext);
+			V_DrawString(224, 8, V_ALLOWLOWERCASE, cutscene_disptext);
 
 			F_WipeEndScreen();
 			F_RunWipe(99,true);
@@ -2097,16 +2190,24 @@ void F_InitMenuPresValues(void)
 	// Set defaults for presentation values
 	strncpy(curbgname, "TITLESKY", 9);
 	curfadevalue = 16;
-	curhidepics = hidetitlepics;
 	curbgcolor = -1;
 	curbgxspeed = (gamestate == GS_TIMEATTACK) ? 0 : titlescrollxspeed;
 	curbgyspeed = (gamestate == GS_TIMEATTACK) ? 22 : titlescrollyspeed;
 	curbghide = (gamestate == GS_TIMEATTACK) ? false : true;
 
+	curhidepics = hidetitlepics;
+	curttmode = ttmode;
+	curttscale = ttscale;
+	strncpy(curttname, ttname, 9);
+	curttx = ttx;
+	curtty = tty;
+	curttloop = ttloop;
+	curtttics = tttics;
+
 	// Find current presentation values
 	M_SetMenuCurBackground((gamestate == GS_TIMEATTACK) ? "RECATTBG" : "TITLESKY");
 	M_SetMenuCurFadeValue(16);
-	M_SetMenuCurHideTitlePics();
+	M_SetMenuCurTitlePics();
 }
 
 //
@@ -2173,6 +2274,32 @@ void F_SkyScroll(INT32 scrollxspeed, INT32 scrollyspeed, const char *patchname)
 	W_UnlockCachedPatch(pat);
 }
 
+#define LOADTTGFX(arr, name, maxf) \
+lumpnum = W_CheckNumForName(name); \
+if (lumpnum != LUMPERROR) \
+{ \
+	arr[0] = W_CachePatchName(name, PU_LEVEL); \
+	arr[min(1, maxf-1)] = 0; \
+} \
+else if (strlen(name) <= 6) \
+{ \
+	fixed_t cnt = strlen(name); \
+	strncpy(lumpname, name, 7); \
+	for (i = 0; i < maxf-1; i++) \
+	{ \
+		sprintf(&lumpname[cnt], "%.2hu", (UINT16)(i+1)); \
+		lumpname[8] = 0; \
+		lumpnum = W_CheckNumForName(lumpname); \
+		if (lumpnum != LUMPERROR) \
+			arr[i] = W_CachePatchName(lumpname, PU_LEVEL); \
+		else \
+			break; \
+	} \
+	arr[min(i, maxf-1)] = 0; \
+} \
+else \
+	arr[0] = 0;
+
 void F_StartTitleScreen(void)
 {
 	if (menupres[MN_MAIN].musname[0])
@@ -2182,7 +2309,19 @@ void F_StartTitleScreen(void)
 
 	if (gamestate != GS_TITLESCREEN && gamestate != GS_WAITINGPLAYERS)
 	{
-		finalecount = 0;
+		ttuser_count =\
+		 ttloaded[0] = ttloaded[1] = ttloaded[2] = ttloaded[3] = ttloaded[4] = ttloaded[5] =\
+		 testttscale = activettscale =\
+		 sonic_blink = sonic_blink_twice = sonic_idle_start = sonic_idle_end =\
+		 tails_blink = tails_blink_twice = tails_idle_start = tails_idle_end =\
+		 knux_blink  = knux_blink_twice  = knux_idle_start  = knux_idle_end  = 0;
+
+		sonic_blinked_already = tails_blinked_already = knux_blinked_already = 1; // don't blink on the first idle cycle
+
+		if (curttmode == TTMODE_ALACROIX)
+			finalecount = -3; // hack so that frames don't advance during the entry wipe
+		else
+			finalecount = 0;
 		wipetypepost = menupres[MN_MAIN].enterwipe;
 	}
 	else
@@ -2257,27 +2396,183 @@ void F_StartTitleScreen(void)
 	demoDelayLeft = demoDelayTime;
 	demoIdleLeft = demoIdleTime;
 
-	ttbanner = W_CachePatchName("TTBANNER", PU_LEVEL);
-	ttwing = W_CachePatchName("TTWING", PU_LEVEL);
-	ttsonic = W_CachePatchName("TTSONIC", PU_LEVEL);
-	ttswave1 = W_CachePatchName("TTSWAVE1", PU_LEVEL);
-	ttswave2 = W_CachePatchName("TTSWAVE2", PU_LEVEL);
-	ttswip1 = W_CachePatchName("TTSWIP1", PU_LEVEL);
-	ttsprep1 = W_CachePatchName("TTSPREP1", PU_LEVEL);
-	ttsprep2 = W_CachePatchName("TTSPREP2", PU_LEVEL);
-	ttspop1 = W_CachePatchName("TTSPOP1", PU_LEVEL);
-	ttspop2 = W_CachePatchName("TTSPOP2", PU_LEVEL);
-	ttspop3 = W_CachePatchName("TTSPOP3", PU_LEVEL);
-	ttspop4 = W_CachePatchName("TTSPOP4", PU_LEVEL);
-	ttspop5 = W_CachePatchName("TTSPOP5", PU_LEVEL);
-	ttspop6 = W_CachePatchName("TTSPOP6", PU_LEVEL);
-	ttspop7 = W_CachePatchName("TTSPOP7", PU_LEVEL);
+	switch(curttmode)
+	{
+		case TTMODE_OLD:
+		case TTMODE_NONE:
+			ttbanner = W_CachePatchName("TTBANNER", PU_LEVEL);
+			ttwing = W_CachePatchName("TTWING", PU_LEVEL);
+			ttsonic = W_CachePatchName("TTSONIC", PU_LEVEL);
+			ttswave1 = W_CachePatchName("TTSWAVE1", PU_LEVEL);
+			ttswave2 = W_CachePatchName("TTSWAVE2", PU_LEVEL);
+			ttswip1 = W_CachePatchName("TTSWIP1", PU_LEVEL);
+			ttsprep1 = W_CachePatchName("TTSPREP1", PU_LEVEL);
+			ttsprep2 = W_CachePatchName("TTSPREP2", PU_LEVEL);
+			ttspop1 = W_CachePatchName("TTSPOP1", PU_LEVEL);
+			ttspop2 = W_CachePatchName("TTSPOP2", PU_LEVEL);
+			ttspop3 = W_CachePatchName("TTSPOP3", PU_LEVEL);
+			ttspop4 = W_CachePatchName("TTSPOP4", PU_LEVEL);
+			ttspop5 = W_CachePatchName("TTSPOP5", PU_LEVEL);
+			ttspop6 = W_CachePatchName("TTSPOP6", PU_LEVEL);
+			ttspop7 = W_CachePatchName("TTSPOP7", PU_LEVEL);
+			break;
+
+		// don't load alacroix gfx yet; we do that upon first draw.
+		case TTMODE_ALACROIX:
+			break;
+
+		case TTMODE_USER:
+		{
+			UINT16 i;
+			lumpnum_t lumpnum;
+			char lumpname[9];
+
+			LOADTTGFX(ttuser, curttname, TTMAX_USER)
+			break;
+		}
+	}
+}
+
+static void F_UnloadAlacroixGraphics(SINT8 oldttscale)
+{
+	// This all gets freed by PU_LEVEL when exiting the menus.
+	// When re-visiting the menus (e.g., from exiting in-game), the gfx are force-reloaded.
+	// So leftover addresses here should not be a problem.
+
+	UINT16 i;
+	oldttscale--; // zero-based index
+	for (i = 0; i < TTMAX_ALACROIX; i++)
+	{
+		if(ttembl[oldttscale][i]) { Z_Free(ttembl[oldttscale][i]); ttembl[oldttscale][i] = 0; }
+		if(ttribb[oldttscale][i]) { Z_Free(ttribb[oldttscale][i]); ttribb[oldttscale][i] = 0; }
+		if(ttsont[oldttscale][i]) { Z_Free(ttsont[oldttscale][i]); ttsont[oldttscale][i] = 0; }
+		if(ttrobo[oldttscale][i]) { Z_Free(ttrobo[oldttscale][i]); ttrobo[oldttscale][i] = 0; }
+		if(tttwot[oldttscale][i]) { Z_Free(tttwot[oldttscale][i]); tttwot[oldttscale][i] = 0; }
+		if(ttrbtx[oldttscale][i]) { Z_Free(ttrbtx[oldttscale][i]); ttrbtx[oldttscale][i] = 0; }
+		if(ttsoib[oldttscale][i]) { Z_Free(ttsoib[oldttscale][i]); ttsoib[oldttscale][i] = 0; }
+		if(ttsoif[oldttscale][i]) { Z_Free(ttsoif[oldttscale][i]); ttsoif[oldttscale][i] = 0; }
+		if(ttsoba[oldttscale][i]) { Z_Free(ttsoba[oldttscale][i]); ttsoba[oldttscale][i] = 0; }
+		if(ttsobk[oldttscale][i]) { Z_Free(ttsobk[oldttscale][i]); ttsobk[oldttscale][i] = 0; }
+		if(ttsodh[oldttscale][i]) { Z_Free(ttsodh[oldttscale][i]); ttsodh[oldttscale][i] = 0; }
+		if(tttaib[oldttscale][i]) { Z_Free(tttaib[oldttscale][i]); tttaib[oldttscale][i] = 0; }
+		if(tttaif[oldttscale][i]) { Z_Free(tttaif[oldttscale][i]); tttaif[oldttscale][i] = 0; }
+		if(tttaba[oldttscale][i]) { Z_Free(tttaba[oldttscale][i]); tttaba[oldttscale][i] = 0; }
+		if(tttabk[oldttscale][i]) { Z_Free(tttabk[oldttscale][i]); tttabk[oldttscale][i] = 0; }
+		if(tttabt[oldttscale][i]) { Z_Free(tttabt[oldttscale][i]); tttabt[oldttscale][i] = 0; }
+		if(tttaft[oldttscale][i]) { Z_Free(tttaft[oldttscale][i]); tttaft[oldttscale][i] = 0; }
+		if(ttknib[oldttscale][i]) { Z_Free(ttknib[oldttscale][i]); ttknib[oldttscale][i] = 0; }
+		if(ttknif[oldttscale][i]) { Z_Free(ttknif[oldttscale][i]); ttknif[oldttscale][i] = 0; }
+		if(ttknba[oldttscale][i]) { Z_Free(ttknba[oldttscale][i]); ttknba[oldttscale][i] = 0; }
+		if(ttknbk[oldttscale][i]) { Z_Free(ttknbk[oldttscale][i]); ttknbk[oldttscale][i] = 0; }
+		if(ttkndh[oldttscale][i]) { Z_Free(ttkndh[oldttscale][i]); ttkndh[oldttscale][i] = 0; }
+	}
+	ttloaded[oldttscale] = false;
+}
+
+static void F_LoadAlacroixGraphics(SINT8 newttscale)
+{
+	UINT16 i, j;
+	lumpnum_t lumpnum;
+	char lumpname[9];
+	char names[22][5] = {
+		"EMBL",
+		"RIBB",
+		"SONT",
+		"ROBO",
+		"TWOT",
+		"RBTX",
+		"SOIB",
+		"SOIF",
+		"SOBA",
+		"SOBK",
+		"SODH",
+		"TAIB",
+		"TAIF",
+		"TABA",
+		"TABK",
+		"TABT",
+		"TAFT",
+		"KNIB",
+		"KNIF",
+		"KNBA",
+		"KNBK",
+		"KNDH"
+	};
+	char lumpnames[22][7];
+
+	newttscale--; // 0-based index
+
+	if (!ttloaded[newttscale])
+	{
+		for (j = 0; j < 22; j++)
+			sprintf(&lumpnames[j][0], "T%.1hu%s", (UINT8)newttscale+1, names[j]);
+
+		LOADTTGFX(ttembl[newttscale], lumpnames[0], TTMAX_ALACROIX)
+		LOADTTGFX(ttribb[newttscale], lumpnames[1], TTMAX_ALACROIX)
+		LOADTTGFX(ttsont[newttscale], lumpnames[2], TTMAX_ALACROIX)
+		LOADTTGFX(ttrobo[newttscale], lumpnames[3], TTMAX_ALACROIX)
+		LOADTTGFX(tttwot[newttscale], lumpnames[4], TTMAX_ALACROIX)
+		LOADTTGFX(ttrbtx[newttscale], lumpnames[5], TTMAX_ALACROIX)
+		LOADTTGFX(ttsoib[newttscale], lumpnames[6], TTMAX_ALACROIX)
+		LOADTTGFX(ttsoif[newttscale], lumpnames[7], TTMAX_ALACROIX)
+		LOADTTGFX(ttsoba[newttscale], lumpnames[8], TTMAX_ALACROIX)
+		LOADTTGFX(ttsobk[newttscale], lumpnames[9], TTMAX_ALACROIX)
+		LOADTTGFX(ttsodh[newttscale], lumpnames[10], TTMAX_ALACROIX)
+		LOADTTGFX(tttaib[newttscale], lumpnames[11], TTMAX_ALACROIX)
+		LOADTTGFX(tttaif[newttscale], lumpnames[12], TTMAX_ALACROIX)
+		LOADTTGFX(tttaba[newttscale], lumpnames[13], TTMAX_ALACROIX)
+		LOADTTGFX(tttabk[newttscale], lumpnames[14], TTMAX_ALACROIX)
+		LOADTTGFX(tttabt[newttscale], lumpnames[15], TTMAX_ALACROIX)
+		LOADTTGFX(tttaft[newttscale], lumpnames[16], TTMAX_ALACROIX)
+		LOADTTGFX(ttknib[newttscale], lumpnames[17], TTMAX_ALACROIX)
+		LOADTTGFX(ttknif[newttscale], lumpnames[18], TTMAX_ALACROIX)
+		LOADTTGFX(ttknba[newttscale], lumpnames[19], TTMAX_ALACROIX)
+		LOADTTGFX(ttknbk[newttscale], lumpnames[20], TTMAX_ALACROIX)
+		LOADTTGFX(ttkndh[newttscale], lumpnames[21], TTMAX_ALACROIX)
+
+		ttloaded[newttscale] = true;
+	}
+}
+
+#undef LOADTTGFX
+
+static void F_FigureActiveTtScale(void)
+{
+	SINT8 newttscale = max(1, min(6, vid.dupx));
+	SINT8 oldttscale = activettscale;
+
+	if (newttscale == testttscale)
+		return;
+	testttscale = newttscale;
+
+	// If ttscale is unavailable: look for lower scales, then higher scales.
+	for (; newttscale >= 1; newttscale--)
+	{
+		if (ttavailable[newttscale-1])
+			break;
+	}
+
+	for (; newttscale <= 6; newttscale++)
+	{
+		if (ttavailable[newttscale-1])
+			break;
+	}
+
+	activettscale = (newttscale >= 1 && newttscale <= 6) ? newttscale : 0;
+
+	// We have a new ttscale, so load gfx
+	if(oldttscale > 0)
+		F_UnloadAlacroixGraphics(oldttscale);
+
+	if(activettscale > 0)
+		F_LoadAlacroixGraphics(activettscale);
 }
 
 // (no longer) De-Demo'd Title Screen
 void F_TitleScreenDrawer(void)
 {
 	boolean hidepics;
+	fixed_t sc = FRACUNIT / max(1, curttscale);
 
 	if (modeattacking)
 		return; // We likely came here from retrying. Don't do a damn thing.
@@ -2289,7 +2584,7 @@ void F_TitleScreenDrawer(void)
 		F_SkyScroll(curbgxspeed, curbgyspeed, curbgname);
 
 	// Don't draw outside of the title screen, or if the patch isn't there.
-	if (!ttwing || (gamestate != GS_TITLESCREEN && gamestate != GS_WAITINGPLAYERS))
+	if (gamestate != GS_TITLESCREEN && gamestate != GS_WAITINGPLAYERS)
 		return;
 
 	// rei|miru: use title pics?
@@ -2301,42 +2596,701 @@ void F_TitleScreenDrawer(void)
 		return;
 #endif
 
-	V_DrawScaledPatch(30, 14, 0, ttwing);
-
-	if (finalecount < 57)
-	{
-		if (finalecount == 35)
-			V_DrawScaledPatch(115, 15, 0, ttspop1);
-		else if (finalecount == 36)
-			V_DrawScaledPatch(114, 15, 0,ttspop2);
-		else if (finalecount == 37)
-			V_DrawScaledPatch(113, 15, 0,ttspop3);
-		else if (finalecount == 38)
-			V_DrawScaledPatch(112, 15, 0,ttspop4);
-		else if (finalecount == 39)
-			V_DrawScaledPatch(111, 15, 0,ttspop5);
-		else if (finalecount == 40)
-			V_DrawScaledPatch(110, 15, 0, ttspop6);
-		else if (finalecount >= 41 && finalecount <= 44)
-			V_DrawScaledPatch(109, 15, 0, ttspop7);
-		else if (finalecount >= 45 && finalecount <= 48)
-			V_DrawScaledPatch(108, 12, 0, ttsprep1);
-		else if (finalecount >= 49 && finalecount <= 52)
-			V_DrawScaledPatch(107, 9, 0, ttsprep2);
-		else if (finalecount >= 53 && finalecount <= 56)
-			V_DrawScaledPatch(106, 6, 0, ttswip1);
-		V_DrawScaledPatch(93, 106, 0, ttsonic);
-	}
-	else
+	switch(curttmode)
 	{
-		V_DrawScaledPatch(93, 106, 0,ttsonic);
-		if (finalecount/5 & 1)
-			V_DrawScaledPatch(100, 3, 0,ttswave1);
-		else
-			V_DrawScaledPatch(100,3, 0,ttswave2);
-	}
+		case TTMODE_OLD:
+		case TTMODE_NONE:
+			V_DrawSciencePatch(30<<FRACBITS, 14<<FRACBITS, 0, ttwing, sc);
+
+			if (finalecount < 57)
+			{
+				if (finalecount == 35)
+					V_DrawSciencePatch(115<<FRACBITS, 15<<FRACBITS, 0, ttspop1, sc);
+				else if (finalecount == 36)
+					V_DrawSciencePatch(114<<FRACBITS, 15<<FRACBITS, 0,ttspop2, sc);
+				else if (finalecount == 37)
+					V_DrawSciencePatch(113<<FRACBITS, 15<<FRACBITS, 0,ttspop3, sc);
+				else if (finalecount == 38)
+					V_DrawSciencePatch(112<<FRACBITS, 15<<FRACBITS, 0,ttspop4, sc);
+				else if (finalecount == 39)
+					V_DrawSciencePatch(111<<FRACBITS, 15<<FRACBITS, 0,ttspop5, sc);
+				else if (finalecount == 40)
+					V_DrawSciencePatch(110<<FRACBITS, 15<<FRACBITS, 0, ttspop6, sc);
+				else if (finalecount >= 41 && finalecount <= 44)
+					V_DrawSciencePatch(109<<FRACBITS, 15<<FRACBITS, 0, ttspop7, sc);
+				else if (finalecount >= 45 && finalecount <= 48)
+					V_DrawSciencePatch(108<<FRACBITS, 12<<FRACBITS, 0, ttsprep1, sc);
+				else if (finalecount >= 49 && finalecount <= 52)
+					V_DrawSciencePatch(107<<FRACBITS, 9<<FRACBITS, 0, ttsprep2, sc);
+				else if (finalecount >= 53 && finalecount <= 56)
+					V_DrawSciencePatch(106<<FRACBITS, 6<<FRACBITS, 0, ttswip1, sc);
+				V_DrawSciencePatch(93<<FRACBITS, 106<<FRACBITS, 0, ttsonic, sc);
+			}
+			else
+			{
+				V_DrawSciencePatch(93<<FRACBITS, 106<<FRACBITS, 0,ttsonic, sc);
+				if (finalecount/5 & 1)
+					V_DrawSciencePatch(100<<FRACBITS, 3<<FRACBITS, 0,ttswave1, sc);
+				else
+					V_DrawSciencePatch(100<<FRACBITS, 3<<FRACBITS, 0,ttswave2, sc);
+			}
+
+			V_DrawSciencePatch(48<<FRACBITS, 142<<FRACBITS, 0,ttbanner, sc);
+			break;
 
-	V_DrawScaledPatch(48, 142, 0,ttbanner);
+		case TTMODE_ALACROIX:
+			//
+			// PRE-INTRO: WING ON BLACK BACKGROUND
+			//
+
+			// Figure the gfx scale and load gfx if necessary
+			F_FigureActiveTtScale();
+
+			if (!activettscale) // invalid scale, draw nothing
+				break;
+			sc = FRACUNIT / activettscale;
+
+			// Start at black background. Draw it until tic 30, where we replace with a white flash.
+			//
+			// TODO: How to NOT draw the titlemap while this background is drawn?
+			//
+			if (finalecount <= 29)
+				V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31);
+
+			// Draw emblem
+			V_DrawSciencePatch(40<<FRACBITS, 20<<FRACBITS, 0, TTEMBL[0], sc);
+
+			// Animate SONIC ROBO BLAST 2 before the white flash at tic 30.
+			if (finalecount <= 29)
+			{
+				// Ribbon unfurls, revealing SONIC text, from tic 0 to tic 24. SONIC text is pre-baked into this ribbon graphic.
+				V_DrawSciencePatch(39<<FRACBITS, 88<<FRACBITS, 0, TTRIBB[min(max(0, finalecount), 24)], sc);
+
+				// Animate SONIC text while the ribbon unfurls, from tic 0 to tic 28.
+				if(finalecount >= 0)
+					V_DrawSciencePatch(89<<FRACBITS, 92<<FRACBITS, 0, TTSONT[min(finalecount, 28)], sc);
+
+				// Fade in ROBO BLAST 2 starting at tic 10.
+				if (finalecount > 9)
+				{
+					INT32 fadeval = 0;
+
+					// Fade between tic 10 and tic 29.
+					if (finalecount < 30)
+					{
+						UINT8 fadecounter = 30-finalecount;
+						switch(fadecounter)
+						{
+							case 20: case 19: fadeval = V_90TRANS; break;
+							case 18: case 17: fadeval = V_80TRANS; break;
+							case 16: case 15: fadeval = V_70TRANS; break;
+							case 14: case 13: fadeval = V_60TRANS; break;
+							case 12: case 11: fadeval = V_TRANSLUCENT; break;
+							case 10: case 9: fadeval = V_40TRANS; break;
+							case 8: case 7: fadeval = V_30TRANS; break;
+							case 6: case 5: fadeval = V_20TRANS; break;
+							case 4: case 3: fadeval = V_10TRANS; break;
+						}
+					}
+					V_DrawSciencePatch(79<<FRACBITS, 132<<FRACBITS, fadeval, TTROBO[0], sc);
+
+					// Draw the TWO from tic 16 to tic 31, so the TWO lands right when the screen flashes white.
+					if(finalecount > 15)
+						V_DrawSciencePatch(106<<FRACBITS, 118<<FRACBITS, fadeval, TTTWOT[min(finalecount-16, 15)], sc);
+				}
+			}
+
+			//
+			// ALACROIX CHARACTER FRAMES
+			//
+			// Start all animation from tic 34 (or whenever the white flash begins to fade; see below.)
+			// Delay the start a bit for better music timing.
+			//
+
+#define CHARSTART 41
+#define SONICSTART (CHARSTART+0)
+#define SONICIDLE (SONICSTART+57)
+#define SONICX 89
+#define SONICY 13
+#define TAILSSTART (CHARSTART+27)
+#define TAILSIDLE (TAILSSTART+60)
+#define TAILSX 35
+#define TAILSY 19
+#define KNUXSTART (CHARSTART+44)
+#define KNUXIDLE (KNUXSTART+70)
+#define KNUXX 167
+#define KNUXY 7
+
+			// Decide who gets to blink or not.
+			// Make this decision at the END of an idle/blink cycle.
+			// Upon first idle, both idle_start and idle_end will be 0.
+
+			if (finalecount >= KNUXIDLE)
+			{
+				if (!knux_idle_start || finalecount - knux_idle_start >= knux_idle_end)
+				{
+					if (knux_blink)
+					{
+						knux_blink = false; // don't run the cycle twice in a row
+						knux_blinked_already = true;
+					}
+					else if (knux_blinked_already) // or after the first non-blink cycle, either.
+						knux_blinked_already = false;
+					else
+					{
+						// make this chance higher than Sonic/Tails because Knux's idle cycle is longer
+						knux_blink = !(M_RandomKey(100) % 2);
+						knux_blink_twice = knux_blink ? !(M_RandomKey(100) % 5) : false;
+					}
+					knux_idle_start = finalecount;
+				}
+
+				knux_idle_end = knux_blink ? (knux_blink_twice ? 17 : 7) : 46;
+			}
+
+			if (finalecount >= TAILSIDLE)
+			{
+				if (!tails_idle_start || finalecount - tails_idle_start >= tails_idle_end)
+				{
+					if (tails_blink)
+					{
+						tails_blink = false; // don't run the cycle twice in a row
+						tails_blinked_already = true;
+					}
+					else if (tails_blinked_already) // or after the first non-blink cycle, either.
+						tails_blinked_already = false;
+					else
+					{
+						tails_blink = !(M_RandomKey(100) % 3);
+						tails_blink_twice = tails_blink ? !(M_RandomKey(100) % 5) : false;
+					}
+					tails_idle_start = finalecount;
+				}
+
+				// Tails does not actually have a non-blink idle cycle, but make up a number
+				// so he can still blink.
+				tails_idle_end = tails_blink ? (tails_blink_twice ? 17 : 7) : 30;
+			}
+
+			if (finalecount >= SONICIDLE)
+			{
+				if (!sonic_idle_start || finalecount - sonic_idle_start >= sonic_idle_end)
+				{
+					if (sonic_blink)
+					{
+						sonic_blink = false; // don't run the cycle twice in a row
+						sonic_blinked_already = true;
+					}
+					else if (sonic_blinked_already) // or after the first non-blink cycle, either.
+						sonic_blinked_already = false;
+					else
+					{
+						sonic_blink = !(M_RandomKey(100) % 3);
+						sonic_blink_twice = sonic_blink ? !(M_RandomKey(100) % 5) : false;
+					}
+					sonic_idle_start = finalecount;
+				}
+
+				sonic_idle_end = sonic_blink ? (sonic_blink_twice ? 17 : 7) : 25;
+			}
+
+
+			//
+			// BACK TAIL LAYER
+			//
+
+			if (finalecount >= TAILSSTART)
+			{
+				if (finalecount >= TAILSIDLE)
+				{
+					//
+					// Tails Back Tail Layer Idle
+					//
+					SINT8 taftcount = (finalecount - (TAILSIDLE)) % 41;
+					if      (taftcount >= 0   && taftcount < 5  )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABT[0 ], sc);
+					else if (taftcount >= 5   && taftcount < 9 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABT[1 ], sc);
+					else if (taftcount >= 9   && taftcount < 12 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABT[2 ], sc);
+					else if (taftcount >= 12  && taftcount < 14 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABT[3 ], sc);
+					else if (taftcount >= 14  && taftcount < 17 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABT[4 ], sc);
+					else if (taftcount >= 17  && taftcount < 21 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABT[5 ], sc);
+					else if (taftcount >= 21  && taftcount < 24 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABT[6 ], sc);
+					else if (taftcount >= 24  && taftcount < 25 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABT[7 ], sc);
+					else if (taftcount >= 25  && taftcount < 28 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABT[8 ], sc);
+					else if (taftcount >= 28  && taftcount < 31 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABT[9 ], sc);
+					else if (taftcount >= 31  && taftcount < 35 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABT[10], sc);
+					else if (taftcount >= 35  && taftcount < 41 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABT[11], sc);
+				}
+			}
+
+			//
+			// FRONT TAIL LAYER
+			//
+
+			if (finalecount >= TAILSSTART)
+			{
+				if (finalecount >= TAILSIDLE)
+				{
+					//
+					// Tails Front Tail Layer Idle
+					//
+					SINT8 tabtcount = (finalecount - (TAILSIDLE)) % 41;
+					if      (tabtcount >= 0   && tabtcount < 6  )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAFT[0 ], sc);
+					else if (tabtcount >= 6   && tabtcount < 11 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAFT[1 ], sc);
+					else if (tabtcount >= 11  && tabtcount < 15 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAFT[2 ], sc);
+					else if (tabtcount >= 15  && tabtcount < 18 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAFT[3 ], sc);
+					else if (tabtcount >= 18  && tabtcount < 19 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAFT[4 ], sc);
+					else if (tabtcount >= 19  && tabtcount < 22 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAFT[5 ], sc);
+					else if (tabtcount >= 22  && tabtcount < 27 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAFT[6 ], sc);
+					else if (tabtcount >= 27  && tabtcount < 30 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAFT[7 ], sc);
+					else if (tabtcount >= 30  && tabtcount < 31 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAFT[8 ], sc);
+					else if (tabtcount >= 31  && tabtcount < 34 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAFT[9 ], sc);
+					else if (tabtcount >= 34  && tabtcount < 37 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAFT[10], sc);
+					else if (tabtcount >= 37  && tabtcount < 41 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAFT[11], sc);
+				}
+			}
+
+			//
+			// BACK LAYER CHARACTERS
+			//
+
+			if (finalecount >= KNUXSTART)
+			{
+				if (finalecount < KNUXIDLE)
+				{
+					//
+					// Knux Back Layer Intro
+					//
+					if      (finalecount >= KNUXSTART+0   && finalecount < KNUXSTART+6  )
+						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIB[0 ], sc);
+					else if (finalecount >= KNUXSTART+6   && finalecount < KNUXSTART+10 )
+						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIB[1 ], sc);
+					else if (finalecount >= KNUXSTART+10  && finalecount < KNUXSTART+13 )
+						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIB[2 ], sc);
+					else if (finalecount >= KNUXSTART+13  && finalecount < KNUXSTART+15 )
+						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIB[3 ], sc);
+					else if (finalecount >= KNUXSTART+15  && finalecount < KNUXSTART+18 )
+						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIB[4 ], sc);
+					else if (finalecount >= KNUXSTART+18  && finalecount < KNUXSTART+22 )
+						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIB[5 ], sc);
+					else if (finalecount >= KNUXSTART+22  && finalecount < KNUXSTART+28 )
+						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIB[6 ], sc);
+					else if (finalecount >= KNUXSTART+28  && finalecount < KNUXSTART+32 )
+						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIB[7 ], sc);
+					else if (finalecount >= KNUXSTART+32  && finalecount < KNUXSTART+35 )
+						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIB[8 ], sc);
+					else if (finalecount >= KNUXSTART+35  && finalecount < KNUXSTART+40 )
+						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIB[9 ], sc);
+					else if (finalecount >= KNUXSTART+40  && finalecount < KNUXSTART+41 )
+						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIB[10], sc);
+					else if (finalecount >= KNUXSTART+41  && finalecount < KNUXSTART+44 )
+						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIB[11], sc);
+					else if (finalecount >= KNUXSTART+44  && finalecount < KNUXSTART+50 )
+						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIB[12], sc);
+					else if (finalecount >= KNUXSTART+50  && finalecount < KNUXSTART+56 )
+						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIB[13], sc);
+					else if (finalecount >= KNUXSTART+56  && finalecount < KNUXSTART+57 )
+						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIB[14], sc);
+					else if (finalecount >= KNUXSTART+57  && finalecount < KNUXSTART+60 )
+						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIB[15], sc);
+					else if (finalecount >= KNUXSTART+60  && finalecount < KNUXSTART+63 )
+						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIB[16], sc);
+					else if (finalecount >= KNUXSTART+63  && finalecount < KNUXSTART+67 )
+						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIB[17], sc);
+					else if (finalecount >= KNUXSTART+67  && finalecount < KNUXSTART+70 )
+						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIB[18], sc);
+					// Start idle animation (frame K20-B)
+				}
+				else
+				{
+					//
+					// Knux Back Layer Idle
+					//
+					if (!knux_blink)
+						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNBA[0], sc);
+					else
+					{
+						//
+						// Knux Blinking
+						//
+						SINT8 idlecount = finalecount - knux_idle_start;
+						if      (idlecount >= 0  && idlecount < 2 )
+							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNBK[0], sc);
+						else if (idlecount >= 2  && idlecount < 6 )
+							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNBK[1], sc);
+						else if (idlecount >= 6  && idlecount < 7 )
+							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNBK[2], sc);
+						// We reach this point if knux_blink_twice == true
+						else if (idlecount >= 7  && idlecount < 10)
+							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNBA[0], sc);
+						else if (idlecount >= 10 && idlecount < 12)
+							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNBK[0], sc);
+						else if (idlecount >= 12 && idlecount < 16)
+							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNBK[1], sc);
+						else if (idlecount >= 16 && idlecount < 17)
+							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNBK[2], sc);
+					}
+				}
+			}
+
+			if (finalecount >= TAILSSTART)
+			{
+				if (finalecount < TAILSIDLE)
+				{
+					//
+					// Tails Back Layer Intro
+					//
+					if      (finalecount >= TAILSSTART+0   && finalecount < TAILSSTART+6  )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIB[0 ], sc);
+					else if (finalecount >= TAILSSTART+6   && finalecount < TAILSSTART+10 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIB[1 ], sc);
+					else if (finalecount >= TAILSSTART+10  && finalecount < TAILSSTART+12 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIB[2 ], sc);
+					else if (finalecount >= TAILSSTART+12  && finalecount < TAILSSTART+16 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIB[3 ], sc);
+					else if (finalecount >= TAILSSTART+16  && finalecount < TAILSSTART+22 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIB[4 ], sc);
+					else if (finalecount >= TAILSSTART+22  && finalecount < TAILSSTART+23 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIB[5 ], sc);
+					else if (finalecount >= TAILSSTART+23  && finalecount < TAILSSTART+26 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIB[6 ], sc);
+					else if (finalecount >= TAILSSTART+26  && finalecount < TAILSSTART+30 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIB[7 ], sc);
+					else if (finalecount >= TAILSSTART+30  && finalecount < TAILSSTART+35 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIB[8 ], sc);
+					else if (finalecount >= TAILSSTART+35  && finalecount < TAILSSTART+41 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIB[9 ], sc);
+					else if (finalecount >= TAILSSTART+41  && finalecount < TAILSSTART+43 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIB[10], sc);
+					else if (finalecount >= TAILSSTART+43  && finalecount < TAILSSTART+47 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIB[11], sc);
+					else if (finalecount >= TAILSSTART+47  && finalecount < TAILSSTART+51 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIB[12], sc);
+					else if (finalecount >= TAILSSTART+51  && finalecount < TAILSSTART+53 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIB[13], sc);
+					else if (finalecount >= TAILSSTART+53  && finalecount < TAILSSTART+56 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIB[14], sc);
+					else if (finalecount >= TAILSSTART+56  && finalecount < TAILSSTART+60 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIB[15], sc);
+					// Start idle animation (frame T17-B)
+				}
+				else
+				{
+					//
+					// Tails Back Layer Idle
+					//
+					if (!tails_blink)
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABA[0], sc);
+					else
+					{
+						//
+						// Tails Blinking
+						//
+						SINT8 idlecount = finalecount - tails_idle_start;
+						if      (idlecount >= +0  && idlecount < +2 )
+							V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABK[0], sc);
+						else if (idlecount >= +2  && idlecount < +6 )
+							V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABK[1], sc);
+						else if (idlecount >= +6  && idlecount < +7 )
+							V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABK[2], sc);
+						// We reach this point if tails_blink_twice == true
+						else if (idlecount >= +7  && idlecount < +10)
+							V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABA[0], sc);
+						else if (idlecount >= +10 && idlecount < +12)
+							V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABK[0], sc);
+						else if (idlecount >= +12 && idlecount < +16)
+							V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABK[1], sc);
+						else if (idlecount >= +16 && idlecount < +17)
+							V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABK[2], sc);
+					}
+				}
+			}
+
+			if (finalecount >= SONICSTART)
+			{
+				if (finalecount < SONICIDLE)
+				{
+					//
+					// Sonic Back Layer Intro
+					//
+					if      (finalecount >= SONICSTART+0   && finalecount < SONICSTART+6  )
+						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIB[0 ], sc);
+					else if (finalecount >= SONICSTART+6   && finalecount < SONICSTART+11 )
+						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIB[1 ], sc);
+					else if (finalecount >= SONICSTART+11  && finalecount < SONICSTART+14 )
+						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIB[2 ], sc);
+					else if (finalecount >= SONICSTART+14  && finalecount < SONICSTART+18 )
+						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIB[3 ], sc);
+					else if (finalecount >= SONICSTART+18  && finalecount < SONICSTART+19 )
+						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIB[4 ], sc);
+					else if (finalecount >= SONICSTART+19  && finalecount < SONICSTART+27 )
+						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIB[5 ], sc);
+					else if (finalecount >= SONICSTART+27  && finalecount < SONICSTART+31 )
+						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIB[6 ], sc);
+					//else if (finalecount >= SONICSTART+31  && finalecount < SONICSTART+33 )
+					//  Frame is blank
+					//	V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIB[7 ], sc);
+					else if (finalecount >= SONICSTART+33  && finalecount < SONICSTART+36 )
+						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIB[8 ], sc);
+					else if (finalecount >= SONICSTART+36  && finalecount < SONICSTART+40 )
+						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIB[9 ], sc);
+					else if (finalecount >= SONICSTART+40  && finalecount < SONICSTART+44 )
+						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIB[10], sc);
+					else if (finalecount >= SONICSTART+44  && finalecount < SONICSTART+47 )
+						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIB[11], sc);
+					else if (finalecount >= SONICSTART+47  && finalecount < SONICSTART+49 )
+						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIB[12], sc);
+					else if (finalecount >= SONICSTART+49  && finalecount < SONICSTART+50 )
+						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIB[13], sc);
+					else if (finalecount >= SONICSTART+50  && finalecount < SONICSTART+53 )
+						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIB[14], sc);
+					else if (finalecount >= SONICSTART+53  && finalecount < SONICSTART+57 )
+						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIB[15], sc);
+					// Start idle animation (frame S17-B)
+				}
+				else
+				{
+					//
+					// Sonic Back Layer Idle
+					//
+					if (!sonic_blink)
+						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOBA[0], sc);
+					else
+					{
+						//
+						// Sonic Blinking
+						//
+						SINT8 idlecount = finalecount - sonic_idle_start;
+						if      (idlecount >= 0  && idlecount < 2 )
+							V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOBK[0], sc);
+						else if (idlecount >= 2  && idlecount < 6 )
+							V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOBK[1], sc);
+						else if (idlecount >= 6  && idlecount < 7 )
+							V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOBK[2], sc);
+						// We reach this point if sonic_blink_twice == true
+						else if (idlecount >= 7  && idlecount < 10)
+							V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOBA[0], sc);
+						else if (idlecount >= 10 && idlecount < 12)
+							V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOBK[0], sc);
+						else if (idlecount >= 12 && idlecount < 16)
+							V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOBK[1], sc);
+						else if (idlecount >= 16 && idlecount < 17)
+							V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOBK[2], sc);
+					}
+				}
+			}
+
+			//
+			// LOGO LAYER
+			//
+
+			// After tic 34, starting when the flash fades,
+			// draw the combined ribbon and SONIC ROBO BLAST 2 logo. Note the different Y value, because this
+			// graphic is cropped differently from the unfurling ribbon.
+			if (finalecount > 34)
+				V_DrawSciencePatch(39<<FRACBITS, 93<<FRACBITS, 0, TTRBTX[0], sc);
+
+			//
+			// FRONT LAYER CHARACTERS
+			//
+
+			if (finalecount >= KNUXSTART)
+			{
+				if (finalecount < KNUXIDLE)
+				{
+					//
+					// Knux Front Layer Intro
+					//
+					if      (finalecount >= KNUXSTART+22  && finalecount < KNUXSTART+28 )
+						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIF[6 ], sc);
+					else if (finalecount >= KNUXSTART+28  && finalecount < KNUXSTART+32 )
+						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIF[7 ], sc);
+					else if (finalecount >= KNUXSTART+32  && finalecount < KNUXSTART+35 )
+						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIF[8 ], sc);
+				}
+				else
+				{
+					//
+					// Knux Front Layer Idle
+					//
+					if (!knux_blink)
+					{
+						SINT8 idlecount = finalecount - knux_idle_start;
+						if      (idlecount >= 0  && idlecount < 5 )
+							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNDH[0 ], sc);
+						else if (idlecount >= 5  && idlecount < 10)
+							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNDH[1 ], sc);
+						else if (idlecount >= 10 && idlecount < 13)
+							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNDH[2 ], sc);
+						else if (idlecount >= 13 && idlecount < 14)
+							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNDH[3 ], sc);
+						else if (idlecount >= 14 && idlecount < 17)
+							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNDH[4 ], sc);
+						else if (idlecount >= 17 && idlecount < 21)
+							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNDH[5 ], sc);
+						else if (idlecount >= 21 && idlecount < 27)
+							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNDH[6 ], sc);
+						else if (idlecount >= 27 && idlecount < 32)
+							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNDH[7 ], sc);
+						else if (idlecount >= 32 && idlecount < 34)
+							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNDH[8 ], sc);
+						else if (idlecount >= 34 && idlecount < 37)
+							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNDH[9 ], sc);
+						else if (idlecount >= 37 && idlecount < 39)
+							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNDH[10], sc);
+						else if (idlecount >= 39 && idlecount < 42)
+							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNDH[11], sc);
+						else if (idlecount >= 42 && idlecount < 46)
+							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNDH[12], sc);
+					}
+					else
+						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNDH[0 ], sc);
+				}
+			}
+
+			if (finalecount >= TAILSSTART)
+			{
+				if (finalecount < TAILSIDLE)
+				{
+					//
+					// Tails Front Layer Intro
+					//
+					if      (finalecount >= TAILSSTART+26  && finalecount < TAILSSTART+30 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIF[7 ], sc);
+					else if (finalecount >= TAILSSTART+30  && finalecount < TAILSSTART+35 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIF[8 ], sc);
+					else if (finalecount >= TAILSSTART+35  && finalecount < TAILSSTART+41 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIF[9 ], sc);
+					else if (finalecount >= TAILSSTART+41  && finalecount < TAILSSTART+43 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIF[10], sc);
+					else if (finalecount >= TAILSSTART+43  && finalecount < TAILSSTART+47 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIF[11], sc);
+					else if (finalecount >= TAILSSTART+47  && finalecount < TAILSSTART+51 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIF[12], sc);
+				}
+				// No Tails Front Layer Idle
+			}
+
+			if (finalecount >= SONICSTART)
+			{
+				if (finalecount < SONICIDLE)
+				{
+					//
+					// Sonic Front Layer Intro
+					//
+					if      (finalecount >= SONICSTART+19  && finalecount < SONICSTART+27 )
+						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIF[5 ], sc);
+					else if (finalecount >= SONICSTART+27  && finalecount < SONICSTART+31 )
+						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIF[6 ], sc);
+					else if (finalecount >= SONICSTART+31  && finalecount < SONICSTART+33 )
+						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIF[7 ], sc);
+					else if (finalecount >= SONICSTART+33  && finalecount < SONICSTART+36 )
+						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIF[8 ], sc);
+					else if (finalecount >= SONICSTART+36  && finalecount < SONICSTART+40 )
+						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIF[9 ], sc);
+					else if (finalecount >= SONICSTART+40  && finalecount < SONICSTART+44 )
+						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIF[10], sc);
+					else if (finalecount >= SONICSTART+44  && finalecount < SONICSTART+47 )
+						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIF[11], sc);
+					// ...
+					else if (finalecount >= SONICSTART+53  && finalecount < SONICSTART+57 )
+						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIF[15], sc);
+				}
+				else
+				{
+					//
+					// Sonic Front Layer Idle
+					//
+					if (!sonic_blink)
+					{
+						SINT8 idlecount = finalecount - sonic_idle_start;
+						if      (idlecount >= 0  && idlecount < 5 )
+							V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSODH[0], sc);
+						else if (idlecount >= 5  && idlecount < 8 )
+							V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSODH[1], sc);
+						else if (idlecount >= 8  && idlecount < 9 )
+							V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSODH[2], sc);
+						else if (idlecount >= 9  && idlecount < 12)
+							V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSODH[3], sc);
+						else if (idlecount >= 12 && idlecount < 17)
+							V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSODH[4], sc);
+						else if (idlecount >= 17 && idlecount < 19)
+							V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSODH[5], sc);
+						else if (idlecount >= 19 && idlecount < 21)
+							V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSODH[6], sc);
+						else if (idlecount >= 21 && idlecount < 22)
+							V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSODH[7], sc);
+						else if (idlecount >= 22 && idlecount < 25)
+							V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSODH[8], sc);
+					}
+					else
+						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSODH[0], sc);
+				}
+			}
+
+			// Flash at tic 30, timed to O__TITLE percussion. Hold the flash until tic 34.
+			// After tic 34, fade the flash until tic 44.
+			if (finalecount > 29 && finalecount < 35)
+				V_DrawFadeScreen(0, 9);
+			else if (finalecount > 34 && 44-finalecount > 0 && 44-finalecount < 10)
+				V_DrawFadeScreen(0, 44-finalecount);
+
+#undef CHARSTART
+#undef SONICSTART
+#undef SONICIDLE
+#undef SONICX
+#undef SONICY
+#undef TAILSSTART
+#undef TAILSIDLE
+#undef TAILSX
+#undef TAILSY
+#undef KNUXSTART
+#undef KNUXIDLE
+#undef KNUXX
+#undef KNUXY
+
+			break;
+
+		case TTMODE_USER:
+			if (!ttuser[max(0, ttuser_count)])
+			{
+				if(curttloop > -1 && ttuser[curttloop])
+					ttuser_count = curttloop;
+				else if (ttuser[max(0, ttuser_count-1)])
+					ttuser_count = max(0, ttuser_count-1);
+				else
+					break; // draw nothing
+			}
+
+			V_DrawSciencePatch(curttx<<FRACBITS, curtty<<FRACBITS, 0, ttuser[ttuser_count], sc);
+
+			if (!(finalecount % max(1, curtttics)))
+				ttuser_count++;
+			break;
+	}
 
 #ifdef HAVE_BLUA
 luahook:
@@ -2586,8 +3540,8 @@ void F_ContinueDrawer(void)
 	{
 		if (!(continuetime & 1) || continuetime > 17)
 			V_DrawContinueIcon(x, 68, 0, players[consoleplayer].skin, players[consoleplayer].skincolor);
-		V_DrawScaledPatch(x+12, 68-2, 0, stlivex);
-		V_DrawRightAlignedString(x+36, 69-5, 0,
+		V_DrawScaledPatch(x+12, 66, 0, stlivex);
+		V_DrawRightAlignedString(x+38, 64, 0,
 			va("%d",(imcontinuing ? ncontinues-1 : ncontinues)));
 	}
 	else
diff --git a/src/f_finale.h b/src/f_finale.h
index 58c492c3d9578725a59df4049b78dc027810a2c6..f75f93c7745057f6b845054070af3a792bae6a3b 100644
--- a/src/f_finale.h
+++ b/src/f_finale.h
@@ -77,6 +77,28 @@ void F_ContinueDrawer(void);
 extern INT32 titlescrollxspeed;
 extern INT32 titlescrollyspeed;
 
+typedef enum
+{
+	TTMODE_NONE = 0,
+	TTMODE_OLD,
+	TTMODE_ALACROIX,
+	TTMODE_USER
+} ttmode_enum;
+
+#define TTMAX_ALACROIX 30 // max frames for SONIC typeface, plus one for NULL terminating entry
+#define TTMAX_USER 100
+
+extern ttmode_enum ttmode;
+extern UINT8 ttscale;
+// ttmode user vars
+extern char ttname[9];
+extern INT16 ttx;
+extern INT16 tty;
+extern INT16 ttloop;
+extern UINT16 tttics;
+extern boolean ttavailable[6];
+
+
 typedef enum
 {
 	TITLEMAP_OFF = 0,
@@ -89,13 +111,22 @@ typedef enum
 extern mobj_t *titlemapcameraref;
 extern char curbgname[9];
 extern SINT8 curfadevalue;
-extern boolean curhidepics;
 extern INT32 curbgcolor;
 extern INT32 curbgxspeed;
 extern INT32 curbgyspeed;
 extern boolean curbghide;
 extern boolean hidetitlemap;
 
+extern boolean curhidepics;
+extern ttmode_enum curttmode;
+extern UINT8 curttscale;
+// ttmode user vars
+extern char curttname[9];
+extern INT16 curttx;
+extern INT16 curtty;
+extern INT16 curttloop;
+extern UINT16 curtttics;
+
 #define TITLEBACKGROUNDACTIVE (curfadevalue >= 0 || curbgname[0])
 
 void F_InitMenuPresValues(void);
diff --git a/src/g_game.c b/src/g_game.c
index e2f43e4f26e13948b94b2cbfbf32e7b3f69e76e5..ceab3a465d4ff48f1de7714badf0634b47fde439 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -2803,7 +2803,7 @@ void G_AddPlayer(INT32 playernum)
 
 			countplayers++;
 
-			if (!players->exiting)
+			if (!players[i].exiting)
 				notexiting++;
 
 			if (!(cv_coopstarposts.value && (gametype == GT_COOP) && (p->starpostnum < players[i].starpostnum)))
@@ -3358,6 +3358,11 @@ void G_LoadGameData(void)
 	// Allow saving of gamedata beyond this point
 	gamedataloaded = true;
 
+	if (M_CheckParm("-gamedata") && M_IsNextParm())
+	{
+		strlcpy(gamedatafilename, M_GetNextParm(), sizeof gamedatafilename);
+	}
+
 	if (M_CheckParm("-resetdata"))
 		return; // Don't load (essentially, reset).
 
diff --git a/src/hardware/hw_cache.c b/src/hardware/hw_cache.c
index 350b845c06c72adb1040bf42d010b3380cb224db..7f5a04526115859ec9ea89fa802749af19be8cc0 100644
--- a/src/hardware/hw_cache.c
+++ b/src/hardware/hw_cache.c
@@ -691,7 +691,9 @@ static void HWR_GenerateTexture(INT32 texnum, GLTexture_t *grtex)
 	// Composite the columns together.
 	for (i = 0, patch = texture->patches; i < texture->patchcount; i++, patch++)
 	{
+#ifndef NO_PNG_LUMPS
 		size_t lumplength = W_LumpLengthPwad(patch->wad, patch->lump);
+#endif
 		realpatch = W_CacheLumpNumPwad(patch->wad, patch->lump, PU_CACHE);
 #ifndef NO_PNG_LUMPS
 		if (R_IsLumpPNG((UINT8 *)realpatch, lumplength))
@@ -824,10 +826,10 @@ static void FreeMipmapColormap(INT32 patchnum, void *patch)
 {
 	GLPatch_t* const grpatch = patch;
 	(void)patchnum; //unused
-	while (grpatch->mipmap.nextcolormap)
+	while (grpatch->mipmap->nextcolormap)
 	{
-		GLMipmap_t *grmip = grpatch->mipmap.nextcolormap;
-		grpatch->mipmap.nextcolormap = grmip->nextcolormap;
+		GLMipmap_t *grmip = grpatch->mipmap->nextcolormap;
+		grpatch->mipmap->nextcolormap = grmip->nextcolormap;
 		if (grmip->grInfo.data) Z_Free(grmip->grInfo.data);
 		free(grmip);
 	}
@@ -930,9 +932,9 @@ static void HWR_LoadPatchFlat(GLMipmap_t *grMipmap, lumpnum_t flatlumpnum)
 {
 	UINT8 *flat;
 	patch_t *patch = (patch_t *)W_CacheLumpNum(flatlumpnum, PU_STATIC);
+#ifndef NO_PNG_LUMPS
 	size_t lumplength = W_LumpLength(flatlumpnum);
 
-#ifndef NO_PNG_LUMPS
 	if (R_IsLumpPNG((UINT8 *)patch, lumplength))
 		patch = R_PNGToPatch((UINT8 *)patch, lumplength, NULL, false);
 #endif
@@ -1004,7 +1006,7 @@ void HWR_GetFlat(lumpnum_t flatlumpnum)
 {
 	GLMipmap_t *grmip;
 
-	grmip = &HWR_GetCachedGLPatch(flatlumpnum)->mipmap;
+	grmip = HWR_GetCachedGLPatch(flatlumpnum)->mipmap;
 
 	if (!grmip->downloaded && !grmip->grInfo.data)
 		HWR_CacheFlat(grmip, flatlumpnum);
@@ -1091,14 +1093,14 @@ static void HWR_LoadMappedPatch(GLMipmap_t *grmip, GLPatch_t *gpatch)
 void HWR_GetPatch(GLPatch_t *gpatch)
 {
 	// is it in hardware cache
-	if (!gpatch->mipmap.downloaded && !gpatch->mipmap.grInfo.data)
+	if (!gpatch->mipmap->downloaded && !gpatch->mipmap->grInfo.data)
 	{
 		// load the software patch, PU_STATIC or the Z_Malloc for hardware patch will
 		// flush the software patch before the conversion! oh yeah I suffered
 		patch_t *ptr = gpatch->rawpatch;
 		if (!ptr)
 			ptr = W_CacheLumpNumPwad(gpatch->wadnum, gpatch->lumpnum, PU_STATIC);
-		HWR_MakePatch(ptr, gpatch, &gpatch->mipmap, true);
+		HWR_MakePatch(ptr, gpatch, gpatch->mipmap, true);
 
 		// this is inefficient.. but the hardware patch in heap is purgeable so it should
 		// not fragment memory, and besides the REAL cache here is the hardware memory
@@ -1107,10 +1109,10 @@ void HWR_GetPatch(GLPatch_t *gpatch)
 			Z_Free(ptr);
 	}
 
-	HWD.pfnSetTexture(&gpatch->mipmap);
+	HWD.pfnSetTexture(gpatch->mipmap);
 
 	// The system-memory patch data can be purged now.
-	Z_ChangeTag(gpatch->mipmap.grInfo.data, PU_HWRCACHE_UNLOCKED);
+	Z_ChangeTag(gpatch->mipmap->grInfo.data, PU_HWRCACHE_UNLOCKED);
 }
 
 
@@ -1130,7 +1132,7 @@ void HWR_GetMappedPatch(GLPatch_t *gpatch, const UINT8 *colormap)
 
 	// search for the mimmap
 	// skip the first (no colormap translated)
-	for (grmip = &gpatch->mipmap; grmip->nextcolormap; )
+	for (grmip = gpatch->mipmap; grmip->nextcolormap; )
 	{
 		grmip = grmip->nextcolormap;
 		if (grmip->colormap == colormap)
@@ -1160,7 +1162,7 @@ void HWR_UnlockCachedPatch(GLPatch_t *gpatch)
 	if (!gpatch)
 		return;
 
-	Z_ChangeTag(gpatch->mipmap.grInfo.data, PU_HWRCACHE_UNLOCKED);
+	Z_ChangeTag(gpatch->mipmap->grInfo.data, PU_HWRCACHE_UNLOCKED);
 	Z_ChangeTag(gpatch, PU_HWRPATCHINFO_UNLOCKED);
 }
 
@@ -1248,7 +1250,7 @@ GLPatch_t *HWR_GetPic(lumpnum_t lumpnum)
 
 	grpatch = HWR_GetCachedGLPatch(lumpnum);
 
-	if (!grpatch->mipmap.downloaded && !grpatch->mipmap.grInfo.data)
+	if (!grpatch->mipmap->downloaded && !grpatch->mipmap->grInfo.data)
 	{
 		pic_t *pic;
 		UINT8 *block;
@@ -1264,19 +1266,19 @@ GLPatch_t *HWR_GetPic(lumpnum_t lumpnum)
 		grpatch->topoffset = 0;
 
 		// find the good 3dfx size (boring spec)
-		HWR_ResizeBlock (grpatch->width, grpatch->height, &grpatch->mipmap.grInfo);
-		grpatch->mipmap.width = (UINT16)blockwidth;
-		grpatch->mipmap.height = (UINT16)blockheight;
+		HWR_ResizeBlock (grpatch->width, grpatch->height, &grpatch->mipmap->grInfo);
+		grpatch->mipmap->width = (UINT16)blockwidth;
+		grpatch->mipmap->height = (UINT16)blockheight;
 
 		if (pic->mode == PALETTE)
-			grpatch->mipmap.grInfo.format = textureformat; // can be set by driver
+			grpatch->mipmap->grInfo.format = textureformat; // can be set by driver
 		else
-			grpatch->mipmap.grInfo.format = picmode2GR[pic->mode];
+			grpatch->mipmap->grInfo.format = picmode2GR[pic->mode];
 
-		Z_Free(grpatch->mipmap.grInfo.data);
+		Z_Free(grpatch->mipmap->grInfo.data);
 
 		// allocate block
-		block = MakeBlock(&grpatch->mipmap);
+		block = MakeBlock(grpatch->mipmap);
 
 		// if rounddown, rounddown patches as well as textures
 		if (cv_grrounddown.value)
@@ -1306,25 +1308,25 @@ GLPatch_t *HWR_GetPic(lumpnum_t lumpnum)
 
 		if (grpatch->width  == blockwidth &&
 			grpatch->height == blockheight &&
-			format2bpp[grpatch->mipmap.grInfo.format] == format2bpp[picmode2GR[pic->mode]])
+			format2bpp[grpatch->mipmap->grInfo.format] == format2bpp[picmode2GR[pic->mode]])
 		{
 			// no conversion needed
-			M_Memcpy(grpatch->mipmap.grInfo.data, pic->data,len);
+			M_Memcpy(grpatch->mipmap->grInfo.data, pic->data,len);
 		}
 		else
 			HWR_DrawPicInCache(block, newwidth, newheight,
-			                   blockwidth*format2bpp[grpatch->mipmap.grInfo.format],
+			                   blockwidth*format2bpp[grpatch->mipmap->grInfo.format],
 			                   pic,
-			                   format2bpp[grpatch->mipmap.grInfo.format]);
+			                   format2bpp[grpatch->mipmap->grInfo.format]);
 
 		Z_Unlock(pic);
 		Z_ChangeTag(block, PU_HWRCACHE_UNLOCKED);
 
-		grpatch->mipmap.flags = 0;
+		grpatch->mipmap->flags = 0;
 		grpatch->max_s = (float)newwidth  / (float)blockwidth;
 		grpatch->max_t = (float)newheight / (float)blockheight;
 	}
-	HWD.pfnSetTexture(&grpatch->mipmap);
+	HWD.pfnSetTexture(grpatch->mipmap);
 	//CONS_Debug(DBG_RENDER, "picloaded at %x as texture %d\n",grpatch->mipmap.grInfo.data, grpatch->mipmap.downloaded);
 
 	return grpatch;
@@ -1340,6 +1342,7 @@ GLPatch_t *HWR_GetCachedGLPatchPwad(UINT16 wadnum, UINT16 lumpnum)
 		grpatch = Z_Calloc(sizeof(GLPatch_t), PU_HWRPATCHINFO, NULL);
 		grpatch->wadnum = wadnum;
 		grpatch->lumpnum = lumpnum;
+		grpatch->mipmap = Z_Calloc(sizeof(GLMipmap_t), PU_HWRPATCHINFO, NULL);
 		M_AATreeSet(hwrcache, lumpnum, grpatch);
 	}
 
@@ -1360,6 +1363,7 @@ GLPatch_t *HWR_GetCachedGLRotSprite(aatree_t *hwrcache, UINT16 rollangle, patch_
 	{
 		grpatch = Z_Calloc(sizeof(GLPatch_t), PU_HWRPATCHINFO, NULL);
 		grpatch->rawpatch = rawpatch;
+		grpatch->mipmap = Z_Calloc(sizeof(GLMipmap_t), PU_HWRPATCHINFO, NULL);
 		M_AATreeSet(hwrcache, rollangle, grpatch);
 	}
 
@@ -1459,7 +1463,7 @@ void HWR_GetFadeMask(lumpnum_t fademasklumpnum)
 {
 	GLMipmap_t *grmip;
 
-	grmip = &HWR_GetCachedGLPatch(fademasklumpnum)->mipmap;
+	grmip = HWR_GetCachedGLPatch(fademasklumpnum)->mipmap;
 
 	if (!grmip->downloaded && !grmip->grInfo.data)
 		HWR_CacheFadeMask(grmip, fademasklumpnum);
diff --git a/src/hardware/hw_data.h b/src/hardware/hw_data.h
index a8f030f73950171bfefb0514562983bf16211d71..ef57426a4b66b484d3285a2a39f92dd7be14a474 100644
--- a/src/hardware/hw_data.h
+++ b/src/hardware/hw_data.h
@@ -84,8 +84,8 @@ struct GLPatch_s
 	UINT16              wadnum;      // the software patch lump num for when the hardware patch
 	UINT16              lumpnum;     // was flushed, and we need to re-create it
 	void                *rawpatch;   // :^)
-	GLMipmap_t          mipmap;
-};
+	GLMipmap_t          *mipmap;
+} ATTRPACK;
 typedef struct GLPatch_s GLPatch_t;
 
 #endif //_HWR_DATA_
diff --git a/src/hardware/hw_light.c b/src/hardware/hw_light.c
index fa66536b6592edd1687ba80e066bb6603f405780..491cb739f999a82c8013c68fcbe45e7ad3798568 100644
--- a/src/hardware/hw_light.c
+++ b/src/hardware/hw_light.c
@@ -406,7 +406,7 @@ light_t *t_lspr[NUMSPRITES] =
 	&lspr[NOLIGHT],     // SPR_HHPL
 	&lspr[NOLIGHT],     // SPR_SHRM
 	&lspr[NOLIGHT],     // SPR_HHZM
-	
+
 	// Azure Temple Scenery
 	&lspr[NOLIGHT],     // SPR_BGAR
 	&lspr[NOLIGHT],     // SPR_RCRY
@@ -1205,7 +1205,8 @@ void HWR_DL_AddLight(gr_vissprite_t *spr, GLPatch_t *patch)
 	dynlights->nb++;
 }
 
-static GLPatch_t lightmappatch;
+static GLMipmap_t lightmappatchmipmap;
+static GLPatch_t lightmappatch = { .mipmap = &lightmappatchmipmap };
 
 void HWR_InitLight(void)
 {
@@ -1215,7 +1216,7 @@ void HWR_InitLight(void)
 	for (i = 0;i < NUMLIGHTS;i++)
 		lspr[i].dynamic_sqrradius = lspr[i].dynamic_radius*lspr[i].dynamic_radius;
 
-	lightmappatch.mipmap.downloaded = false;
+	lightmappatch.mipmap->downloaded = false;
 	coronalumpnum = W_CheckNumForName("CORONA");
 }
 
@@ -1226,10 +1227,10 @@ static void HWR_SetLight(void)
 {
 	int    i, j;
 
-	if (!lightmappatch.mipmap.downloaded && !lightmappatch.mipmap.grInfo.data)
+	if (!lightmappatch.mipmap->downloaded && !lightmappatch.mipmap->grInfo.data)
 	{
 
-		UINT16 *Data = Z_Malloc(129*128*sizeof (UINT16), PU_HWRCACHE, &lightmappatch.mipmap.grInfo.data);
+		UINT16 *Data = Z_Malloc(129*128*sizeof (UINT16), PU_HWRCACHE, &lightmappatch.mipmap->grInfo.data);
 
 		for (i = 0; i < 128; i++)
 		{
@@ -1242,23 +1243,23 @@ static void HWR_SetLight(void)
 					Data[i*128+j] = 0;
 			}
 		}
-		lightmappatch.mipmap.grInfo.format = GR_TEXFMT_ALPHA_INTENSITY_88;
+		lightmappatch.mipmap->grInfo.format = GR_TEXFMT_ALPHA_INTENSITY_88;
 
 		lightmappatch.width = 128;
 		lightmappatch.height = 128;
-		lightmappatch.mipmap.width = 128;
-		lightmappatch.mipmap.height = 128;
+		lightmappatch.mipmap->width = 128;
+		lightmappatch.mipmap->height = 128;
 #ifdef GLIDE_API_COMPATIBILITY
-		lightmappatch.mipmap.grInfo.smallLodLog2 = GR_LOD_LOG2_128;
-		lightmappatch.mipmap.grInfo.largeLodLog2 = GR_LOD_LOG2_128;
-		lightmappatch.mipmap.grInfo.aspectRatioLog2 = GR_ASPECT_LOG2_1x1;
+		lightmappatch.mipmap->grInfo.smallLodLog2 = GR_LOD_LOG2_128;
+		lightmappatch.mipmap->grInfo.largeLodLog2 = GR_LOD_LOG2_128;
+		lightmappatch.mipmap->grInfo.aspectRatioLog2 = GR_ASPECT_LOG2_1x1;
 #endif
-		lightmappatch.mipmap.flags = 0; //TF_WRAPXY; // DEBUG: view the overdraw !
+		lightmappatch.mipmap->flags = 0; //TF_WRAPXY; // DEBUG: view the overdraw !
 	}
-	HWD.pfnSetTexture(&lightmappatch.mipmap);
+	HWD.pfnSetTexture(lightmappatch.mipmap);
 
 	// The system-memory data can be purged now.
-	Z_ChangeTag(lightmappatch.mipmap.grInfo.data, PU_HWRCACHE_UNLOCKED);
+	Z_ChangeTag(lightmappatch.mipmap->grInfo.data, PU_HWRCACHE_UNLOCKED);
 }
 
 //**********************************************************
diff --git a/src/hardware/hw_md2.c b/src/hardware/hw_md2.c
index 479d64cad3bf51db96562f2343d63eea562c1036..6b69e45bf0b980ff661972f60e2d8f6adb632472 100644
--- a/src/hardware/hw_md2.c
+++ b/src/hardware/hw_md2.c
@@ -567,7 +567,7 @@ static GrTextureFormat_t PNG_Load(const char *filename, int *w, int *h, GLPatch_
 		//CONS_Debug(DBG_RENDER, "libpng load error on %s\n", filename);
 		png_destroy_read_struct(&png_ptr, &png_info_ptr, NULL);
 		fclose(png_FILE);
-		Z_Free(grpatch->mipmap.grInfo.data);
+		Z_Free(grpatch->mipmap->grInfo.data);
 		return 0;
 	}
 #ifdef USE_FAR_KEYWORD
@@ -608,7 +608,7 @@ static GrTextureFormat_t PNG_Load(const char *filename, int *w, int *h, GLPatch_
 
 	{
 		png_uint_32 i, pitch = png_get_rowbytes(png_ptr, png_info_ptr);
-		png_bytep PNG_image = Z_Malloc(pitch*height, PU_HWRCACHE, &grpatch->mipmap.grInfo.data);
+		png_bytep PNG_image = Z_Malloc(pitch*height, PU_HWRCACHE, &grpatch->mipmap->grInfo.data);
 		png_bytepp row_pointers = png_malloc(png_ptr, height * sizeof (png_bytep));
 		for (i = 0; i < height; i++)
 			row_pointers[i] = PNG_image + i*pitch;
@@ -682,7 +682,7 @@ static GrTextureFormat_t PCX_Load(const char *filename, int *w, int *h,
 
 	pw = *w = header.xmax - header.xmin + 1;
 	ph = *h = header.ymax - header.ymin + 1;
-	image = Z_Malloc(pw*ph*4, PU_HWRCACHE, &grpatch->mipmap.grInfo.data);
+	image = Z_Malloc(pw*ph*4, PU_HWRCACHE, &grpatch->mipmap->grInfo.data);
 
 	if (fread(palette, sizeof (UINT8), PALSIZE, file) != PALSIZE)
 	{
@@ -730,39 +730,42 @@ static void md2_loadTexture(md2_t *model)
 	if (model->grpatch)
 	{
 		grpatch = model->grpatch;
-		Z_Free(grpatch->mipmap.grInfo.data);
+		Z_Free(grpatch->mipmap->grInfo.data);
 	}
 	else
+	{
 		grpatch = Z_Calloc(sizeof *grpatch, PU_HWRPATCHINFO,
 		                   &(model->grpatch));
+		grpatch->mipmap = Z_Calloc(sizeof (GLMipmap_t), PU_HWRPATCHINFO, NULL);
+	}
 
-	if (!grpatch->mipmap.downloaded && !grpatch->mipmap.grInfo.data)
+	if (!grpatch->mipmap->downloaded && !grpatch->mipmap->grInfo.data)
 	{
 		int w = 0, h = 0;
 #ifdef HAVE_PNG
-		grpatch->mipmap.grInfo.format = PNG_Load(filename, &w, &h, grpatch);
-		if (grpatch->mipmap.grInfo.format == 0)
+		grpatch->mipmap->grInfo.format = PNG_Load(filename, &w, &h, grpatch);
+		if (grpatch->mipmap->grInfo.format == 0)
 #endif
-		grpatch->mipmap.grInfo.format = PCX_Load(filename, &w, &h, grpatch);
-		if (grpatch->mipmap.grInfo.format == 0)
+		grpatch->mipmap->grInfo.format = PCX_Load(filename, &w, &h, grpatch);
+		if (grpatch->mipmap->grInfo.format == 0)
 			return;
 
-		grpatch->mipmap.downloaded = 0;
-		grpatch->mipmap.flags = 0;
+		grpatch->mipmap->downloaded = 0;
+		grpatch->mipmap->flags = 0;
 
 		grpatch->width = (INT16)w;
 		grpatch->height = (INT16)h;
-		grpatch->mipmap.width = (UINT16)w;
-		grpatch->mipmap.height = (UINT16)h;
+		grpatch->mipmap->width = (UINT16)w;
+		grpatch->mipmap->height = (UINT16)h;
 
 #ifdef GLIDE_API_COMPATIBILITY
 		// not correct!
-		grpatch->mipmap.grInfo.smallLodLog2 = GR_LOD_LOG2_256;
-		grpatch->mipmap.grInfo.largeLodLog2 = GR_LOD_LOG2_256;
-		grpatch->mipmap.grInfo.aspectRatioLog2 = GR_ASPECT_LOG2_1x1;
+		grpatch->mipmap->grInfo.smallLodLog2 = GR_LOD_LOG2_256;
+		grpatch->mipmap->grInfo.largeLodLog2 = GR_LOD_LOG2_256;
+		grpatch->mipmap->grInfo.aspectRatioLog2 = GR_ASPECT_LOG2_1x1;
 #endif
 	}
-	HWD.pfnSetTexture(&grpatch->mipmap);
+	HWD.pfnSetTexture(grpatch->mipmap);
 	HWR_UnlockCachedPatch(grpatch);
 }
 
@@ -780,42 +783,45 @@ static void md2_loadBlendTexture(md2_t *model)
 	if (model->blendgrpatch)
 	{
 		grpatch = model->blendgrpatch;
-		Z_Free(grpatch->mipmap.grInfo.data);
+		Z_Free(grpatch->mipmap->grInfo.data);
 	}
 	else
+	{
 		grpatch = Z_Calloc(sizeof *grpatch, PU_HWRPATCHINFO,
 		                   &(model->blendgrpatch));
+		grpatch->mipmap = Z_Calloc(sizeof (GLMipmap_t), PU_HWRPATCHINFO, NULL);
+	}
 
-	if (!grpatch->mipmap.downloaded && !grpatch->mipmap.grInfo.data)
+	if (!grpatch->mipmap->downloaded && !grpatch->mipmap->grInfo.data)
 	{
 		int w = 0, h = 0;
 #ifdef HAVE_PNG
-		grpatch->mipmap.grInfo.format = PNG_Load(filename, &w, &h, grpatch);
-		if (grpatch->mipmap.grInfo.format == 0)
+		grpatch->mipmap->grInfo.format = PNG_Load(filename, &w, &h, grpatch);
+		if (grpatch->mipmap->grInfo.format == 0)
 #endif
-		grpatch->mipmap.grInfo.format = PCX_Load(filename, &w, &h, grpatch);
-		if (grpatch->mipmap.grInfo.format == 0)
+		grpatch->mipmap->grInfo.format = PCX_Load(filename, &w, &h, grpatch);
+		if (grpatch->mipmap->grInfo.format == 0)
 		{
 			Z_Free(filename);
 			return;
 		}
 
-		grpatch->mipmap.downloaded = 0;
-		grpatch->mipmap.flags = 0;
+		grpatch->mipmap->downloaded = 0;
+		grpatch->mipmap->flags = 0;
 
 		grpatch->width = (INT16)w;
 		grpatch->height = (INT16)h;
-		grpatch->mipmap.width = (UINT16)w;
-		grpatch->mipmap.height = (UINT16)h;
+		grpatch->mipmap->width = (UINT16)w;
+		grpatch->mipmap->height = (UINT16)h;
 
 #ifdef GLIDE_API_COMPATIBILITY
 		// not correct!
-		grpatch->mipmap.grInfo.smallLodLog2 = GR_LOD_LOG2_256;
-		grpatch->mipmap.grInfo.largeLodLog2 = GR_LOD_LOG2_256;
-		grpatch->mipmap.grInfo.aspectRatioLog2 = GR_ASPECT_LOG2_1x1;
+		grpatch->mipmap->grInfo.smallLodLog2 = GR_LOD_LOG2_256;
+		grpatch->mipmap->grInfo.largeLodLog2 = GR_LOD_LOG2_256;
+		grpatch->mipmap->grInfo.aspectRatioLog2 = GR_ASPECT_LOG2_1x1;
 #endif
 	}
-	HWD.pfnSetTexture(&grpatch->mipmap); // We do need to do this so that it can be cleared and knows to recreate it when necessary
+	HWD.pfnSetTexture(grpatch->mipmap); // We do need to do this so that it can be cleared and knows to recreate it when necessary
 	HWR_UnlockCachedPatch(grpatch);
 
 	Z_Free(filename);
@@ -1029,8 +1035,8 @@ static void HWR_CreateBlendedTexture(GLPatch_t *gpatch, GLPatch_t *blendgpatch,
 	cur = Z_Malloc(size*4, PU_HWRCACHE, &grmip->grInfo.data);
 	memset(cur, 0x00, size*4);
 
-	image = gpatch->mipmap.grInfo.data;
-	blendimage = blendgpatch->mipmap.grInfo.data;
+	image = gpatch->mipmap->grInfo.data;
+	blendimage = blendgpatch->mipmap->grInfo.data;
 
 	// Average all of the translation's colors
 	if (color == SKINCOLOR_NONE || color >= MAXTRANSLATIONS)
@@ -1145,13 +1151,13 @@ static void HWR_GetBlendedTexture(GLPatch_t *gpatch, GLPatch_t *blendgpatch, INT
 	if (colormap == colormaps || colormap == NULL)
 	{
 		// Don't do any blending
-		HWD.pfnSetTexture(&gpatch->mipmap);
+		HWD.pfnSetTexture(gpatch->mipmap);
 		return;
 	}
 
 	// search for the mimmap
 	// skip the first (no colormap translated)
-	for (grmip = &gpatch->mipmap; grmip->nextcolormap; )
+	for (grmip = gpatch->mipmap; grmip->nextcolormap; )
 	{
 		grmip = grmip->nextcolormap;
 		if (grmip->colormap == colormap)
@@ -1371,18 +1377,18 @@ void HWR_DrawMD2(gr_vissprite_t *spr)
 		finalscale = md2->scale;
 		//Hurdler: arf, I don't like that implementation at all... too much crappy
 		gpatch = md2->grpatch;
-		if (!gpatch || !gpatch->mipmap.grInfo.format || !gpatch->mipmap.downloaded)
+		if (!gpatch || !gpatch->mipmap->grInfo.format || !gpatch->mipmap->downloaded)
 			md2_loadTexture(md2);
 		gpatch = md2->grpatch; // Load it again, because it isn't being loaded into gpatch after md2_loadtexture...
 
-		if ((gpatch && gpatch->mipmap.grInfo.format) // don't load the blend texture if the base texture isn't available
-			&& (!md2->blendgrpatch || !((GLPatch_t *)md2->blendgrpatch)->mipmap.grInfo.format || !((GLPatch_t *)md2->blendgrpatch)->mipmap.downloaded))
+		if ((gpatch && gpatch->mipmap->grInfo.format) // don't load the blend texture if the base texture isn't available
+			&& (!md2->blendgrpatch || !((GLPatch_t *)md2->blendgrpatch)->mipmap->grInfo.format || !((GLPatch_t *)md2->blendgrpatch)->mipmap->downloaded))
 			md2_loadBlendTexture(md2);
 
-		if (gpatch && gpatch->mipmap.grInfo.format) // else if meant that if a texture couldn't be loaded, it would just end up using something else's texture
+		if (gpatch && gpatch->mipmap->grInfo.format) // else if meant that if a texture couldn't be loaded, it would just end up using something else's texture
 		{
 			if ((skincolors_t)spr->mobj->color != SKINCOLOR_NONE &&
-				md2->blendgrpatch && ((GLPatch_t *)md2->blendgrpatch)->mipmap.grInfo.format
+				md2->blendgrpatch && ((GLPatch_t *)md2->blendgrpatch)->mipmap->grInfo.format
 				&& gpatch->width == ((GLPatch_t *)md2->blendgrpatch)->width && gpatch->height == ((GLPatch_t *)md2->blendgrpatch)->height)
 			{
 				INT32 skinnum = TC_DEFAULT;
@@ -1413,7 +1419,7 @@ void HWR_DrawMD2(gr_vissprite_t *spr)
 			else
 			{
 				// This is safe, since we know the texture has been downloaded
-				HWD.pfnSetTexture(&gpatch->mipmap);
+				HWD.pfnSetTexture(gpatch->mipmap);
 			}
 		}
 		else
diff --git a/src/info.c b/src/info.c
index 683298a927a81ff16187072d190024089401f0fd..612050d6524c25c922bfa866b526c3699fb4e94b 100644
--- a/src/info.c
+++ b/src/info.c
@@ -808,7 +808,7 @@ state_t states[NUMSTATES] =
 	{SPR_PLAY, SPR2_LIFE, 20, {NULL}, 0,  4, S_NULL},       // S_PLAY_ICON3
 
 	// Level end sign (uses player sprite)
-	{SPR_PLAY, SPR2_SIGN, 1, {NULL}, 0, 24, S_PLAY_SIGN},         // S_PLAY_SIGN
+	{SPR_PLAY, SPR2_SIGN|FF_PAPERSPRITE, -1, {NULL}, 0, 29, S_PLAY_SIGN},         // S_PLAY_SIGN
 
 	// NiGHTS Player, transforming
 	{SPR_PLAY, SPR2_TRNS|FF_ANIMATE,     7, {NULL},          0, 4, S_PLAY_NIGHTS_TRANS2}, // S_PLAY_NIGHTS_TRANS1
@@ -1925,59 +1925,18 @@ state_t states[NUMSTATES] =
 	{SPR_BBLS, 3, 8, {A_BubbleCheck}, 0, 0, S_BUBBLES1}, // S_BUBBLES4
 
 	// Level End Sign
-	{SPR_SIGN, 0, 1, {NULL}, 0, 0, S_SIGN2},         // S_SIGN1
-	{SPR_SIGN, 1, 1, {NULL}, 0, 0, S_SIGN3},         // S_SIGN2
-	{SPR_SIGN, 2, 1, {NULL}, 0, 0, S_SIGN4},         // S_SIGN3
-	{SPR_SIGN, 5, 1, {NULL}, 0, 0, S_SIGN5},         // S_SIGN4
-	{SPR_SIGN, 0, 1, {NULL}, 0, 0, S_SIGN6},         // S_SIGN5
-	{SPR_SIGN, 1, 1, {NULL}, 0, 0, S_SIGN7},         // S_SIGN6
-	{SPR_SIGN, 2, 1, {NULL}, 0, 0, S_SIGN8},         // S_SIGN7
-	{SPR_SIGN, 3, 1, {NULL}, 0, 0, S_SIGN9},         // S_SIGN8
-	{SPR_SIGN, 0, 1, {NULL}, 0, 0, S_SIGN10},        // S_SIGN9
-	{SPR_SIGN, 1, 1, {NULL}, 0, 0, S_SIGN11},        // S_SIGN10
-	{SPR_SIGN, 2, 1, {NULL}, 0, 0, S_SIGN12},        // S_SIGN11
-	{SPR_SIGN, 4, 1, {NULL}, 0, 0, S_SIGN13},        // S_SIGN12
-	{SPR_SIGN, 0, 1, {NULL}, 0, 0, S_SIGN14},        // S_SIGN13
-	{SPR_SIGN, 1, 1, {NULL}, 0, 0, S_SIGN15},        // S_SIGN14
-	{SPR_SIGN, 2, 1, {NULL}, 0, 0, S_SIGN16},        // S_SIGN15
-	{SPR_SIGN, 3, 1, {NULL}, 0, 0, S_SIGN17},        // S_SIGN16
-	{SPR_SIGN, 0, 1, {NULL}, 0, 0, S_SIGN18},        // S_SIGN17
-	{SPR_SIGN, 1, 1, {NULL}, 0, 0, S_SIGN19},        // S_SIGN18
-	{SPR_SIGN, 2, 1, {NULL}, 0, 0, S_SIGN20},        // S_SIGN19
-	{SPR_SIGN, 6, 1, {NULL}, 0, 0, S_SIGN21},        // S_SIGN20
-	{SPR_SIGN, 0, 1, {NULL}, 0, 0, S_SIGN22},        // S_SIGN21
-	{SPR_SIGN, 1, 1, {NULL}, 0, 0, S_SIGN23},        // S_SIGN22
-	{SPR_SIGN, 2, 1, {NULL}, 0, 0, S_SIGN24},        // S_SIGN23
-	{SPR_SIGN, 3, 1, {NULL}, 0, 0, S_SIGN25},        // S_SIGN24
-	{SPR_SIGN, 0, 1, {NULL}, 0, 0, S_SIGN26},        // S_SIGN25
-	{SPR_SIGN, 1, 1, {NULL}, 0, 0, S_SIGN27},        // S_SIGN26
-	{SPR_SIGN, 2, 1, {NULL}, 0, 0, S_SIGN28},        // S_SIGN27
-	{SPR_SIGN, 5, 1, {NULL}, 0, 0, S_SIGN29},        // S_SIGN28
-	{SPR_SIGN, 0, 1, {NULL}, 0, 0, S_SIGN30},        // S_SIGN29
-	{SPR_SIGN, 1, 1, {NULL}, 0, 0, S_SIGN31},        // S_SIGN30
-	{SPR_SIGN, 2, 1, {NULL}, 0, 0, S_SIGN32},        // S_SIGN31
-	{SPR_SIGN, 3, 1, {NULL}, 0, 0, S_SIGN33},        // S_SIGN32
-	{SPR_SIGN, 0, 1, {NULL}, 0, 0, S_SIGN34},        // S_SIGN33
-	{SPR_SIGN, 1, 1, {NULL}, 0, 0, S_SIGN35},        // S_SIGN34
-	{SPR_SIGN, 2, 1, {NULL}, 0, 0, S_SIGN36},        // S_SIGN35
-	{SPR_SIGN, 4, 1, {NULL}, 0, 0, S_SIGN37},        // S_SIGN36
-	{SPR_SIGN, 0, 1, {NULL}, 0, 0, S_SIGN38},        // S_SIGN37
-	{SPR_SIGN, 1, 1, {NULL}, 0, 0, S_SIGN39},        // S_SIGN38
-	{SPR_SIGN, 2, 1, {NULL}, 0, 0, S_SIGN40},        // S_SIGN39
-	{SPR_SIGN, 3, 1, {NULL}, 0, 0, S_SIGN41},        // S_SIGN40
-	{SPR_SIGN, 0, 1, {NULL}, 0, 0, S_SIGN42},        // S_SIGN41
-	{SPR_SIGN, 1, 1, {NULL}, 0, 0, S_SIGN43},        // S_SIGN42
-	{SPR_SIGN, 2, 1, {NULL}, 0, 0, S_SIGN44},        // S_SIGN43
-	{SPR_SIGN, 6, 1, {NULL}, 0, 0, S_SIGN45},        // S_SIGN44
-	{SPR_SIGN, 0, 1, {NULL}, 0, 0, S_SIGN46},        // S_SIGN45
-	{SPR_SIGN, 1, 1, {NULL}, 0, 0, S_SIGN47},        // S_SIGN46
-	{SPR_SIGN, 2, 1, {NULL}, 0, 0, S_SIGN48},        // S_SIGN47
-	{SPR_SIGN, 3, 1, {NULL}, 0, 0, S_SIGN49},        // S_SIGN48
-	{SPR_SIGN, 0, 1, {NULL}, 0, 0, S_SIGN50},        // S_SIGN49
-	{SPR_SIGN, 1, 1, {NULL}, 0, 0, S_SIGN51},        // S_SIGN50
-	{SPR_SIGN, 2, 1, {NULL}, 0, 0, S_SIGN53},        // S_SIGN51
-	{SPR_SIGN, 3, -1, {NULL}, 0, 0, S_NULL},         // S_SIGN52 Eggman
-	{SPR_SIGN, 7, -1, {A_SignPlayer}, 0, 0, S_NULL}, // S_SIGN53 Blank
+	{SPR_SIGN,                0, -1, {A_SignPlayer}, -3, 0, S_NULL},                    // S_SIGN
+	{SPR_SIGN,                0,  1,   {A_SignSpin}, 30, 0, S_SIGNSPIN2},            // S_SIGNSPIN1
+	{SPR_SIGN,                0,  0,     {A_Repeat},  4, S_SIGNSPIN1, S_SIGNSPIN3},   // S_SIGNSPIN2
+	{SPR_SIGN,                0,  0, {A_SignPlayer}, -2, 0, S_SIGNSPIN4},                // S_SIGNSPIN3
+	{SPR_SIGN,                0,  1,   {A_SignSpin}, 30, 0, S_SIGNSPIN5},            // S_SIGNSPIN4
+	{SPR_SIGN,                0,  0,     {A_Repeat},  4, S_SIGNSPIN4, S_SIGNSPIN6},   // S_SIGNSPIN5
+	{SPR_SIGN,                0,  0, {A_SignPlayer}, -3, 0, S_SIGNSPIN1},                // S_SIGNSPIN6
+	{SPR_SIGN,                0,  1, {A_SignPlayer}, -1, 0, S_SIGNSLOW},                // S_SIGNPLAYER
+	{SPR_SIGN,                0,  1,   {A_SignSpin}, 30, 0, S_SIGNSLOW},                // S_SIGNSLOW
+	{SPR_SIGN,                0, -1,         {NULL},  0, 0, S_NULL},                    // S_SIGNSTOP
+	{SPR_SIGN, FF_PAPERSPRITE|2, -1,         {NULL},  0, 0, S_NULL},                    // S_SIGNBOARD
+	{SPR_SIGN, FF_PAPERSPRITE|1, -1,         {NULL},  0, 29, S_NULL},                   // S_EGGMANSIGN
 
 	// Spike Ball
 	{SPR_SPIK, 0, 1, {NULL}, 0, 0, S_SPIKEBALL2}, // S_SPIKEBALL1
@@ -5298,7 +5257,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOGRAVITY|MF_NOCLIPTHING|MF_NOBLOCKMAP|MF_RUNSPAWNFUNC, // flags
+		MF_NOGRAVITY|MF_NOCLIPTHING|MF_NOBLOCKMAP|MF_RUNSPAWNFUNC|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
@@ -5325,7 +5284,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOGRAVITY|MF_NOCLIPTHING|MF_NOBLOCKMAP|MF_RUNSPAWNFUNC, // flags
+		MF_NOGRAVITY|MF_NOCLIPTHING|MF_NOBLOCKMAP|MF_RUNSPAWNFUNC|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
@@ -7836,29 +7795,29 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 
 	{           // MT_SIGN
 		501,            // doomednum
-		S_SIGN52,       // spawnstate
+		S_SIGN,         // spawnstate
 		1000,           // spawnhealth
 		S_PLAY_SIGN,    // seestate
 		sfx_lvpass,     // seesound
 		8,              // reactiontime
 		sfx_None,       // attacksound
-		S_NULL,         // painstate
-		0,              // painchance
+		S_SIGNPLAYER,   // painstate
+		MT_SPARK,       // painchance
 		sfx_None,       // painsound
-		S_NULL,         // meleestate
+		S_EGGMANSIGN,   // meleestate
 		S_NULL,         // missilestate
-		S_NULL,         // deathstate
+		S_SIGNSTOP,     // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		8,              // speed
-		8*FRACUNIT,     // radius
+		36*FRACUNIT,    // radius
 		32*FRACUNIT,    // height
 		0,              // display offset
 		16,             // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOCLIP|MF_SCENERY, // flags
-		S_NULL          // raisestate
+		MF_NOCLIP|MF_SCENERY|MF_BOUNCE|MF_RUNSPAWNFUNC, // flags
+		S_SIGNBOARD     // raisestate
 	},
 
 	{           // MT_SPIKEBALL
@@ -13304,7 +13263,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		0,              // speed
+		3200*FRACUNIT,  // speed
 		30*FRACUNIT,    // radius
 		32*FRACUNIT,    // height
 		1,              // display offset
@@ -13392,7 +13351,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIP|MF_SPAWNCEILING,  // flags
+		MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIP|MF_SPAWNCEILING|MF_SCENERY,  // flags
 		S_NULL          // raisestate
 	},
 
@@ -19042,7 +19001,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_PUMA_DOWN3,   // xdeathstate
 		sfx_None,       // deathsound
-		0,              // speed
+		2000*FRACUNIT,  // speed
 		8*FRACUNIT,     // radius
 		16*FRACUNIT,    // height
 		0,              // display offset
@@ -19076,7 +19035,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY, // flags
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
diff --git a/src/info.h b/src/info.h
index 48354d65a15b2603f3cebb3af7f5c7e4e71c731f..9cc99982ceb48cfd4f38b2e1a51c3ffdaea6f2c0 100644
--- a/src/info.h
+++ b/src/info.h
@@ -63,6 +63,7 @@ void A_FishJump(); // Fish Jump
 void A_ThrownRing(); // Sparkle trail for red ring
 void A_SetSolidSteam();
 void A_UnsetSolidSteam();
+void A_SignSpin();
 void A_SignPlayer();
 void A_OverlayThink();
 void A_JetChase();
@@ -561,7 +562,7 @@ typedef enum sprite
 	SPR_HHPL, // Dr Seuss Trees
 	SPR_SHRM, // Mushroom
 	SPR_HHZM, // Misc
-	
+
 	// Azure Temple Scenery
 	SPR_BGAR, // ATZ Gargoyles
 	SPR_RCRY, // ATZ Red Crystal (Target)
@@ -2063,59 +2064,18 @@ typedef enum state
 	S_BUBBLES4,
 
 	// Level End Sign
-	S_SIGN1,
-	S_SIGN2,
-	S_SIGN3,
-	S_SIGN4,
-	S_SIGN5,
-	S_SIGN6,
-	S_SIGN7,
-	S_SIGN8,
-	S_SIGN9,
-	S_SIGN10,
-	S_SIGN11,
-	S_SIGN12,
-	S_SIGN13,
-	S_SIGN14,
-	S_SIGN15,
-	S_SIGN16,
-	S_SIGN17,
-	S_SIGN18,
-	S_SIGN19,
-	S_SIGN20,
-	S_SIGN21,
-	S_SIGN22,
-	S_SIGN23,
-	S_SIGN24,
-	S_SIGN25,
-	S_SIGN26,
-	S_SIGN27,
-	S_SIGN28,
-	S_SIGN29,
-	S_SIGN30,
-	S_SIGN31,
-	S_SIGN32,
-	S_SIGN33,
-	S_SIGN34,
-	S_SIGN35,
-	S_SIGN36,
-	S_SIGN37,
-	S_SIGN38,
-	S_SIGN39,
-	S_SIGN40,
-	S_SIGN41,
-	S_SIGN42,
-	S_SIGN43,
-	S_SIGN44,
-	S_SIGN45,
-	S_SIGN46,
-	S_SIGN47,
-	S_SIGN48,
-	S_SIGN49,
-	S_SIGN50,
-	S_SIGN51,
-	S_SIGN52, // Eggman
-	S_SIGN53,
+	S_SIGN,
+	S_SIGNSPIN1,
+	S_SIGNSPIN2,
+	S_SIGNSPIN3,
+	S_SIGNSPIN4,
+	S_SIGNSPIN5,
+	S_SIGNSPIN6,
+	S_SIGNPLAYER,
+	S_SIGNSLOW,
+	S_SIGNSTOP,
+	S_SIGNBOARD,
+	S_EGGMANSIGN,
 
 	// Spike Ball
 	S_SPIKEBALL1,
diff --git a/src/m_menu.c b/src/m_menu.c
index 8fe782cac20fad747cc878b73f25432928f65b17..aec4da32113cc8b2c0f2874c6733e62baa7c675b 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -2289,6 +2289,13 @@ void M_InitMenuPresTables(void)
 		// so-called "undefined"
 		menupres[i].fadestrength = -1;
 		menupres[i].hidetitlepics = -1; // inherits global hidetitlepics
+		menupres[i].ttmode = TTMODE_NONE;
+		menupres[i].ttscale = UINT8_MAX;
+		menupres[i].ttname[0] = 0;
+		menupres[i].ttx = INT16_MAX;
+		menupres[i].tty = INT16_MAX;
+		menupres[i].ttloop = INT16_MAX;
+		menupres[i].tttics = UINT16_MAX;
 		menupres[i].enterwipe = -1;
 		menupres[i].exitwipe = -1;
 		menupres[i].bgcolor = -1;
@@ -2307,7 +2314,7 @@ void M_InitMenuPresTables(void)
 			strncpy(menupres[i].musname, "_recat", 7);
 		else if (i == MN_SP_NIGHTSATTACK)
 			strncpy(menupres[i].musname, "_nitat", 7);
-		else if (i == MN_SP_PLAYER)
+		else if (i == MN_SP_PLAYER || i == MN_SR_PLAYER)
 			strncpy(menupres[i].musname, "_chsel", 7);
 	}
 }
@@ -2467,7 +2474,7 @@ static boolean MIT_SetCurFadeValue(UINT32 menutype, INT32 level, INT32 *retval,
 	return false;
 }
 
-static boolean MIT_SetCurHideTitlePics(UINT32 menutype, INT32 level, INT32 *retval, void **input, boolean fromoldest)
+static boolean MIT_SetCurTitlePics(UINT32 menutype, INT32 level, INT32 *retval, void **input, boolean fromoldest)
 {
 	(void)input;
 	(void)retval;
@@ -2481,8 +2488,41 @@ static boolean MIT_SetCurHideTitlePics(UINT32 menutype, INT32 level, INT32 *retv
 		curhidepics = menupres[menutype].hidetitlepics;
 		return true;
 	}
+	else if (menupres[menutype].ttmode == TTMODE_USER)
+	{
+		if (menupres[menutype].ttname[0])
+		{
+			curhidepics = menupres[menutype].hidetitlepics;
+			curttmode = menupres[menutype].ttmode;
+			curttscale = (menupres[menutype].ttscale != UINT8_MAX ? menupres[menutype].ttscale : ttscale);
+			strncpy(curttname, menupres[menutype].ttname, 9);
+			curttx = (menupres[menutype].ttx != INT16_MAX ? menupres[menutype].ttx : ttx);
+			curtty = (menupres[menutype].tty != INT16_MAX ? menupres[menutype].tty : tty);
+			curttloop = (menupres[menutype].ttloop != INT16_MAX ? menupres[menutype].ttloop : ttloop);
+			curtttics = (menupres[menutype].tttics != UINT16_MAX ? menupres[menutype].tttics : tttics);
+		}
+		else
+			curhidepics = menupres[menutype].hidetitlepics;
+		return true;
+	}
+	else if (menupres[menutype].ttmode != TTMODE_NONE)
+	{
+		curhidepics = menupres[menutype].hidetitlepics;
+		curttmode = menupres[menutype].ttmode;
+		curttscale = (menupres[menutype].ttscale != UINT8_MAX ? menupres[menutype].ttscale : ttscale);
+		return true;
+	}
 	else if (!level)
+	{
 		curhidepics = hidetitlepics;
+		curttmode = ttmode;
+		curttscale = ttscale;
+		strncpy(curttname, ttname, 9);
+		curttx = ttx;
+		curtty = tty;
+		curttloop = ttloop;
+		curtttics = tttics;
+	}
 	return false;
 }
 
@@ -2527,9 +2567,9 @@ void M_SetMenuCurFadeValue(UINT8 defaultvalue)
 	M_IterateMenuTree(MIT_SetCurFadeValue, &defaultvalue);
 }
 
-void M_SetMenuCurHideTitlePics(void)
+void M_SetMenuCurTitlePics(void)
 {
-	M_IterateMenuTree(MIT_SetCurHideTitlePics, NULL);
+	M_IterateMenuTree(MIT_SetCurTitlePics, NULL);
 }
 
 // ====================================
@@ -2579,12 +2619,20 @@ static void M_HandleMenuPresState(menu_t *newMenu)
 	curbgyspeed = titlescrollyspeed;
 	curbghide = (gamestate != GS_TIMEATTACK); // show in time attack, hide in other menus
 
+	curttmode = ttmode;
+	curttscale = ttscale;
+	strncpy(curttname, ttname, 9);
+	curttx = ttx;
+	curtty = tty;
+	curttloop = ttloop;
+	curtttics = tttics;
+
 	// don't do the below during the in-game menus
 	if (gamestate != GS_TITLESCREEN && gamestate != GS_TIMEATTACK)
 		return;
 
 	M_SetMenuCurFadeValue(16);
-	M_SetMenuCurHideTitlePics();
+	M_SetMenuCurTitlePics();
 
 	// Loop through both menu IDs in parallel and look for type changes
 	// The youngest child in activeMenuId is the entered menu
@@ -8723,6 +8771,10 @@ void M_DrawTimeAttackMenu(void)
 		char beststr[40];
 		char reqscore[40], reqtime[40], reqrings[40];
 
+		strcpy(reqscore, "\0");
+		strcpy(reqtime, "\0");
+		strcpy(reqrings, "\0");
+
 		M_DrawLevelPlatterHeader(32-lsheadingheight/2, cv_nextmap.string, true, false);
 
 		//  A 160x100 image of the level as entry MAPxxP
diff --git a/src/m_menu.h b/src/m_menu.h
index 3bfa48597b9d2ecafcb086d571eb6d405581a4c8..51c734a43c8a6199c37857a622c69c2a567113f7 100644
--- a/src/m_menu.h
+++ b/src/m_menu.h
@@ -18,6 +18,7 @@
 #include "d_event.h"
 #include "command.h"
 #include "r_things.h" // for SKINNAMESIZE
+#include "f_finale.h" // for ttmode_enum
 
 //
 // MENUS
@@ -128,19 +129,27 @@ typedef enum
 typedef struct
 {
 	char bgname[8]; // name for background gfx lump; lays over titlemap if this is set
-	SINT8 hidetitlepics; // hide title gfx per menu; -1 means undefined, inherits global setting
+	SINT8 fadestrength;  // darken background when displaying this menu, strength 0-31 or -1 for undefined
+	INT32 bgcolor; // fill color, overrides bg name. -1 means follow bg name rules.
 	INT32 titlescrollxspeed; // background gfx scroll per menu; inherits global setting
 	INT32 titlescrollyspeed; // y scroll
-	INT32 bgcolor; // fill color, overrides bg name. -1 means follow bg name rules.
 	boolean bghide; // for titlemaps, hide the background.
 
+	SINT8 hidetitlepics; // hide title gfx per menu; -1 means undefined, inherits global setting
+	ttmode_enum ttmode; // title wing animation mode; default TTMODE_OLD
+	UINT8 ttscale; // scale of title wing gfx (FRACUNIT / ttscale); -1 means undefined, inherits global setting
+	char ttname[9]; // lump name of title wing gfx. If name length is <= 6, engine will attempt to load numbered frames (TTNAMExx)
+	INT16 ttx; // X position of title wing
+	INT16 tty; // Y position of title wing
+	INT16 ttloop; // # frame to loop; -1 means dont loop
+	UINT16 tttics; // # of tics per frame
+
 	char musname[7]; ///< Music track to play. "" for no music.
 	UINT16 mustrack; ///< Subsong to play. Only really relevant for music modules and specific formats supported by GME. 0 to ignore.
 	boolean muslooping; ///< Loop the music
 	boolean musstop; ///< Don't play any music
 	boolean musignore; ///< Let the current music keep playing
 
-	SINT8 fadestrength;  // darken background when displaying this menu, strength 0-31 or -1 for undefined
 	boolean enterbubble; // run all entrance line execs after common ancestor and up to child. If false, only run the child's exec
 	boolean exitbubble; // run all exit line execs from child and up to before common ancestor. If false, only run the child's exec
 	INT32 entertag; // line exec to run on menu enter, if titlemap
@@ -158,7 +167,7 @@ UINT8 M_GetYoungestChildMenu(void);
 void M_ChangeMenuMusic(const char *defaultmusname, boolean defaultmuslooping);
 void M_SetMenuCurBackground(const char *defaultname);
 void M_SetMenuCurFadeValue(UINT8 defaultvalue);
-void M_SetMenuCurHideTitlePics(void);
+void M_SetMenuCurTitlePics(void);
 
 // Called by main loop,
 // saves config file and calls I_Quit when user exits.
diff --git a/src/p_enemy.c b/src/p_enemy.c
index 389e100f4faf5a5018c6fac80c659146b5b3598e..1f2cef211290f23a8d81b66bf102b82fde4d4e24 100644
--- a/src/p_enemy.c
+++ b/src/p_enemy.c
@@ -129,6 +129,7 @@ void A_FishJump(mobj_t *actor);
 void A_ThrownRing(mobj_t *actor);
 void A_SetSolidSteam(mobj_t *actor);
 void A_UnsetSolidSteam(mobj_t *actor);
+void A_SignSpin(mobj_t *actor);
 void A_SignPlayer(mobj_t *actor);
 void A_OverlayThink(mobj_t *actor);
 void A_JetChase(mobj_t *actor);
@@ -4764,7 +4765,7 @@ void A_DropMine(mobj_t *actor)
 // Description: Makes the stupid harmless fish in Greenflower Zone jump.
 //
 // var1 = Jump strength (in FRACBITS), if specified. Otherwise, uses the angle value.
-// var2 = unused
+// var2 = Trail object to spawn, if desired.
 //
 void A_FishJump(mobj_t *actor)
 {
@@ -4777,8 +4778,17 @@ void A_FishJump(mobj_t *actor)
 
 	if (locvar2)
 	{
-		fixed_t rad = actor->radius>>FRACBITS;
-		P_SpawnMobjFromMobj(actor, P_RandomRange(rad, -rad)<<FRACBITS, P_RandomRange(rad, -rad)<<FRACBITS, 0, (mobjtype_t)locvar2);
+		UINT8 i;
+		// Don't spawn trail unless a player is nearby.
+		for (i = 0; i < MAXPLAYERS; ++i)
+			if (playeringame[i] && players[i].mo
+				&& P_AproxDistance(actor->x - players[i].mo->x, actor->y - players[i].mo->y) < (actor->info->speed))
+				break; // Stop looking.
+		if (i < MAXPLAYERS)
+		{
+			fixed_t rad = actor->radius>>FRACBITS;
+			P_SpawnMobjFromMobj(actor, P_RandomRange(rad, -rad)<<FRACBITS, P_RandomRange(rad, -rad)<<FRACBITS, 0, (mobjtype_t)locvar2);
+		}
 	}
 
 	if ((actor->z <= actor->floorz) || (actor->z <= actor->watertop - FixedMul((64 << FRACBITS), actor->scale)))
@@ -5011,59 +5021,186 @@ void A_UnsetSolidSteam(mobj_t *actor)
 	actor->flags |= MF_NOCLIP;
 }
 
+// Function: A_SignSpin
+//
+// Description: Spins a signpost until it hits the ground and reaches its mapthing's angle.
+//
+// var1 = degrees to rotate object (must be positive, because I'm lazy)
+// var2 = unused
+//
+void A_SignSpin(mobj_t *actor)
+{
+	INT32 locvar1 = var1;
+	INT32 locvar2 = var2;
+	INT16 i;
+	angle_t rotateangle = FixedAngle(locvar1 << FRACBITS);
+
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_SignSpin", actor))
+		return;
+#endif
+
+	if (P_IsObjectOnGround(actor) && P_MobjFlip(actor) * actor->momz <= 0)
+	{
+		if (actor->spawnpoint)
+		{
+			angle_t mapangle = FixedAngle(actor->spawnpoint->angle << FRACBITS);
+			angle_t diff = mapangle - actor->angle;
+			if (diff < ANG2)
+			{
+				actor->angle = mapangle;
+				P_SetMobjState(actor, actor->info->deathstate);
+				return;
+			}
+			if ((statenum_t)(actor->state-states) != actor->info->painstate)
+				P_SetMobjState(actor, actor->info->painstate);
+			actor->movedir = min((mapangle - actor->angle) >> 2, actor->movedir);
+		}
+		else // no mapthing? just finish in your current angle
+		{
+			P_SetMobjState(actor, locvar2);
+			return;
+		}
+	}
+	else
+	{
+		actor->movedir = rotateangle;
+	}
+	actor->angle += actor->movedir;
+	if (actor->tracer == NULL || P_MobjWasRemoved(actor->tracer)) return;
+	for (i = -1; i < 2; i += 2)
+	{
+		P_SpawnMobjFromMobj(actor,
+			P_ReturnThrustX(actor, actor->tracer->angle, i * actor->radius),
+			P_ReturnThrustY(actor, actor->tracer->angle, i * actor->radius),
+			(actor->eflags & MFE_VERTICALFLIP) ? 0 : actor->height,
+			actor->info->painchance)->destscale >>= 1;
+	}
+}
+
 // Function: A_SignPlayer
 //
 // Description: Changes the state of a level end sign to reflect the player that hit it.
+//              Also used to display Eggman or the skin roulette whilst spinning.
 //
-// var1 = unused
-// var2 = unused
+// var1 = number of skin to display (e.g. 2 = Knuckles; special cases: -1 = target's skin, -2 = skin roulette, -3 = Eggman)
+// var2 = custom sign color, if desired.
 //
 void A_SignPlayer(mobj_t *actor)
 {
+	INT32 locvar1 = var1;
+	INT32 locvar2 = var2;
+	skin_t *skin = NULL;
 	mobj_t *ov;
-	skin_t *skin;
+	UINT8 facecolor, signcolor = (UINT8)locvar2;
+	UINT32 signframe = states[actor->info->raisestate].frame;
+
 #ifdef HAVE_BLUA
 	if (LUA_CallAction("A_SignPlayer", actor))
 		return;
 #endif
-	if (!actor->target)
-		return;
 
-	if (!actor->target->player)
+	if (actor->tracer == NULL || locvar1 < -3 || locvar1 >= numskins)
 		return;
 
-	skin = &skins[actor->target->player->skin];
+	// if no face overlay, spawn one
+	if (actor->tracer->tracer == NULL || P_MobjWasRemoved(actor->tracer->tracer))
+	{
+		ov = P_SpawnMobj(actor->x, actor->y, actor->z, MT_OVERLAY);
+		P_SetTarget(&ov->target, actor->tracer);
+		P_SetTarget(&actor->tracer->tracer, ov);
+	}
+	else
+		ov = actor->tracer->tracer;
 
-	if ((actor->target->player->skincolor == skin->prefcolor) && (skin->prefoppositecolor)) // Set it as the skin's preferred oppositecolor?
+	if (locvar1 == -1) // set to target's skin
 	{
-		actor->color = skin->prefoppositecolor;
-		/*
-		If you're here from the comment above Color_Opposite,
-		the following line is the one which is dependent on the
-		array being symmetrical. It gets the opposite of the
-		opposite of your desired colour just so it can get the
-		brightness frame for the End Sign. It's not a great
-		design choice, but it's constant time array access and
-		the idea that the colours should be OPPOSITES is kind
-		of in the name. If you have a better idea, feel free
-		to let me know. ~toast 2016/07/20
-		*/
-		actor->frame += (15 - Color_Opposite[Color_Opposite[skin->prefoppositecolor - 1][0] - 1][1]);
+		if (!actor->target)
+			return;
+
+		if (!actor->target->player)
+			return;
+
+		skin = &skins[actor->target->player->skin];
+		facecolor = actor->target->player->skincolor;
+
+		if (signcolor)
+			;
+		else if ((actor->target->player->skincolor == skin->prefcolor) && (skin->prefoppositecolor)) // Set it as the skin's preferred oppositecolor?
+		{
+			signcolor = skin->prefoppositecolor;
+			/*
+			If you're here from the comment above Color_Opposite,
+			the following line is the one which is dependent on the
+			array being symmetrical. It gets the opposite of the
+			opposite of your desired colour just so it can get the
+			brightness frame for the End Sign. It's not a great
+			design choice, but it's constant time array access and
+			the idea that the colours should be OPPOSITES is kind
+			of in the name. If you have a better idea, feel free
+			to let me know. ~toast 2016/07/20
+			*/
+			signframe += (15 - Color_Opposite[Color_Opposite[skin->prefoppositecolor - 1][0] - 1][1]);
+		}
+		else if (actor->target->player->skincolor) // Set the sign to be an appropriate background color for this player's skincolor.
+		{
+			signcolor = Color_Opposite[actor->target->player->skincolor - 1][0];
+			signframe += (15 - Color_Opposite[actor->target->player->skincolor - 1][1]);
+		}
+		else
+			signcolor = SKINCOLOR_NONE;
 	}
-	else if (actor->target->player->skincolor) // Set the sign to be an appropriate background color for this player's skincolor.
+	else if (locvar1 != -3) // set to a defined skin
 	{
-		actor->color = Color_Opposite[actor->target->player->skincolor - 1][0];
-		actor->frame += (15 - Color_Opposite[actor->target->player->skincolor - 1][1]);
+		// I turned this function into a fucking mess. I'm so sorry. -Lach
+		if (locvar1 == -2) // next skin
+		{
+			if (ov->skin == NULL) // pick a random skin to start with!
+				skin = &skins[P_RandomKey(numskins)];
+			else // otherwise, advance 1 skin
+			{
+				UINT8 skinnum = (skin_t*)ov->skin-skins;
+				player_t *player = actor->target ? actor->target->player : NULL;
+				while ((skinnum = (skinnum + 1) % numskins) && (player ? !R_SkinUsable(player-players, skinnum) : skins[skinnum].availability > 0));
+				skin = &skins[skinnum];
+			}
+		}
+		else // specific skin
+		{
+			skin = &skins[locvar1];
+		}
+
+		facecolor = skin->prefcolor;
+		if (signcolor)
+			;
+		else if (skin->prefoppositecolor)
+		{
+			signcolor = skin->prefoppositecolor;
+		}
+		else
+		{
+			signcolor = Color_Opposite[facecolor - 1][0];
+		}
+		signframe += (15 - Color_Opposite[Color_Opposite[signcolor - 1][0] - 1][1]);
 	}
 
-	if (skin->sprites[SPR2_SIGN].numframes)
+	if (skin != NULL && skin->sprites[SPR2_SIGN].numframes) // player face
 	{
-		// spawn an overlay of the player's face.
-		ov = P_SpawnMobj(actor->x, actor->y, actor->z, MT_OVERLAY);
-		P_SetTarget(&ov->target, actor);
-		ov->color = actor->target->player->skincolor;
+		ov->color = facecolor;
 		ov->skin = skin;
 		P_SetMobjState(ov, actor->info->seestate); // S_PLAY_SIGN
+		actor->tracer->color = signcolor;
+		actor->tracer->frame = signframe;
+	}
+	else // Eggman face
+	{
+		ov->color = SKINCOLOR_NONE;
+		P_SetMobjState(ov, actor->info->meleestate); // S_EGGMANSIGN
+		if (signcolor)
+			actor->tracer->color = signcolor;
+		else
+			actor->tracer->color = signcolor = SKINCOLOR_CARBON;
+		actor->tracer->frame = signframe += (15 - Color_Opposite[Color_Opposite[signcolor - 1][0] - 1][1]);
 	}
 }
 
@@ -5111,7 +5248,7 @@ void A_OverlayThink(mobj_t *actor)
 		actor->z = actor->target->z + actor->target->height - mobjinfo[actor->type].height  - ((var2>>16) ? -1 : 1)*(var2&0xFFFF)*FRACUNIT;
 	else
 		actor->z = actor->target->z + ((var2>>16) ? -1 : 1)*(var2&0xFFFF)*FRACUNIT;
-	actor->angle = actor->target->angle;
+	actor->angle = actor->target->angle + actor->movedir;
 	actor->eflags = actor->target->eflags;
 
 	actor->momx = actor->target->momx;
@@ -14128,7 +14265,7 @@ void A_LavafallRocks(mobj_t *actor)
 	// Don't spawn rocks unless a player is relatively close by.
 	for (i = 0; i < MAXPLAYERS; ++i)
 		if (playeringame[i] && players[i].mo
-			&& P_AproxDistance(actor->x - players[i].mo->x, actor->y - players[i].mo->y) < (1600 << FRACBITS))
+			&& P_AproxDistance(actor->x - players[i].mo->x, actor->y - players[i].mo->y) < (actor->info->speed >> 1))
 			break; // Stop looking.
 
 	if (i < MAXPLAYERS)
@@ -14151,6 +14288,7 @@ void A_LavafallRocks(mobj_t *actor)
 void A_LavafallLava(mobj_t *actor)
 {
 	mobj_t *lavafall;
+	UINT8 i;
 
 #ifdef HAVE_BLUA
 	if (LUA_CallAction("A_LavafallLava", actor))
@@ -14160,6 +14298,15 @@ void A_LavafallLava(mobj_t *actor)
 	if ((40 - actor->fuse) % (2*(actor->scale >> FRACBITS)))
 		return;
 
+	// Don't spawn lava unless a player is nearby.
+	for (i = 0; i < MAXPLAYERS; ++i)
+		if (playeringame[i] && players[i].mo
+			&& P_AproxDistance(actor->x - players[i].mo->x, actor->y - players[i].mo->y) < (actor->info->speed))
+			break; // Stop looking.
+
+	if (i >= MAXPLAYERS)
+		return;
+
 	lavafall = P_SpawnMobjFromMobj(actor, 0, 0, -8*FRACUNIT, MT_LAVAFALL_LAVA);
 	lavafall->momz = -P_MobjFlip(actor)*25*FRACUNIT;
 }
diff --git a/src/p_floor.c b/src/p_floor.c
index 1360375a751c3930ea97bc153b8a07dd44a501f2..423fc1b70a8e7da8fd63f5a98ca65a54c509d3f3 100644
--- a/src/p_floor.c
+++ b/src/p_floor.c
@@ -2459,7 +2459,7 @@ void T_RaiseSector(levelspecthink_t *raise)
 			break;
 		}
 	}
-	
+
 	if (raise->vars[9]) // Dynamically Sinking Platform^tm
 	{
 #define shaketime 10
diff --git a/src/p_inter.c b/src/p_inter.c
index 9017f795d38d6066c9512c37298e581560a02aaa..ee2c54dd7116a3de989a3dfee88b0c7e1fcad9b2 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -633,7 +633,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 				if (!(netgame || multiplayer))
 				{
 					player->continues += 1;
-					players->gotcontinue = true;
+					player->gotcontinue = true;
 					if (P_IsLocalPlayer(player))
 						S_StartSound(NULL, sfx_s3kac);
 					else
@@ -2482,6 +2482,8 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
 		P_UnsetThingPosition(target);
 		target->flags |= MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY;
 		P_SetThingPosition(target);
+		target->standingslope = NULL;
+		target->pmomz = 0;
 
 		if (target->player->powers[pw_super])
 		{
diff --git a/src/p_map.c b/src/p_map.c
index 159489f708c64f5262e7b8c2fa86552608c0944d..bb56a50b16932ae818f2a8e62f13a9e940cd3688 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -540,7 +540,7 @@ static void P_DoFanAndGasJet(mobj_t *spring, mobj_t *object)
 
 static void P_DoPterabyteCarry(player_t *player, mobj_t *ptera)
 {
-	if (player->powers[pw_carry] && players->powers[pw_carry] != CR_ROLLOUT)
+	if (player->powers[pw_carry] && player->powers[pw_carry] != CR_ROLLOUT)
 		return;
 	if (ptera->extravalue1 != 1)
 		return; // Not swooping
@@ -1040,7 +1040,7 @@ static boolean PIT_CheckThing(mobj_t *thing)
 	{
 		if (tmthing->z > thing->z + thing->height || thing->z > tmthing->z + tmthing->height || !thing->health)
 			return true;
-		
+
 		if (thing == tmthing->tracer) // don't collide with rider
 			return true;
 
@@ -1054,7 +1054,7 @@ static boolean PIT_CheckThing(mobj_t *thing)
 			P_KillMobj(thing, tmthing, tmthing->tracer, 0);
 			return true;
 		}
-		
+
 		if (thing->type == tmthing->type // bounce against other rollout rocks
 			&& (tmthing->momx || tmthing->momy || thing->momx || thing->momy))
 		{
diff --git a/src/p_mobj.c b/src/p_mobj.c
index 5735dc27bc8ca95811dac3ee854c7f7bcd27497f..0f0f8305bc5efd164e58ba8a7c1a4754ebde7233 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -1861,6 +1861,9 @@ void P_XYMovement(mobj_t *mo)
 	oldy = mo->y;
 
 #ifdef ESLOPE
+	if (mo->flags & MF_NOCLIPHEIGHT)
+		mo->standingslope = NULL;
+
 	// adjust various things based on slope
 	if (mo->standingslope && abs(mo->standingslope->zdelta) > FRACUNIT>>8) {
 		if (!P_IsObjectOnGround(mo)) { // We fell off at some point? Do the twisty thing!
@@ -1991,7 +1994,7 @@ void P_XYMovement(mobj_t *mo)
 					mo->momz = transfermomz;
 					mo->standingslope = NULL;
 					if (player->pflags & PF_SPINNING)
-						player->pflags = (player->pflags & ~PF_SPINNING) | (PF_JUMPED | PF_THOKKED);
+						player->pflags |= PF_THOKKED;
 				}
 			}
 #endif
@@ -2051,7 +2054,7 @@ void P_XYMovement(mobj_t *mo)
 		return;
 
 #ifdef ESLOPE
-	if (moved && oldslope) { // Check to see if we ran off
+	if (moved && oldslope && !(mo->flags & MF_NOCLIPHEIGHT)) { // Check to see if we ran off
 
 		if (oldslope != mo->standingslope) { // First, compare different slopes
 			angle_t oldangle, newangle;
@@ -2459,16 +2462,6 @@ static boolean P_ZMovement(mobj_t *mo)
 				P_RemoveMobj(mo);
 				return false;
 			}
-			if (mo->momz
-			&& !(mo->flags & MF_NOGRAVITY)
-			&& ((!(mo->eflags & MFE_VERTICALFLIP) && mo->z <= mo->floorz)
-			|| ((mo->eflags & MFE_VERTICALFLIP) && mo->z+mo->height >= mo->ceilingz)))
-			{
-				mo->flags |= MF_NOGRAVITY;
-				mo->momx = 8; // this is a hack which is used to ensure it still behaves as a missile and can damage others
-				mo->momy = mo->momz = 0;
-				mo->z = ((mo->eflags & MFE_VERTICALFLIP) ? mo->ceilingz-mo->height : mo->floorz);
-			}
 			break;
 		case MT_GOOP:
 			if (P_CheckDeathPitCollide(mo))
@@ -3314,7 +3307,7 @@ void P_MobjCheckWater(mobj_t *mobj)
 	ffloor_t *rover;
 	player_t *p = mobj->player; // Will just be null if not a player.
 	fixed_t height = (p ? P_GetPlayerHeight(p) : mobj->height); // for players, calculation height does not necessarily match actual height for gameplay reasons (spin, etc)
-	boolean wasgroundpounding = (p && (mobj->eflags & MFE_GOOWATER) && ((p->powers[pw_shield] & SH_NOSTACK) == SH_ELEMENTAL || (p->powers[pw_shield] & SH_NOSTACK) == SH_BUBBLEWRAP) && (p->pflags & PF_SHIELDABILITY));
+	boolean wasgroundpounding = (p && ((p->powers[pw_shield] & SH_NOSTACK) == SH_ELEMENTAL || (p->powers[pw_shield] & SH_NOSTACK) == SH_BUBBLEWRAP) && (p->pflags & PF_SHIELDABILITY));
 
 	// Default if no water exists.
 	mobj->watertop = mobj->waterbottom = mobj->z - 1000*FRACUNIT;
@@ -3414,7 +3407,7 @@ void P_MobjCheckWater(mobj_t *mobj)
 			p->powers[pw_underwater] = underwatertics + 1;
 		}
 
-		if (wasgroundpounding)
+		if ((wasgroundpounding = ((mobj->eflags & MFE_GOOWATER) && wasgroundpounding)))
 		{
 			p->pflags &= ~PF_SHIELDABILITY;
 			mobj->momz >>= 1;
@@ -6914,7 +6907,7 @@ void P_RunOverlays(void)
 
 		mo->eflags = (mo->eflags & ~MFE_VERTICALFLIP) | (mo->target->eflags & MFE_VERTICALFLIP);
 		mo->scale = mo->destscale = mo->target->scale;
-		mo->angle = mo->target->angle;
+		mo->angle = mo->target->angle + mo->movedir;
 
 		if (!(mo->state->frame & FF_ANIMATE))
 			zoffs = FixedMul(((signed)mo->state->var2)*FRACUNIT, mo->scale);
@@ -9436,6 +9429,13 @@ void P_MobjThinker(mobj_t *mobj)
 
 					hdist = R_PointToDist2(mobj->x, mobj->y, mobj->target->x, mobj->target->y);
 
+					if (hdist > 1500*FRACUNIT)
+					{
+						mobj->flags2 &= ~MF2_BOSSNOTRAP;
+						P_SetTarget(&mobj->target, NULL);
+						break;
+					}
+
 					if (!(mobj->flags2 & MF2_BOSSNOTRAP) && hdist <= 450*FRACUNIT)
 						mobj->flags2 |= MF2_BOSSNOTRAP;
 
@@ -9455,11 +9455,6 @@ void P_MobjThinker(mobj_t *mobj)
 						mobj->momx = 0;
 						mobj->momy = 0;
 						mobj->momz = 0;
-						if (hdist >= 1500*FRACUNIT)
-						{
-							mobj->flags2 &= ~MF2_BOSSNOTRAP;
-							P_SetTarget(&mobj->target, NULL);
-						}
 					}
 					break;
 				}
@@ -9569,6 +9564,14 @@ void P_MobjThinker(mobj_t *mobj)
 					else
 						mobj->z = mobj->floorz;
 				}
+				else if ((!(mobj->eflags & MFE_VERTICALFLIP) && mobj->z <= mobj->floorz)
+				|| ((mobj->eflags & MFE_VERTICALFLIP) && mobj->z+mobj->height >= mobj->ceilingz))
+				{
+					mobj->flags |= MF_NOGRAVITY;
+					mobj->momx = 8; // this is a hack which is used to ensure it still behaves as a missile and can damage others
+					mobj->momy = mobj->momz = 0;
+					mobj->z = ((mobj->eflags & MFE_VERTICALFLIP) ? mobj->ceilingz-mobj->height : mobj->floorz);
+				}
 				/* FALLTHRU */
 			default:
 				// check mobj against possible water content, before movement code
@@ -10441,6 +10444,11 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
 			mobj->extravalue2 = 0;
 			mobj->fuse = 100;
 			break;
+		case MT_SIGN:
+			P_SetTarget(&mobj->tracer, P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_OVERLAY));
+			P_SetTarget(&mobj->tracer->target, mobj);
+			P_SetMobjState(mobj->tracer, S_SIGNBOARD);
+			mobj->tracer->movedir = ANGLE_90;
 		default:
 			break;
 	}
diff --git a/src/p_spec.c b/src/p_spec.c
index 50939ae5b2fd8beca0ddce735490bb7711487105..366377bedbf926228dc5253173ece07678f8dccd 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -4054,11 +4054,15 @@ void P_SetupSignExit(player_t *player)
 		if (thing->type != MT_SIGN)
 			continue;
 
+		if (!player->mo->target || player->mo->target->type != MT_SIGN)
+			P_SetTarget(&player->mo->target, thing);
+
 		if (thing->state != &states[thing->info->spawnstate])
 			continue;
 
 		P_SetTarget(&thing->target, player->mo);
-		P_SetMobjState(thing, S_SIGN1);
+		P_SetObjectMomZ(thing, 12*FRACUNIT, false);
+		P_SetMobjState(thing, S_SIGNSPIN1);
 		if (thing->info->seesound)
 			S_StartSound(thing, thing->info->seesound);
 
@@ -4079,11 +4083,15 @@ void P_SetupSignExit(player_t *player)
 		if (thing->type != MT_SIGN)
 			continue;
 
+		if (!player->mo->target || player->mo->target->type != MT_SIGN)
+			P_SetTarget(&player->mo->target, thing);
+
 		if (thing->state != &states[thing->info->spawnstate])
 			continue;
 
 		P_SetTarget(&thing->target, player->mo);
-		P_SetMobjState(thing, S_SIGN1);
+		P_SetObjectMomZ(thing, 12*FRACUNIT, false);
+		P_SetMobjState(thing, S_SIGNSPIN1);
 		if (thing->info->seesound)
 			S_StartSound(thing, thing->info->seesound);
 
@@ -6068,7 +6076,7 @@ static void P_AddAirbob(sector_t *sec, line_t *sourceline, boolean noadjust, boo
 	airbob->vars[5] = sec->ceilingheight;
 	airbob->vars[4] = airbob->vars[5]
 			- (sec->ceilingheight - sec->floorheight);
-	
+
 	airbob->vars[9] = dynamic ? 1 : 0;
 
 	airbob->sourceline = sourceline;
diff --git a/src/p_user.c b/src/p_user.c
index 764802e519f878ccb3c47c09be954dff5f801ed2..d5d8c6ccdc5371afc50594a1dd2a96784a29f5b0 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -1240,6 +1240,8 @@ void P_GivePlayerLives(player_t *player, INT32 numlives)
 			numlives = (numlives + prevlives - player->lives);
 		}
 	}
+	else if (player->lives == INFLIVES)
+		return;
 
 	player->lives += numlives;
 
@@ -2225,8 +2227,8 @@ boolean P_PlayerHitFloor(player_t *player, boolean dorollstuff)
 	{
 		if (dorollstuff)
 		{
-			if ((player->charability2 == CA2_SPINDASH) && !(player->pflags & PF_THOKKED) && (player->cmd.buttons & BT_USE) && (FixedHypot(player->mo->momx, player->mo->momy) > (5*player->mo->scale)))
-				player->pflags |= PF_SPINNING;
+			if ((player->charability2 == CA2_SPINDASH) && !((player->pflags & (PF_SPINNING|PF_THOKKED)) == PF_THOKKED) && (player->cmd.buttons & BT_USE) && (FixedHypot(player->mo->momx, player->mo->momy) > (5*player->mo->scale)))
+				player->pflags = (player->pflags|PF_SPINNING) & ~PF_THOKKED;
 			else if (!(player->pflags & PF_STARTDASH))
 				player->pflags &= ~PF_SPINNING;
 		}
@@ -5767,7 +5769,7 @@ static void P_2dMovement(player_t *player)
 			movepushforward >>= 1; // Proper air movement
 
 		// Allow a bit of movement while spinning
-		if (player->pflags & PF_SPINNING)
+		if ((player->pflags & (PF_SPINNING|PF_THOKKED)) == PF_SPINNING)
 		{
 			if (!(player->pflags & PF_STARTDASH))
 				movepushforward = movepushforward/48;
@@ -5794,7 +5796,7 @@ static void P_3dMovement(player_t *player)
 	angle_t dangle; // replaces old quadrants bits
 	fixed_t normalspd = FixedMul(player->normalspeed, player->mo->scale);
 	boolean analogmove = false;
-	boolean spin = ((onground = P_IsObjectOnGround(player->mo)) && player->pflags & PF_SPINNING && (player->rmomx || player->rmomy) && !(player->pflags & PF_STARTDASH));
+	boolean spin = ((onground = P_IsObjectOnGround(player->mo)) && (player->pflags & (PF_SPINNING|PF_THOKKED)) == PF_SPINNING && (player->rmomx || player->rmomy) && !(player->pflags & PF_STARTDASH));
 	fixed_t oldMagnitude, newMagnitude;
 #ifdef ESLOPE
 	vector3_t totalthrust;
@@ -5979,7 +5981,7 @@ static void P_3dMovement(player_t *player)
 		movepushforward = cmd->forwardmove * (thrustfactor * acceleration);
 
 		// Allow a bit of movement while spinning
-		if (player->pflags & PF_SPINNING)
+		if ((player->pflags & (PF_SPINNING|PF_THOKKED)) == PF_SPINNING)
 		{
 			if ((mforward && cmd->forwardmove > 0) || (mbackward && cmd->forwardmove < 0)
 			|| (player->pflags & PF_STARTDASH))
@@ -6020,7 +6022,7 @@ static void P_3dMovement(player_t *player)
 			movepushforward = max(abs(cmd->sidemove), abs(cmd->forwardmove)) * (thrustfactor * acceleration);
 
 			// Allow a bit of movement while spinning
-			if (player->pflags & PF_SPINNING)
+			if ((player->pflags & (PF_SPINNING|PF_THOKKED)) == PF_SPINNING)
 			{
 				if ((mforward && cmd->forwardmove > 0) || (mbackward && cmd->forwardmove < 0)
 				|| (player->pflags & PF_STARTDASH))
@@ -6055,11 +6057,11 @@ static void P_3dMovement(player_t *player)
 		{
 			movepushside >>= 2; // proper air movement
 			// Reduce movepushslide even more if over "max" flight speed
-			if ((player->pflags & PF_SPINNING) || (player->powers[pw_tailsfly] && player->speed > topspeed))
+			if (((player->pflags & (PF_SPINNING|PF_THOKKED)) == PF_SPINNING) || (player->powers[pw_tailsfly] && player->speed > topspeed))
 				movepushside >>= 2;
 		}
 		// Allow a bit of movement while spinning
-		else if (player->pflags & PF_SPINNING)
+		else if ((player->pflags & (PF_SPINNING|PF_THOKKED)) == PF_SPINNING)
 		{
 			if (player->pflags & PF_STARTDASH)
 				movepushside = 0;
@@ -8632,6 +8634,9 @@ static void P_MovePlayer(player_t *player)
 	// Look for Quicksand!
 	if (CheckForQuicksand)
 		P_CheckQuicksand(player);
+
+	if (P_IsObjectOnGround(player->mo))
+		player->mo->pmomz = 0;
 }
 
 static void P_DoZoomTube(player_t *player)
@@ -9535,7 +9540,7 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 	fixed_t x, y, z, dist, distxy, distz, checkdist, viewpointx, viewpointy, camspeed, camdist, camheight, pviewheight, slopez = 0;
 	INT32 camrotate;
 	boolean camstill, cameranoclip, camorbit;
-	mobj_t *mo;
+	mobj_t *mo, *sign = NULL;
 	subsector_t *newsubsec;
 	fixed_t f1, f2;
 
@@ -9545,6 +9550,9 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 
 	mo = player->mo;
 
+	if (player->exiting && mo->target && mo->target->type == MT_SIGN)
+		sign = mo->target;
+
 	cameranoclip = (player->powers[pw_carry] == CR_NIGHTSMODE || player->pflags & PF_NOCLIP) || (mo->flags & (MF_NOCLIP|MF_NOCLIPHEIGHT)); // Noclipping player camera noclips too!!
 
 	if (!(player->climbing || (player->powers[pw_carry] == CR_NIGHTSMODE) || player->playerstate == PST_DEAD || tutorialmode))
@@ -9591,6 +9599,11 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 		focusangle = mo->angle;
 		focusaiming = 0;
 	}
+	else if (sign)
+	{
+		focusangle = FixedAngle(sign->spawnpoint->angle << FRACBITS) + ANGLE_180;
+		focusaiming = 0;
+	}
 	else if (player == &players[consoleplayer])
 	{
 		focusangle = localangle;
@@ -9739,6 +9752,12 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 			camheight = FixedMul(camheight, 6*FRACUNIT/5);
 		}
 
+		if (sign)
+		{
+			camheight = mo->scale << 7;
+			camspeed = FRACUNIT/12;
+		}
+
 		if (player->climbing || player->exiting || player->playerstate == PST_DEAD || (player->powers[pw_carry] == CR_ROPEHANG || player->powers[pw_carry] == CR_GENERIC || player->powers[pw_carry] == CR_MACESPIN))
 			dist <<= 1;
 	}
@@ -9785,8 +9804,16 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 		distz = slopez;
 	}
 
-	x = mo->x - FixedMul(FINECOSINE((angle>>ANGLETOFINESHIFT) & FINEMASK), distxy);
-	y = mo->y - FixedMul(FINESINE((angle>>ANGLETOFINESHIFT) & FINEMASK), distxy);
+	if (sign)
+	{
+		x = sign->x - FixedMul(FINECOSINE((angle>>ANGLETOFINESHIFT) & FINEMASK), distxy);
+		y = sign->y - FixedMul(FINESINE((angle>>ANGLETOFINESHIFT) & FINEMASK), distxy);
+	}
+	else
+	{
+		x = mo->x - FixedMul(FINECOSINE((angle>>ANGLETOFINESHIFT) & FINEMASK), distxy);
+		y = mo->y - FixedMul(FINESINE((angle>>ANGLETOFINESHIFT) & FINEMASK), distxy);
+	}
 
 #if 0
 	if (twodlevel || (mo->flags2 & MF2_TWOD))
@@ -10031,14 +10058,30 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 	// point viewed by the camera
 	// this point is just 64 unit forward the player
 	dist = FixedMul(64 << FRACBITS, mo->scale);
-	viewpointx = mo->x + FixedMul(FINECOSINE((angle>>ANGLETOFINESHIFT) & FINEMASK), dist);
-	viewpointy = mo->y + FixedMul(FINESINE((angle>>ANGLETOFINESHIFT) & FINEMASK), dist);
+	if (sign)
+	{
+		viewpointx = sign->x + FixedMul(FINECOSINE((angle>>ANGLETOFINESHIFT) & FINEMASK), dist);
+		viewpointy = sign->y + FixedMul(FINESINE((angle>>ANGLETOFINESHIFT) & FINEMASK), dist);
+	}
+	else
+	{
+		viewpointx = mo->x + FixedMul(FINECOSINE((angle>>ANGLETOFINESHIFT) & FINEMASK), dist);
+		viewpointy = mo->y + FixedMul(FINESINE((angle>>ANGLETOFINESHIFT) & FINEMASK), dist);
+	}
 
 	if (!camstill && !resetcalled && !paused)
 		thiscam->angle = R_PointToAngle2(thiscam->x, thiscam->y, viewpointx, viewpointy);
 
-	viewpointx = mo->x + FixedMul(FINECOSINE((angle>>ANGLETOFINESHIFT) & FINEMASK), dist);
-	viewpointy = mo->y + FixedMul(FINESINE((angle>>ANGLETOFINESHIFT) & FINEMASK), dist);
+	if (sign)
+	{
+		viewpointx = sign->x + FixedMul(FINECOSINE((angle>>ANGLETOFINESHIFT) & FINEMASK), dist);
+		viewpointy = sign->y + FixedMul(FINESINE((angle>>ANGLETOFINESHIFT) & FINEMASK), dist);
+	}
+	else
+	{
+		viewpointx = mo->x + FixedMul(FINECOSINE((angle>>ANGLETOFINESHIFT) & FINEMASK), dist);
+		viewpointy = mo->y + FixedMul(FINESINE((angle>>ANGLETOFINESHIFT) & FINEMASK), dist);
+	}
 
 /*
 	if (twodlevel || (mo->flags2 & MF2_TWOD))
@@ -12419,9 +12462,6 @@ void P_PlayerAfterThink(player_t *player)
 		player->mo->flags |= MF_NOGRAVITY;
 	}
 
-	if (P_IsObjectOnGround(player->mo))
-		player->mo->pmomz = 0;
-
 	if (player->followmobj && (player->spectator || player->mo->health <= 0 || player->followmobj->type != player->followitem))
 	{
 		P_RemoveMobj(player->followmobj);
diff --git a/src/r_data.c b/src/r_data.c
index 32dc4f6fff0d764ae4bc430e57055f5534dc4d84..68fee3d64d6e47d3571e5ed44bfd42d190130dc2 100644
--- a/src/r_data.c
+++ b/src/r_data.c
@@ -438,7 +438,7 @@ static UINT8 *R_GenerateTexture(size_t texnum)
 	int x, x1, x2, i, width, height;
 	size_t blocksize;
 	column_t *patchcol;
-	UINT32 *colofs;
+	UINT8 *colofs;
 
 	UINT16 wadnum;
 	lumpnum_t lumpnum;
@@ -475,10 +475,10 @@ static UINT8 *R_GenerateTexture(size_t texnum)
 		// Check the patch for holes.
 		if (texture->width > SHORT(realpatch->width) || texture->height > SHORT(realpatch->height))
 			holey = true;
-		colofs = (UINT32 *)realpatch->columnofs;
+		colofs = (UINT8 *)realpatch->columnofs;
 		for (x = 0; x < texture->width && !holey; x++)
 		{
-			column_t *col = (column_t *)((UINT8 *)realpatch + LONG(colofs[x]));
+			column_t *col = (column_t *)((UINT8 *)realpatch + LONG(*(UINT32 *)&colofs[x<<2]));
 			INT32 topdelta, prevdelta = -1, y = 0;
 			while (col->topdelta != 0xff)
 			{
@@ -507,19 +507,19 @@ static UINT8 *R_GenerateTexture(size_t texnum)
 			texturememory += blocksize;
 
 			// use the patch's column lookup
-			colofs = (UINT32 *)(void *)(block + 8);
-			texturecolumnofs[texnum] = colofs;
+			colofs = (block + 8);
+			texturecolumnofs[texnum] = (UINT32 *)colofs;
 			blocktex = block;
 			if (patch->flip & 1) // flip the patch horizontally
 			{
-				UINT32 *realcolofs = (UINT32 *)realpatch->columnofs;
+				UINT8 *realcolofs = (UINT8 *)realpatch->columnofs;
 				for (x = 0; x < texture->width; x++)
-					colofs[x] = realcolofs[texture->width-1-x]; // swap with the offset of the other side of the texture
+					*(UINT32 *)&colofs[x<<2] = realcolofs[( texture->width-1-x )<<2]; // swap with the offset of the other side of the texture
 			}
 			// we can't as easily flip the patch vertically sadly though,
 			//  we have wait until the texture itself is drawn to do that
 			for (x = 0; x < texture->width; x++)
-				colofs[x] = LONG(LONG(colofs[x]) + 3);
+				*(UINT32 *)&colofs[x<<2] = LONG(LONG(*(UINT32 *)&colofs[x<<2]) + 3);
 			goto done;
 		}
 
@@ -539,8 +539,8 @@ static UINT8 *R_GenerateTexture(size_t texnum)
 	memset(block, TRANSPARENTPIXEL, blocksize+1); // Transparency hack
 
 	// columns lookup table
-	colofs = (UINT32 *)(void *)block;
-	texturecolumnofs[texnum] = colofs;
+	colofs = block;
+	texturecolumnofs[texnum] = (UINT32 *)colofs;
 
 	// texture data after the lookup table
 	blocktex = block + (texture->width*4);
@@ -595,8 +595,8 @@ static UINT8 *R_GenerateTexture(size_t texnum)
 				patchcol = (column_t *)((UINT8 *)realpatch + LONG(realpatch->columnofs[x-x1]));
 
 			// generate column ofset lookup
-			colofs[x] = LONG((x * texture->height) + (texture->width*4));
-			ColumnDrawerPointer(patchcol, block + LONG(colofs[x]), patch, texture->height, height);
+			*(UINT32 *)&colofs[x<<2] = LONG((x * texture->height) + (texture->width*4));
+			ColumnDrawerPointer(patchcol, block + LONG(*(UINT32 *)&colofs[x<<2]), patch, texture->height, height);
 		}
 	}
 
@@ -810,7 +810,9 @@ void R_LoadTextures(void)
 		{
 			UINT16 wadnum = (UINT16)w;
 			lumpnum_t lumpnum = texstart + j;
+#ifndef NO_PNG_LUMPS
 			size_t lumplength;
+#endif
 
 			if (wadfiles[w]->type == RET_PK3)
 			{
@@ -818,8 +820,10 @@ void R_LoadTextures(void)
 					continue; // If it is then SKIP IT
 			}
 
-			lumplength = W_LumpLengthPwad(wadnum, lumpnum);
 			patchlump = W_CacheLumpNumPwad(wadnum, lumpnum, PU_CACHE);
+#ifndef NO_PNG_LUMPS
+			lumplength = W_LumpLengthPwad(wadnum, lumpnum);
+#endif
 
 			//CONS_Printf("\n\"%s\" is a single patch, dimensions %d x %d",W_CheckNameForNumPwad((UINT16)w,texstart+j),patchlump->width, patchlump->height);
 			texture = textures[i] = Z_Calloc(sizeof(texture_t) + sizeof(texpatch_t), PU_STATIC, NULL);
diff --git a/src/r_patch.c b/src/r_patch.c
index 8545952f7e95f74213581c8be815d216b637d2d5..ce68b49aee3980a01d2e010bb13b878dc8117265 100644
--- a/src/r_patch.c
+++ b/src/r_patch.c
@@ -1115,7 +1115,7 @@ void R_CacheRotSprite(spritenum_t sprnum, UINT8 frame, spriteinfo_t *sprinfo, sp
 		if (!R_CheckIfPatch(lump))
 			return;
 
-		patch = (patch_t *)W_CachePatchNum(lump, PU_STATIC);
+		patch = (patch_t *)W_CacheLumpNum(lump, PU_STATIC);
 		width = patch->width;
 		height = patch->height;
 
@@ -1255,7 +1255,7 @@ void R_CacheRotSprite(spritenum_t sprnum, UINT8 frame, spriteinfo_t *sprinfo, sp
 			if (rendermode == render_opengl)
 			{
 				GLPatch_t *grPatch = HWR_GetCachedGLRotSprite(sprframe->rotsprite.hardware_patch[rot], angle, newpatch);
-				HWR_MakePatch(newpatch, grPatch, &grPatch->mipmap, false);
+				HWR_MakePatch(newpatch, grPatch, grPatch->mipmap, false);
 				sprframe->rotsprite.patch[rot][angle] = (patch_t *)grPatch;
 			}
 			else
diff --git a/src/r_things.c b/src/r_things.c
index f9a42bf21f1f8d2478c882dd34dc72a2eb06bfae..872742d4acafa763e85fa9e3d96a04dc57fda84e 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -241,11 +241,15 @@ static void R_FreeRotSprite(spritedef_t *spritedef)
 								Z_Free(grPatch->rawpatch);
 								grPatch->rawpatch = NULL;
 							}
-							if (grPatch->mipmap.grInfo.data)
+							if (grPatch->mipmap)
 							{
-								Z_Free(grPatch->mipmap.grInfo.data);
-								grPatch->mipmap.grInfo.data = NULL;
-								grPatch->mipmap.downloaded = 0;
+								if (grPatch->mipmap->grInfo.data)
+								{
+									Z_Free(grPatch->mipmap->grInfo.data);
+									grPatch->mipmap->grInfo.data = NULL;
+								}
+								Z_Free(grPatch->mipmap);
+								grPatch->mipmap = NULL;
 							}
 						}
 #endif
@@ -472,10 +476,6 @@ void R_AddSpriteDefs(UINT16 wadnum)
 		start = W_CheckNumForNamePwad("S_START", wadnum, 0);
 		if (start == INT16_MAX)
 			start = W_CheckNumForNamePwad("SS_START", wadnum, 0); //deutex compatib.
-		if (start == INT16_MAX)
-			start = 0; //let say S_START is lump 0
-		else
-			start++;   // just after S_START
 
 		end = W_CheckNumForNamePwad("S_END",wadnum,start);
 		if (end == INT16_MAX)
@@ -489,9 +489,16 @@ void R_AddSpriteDefs(UINT16 wadnum)
 		return;
 	}
 
-	// ignore skin wads (we don't want skin sprites interfering with vanilla sprites)
-	if (start == 0 && W_CheckNumForNamePwad("S_SKIN", wadnum, 0) != UINT16_MAX)
-		return;
+	if (start == INT16_MAX)
+	{
+		// ignore skin wads (we don't want skin sprites interfering with vanilla sprites)
+		if (W_CheckNumForNamePwad("S_SKIN", wadnum, 0) != UINT16_MAX)
+			return;
+
+		start = 0; //let say S_START is lump 0
+	}
+	else
+		start++;   // just after S_START
 
 	if (end == INT16_MAX)
 	{
diff --git a/src/w_wad.c b/src/w_wad.c
index 67f1114129df066f77aceff4a757134aa80c1793..5d2d74ef5e1556ecde9bae6ff56d797362327e0a 100644
--- a/src/w_wad.c
+++ b/src/w_wad.c
@@ -1517,7 +1517,10 @@ void *W_CachePatchNumPwad(UINT16 wad, UINT16 lump, INT32 tag)
 		if (!lumpcache[lump])
 		{
 			size_t len = W_LumpLengthPwad(wad, lump);
-			void *ptr, *lumpdata, *srcdata = NULL;
+			void *ptr, *lumpdata;
+#ifndef NO_PNG_LUMPS
+			void *srcdata = NULL;
+#endif
 
 			ptr = Z_Malloc(len, tag, &lumpcache[lump]);
 			lumpdata = Z_Malloc(len, tag, NULL);
@@ -1548,22 +1551,22 @@ void *W_CachePatchNumPwad(UINT16 wad, UINT16 lump, INT32 tag)
 
 	grPatch = HWR_GetCachedGLPatchPwad(wad, lump);
 
-	if (grPatch->mipmap.grInfo.data)
+	if (grPatch->mipmap->grInfo.data)
 	{
 		if (tag == PU_CACHE)
 			tag = PU_HWRCACHE;
-		Z_ChangeTag(grPatch->mipmap.grInfo.data, tag);
+		Z_ChangeTag(grPatch->mipmap->grInfo.data, tag);
 	}
 	else
 	{
 		patch_t *ptr = NULL;
 
 		// Only load the patch if we haven't initialised the grPatch yet
-		if (grPatch->mipmap.width == 0)
+		if (grPatch->mipmap->width == 0)
 			ptr = W_CacheLumpNumPwad(grPatch->wadnum, grPatch->lumpnum, PU_STATIC);
 
 		// Run HWR_MakePatch in all cases, to recalculate some things
-		HWR_MakePatch(ptr, grPatch, &grPatch->mipmap, false);
+		HWR_MakePatch(ptr, grPatch, grPatch->mipmap, false);
 		Z_Free(ptr);
 	}
 
diff --git a/src/y_inter.c b/src/y_inter.c
index 2fed35de30cbfedbae5c930cf29a9b78a9dae2f9..21e4bc56e480503693286014246ea7dbc0c08679 100644
--- a/src/y_inter.c
+++ b/src/y_inter.c
@@ -143,9 +143,21 @@ static patch_t *widebgpatch = NULL; // INTERSCW
 static patch_t *bgtile = NULL;      // SPECTILE/SRB2BACK
 static patch_t *interpic = NULL;    // custom picture defined in map header
 static boolean usetile;
+static INT32 timer;
+
+typedef struct
+{
+	INT32 source_width, source_height;
+	INT32 source_bpp, source_rowbytes;
+	UINT8 *source_picture;
+	INT32 target_width, target_height;
+	INT32 target_bpp, target_rowbytes;
+	UINT8 *target_picture;
+} y_buffer_t;
+
 boolean usebuffer = false;
 static boolean useinterpic;
-static INT32 timer;
+static y_buffer_t *y_buffer;
 
 static INT32 intertic;
 static INT32 tallydonetic = -1;
@@ -153,6 +165,8 @@ static INT32 endtic = -1;
 
 intertype_t intertype = int_none;
 
+static void Y_RescaleScreenBuffer(void);
+static void Y_CleanupScreenBuffer(void);
 static void Y_AwardCoopBonuses(void);
 static void Y_AwardSpecialStageBonus(void);
 static void Y_CalculateCompetitionWinners(void);
@@ -207,6 +221,94 @@ static void Y_IntermissionTokenDrawer(void)
 		V_DrawCroppedPatch(32<<FRACBITS, y<<FRACBITS, FRACUNIT/2, 0, tokenicon, 0, 0, SHORT(tokenicon->width), calc);
 }
 
+//
+// Y_ConsiderScreenBuffer
+//
+// Can we copy the current screen
+// to a buffer?
+//
+void Y_ConsiderScreenBuffer(void)
+{
+	if (gameaction != ga_completed)
+		return;
+
+	if (y_buffer == NULL)
+		y_buffer = Z_Calloc(sizeof(y_buffer_t), PU_STATIC, NULL);
+	else
+		return;
+
+	y_buffer->source_width = vid.width;
+	y_buffer->source_height = vid.height;
+	y_buffer->source_bpp = vid.bpp;
+	y_buffer->source_rowbytes = vid.rowbytes;
+	y_buffer->source_picture = ZZ_Alloc(y_buffer->source_width*vid.bpp * y_buffer->source_height);
+	VID_BlitLinearScreen(screens[1], y_buffer->source_picture, vid.width*vid.bpp, vid.height, vid.width*vid.bpp, vid.rowbytes);
+
+	// Make the rescaled screen buffer
+	Y_RescaleScreenBuffer();
+}
+
+//
+// Y_RescaleScreenBuffer
+//
+// Write the rescaled source picture,
+// to the destination picture that
+// has the current screen's resolutions.
+//
+static void Y_RescaleScreenBuffer(void)
+{
+	INT32 sx, sy; // source
+	INT32 dx, dy; // dest
+	fixed_t scalefac, yscalefac;
+	fixed_t rowfrac, colfrac;
+	UINT8 *dest;
+
+	// Who knows?
+	if (y_buffer == NULL)
+		return;
+
+	if (y_buffer->target_picture)
+		Z_Free(y_buffer->target_picture);
+
+	y_buffer->target_width = vid.width;
+	y_buffer->target_height = vid.height;
+	y_buffer->target_rowbytes = vid.rowbytes;
+	y_buffer->target_bpp = vid.bpp;
+	y_buffer->target_picture = ZZ_Alloc(y_buffer->target_width*vid.bpp * y_buffer->target_height);
+	dest = y_buffer->target_picture;
+
+	scalefac = FixedDiv(y_buffer->target_width*FRACUNIT, y_buffer->source_width*FRACUNIT);
+	yscalefac = FixedDiv(y_buffer->target_height*FRACUNIT, y_buffer->source_height*FRACUNIT);
+
+	rowfrac = FixedDiv(FRACUNIT, yscalefac);
+	colfrac = FixedDiv(FRACUNIT, scalefac);
+
+	for (sy = 0, dy = 0; sy < (y_buffer->source_height << FRACBITS) && dy < y_buffer->target_height; sy += rowfrac, dy++)
+		for (sx = 0, dx = 0; sx < (y_buffer->source_width << FRACBITS) && dx < y_buffer->target_width; sx += colfrac, dx += y_buffer->target_bpp)
+			dest[(dy * y_buffer->target_rowbytes) + dx] = y_buffer->source_picture[((sy>>FRACBITS) * y_buffer->source_width) + (sx>>FRACBITS)];
+}
+
+//
+// Y_CleanupScreenBuffer
+//
+// Free all related memory.
+//
+static void Y_CleanupScreenBuffer(void)
+{
+	// Who knows?
+	if (y_buffer == NULL)
+		return;
+
+	if (y_buffer->target_picture)
+		Z_Free(y_buffer->target_picture);
+
+	if (y_buffer->source_picture)
+		Z_Free(y_buffer->source_picture);
+
+	Z_Free(y_buffer);
+	y_buffer = NULL;
+}
+
 //
 // Y_IntermissionDrawer
 //
@@ -229,12 +331,23 @@ void Y_IntermissionDrawer(void)
 	else if (!usetile)
 	{
 		if (rendermode == render_soft && usebuffer)
-			VID_BlitLinearScreen(screens[1], screens[0], vid.width*vid.bpp, vid.height, vid.width*vid.bpp, vid.rowbytes);
-#ifdef HWRENDER
-		else if(rendermode != render_soft && usebuffer)
 		{
-			HWR_DrawIntermissionBG();
+			// no y_buffer
+			if (y_buffer == NULL)
+				VID_BlitLinearScreen(screens[1], screens[0], vid.width*vid.bpp, vid.height, vid.width*vid.bpp, vid.rowbytes);
+			else
+			{
+				// Maybe the resolution changed?
+				if ((y_buffer->target_width != vid.width) || (y_buffer->target_height != vid.height))
+					Y_RescaleScreenBuffer();
+
+				// Blit the already-scaled screen buffer to the current screen
+				VID_BlitLinearScreen(y_buffer->target_picture, screens[0], vid.width*vid.bpp, vid.height, vid.width*vid.bpp, vid.rowbytes);
+			}
 		}
+#ifdef HWRENDER
+		else if (rendermode != render_soft && usebuffer)
+			HWR_DrawIntermissionBG();
 #endif
 		else
 		{
@@ -425,11 +538,23 @@ void Y_IntermissionDrawer(void)
 				UINT8 continues = data.spec.continues & 0x7F;
 
 				V_DrawScaledPatch(152 + xoffset5, 150+yoffset, 0, data.spec.pcontinues);
-				for (i = 0; i < continues; ++i)
+				if (continues > 5)
 				{
-					if ((data.spec.continues & 0x80) && i == continues-1 && (endtic < 0 || intertic%20 < 10))
-						break;
-					V_DrawContinueIcon(246 + xoffset5 - (i*20), 162+yoffset, 0, *data.spec.playerchar, *data.spec.playercolor);
+					INT32 leftx = (continues >= 10) ? 216 : 224;
+					V_DrawContinueIcon(leftx + xoffset5, 162+yoffset, 0, *data.spec.playerchar, *data.spec.playercolor);
+					V_DrawScaledPatch(leftx + xoffset5 + 12, 160+yoffset, 0, stlivex);
+					if (!((data.spec.continues & 0x80) && !(endtic < 0 || intertic%20 < 10)))
+						V_DrawRightAlignedString(252 + xoffset5, 158+yoffset, 0,
+							va("%d",(((data.spec.continues & 0x80) && (endtic < 0)) ? continues-1 : continues)));
+				}
+				else
+				{
+					for (i = 0; i < continues; ++i)
+					{
+						if ((data.spec.continues & 0x80) && i == continues-1 && (endtic < 0 || intertic%20 < 10))
+							break;
+						V_DrawContinueIcon(246 + xoffset5 - (i*20), 162+yoffset, 0, *data.spec.playerchar, *data.spec.playercolor);
+					}
 				}
 			}
 		}
@@ -2006,7 +2131,7 @@ static void Y_AwardSpecialStageBonus(void)
 
 	data.spec.score = players[consoleplayer].score;
 	memset(data.spec.bonuses, 0, sizeof(data.spec.bonuses));
-	memset(data.spec.bonuspatches, 0, sizeof(data.coop.bonuspatches));
+	memset(data.spec.bonuspatches, 0, sizeof(data.spec.bonuspatches));
 
 	for (i = 0; i < MAXPLAYERS; i++)
 	{
@@ -2096,6 +2221,8 @@ static void Y_UnloadData(void)
 	if (rendermode != render_soft)
 		return;
 
+	Y_CleanupScreenBuffer();
+
 	// unload the background patches
 	UNLOAD(bgpatch);
 	UNLOAD(widebgpatch);
diff --git a/src/y_inter.h b/src/y_inter.h
index 4c6ad2bdfcfeaff454fffb1ecfc0075f421705dc..ccb48dbd4c3ed5cd43273a3971724bb9b6c897b7 100644
--- a/src/y_inter.h
+++ b/src/y_inter.h
@@ -15,6 +15,7 @@ void Y_IntermissionDrawer(void);
 void Y_Ticker(void);
 void Y_StartIntermission(void);
 void Y_EndIntermission(void);
+void Y_ConsiderScreenBuffer(void);
 
 typedef enum
 {
diff --git a/src/z_zone.c b/src/z_zone.c
index 001c69bb3c9f3adc1a689537a2fc155d042bcb43..d5146f7a3a6ee7d8346ae2180421832455aee9c2 100644
--- a/src/z_zone.c
+++ b/src/z_zone.c
@@ -247,7 +247,11 @@ void Z_Free(void *ptr)
 static void *xm(size_t size)
 {
 	const size_t padedsize = size+sizeof (size_t);
-	void *p = malloc(padedsize);
+	void *p;
+
+	if (padedsize < size)/* overflow check */
+		I_Error("You are allocating memory too large!");
+	p = malloc(padedsize);
 
 	if (p == NULL)
 	{
@@ -295,6 +299,9 @@ void *Z_MallocAlign(size_t size, INT32 tag, void *user, INT32 alignbits)
 	CONS_Debug(DBG_MEMORY, "Z_Malloc %s:%d\n", file, line);
 #endif
 
+	if (blocksize < size)/* overflow check */
+		I_Error("You are allocating memory too large!");
+
 	block = xm(sizeof *block);
 #ifdef HAVE_VALGRIND
 	padsize += (1<<sizeof(size_t))*2;