diff --git a/src/b_bot.c b/src/b_bot.c
index 12cdc36a0f9050019a92a47dd7d5e304a544831f..709a280b57a16a7cc4d7d40b434ed3f7caa2cd31 100644
--- a/src/b_bot.c
+++ b/src/b_bot.c
@@ -31,7 +31,6 @@ static boolean panic = false;
 static UINT8 flymode = 0;
 static boolean spinmode = false;
 static boolean thinkfly = false;
-static mobj_t *overlay;
 
 static inline void B_ResetAI(void)
 {
@@ -336,17 +335,20 @@ static inline void B_BuildTailsTiccmd(mobj_t *sonic, mobj_t *tails, ticcmd_t *cm
 	// Thinkfly overlay
 	if (thinkfly)
 	{
-		if (overlay == NULL)
+		if (!tails->target)
 		{
-			overlay = P_SpawnMobjFromMobj(tails, 0, 0, 0, MT_OVERLAY);
-			P_SetTarget(&overlay->target, tails);
-			P_SetMobjState(overlay, S_FLIGHTINDICATOR);
+			P_SetTarget(&tails->target, P_SpawnMobjFromMobj(tails, 0, 0, 0, MT_OVERLAY));
+			if (tails->target)
+			{
+				P_SetTarget(&tails->target->target, tails);
+				P_SetMobjState(tails->target, S_FLIGHTINDICATOR);
+			}
 		}
 	}
-	else if (overlay != NULL)
+	else if (tails->target && tails->target->type == MT_OVERLAY && tails->target->state == states+S_FLIGHTINDICATOR)
 	{
-		P_RemoveMobj(overlay);
-		overlay = NULL;
+		P_RemoveMobj(tails->target);
+		P_SetTarget(&tails->target, NULL);
 	}
 
 	// Turn the virtual keypresses into ticcmd_t.
diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index aca37df1d2a2c8d282128781e68f13890a780402..2a89116a14e83785655ecdc1e49706b2b68be577 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -1301,10 +1301,23 @@ static void SV_SendServerInfo(INT32 node, tic_t servertime)
 
 	M_Memcpy(netbuffer->u.serverinfo.mapmd5, mapmd5, 16);
 
-	if (strcmp(mapheaderinfo[gamemap-1]->lvlttl, ""))
-		strncpy(netbuffer->u.serverinfo.maptitle, (char *)mapheaderinfo[gamemap-1]->lvlttl, 33);
+	if (*mapheaderinfo[gamemap-1]->lvlttl)
+	{
+		char *read = mapheaderinfo[gamemap-1]->lvlttl, *writ = netbuffer->u.serverinfo.maptitle;
+		while (writ < (netbuffer->u.serverinfo.maptitle+32) && *read != '\0')
+		{
+			if (!(*read & 0x80))
+			{
+				*writ = toupper(*read);
+				writ++;
+			}
+			read++;
+		}
+		*writ = '\0';
+		//strncpy(netbuffer->u.serverinfo.maptitle, (char *)mapheaderinfo[gamemap-1]->lvlttl, 33);
+	}
 	else
-		strncpy(netbuffer->u.serverinfo.maptitle, "UNKNOWN", 33);
+		strncpy(netbuffer->u.serverinfo.maptitle, "UNKNOWN", 32);
 
 	netbuffer->u.serverinfo.maptitle[32] = '\0';
 
@@ -1358,7 +1371,11 @@ static void SV_SendPlayerInfo(INT32 node)
 
 		netbuffer->u.playerinfo[i].score = LONG(players[i].score);
 		netbuffer->u.playerinfo[i].timeinserver = SHORT((UINT16)(players[i].jointime / TICRATE));
-		netbuffer->u.playerinfo[i].skin = (UINT8)players[i].skin;
+		netbuffer->u.playerinfo[i].skin = (UINT8)(players[i].skin
+#ifdef DEVELOP // it's safe to do this only because PLAYERINFO isn't read by the game itself
+		% 3
+#endif
+		);
 
 		// Extra data
 		netbuffer->u.playerinfo[i].data = 0; //players[i].skincolor;
@@ -4602,7 +4619,7 @@ static void Local_Maketic(INT32 realtics)
 {
 	I_OsPolling(); // I_Getevent
 	D_ProcessEvents(); // menu responder, cons responder,
-	                   // game responder calls HU_Responder, AM_Responder, F_Responder,
+	                   // game responder calls HU_Responder, AM_Responder,
 	                   // and G_MapEventsToControls
 	if (!dedicated) rendergametic = gametic;
 	// translate inputs (keyboard/mouse/joystick) into game controls
diff --git a/src/d_main.c b/src/d_main.c
index e48b26ba8a9bd05f7eac019be5054d0dcd552c11..98e16a27722ce6784e60905cde4249ea1f619b3e 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -180,9 +180,6 @@ void D_ProcessEvents(void)
 		if (M_ScreenshotResponder(ev))
 			continue; // ate the event
 
-		if (WipeStageTitle)
-			continue;
-
 		if (gameaction == ga_nothing && gamestate == GS_TITLESCREEN)
 		{
 			if (cht_Responder(ev))
@@ -420,12 +417,6 @@ static void D_Display(void)
 	if (gamestate != GS_TIMEATTACK)
 		CON_Drawer();
 
-#ifdef LEVELWIPES
-	// Running a level wipe
-	if (WipeInAction && WipeInLevel)
-		F_WipeTicker();
-#endif
-
 	M_Drawer(); // menu is drawn even on top of everything
 	// focus lost moved to M_Drawer
 
@@ -445,7 +436,6 @@ static void D_Display(void)
 		{
 			F_WipeEndScreen();
 			// Funny.
-#ifndef LEVELWIPES
 			if (WipeStageTitle && st_overlay)
 			{
 				lt_ticker--;
@@ -454,7 +444,6 @@ static void D_Display(void)
 				V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, levelfadecol);
 				F_WipeStartScreen();
 			}
-#endif
 			F_RunWipe(wipetypepost, gamestate != GS_TIMEATTACK && gamestate != GS_TITLESCREEN);
 		}
 
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 7a8cf539f6ee330e87e1aa5ed046ee1292dca28c..2b6055873e25a6b07ec48308e882260281ca7f27 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -4349,9 +4349,9 @@ static void Command_Isgamemodified_f(void)
 	if (savemoddata)
 		CONS_Printf(M_GetText("modifiedgame is true, but you can save emblem and time data in this mod.\n"));
 	else if (modifiedgame)
-		CONS_Printf(M_GetText("modifiedgame is true, secrets will not be unlocked\n"));
+		CONS_Printf(M_GetText("modifiedgame is true, extras will not be unlocked\n"));
 	else
-		CONS_Printf(M_GetText("modifiedgame is false, you can unlock secrets\n"));
+		CONS_Printf(M_GetText("modifiedgame is false, you can unlock extras\n"));
 }
 
 static void Command_Cheats_f(void)
diff --git a/src/dehacked.c b/src/dehacked.c
index 45148d712415f438329eaa9554b324c922ee5b3d..29992fcd57be9adfb4e72f920997f1e46835ed64 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -4973,18 +4973,10 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_MINUS_UPWARD2",
 	"S_MINUS_UPWARD3",
 	"S_MINUS_UPWARD4",
-	"S_MINUS_UPWARD5",
-	"S_MINUS_UPWARD6",
-	"S_MINUS_UPWARD7",
-	"S_MINUS_UPWARD8",
 	"S_MINUS_DOWNWARD1",
 	"S_MINUS_DOWNWARD2",
 	"S_MINUS_DOWNWARD3",
 	"S_MINUS_DOWNWARD4",
-	"S_MINUS_DOWNWARD5",
-	"S_MINUS_DOWNWARD6",
-	"S_MINUS_DOWNWARD7",
-	"S_MINUS_DOWNWARD8",
 
 	// Minus dirt
 	"S_MINUSDIRT1",
@@ -7669,8 +7661,6 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_DUST3",
 	"S_DUST4",
 
-	"S_WOODDEBRIS",
-
 	"S_ROCKSPAWN",
 
 	"S_ROCKCRUMBLEA",
@@ -7689,7 +7679,9 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_ROCKCRUMBLEN",
 	"S_ROCKCRUMBLEO",
 	"S_ROCKCRUMBLEP",
+	"S_GFZDEBRIS",
 	"S_BRICKDEBRIS",
+	"S_WOODDEBRIS",
 
 #ifdef SEENAMES
 	"S_NAMECHECK",
@@ -8445,7 +8437,6 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_EXPLODE", // Robot Explosion
 	"MT_UWEXPLODE", // Underwater Explosion
 	"MT_DUST",
-	"MT_WOODDEBRIS",
 	"MT_ROCKSPAWNER",
 	"MT_FALLINGROCK",
 	"MT_ROCKCRUMBLE1",
@@ -8464,7 +8455,9 @@ static const char *const MOBJTYPE_LIST[] = {  // array length left dynamic for s
 	"MT_ROCKCRUMBLE14",
 	"MT_ROCKCRUMBLE15",
 	"MT_ROCKCRUMBLE16",
+	"MT_GFZDEBRIS",
 	"MT_BRICKDEBRIS",
+	"MT_WOODDEBRIS",
 
 #ifdef SEENAMES
 	"MT_NAMECHECK",
@@ -8904,7 +8897,7 @@ static const char *const MENUTYPES_LIST[] = {
 	"OP_SCREENSHOTS",
 	"OP_ERASEDATA",
 
-	// Secrets
+	// Extras
 	"SR_MAIN",
 	"SR_PANDORA",
 	"SR_LEVELSELECT",
diff --git a/src/doomdef.h b/src/doomdef.h
index 8cf9e5d093ed1861fe009c18091250eb313cfdca..511dad85820fe04eb16a7d3deaa3b42d617e964c 100644
--- a/src/doomdef.h
+++ b/src/doomdef.h
@@ -628,9 +628,6 @@ extern const char *compdate, *comptime, *comprevision, *compbranch;
 #define ROTANGLES 24	// Needs to be a divisor of 360 (45, 60, 90, 120...)
 #define ROTANGDIFF (360 / ROTANGLES)
 
-/// Level wipes
-//#define LEVELWIPES
-
 #ifndef HAVE_PNG
 #define NO_PNG_LUMPS
 #endif
diff --git a/src/doomstat.h b/src/doomstat.h
index 84939a8db7d7400edf1b51c5b6f2a9d330076fb3..877f5b50b72fda0b81838200562a88bea305c83e 100644
--- a/src/doomstat.h
+++ b/src/doomstat.h
@@ -435,7 +435,6 @@ typedef struct
 	tic_t time;   ///< Time in which the level was finished.
 	UINT32 score; ///< Score when the level was finished.
 	UINT16 rings; ///< Rings when the level was finished.
-	boolean gotperfect; ///< Got perfect bonus?
 } recorddata_t;
 
 /** Setup for one NiGHTS map.
diff --git a/src/f_finale.c b/src/f_finale.c
index 4396aca92a5ed19dee79968f3c4bfcceef772cca..178aa7ab2f6eead13f022412c50e0817bb91a8da 100644
--- a/src/f_finale.c
+++ b/src/f_finale.c
@@ -1447,7 +1447,7 @@ boolean F_CreditResponder(event_t *event)
 
 void F_StartGameEvaluation(void)
 {
-	// Credits option in secrets menu
+	// Credits option in extras menu
 	if (cursaveslot == -1)
 	{
 		S_FadeOutStopMusic(2*MUSICRATE);
@@ -1602,9 +1602,9 @@ void F_GameEvaluationDrawer(void)
 			}
 		}
 		else if (netgame)
-			V_DrawString(8, 96, V_YELLOWMAP, "Prizes only\nawarded in\nsingle player!");
+			V_DrawString(8, 96, V_YELLOWMAP, "Multiplayer games\ncan't unlock\nextras!");
 		else
-			V_DrawString(8, 96, V_YELLOWMAP, "Prizes not\nawarded in\nmodified games!");
+			V_DrawString(8, 96, V_YELLOWMAP, "Modified games\ncan't unlock\nextras!");
 	}
 #endif
 }
@@ -1651,7 +1651,7 @@ void F_GameEvaluationTicker(void)
 		{
 			HU_SetCEchoFlags(V_YELLOWMAP|V_RETURN8);
 			HU_SetCEchoDuration(6);
-			HU_DoCEcho("\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Prizes only awarded in singleplayer!");
+			HU_DoCEcho("\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Multiplayer games can't unlock extras!");
 			S_StartSound(NULL, sfx_s3k68);
 		}
 		else if (!modifiedgame || savemoddata)
@@ -1673,7 +1673,7 @@ void F_GameEvaluationTicker(void)
 		{
 			HU_SetCEchoFlags(V_YELLOWMAP|V_RETURN8);
 			HU_SetCEchoDuration(6);
-			HU_DoCEcho("\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Prizes not awarded in modified games!");
+			HU_DoCEcho("\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\Modified games can't unlock extras!");
 			S_StartSound(NULL, sfx_s3k68);
 		}
 	}
diff --git a/src/f_finale.h b/src/f_finale.h
index 82a7e1408ee38311d5d4cedb99ac2f51cfeedc9f..0002cb3cfa64e0b5ad523edc657ea96ef088276c 100644
--- a/src/f_finale.h
+++ b/src/f_finale.h
@@ -141,7 +141,6 @@ void F_MenuPresTicker(boolean run);
 #define FORCEWIPEOFF -2
 
 extern boolean WipeInAction;
-extern boolean WipeInLevel;
 extern boolean WipeStageTitle;
 
 typedef enum
@@ -153,9 +152,10 @@ extern wipestyle_t wipestyle;
 
 typedef enum
 {
-	WSF_FADEOUT = 1,
-	WSF_FADEIN  = 1<<1,
-	WSF_TOWHITE = 1<<2,
+	WSF_FADEOUT   = 1,
+	WSF_FADEIN    = 1<<1,
+	WSF_TOWHITE   = 1<<2,
+	WSF_CROSSFADE = 1<<3,
 } wipestyleflags_t;
 extern wipestyleflags_t wipestyleflags;
 
@@ -166,7 +166,6 @@ extern wipestyleflags_t wipestyleflags;
 #define FADEGREENFACTOR 15
 #define FADEBLUEFACTOR  10
 
-extern UINT8 wipecolorfill;
 extern INT32 lastwipetic;
 
 // Don't know where else to place this constant
@@ -176,9 +175,8 @@ extern INT32 lastwipetic;
 void F_WipeStartScreen(void);
 void F_WipeEndScreen(void);
 void F_RunWipe(UINT8 wipetype, boolean drawMenu);
-void F_WipeTicker(void);
 void F_WipeStageTitle(void);
-#define F_WipeColorFill(c) V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, (wipecolorfill = c))
+#define F_WipeColorFill(c) V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, c)
 tic_t F_GetWipeLength(UINT8 wipetype);
 boolean F_WipeExists(UINT8 wipetype);
 
diff --git a/src/f_wipe.c b/src/f_wipe.c
index 17ffbe0c482d4aadace176cf757de7c9b79e0c8c..b88b39ef00986404cdc487f6454509c16b95dd54 100644
--- a/src/f_wipe.c
+++ b/src/f_wipe.c
@@ -91,12 +91,11 @@ UINT8 wipedefs[NUMWIPEDEFS] = {
 //--------------------------------------------------------------------------
 
 boolean WipeInAction = false;
-boolean WipeInLevel = false;
 boolean WipeStageTitle = false;
 INT32 lastwipetic = 0;
 
 wipestyle_t wipestyle = WIPESTYLE_NORMAL;
-wipestyleflags_t wipestyleflags = 0;
+wipestyleflags_t wipestyleflags = WSF_CROSSFADE;
 
 #ifndef NOWIPE
 static UINT8 *wipe_scr_start; //screen 3
@@ -104,10 +103,6 @@ static UINT8 *wipe_scr_end; //screen 4
 static UINT8 *wipe_scr; //screen 0 (main drawing)
 static fixed_t paldiv = 0;
 
-static UINT8 curwipetype;
-static UINT8 curwipeframe;
-UINT8 wipecolorfill = 31;
-
 /** Create fademask_t from lump
   *
   * \param	lump	Lump name to get data from
@@ -349,8 +344,6 @@ static void F_DoWipe(fademask_t *fademask)
 		free(scrxpos);
 		free(scrypos);
 	}
-	if (wipestyle == WIPESTYLE_LEVEL)
-		F_WipeStageTitle();
 }
 #endif
 
@@ -411,18 +404,13 @@ void F_RunWipe(UINT8 wipetype, boolean drawMenu)
 
 	// don't know where else to put this.
 	// this any good?
-	if (gamestate == GS_LEVEL || gamestate == GS_TITLESCREEN)
+	if ((gamestate == GS_LEVEL || gamestate == GS_TITLESCREEN)
+	&& (wipestyleflags & (WSF_FADEIN|WSF_FADEOUT)) // only if one of those wipestyleflags are actually set
+	&& !(wipestyleflags & WSF_CROSSFADE)) // and if not crossfading
 		wipestyle = WIPESTYLE_LEVEL;
 	else
 		wipestyle = WIPESTYLE_NORMAL;
 
-	curwipetype = wipetype;
-	curwipeframe = 0;
-#ifdef LEVELWIPES
-	if (WipeInLevel)
-		return;
-#endif
-
 	// lastwipetic should either be 0 or the tic we last wiped
 	// on for fade-to-black
 	for (;;)
@@ -450,6 +438,9 @@ void F_RunWipe(UINT8 wipetype, boolean drawMenu)
 #endif
 			F_DoWipe(fmask);
 
+		if (wipestyle == WIPESTYLE_LEVEL)
+			F_WipeStageTitle();
+
 		I_OsPolling();
 		I_UpdateNoBlit();
 
@@ -463,47 +454,6 @@ void F_RunWipe(UINT8 wipetype, boolean drawMenu)
 	}
 
 	WipeInAction = false;
-	WipeInLevel = false;
-	WipeStageTitle = false;
-#endif
-}
-
-/** Run and display the fade with the level.
-  */
-void F_WipeTicker(void)
-{
-#ifndef NOWIPE
-#ifndef LEVELWIPES
-	WipeInAction = false;
-#else
-	fademask_t *fmask;
-
-	// Wait, what?
-	if (!WipeInAction)
-		return;
-
-	if (rendermode == render_soft)
-		wipe_scr_start = wipe_scr_end = screens[0];
-
-	// get fademask first so we can tell if it exists or not
-	fmask = F_GetFadeMask(curwipetype, curwipeframe++);
-	if (!fmask)
-	{
-		// stop
-		WipeInAction = false;
-		WipeInLevel = false;
-		WipeStageTitle = false;
-		return;
-	}
-
-#ifdef HWRENDER
-	// send in the wipe type and wipe frame because we need to cache the graphic
-	if (rendermode == render_opengl)
-		HWR_DoLevelWipe(curwipetype, curwipeframe-1, wipecolorfill); // also send the wipe color
-	else
-#endif
-		F_DoWipe(fmask);
-#endif
 	WipeStageTitle = false;
 #endif
 }
diff --git a/src/g_game.c b/src/g_game.c
index 5e1645901166db7e9ad1908f3a69f58e9bea6459..faaed13c732929d643908ea1f2e3f495ce3b940a 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -172,6 +172,7 @@ mapheader_t* mapheaderinfo[NUMMAPS] = {NULL};
 
 static boolean exitgame = false;
 static boolean retrying = false;
+static boolean retryingmodeattack = false;
 
 UINT8 stagefailed; // Used for GEMS BONUS? Also to see if you beat the stage.
 
@@ -540,11 +541,99 @@ void G_AddTempNightsRecords(UINT32 pscore, tic_t ptime, UINT8 mare)
 		ntemprecords.nummares = mare;
 }
 
+//
+// G_UpdateRecordReplays
+//
+// Update replay files/data, etc. for Record Attack
+// See G_SetNightsRecords for NiGHTS Attack.
+//
+static void G_UpdateRecordReplays(void)
+{
+	const size_t glen = strlen(srb2home)+1+strlen("replay")+1+strlen(timeattackfolder)+1+strlen("MAPXX")+1;
+	char *gpath;
+	char lastdemo[256], bestdemo[256];
+	UINT8 earnedEmblems;
+
+	// Record new best time
+	if (!mainrecords[gamemap-1])
+		G_AllocMainRecordData(gamemap-1);
+
+	if (players[consoleplayer].score > mainrecords[gamemap-1]->score)
+		mainrecords[gamemap-1]->score = players[consoleplayer].score;
+
+	if ((mainrecords[gamemap-1]->time == 0) || (players[consoleplayer].realtime < mainrecords[gamemap-1]->time))
+		mainrecords[gamemap-1]->time = players[consoleplayer].realtime;
+
+	if ((UINT16)(players[consoleplayer].rings) > mainrecords[gamemap-1]->rings)
+		mainrecords[gamemap-1]->rings = (UINT16)(players[consoleplayer].rings);
+
+	// Save demo!
+	bestdemo[255] = '\0';
+	lastdemo[255] = '\0';
+	G_SetDemoTime(players[consoleplayer].realtime, players[consoleplayer].score, (UINT16)(players[consoleplayer].rings));
+	G_CheckDemoStatus();
+
+	I_mkdir(va("%s"PATHSEP"replay", srb2home), 0755);
+	I_mkdir(va("%s"PATHSEP"replay"PATHSEP"%s", srb2home, timeattackfolder), 0755);
+
+	if ((gpath = malloc(glen)) == NULL)
+		I_Error("Out of memory for replay filepath\n");
+
+	sprintf(gpath,"%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s", srb2home, timeattackfolder, G_BuildMapName(gamemap));
+	snprintf(lastdemo, 255, "%s-%s-last.lmp", gpath, skins[cv_chooseskin.value-1].name);
+
+	if (FIL_FileExists(lastdemo))
+	{
+		UINT8 *buf;
+		size_t len = FIL_ReadFile(lastdemo, &buf);
+
+		snprintf(bestdemo, 255, "%s-%s-time-best.lmp", gpath, skins[cv_chooseskin.value-1].name);
+		if (!FIL_FileExists(bestdemo) || G_CmpDemoTime(bestdemo, lastdemo) & 1)
+		{ // Better time, save this demo.
+			if (FIL_FileExists(bestdemo))
+				remove(bestdemo);
+			FIL_WriteFile(bestdemo, buf, len);
+			CONS_Printf("\x83%s\x80 %s '%s'\n", M_GetText("NEW RECORD TIME!"), M_GetText("Saved replay as"), bestdemo);
+		}
+
+		snprintf(bestdemo, 255, "%s-%s-score-best.lmp", gpath, skins[cv_chooseskin.value-1].name);
+		if (!FIL_FileExists(bestdemo) || (G_CmpDemoTime(bestdemo, lastdemo) & (1<<1)))
+		{ // Better score, save this demo.
+			if (FIL_FileExists(bestdemo))
+				remove(bestdemo);
+			FIL_WriteFile(bestdemo, buf, len);
+			CONS_Printf("\x83%s\x80 %s '%s'\n", M_GetText("NEW HIGH SCORE!"), M_GetText("Saved replay as"), bestdemo);
+		}
+
+		snprintf(bestdemo, 255, "%s-%s-rings-best.lmp", gpath, skins[cv_chooseskin.value-1].name);
+		if (!FIL_FileExists(bestdemo) || (G_CmpDemoTime(bestdemo, lastdemo) & (1<<2)))
+		{ // Better rings, save this demo.
+			if (FIL_FileExists(bestdemo))
+				remove(bestdemo);
+			FIL_WriteFile(bestdemo, buf, len);
+			CONS_Printf("\x83%s\x80 %s '%s'\n", M_GetText("NEW MOST RINGS!"), M_GetText("Saved replay as"), bestdemo);
+		}
+
+		//CONS_Printf("%s '%s'\n", M_GetText("Saved replay as"), lastdemo);
+
+		Z_Free(buf);
+	}
+	free(gpath);
+
+	// Check emblems when level data is updated
+	if ((earnedEmblems = M_CheckLevelEmblems()))
+		CONS_Printf(M_GetText("\x82" "Earned %hu emblem%s for Record Attack records.\n"), (UINT16)earnedEmblems, earnedEmblems > 1 ? "s" : "");
+
+	// Update timeattack menu's replay availability.
+	Nextmap_OnChange();
+}
+
 void G_SetNightsRecords(void)
 {
 	INT32 i;
 	UINT32 totalscore = 0;
 	tic_t totaltime = 0;
+	UINT8 earnedEmblems;
 
 	const size_t glen = strlen(srb2home)+1+strlen("replay")+1+strlen(timeattackfolder)+1+strlen("MAPXX")+1;
 	char *gpath;
@@ -644,6 +733,9 @@ void G_SetNightsRecords(void)
 	}
 	free(gpath);
 
+	if ((earnedEmblems = M_CheckLevelEmblems()))
+		CONS_Printf(M_GetText("\x82" "Earned %hu emblem%s for NiGHTS records.\n"), (UINT16)earnedEmblems, earnedEmblems > 1 ? "s" : "");
+
 	// If the mare count changed, this will update the score display
 	Nextmap_OnChange();
 }
@@ -1712,6 +1804,9 @@ void G_DoLoadLevel(boolean resetplayer)
 //
 void G_StartTitleCard(void)
 {
+	wipestyleflags |= WSF_FADEIN;
+	wipestyleflags &= ~WSF_FADEOUT;
+
 	// The title card has been disabled for this map.
 	// Oh well.
 	if (mapheaderinfo[gamemap-1]->levelflags & LF_NOTITLECARD)
@@ -1728,9 +1823,6 @@ void G_StartTitleCard(void)
 
 	// start the title card
 	WipeStageTitle = (!titlemapinaction);
-	WipeInLevel = true;
-	wipestyleflags |= WSF_FADEIN;
-	wipestyleflags &= ~WSF_FADEOUT;
 }
 
 //
@@ -1749,9 +1841,6 @@ void G_PreLevelTitleCard(tic_t ticker, boolean update)
 			I_Sleep();
 		lasttime = nowtime;
 
-		// Run some bullshit whatever
-		D_ProcessEvents();
-
 		ST_runTitleCard();
 		ST_preLevelTitleCardDrawer(ticker, update);
 
@@ -1927,7 +2016,7 @@ boolean G_Responder(event_t *ev)
 						pausedelay = 1+(NEWTICRATE/2);
 					else if (++pausedelay > 1+(NEWTICRATE/2)+(NEWTICRATE/3))
 					{
-						G_SetRetryFlag();
+						G_SetModeAttackRetryFlag();
 						return true;
 					}
 					pausedelay++; // counteract subsequent subtraction this frame
@@ -2768,6 +2857,7 @@ void G_DoReborn(INT32 playernum)
 
 			// Do a wipe
 			wipegamestate = -1;
+			wipestyleflags = WSF_CROSSFADE;
 
 			if (camera.chase)
 				P_ResetCamera(&players[displayplayer], &camera);
@@ -3122,12 +3212,51 @@ static INT16 RandMap(INT16 tolflags, INT16 pprevmap)
 	return ix;
 }
 
+//
+// G_UpdateVisited
+//
+static void G_UpdateVisited(void)
+{
+	boolean spec = G_IsSpecialStage(gamemap);
+	// Update visitation flags?
+	if ((!modifiedgame || savemoddata) // Not modified
+		&& !multiplayer && !demoplayback && (gametype == GT_COOP) // SP/RA/NiGHTS mode
+		&& !(spec && stagefailed)) // Not failed the special stage
+	{
+		UINT8 earnedEmblems;
+
+		// Update visitation flags
+		mapvisited[gamemap-1] |= MV_BEATEN;
+		// eh, what the hell
+		if (ultimatemode)
+			mapvisited[gamemap-1] |= MV_ULTIMATE;
+		// may seem incorrect but IS possible in what the main game uses as special stages, and nummaprings will be -1 in NiGHTS
+		if (nummaprings > 0 && players[consoleplayer].rings >= nummaprings)
+			mapvisited[gamemap-1] |= MV_PERFECT;
+		if (!spec)
+		{
+			// not available to special stages because they can only really be done in one order in an unmodified game, so impossible for first six and trivial for seventh
+			if (ALL7EMERALDS(emeralds))
+				mapvisited[gamemap-1] |= MV_ALLEMERALDS;
+		}
+
+		if (modeattacking == ATTACKING_RECORD)
+			G_UpdateRecordReplays();
+		else if (modeattacking == ATTACKING_NIGHTS)
+			G_SetNightsRecords();
+
+		if ((earnedEmblems = M_CompletionEmblems()))
+			CONS_Printf(M_GetText("\x82" "Earned %hu emblem%s for level completion.\n"), (UINT16)earnedEmblems, earnedEmblems > 1 ? "s" : "");
+	}
+}
+
 //
 // G_DoCompleted
 //
 static void G_DoCompleted(void)
 {
 	INT32 i;
+	boolean spec = G_IsSpecialStage(gamemap);
 
 	tokenlist = 0; // Reset the list
 
@@ -3160,14 +3289,14 @@ static void G_DoCompleted(void)
 		nextmap = (INT16)(mapheaderinfo[gamemap-1]->nextlevel-1);
 
 	// Remember last map for when you come out of the special stage.
-	if (!G_IsSpecialStage(gamemap))
+	if (!spec)
 		lastmap = nextmap;
 
 	// If nextmap is actually going to get used, make sure it points to
 	// a map of the proper gametype -- skip levels that don't support
 	// the current gametype. (Helps avoid playing boss levels in Race,
 	// for instance).
-	if (!token && !G_IsSpecialStage(gamemap)
+	if (!token && !spec
 		&& (nextmap >= 0 && nextmap < NUMMAPS))
 	{
 		register INT16 cm = nextmap;
@@ -3231,7 +3360,7 @@ static void G_DoCompleted(void)
 			gottoken = false;
 	}
 
-	if (G_IsSpecialStage(gamemap) && !gottoken)
+	if (spec && !gottoken)
 		nextmap = lastmap; // Exiting from a special stage? Go back to the game. Tails 08-11-2001
 
 	automapactive = false;
@@ -3250,17 +3379,29 @@ static void G_DoCompleted(void)
 	if (nextmap < NUMMAPS && !mapheaderinfo[nextmap])
 		P_AllocMapHeader(nextmap);
 
-	if (skipstats && !modeattacking) // Don't skip stats if we're in record attack
+	if ((skipstats && !modeattacking) || (spec && modeattacking && stagefailed))
+	{
+		G_UpdateVisited();
 		G_AfterIntermission();
+	}
 	else
 	{
 		G_SetGamestate(GS_INTERMISSION);
 		Y_StartIntermission();
+		G_UpdateVisited();
 	}
 }
 
 void G_AfterIntermission(void)
 {
+	Y_CleanupScreenBuffer();
+
+	if (modeattacking)
+	{
+		M_EndModeAttackRun();
+		return;
+	}
+
 	HU_ClearCEcho();
 
 	if (mapheaderinfo[gamemap-1]->cutscenenum && !modeattacking && skipstats <= 1) // Start a custom cutscene.
@@ -3427,7 +3568,6 @@ void G_LoadGameData(void)
 	UINT32 recscore;
 	tic_t  rectime;
 	UINT16 recrings;
-	boolean gotperf;
 
 	UINT8 recmares;
 	INT32 curmare;
@@ -3525,7 +3665,7 @@ void G_LoadGameData(void)
 		recscore = READUINT32(save_p);
 		rectime  = (tic_t)READUINT32(save_p);
 		recrings = READUINT16(save_p);
-		gotperf = (boolean)READUINT8(save_p);
+		save_p++; // compat
 
 		if (recrings > 10000 || recscore > MAXSCORE)
 			goto datacorrupt;
@@ -3537,9 +3677,6 @@ void G_LoadGameData(void)
 			mainrecords[i]->time = rectime;
 			mainrecords[i]->rings = recrings;
 		}
-
-		if (gotperf)
-			mainrecords[i]->gotperfect = gotperf;
 	}
 
 	// Nights records
@@ -3671,15 +3808,14 @@ void G_SaveGameData(void)
 			WRITEUINT32(save_p, mainrecords[i]->score);
 			WRITEUINT32(save_p, mainrecords[i]->time);
 			WRITEUINT16(save_p, mainrecords[i]->rings);
-			WRITEUINT8(save_p, mainrecords[i]->gotperfect);
 		}
 		else
 		{
 			WRITEUINT32(save_p, 0);
 			WRITEUINT32(save_p, 0);
 			WRITEUINT16(save_p, 0);
-			WRITEUINT8(save_p, 0);
 		}
+		WRITEUINT8(save_p, 0); // compat
 	}
 
 	// NiGHTS records
@@ -4726,6 +4862,12 @@ void G_WriteGhostTic(mobj_t *ghost)
 			oldghost.flags2 |= MF2_AMBUSH;
 		}
 
+		if (ghost->player->followmobj->scale != ghost->scale)
+		{
+			followtic |= FZT_SCALE;
+			WRITEFIXED(demo_p,ghost->player->followmobj->scale);
+		}
+
 		temp = (INT16)((ghost->player->followmobj->x-ghost->x)>>8);
 		WRITEINT16(demo_p,temp);
 		temp = (INT16)((ghost->player->followmobj->y-ghost->y)>>8);
@@ -4737,11 +4879,6 @@ void G_WriteGhostTic(mobj_t *ghost)
 		WRITEUINT16(demo_p,ghost->player->followmobj->sprite);
 		WRITEUINT8(demo_p,(ghost->player->followmobj->frame & FF_FRAMEMASK));
 		WRITEUINT8(demo_p,ghost->player->followmobj->color);
-		if (ghost->player->followmobj->scale != ghost->scale)
-		{
-			followtic |= FZT_SCALE;
-			WRITEFIXED(demo_p,ghost->player->followmobj->scale);
-		}
 
 		*followtic_p = followtic;
 	}
@@ -6789,6 +6926,22 @@ boolean G_GetRetryFlag(void)
 	return retrying;
 }
 
+void G_SetModeAttackRetryFlag(void)
+{
+	retryingmodeattack = true;
+	G_SetRetryFlag();
+}
+
+void G_ClearModeAttackRetryFlag(void)
+{
+	retryingmodeattack = false;
+}
+
+boolean G_GetModeAttackRetryFlag(void)
+{
+	return retryingmodeattack;
+}
+
 // Time utility functions
 INT32 G_TicsToHours(tic_t tics)
 {
diff --git a/src/g_game.h b/src/g_game.h
index 87232c823ee1f56b9ba3dfd2cccd9094e905102d..0a575c099c6badee900584b9851eadf1dfeed3ef 100644
--- a/src/g_game.h
+++ b/src/g_game.h
@@ -225,10 +225,14 @@ void G_AddPlayer(INT32 playernum);
 void G_SetExitGameFlag(void);
 void G_ClearExitGameFlag(void);
 boolean G_GetExitGameFlag(void);
+
 void G_SetRetryFlag(void);
 void G_ClearRetryFlag(void);
 boolean G_GetRetryFlag(void);
 
+void G_SetModeAttackRetryFlag(void);
+void G_ClearModeAttackRetryFlag(void);
+boolean G_GetModeAttackRetryFlag(void);
 
 void G_LoadGameData(void);
 void G_LoadGameSettings(void);
diff --git a/src/hardware/hw_drv.h b/src/hardware/hw_drv.h
index 1e40a403df1a492aed56106d78e12f8f64f640c9..aed1611f111176ba0ab9c907362e154421894825 100644
--- a/src/hardware/hw_drv.h
+++ b/src/hardware/hw_drv.h
@@ -71,7 +71,6 @@ EXPORT void HWRAPI(FlushScreenTextures) (void);
 EXPORT void HWRAPI(StartScreenWipe) (void);
 EXPORT void HWRAPI(EndScreenWipe) (void);
 EXPORT void HWRAPI(DoScreenWipe) (void);
-EXPORT void HWRAPI(DoScreenWipeLevel) (void);
 EXPORT void HWRAPI(DrawIntermissionBG) (void);
 EXPORT void HWRAPI(MakeScreenTexture) (void);
 EXPORT void HWRAPI(MakeScreenFinalTexture) (void);
@@ -113,7 +112,6 @@ struct hwdriver_s
 	StartScreenWipe     pfnStartScreenWipe;
 	EndScreenWipe       pfnEndScreenWipe;
 	DoScreenWipe        pfnDoScreenWipe;
-	DoScreenWipeLevel   pfnDoScreenWipeLevel;
 	DrawIntermissionBG  pfnDrawIntermissionBG;
 	MakeScreenTexture   pfnMakeScreenTexture;
 	MakeScreenFinalTexture  pfnMakeScreenFinalTexture;
diff --git a/src/hardware/hw_light.c b/src/hardware/hw_light.c
index 584c584632629c4f38185f1c8343ab974f2d3a0f..4df71d145903532f9336ab2b18c6e7ee5cd6d94b 100644
--- a/src/hardware/hw_light.c
+++ b/src/hardware/hw_light.c
@@ -586,7 +586,6 @@ light_t *t_lspr[NUMSPRITES] =
 	&lspr[SUPERSPARK_L], // SPR_BOM3
 	&lspr[NOLIGHT],      // SPR_BOM4
 	&lspr[REDBALL_L],    // SPR_BMNB
-	&lspr[NOLIGHT],      // SPR_WDDB
 
 	// Crumbly rocks
 	&lspr[NOLIGHT],     // SPR_ROIA
@@ -606,8 +605,10 @@ light_t *t_lspr[NUMSPRITES] =
 	&lspr[NOLIGHT],     // SPR_ROIO
 	&lspr[NOLIGHT],     // SPR_ROIP
 
-	// Bricks
+	// Level debris
+	&lspr[NOLIGHT], // SPR_GFZD
 	&lspr[NOLIGHT], // SPR_BRIC
+	&lspr[NOLIGHT], // SPR_WDDB
 
 	// Gravity Well Objects
 	&lspr[NOLIGHT],     // SPR_GWLG
diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index 263344ebe55e7b1f9ca9d471b16e7a70aba56dfb..51c976973d6a0db7aaeeaaa7e055af3592497fce 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -1973,7 +1973,7 @@ static void HWR_StoreWallRange(double startfrac, double endfrac)
 	{
 		// Single sided line... Deal only with the middletexture (if one exists)
 		gr_midtexture = R_GetTextureNum(gr_sidedef->midtexture);
-		if (gr_midtexture && gr_linedef->special != 41) // Ignore horizon line for OGL
+		if (gr_midtexture && gr_linedef->special != HORIZONSPECIAL) // Ignore horizon line for OGL
 		{
 			{
 				fixed_t     texturevpeg;
@@ -7085,26 +7085,6 @@ void HWR_DoTintedWipe(UINT8 wipenum, UINT8 scrnnum)
 	HWR_DoWipe(wipenum, scrnnum);
 }
 
-void HWR_DoLevelWipe(UINT8 wipenum, UINT8 scrnnum, UINT8 colfill)
-{
-#ifndef LEVELWIPES
-	(void)wipenum;
-	(void)scrnnum;
-	(void)colfill;
-#else
-	if (!HWR_WipeCheck(wipenum, scrnnum))
-		return;
-
-	HWR_EndScreenWipe();
-	V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, colfill);
-	HWR_StartScreenWipe();
-	HWR_GetFadeMask(wipelumpnum);
-
-	HWD.pfnDoScreenWipeLevel();
-	F_WipeStageTitle();
-#endif
-}
-
 void HWR_MakeScreenFinalTexture(void)
 {
     HWD.pfnMakeScreenFinalTexture();
diff --git a/src/hardware/hw_main.h b/src/hardware/hw_main.h
index fb726f2f9f90d3a5175489ffa5ea1355df8ec126..e19c557d03ecfd3821a24b17d69da945a2b57b44 100644
--- a/src/hardware/hw_main.h
+++ b/src/hardware/hw_main.h
@@ -67,7 +67,6 @@ void HWR_EndScreenWipe(void);
 void HWR_DrawIntermissionBG(void);
 void HWR_DoWipe(UINT8 wipenum, UINT8 scrnnum);
 void HWR_DoTintedWipe(UINT8 wipenum, UINT8 scrnnum);
-void HWR_DoLevelWipe(UINT8 wipenum, UINT8 scrnnum, UINT8 wipecolorfill);
 void HWR_MakeScreenFinalTexture(void);
 void HWR_DrawScreenFinalTexture(int width, int height);
 
diff --git a/src/hardware/r_opengl/r_opengl.c b/src/hardware/r_opengl/r_opengl.c
index 323772cb0698edf5c30ce36692bbfa2d1b678a7f..8c0ca7155faf94264e53e59163e6213f5dddc00a 100644
--- a/src/hardware/r_opengl/r_opengl.c
+++ b/src/hardware/r_opengl/r_opengl.c
@@ -2587,11 +2587,6 @@ EXPORT void HWRAPI(DoScreenWipe)(void)
 	tex_downloaded = endScreenWipe;
 }
 
-EXPORT void HWRAPI(DoScreenWipeLevel)(void)
-{
-	DoScreenWipe();
-}
-
 // Create a texture from the screen.
 EXPORT void HWRAPI(MakeScreenTexture) (void)
 {
diff --git a/src/info.c b/src/info.c
index 50da228e59297b64a3ac373690535c3c42816717..e14abd575ee5dee315d4cde2c06e0904ac7f89bc 100644
--- a/src/info.c
+++ b/src/info.c
@@ -483,7 +483,6 @@ char sprnames[NUMSPRITES + 1][5] =
 	"BOM3", // Boss Explosion 2
 	"BOM4", // Underwater Explosion
 	"BMNB", // Mine Explosion
-	"WDDB", // Wood Debris
 
 	// Crumbly rocks
 	"ROIA",
@@ -503,8 +502,10 @@ char sprnames[NUMSPRITES + 1][5] =
 	"ROIO",
 	"ROIP",
 
-	// Bricks
-	"BRIC",
+	// Level debris
+	"GFZD", // GFZ debris
+	"BRIC", // Bricks
+	"WDDB", // Wood Debris
 
 	// Gravity Well Objects
 	"GWLG",
@@ -1073,23 +1074,15 @@ state_t states[NUMSTATES] =
 	{SPR_MNUD, 2|FF_ANIMATE, 5,  {NULL},           1, 2, S_MINUS_BURST4},   // S_MINUS_BURST3
 	{SPR_MNUD, 3|FF_ANIMATE, 5,  {NULL},           1, 2, S_MINUS_BURST5},   // S_MINUS_BURST4
 	{SPR_MNUD, 4|FF_ANIMATE, 5,  {NULL},           1, 2, S_MINUSDIRT2},     // S_MINUS_BURST5
-	{SPR_MNUS,  0, 1, {A_MinusPopup},   0, 0, S_MINUS_UPWARD1},   // S_MINUS_POPUP
-	{SPR_MNUS,  0, 1, {A_MinusCheck},   0, 0, S_MINUS_UPWARD2},   // S_MINUS_UPWARD1
-	{SPR_MNUS,  1, 1, {A_MinusCheck},   0, 0, S_MINUS_UPWARD3},   // S_MINUS_UPWARD2
-	{SPR_MNUS,  2, 1, {A_MinusCheck},   0, 0, S_MINUS_UPWARD4},   // S_MINUS_UPWARD3
-	{SPR_MNUS,  3, 1, {A_MinusCheck},   0, 0, S_MINUS_UPWARD5},   // S_MINUS_UPWARD4
-	{SPR_MNUS,  4, 1, {A_MinusCheck},   0, 0, S_MINUS_UPWARD6},   // S_MINUS_UPWARD5
-	{SPR_MNUS,  5, 1, {A_MinusCheck},   0, 0, S_MINUS_UPWARD7},   // S_MINUS_UPWARD6
-	{SPR_MNUS,  6, 1, {A_MinusCheck},   0, 0, S_MINUS_UPWARD8},   // S_MINUS_UPWARD7
-	{SPR_MNUS,  7, 1, {A_MinusCheck},   0, 0, S_MINUS_UPWARD1},   // S_MINUS_UPWARD8
-	{SPR_MNUS,  8, 1, {A_MinusCheck},   0, 0, S_MINUS_DOWNWARD2}, // S_MINUS_DOWNWARD1
-	{SPR_MNUS,  9, 1, {A_MinusCheck},   0, 0, S_MINUS_DOWNWARD3}, // S_MINUS_DOWNWARD2
-	{SPR_MNUS, 10, 1, {A_MinusCheck},   0, 0, S_MINUS_DOWNWARD4}, // S_MINUS_DOWNWARD3
-	{SPR_MNUS, 11, 1, {A_MinusCheck},   0, 0, S_MINUS_DOWNWARD5}, // S_MINUS_DOWNWARD4
-	{SPR_MNUS, 12, 1, {A_MinusCheck},   0, 0, S_MINUS_DOWNWARD6}, // S_MINUS_DOWNWARD5
-	{SPR_MNUS, 13, 1, {A_MinusCheck},   0, 0, S_MINUS_DOWNWARD7}, // S_MINUS_DOWNWARD6
-	{SPR_MNUS, 14, 1, {A_MinusCheck},   0, 0, S_MINUS_DOWNWARD8}, // S_MINUS_DOWNWARD7
-	{SPR_MNUS, 15, 1, {A_MinusCheck},   0, 0, S_MINUS_DOWNWARD1}, // S_MINUS_DOWNWARD8
+	{SPR_MNUS, 3, 1, {A_MinusPopup}, 0, 0, S_MINUS_UPWARD1}, // S_MINUS_POPUP
+	{SPR_MNUS, 0, 1, {A_MinusCheck}, 0, 0, S_MINUS_UPWARD2},   // S_MINUS_UPWARD1
+	{SPR_MNUS, 1, 1, {A_MinusCheck}, 0, 0, S_MINUS_UPWARD3},   // S_MINUS_UPWARD2
+	{SPR_MNUS, 2, 1, {A_MinusCheck}, 0, 0, S_MINUS_UPWARD4},   // S_MINUS_UPWARD3
+	{SPR_MNUS, 3, 1, {A_MinusCheck}, 0, 0, S_MINUS_UPWARD1},   // S_MINUS_UPWARD4
+	{SPR_MNUS, 4, 1, {A_MinusCheck}, 0, 0, S_MINUS_DOWNWARD2}, // S_MINUS_DOWNWARD1
+	{SPR_MNUS, 5, 1, {A_MinusCheck}, 0, 0, S_MINUS_DOWNWARD3}, // S_MINUS_DOWNWARD2
+	{SPR_MNUS, 6, 1, {A_MinusCheck}, 0, 0, S_MINUS_DOWNWARD4}, // S_MINUS_DOWNWARD3
+	{SPR_MNUS, 7, 1, {A_MinusCheck}, 0, 0, S_MINUS_DOWNWARD1}, // S_MINUS_DOWNWARD4
 
 	{SPR_MNUD, FF_ANIMATE, 6, {NULL}, 1, 5, S_MINUSDIRT2}, // S_MINUSDIRT1
 	{SPR_MNUD, 5,          8, {NULL}, 3, 5, S_MINUSDIRT3}, // S_MINUSDIRT2
@@ -3883,8 +3876,6 @@ state_t states[NUMSTATES] =
 	{SPR_DUST, 2|FF_TRANS60, 3, {NULL}, 0, 0, S_DUST4}, // S_DUST3
 	{SPR_DUST, 3|FF_TRANS70, 2, {NULL}, 0, 0, S_NULL},  // S_DUST4
 
-	{SPR_WDDB, FF_ANIMATE, -1, {A_DebrisRandom}, 7, 2, S_NULL},  // S_WOODDEBRIS
-
 	{SPR_NULL, 0, 1, {A_RockSpawn}, 0, 0, S_ROCKSPAWN}, // S_ROCKSPAWN
 
 	{SPR_ROIA, FF_ANIMATE|FF_RANDOMANIM, -1, {NULL}, 4, 2, S_NULL}, // S_ROCKCRUMBLEA
@@ -3904,7 +3895,9 @@ state_t states[NUMSTATES] =
 	{SPR_ROIO, FF_ANIMATE|FF_RANDOMANIM, -1, {NULL}, 7, 2, S_NULL}, // S_ROCKCRUMBLEO
 	{SPR_ROIP, FF_ANIMATE|FF_RANDOMANIM, -1, {NULL}, 7, 2, S_NULL}, // S_ROCKCRUMBLEP
 
+	{SPR_GFZD, FF_ANIMATE|FF_RANDOMANIM, -1, {NULL}, 31, 1, S_NULL}, // S_GFZDEBRIS
 	{SPR_BRIC, FF_ANIMATE, -1, {A_DebrisRandom}, 7, 2, S_NULL}, // S_BRICKDEBRIS
+	{SPR_WDDB, FF_ANIMATE, -1, {A_DebrisRandom}, 7, 2, S_NULL}, // S_WOODDEBRIS
 
 #ifdef SEENAMES
 	{SPR_NULL, 0, 1, {NULL}, 0, 0, S_NULL}, // S_NAMECHECK
@@ -13448,7 +13441,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
 		12*TICRATE,     // painchance (sets how long an unridden rock should last before disappearing - set to 0 to disable)
-		sfx_None,       // painsound
+		sfx_s3k49,      // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
 		S_NULL,         // deathstate
@@ -20960,33 +20953,6 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
-	{           // MT_WOODDEBRIS
-		-1,             // doomednum
-		S_WOODDEBRIS,   // spawnstate
-		1,              // spawnhealth
-		S_NULL,         // seestate
-		sfx_None,       // seesound
-		0,              // reactiontime
-		sfx_None,       // attacksound
-		S_NULL,         // painstate
-		0,              // painchance
-		sfx_None,       // painsound
-		S_NULL,         // meleestate
-		S_NULL,         // missilestate
-		S_NULL,         // deathstate
-		S_NULL,         // xdeathstate
-		sfx_None,       // deathsound
-		0,              // speed
-		16*FRACUNIT,    // radius
-		16*FRACUNIT,    // height
-		0,              // display offset
-		100,            // mass
-		0,              // damage
-		sfx_wbreak,     // activesound
-		MF_NOBLOCKMAP|MF_NOCLIPTHING|MF_RUNSPAWNFUNC|MF_NOCLIPHEIGHT|MF_SCENERY, // flags
-		S_NULL          // raisestate
-	},
-
 	{           // MT_ROCKSPAWNER
 		1202,           // doomednum
 		S_ROCKSPAWN,    // spawnstate
@@ -21473,16 +21439,43 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL          // raisestate
 	},
 
+	{           // MT_GFZDEBRIS
+		-1,             // doomednum
+		S_GFZDEBRIS,    // spawnstate
+		1,              // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		0,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		32*FRACUNIT,    // radius
+		64*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_crumbl,     // activesound
+		MF_NOBLOCKMAP|MF_NOCLIPTHING|MF_RUNSPAWNFUNC|MF_NOCLIPHEIGHT|MF_SCENERY, // flags
+		S_NULL          // raisestate
+	},
+
 	{           // MT_BRICKDEBRIS
 		-1,             // doomednum
-		S_BRICKDEBRIS, // spawnstate
-		1,           // spawnhealth
+		S_BRICKDEBRIS,  // spawnstate
+		1,              // spawnhealth
 		S_NULL,         // seestate
-		sfx_None,     // seesound
+		sfx_None,       // seesound
 		0,              // reactiontime
 		sfx_None,       // attacksound
 		S_NULL,         // painstate
-		0,            // painchance
+		0,              // painchance
 		sfx_None,       // painsound
 		S_NULL,         // meleestate
 		S_NULL,         // missilestate
@@ -21490,16 +21483,43 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		0,              // speed
-		16*FRACUNIT,     // radius
+		16*FRACUNIT,    // radius
 		16*FRACUNIT,    // height
 		0,              // display offset
-		100,           // mass
+		100,            // mass
 		0,              // damage
-		sfx_None,     // activesound
+		sfx_None,       // activesound
 		MF_NOBLOCKMAP|MF_NOCLIPTHING|MF_RUNSPAWNFUNC|MF_NOCLIPHEIGHT|MF_SCENERY,  // flags
 		S_NULL          // raisestate
 	},
 
+	{           // MT_WOODDEBRIS
+		-1,             // doomednum
+		S_WOODDEBRIS,   // spawnstate
+		1,              // spawnhealth
+		S_NULL,         // seestate
+		sfx_None,       // seesound
+		0,              // reactiontime
+		sfx_None,       // attacksound
+		S_NULL,         // painstate
+		0,              // painchance
+		sfx_None,       // painsound
+		S_NULL,         // meleestate
+		S_NULL,         // missilestate
+		S_NULL,         // deathstate
+		S_NULL,         // xdeathstate
+		sfx_None,       // deathsound
+		0,              // speed
+		16*FRACUNIT,    // radius
+		16*FRACUNIT,    // height
+		0,              // display offset
+		100,            // mass
+		0,              // damage
+		sfx_wbreak,     // activesound
+		MF_NOBLOCKMAP|MF_NOCLIPTHING|MF_RUNSPAWNFUNC|MF_NOCLIPHEIGHT|MF_SCENERY, // flags
+		S_NULL          // raisestate
+	},
+
 #ifdef SEENAMES
 	{           // MT_NAMECHECK
 		-1,             // doomednum
diff --git a/src/info.h b/src/info.h
index e28a24ade5822933359d812446d2e866fa39a978..ff346412e33d563665ea090f613bdda5d0d7de8f 100644
--- a/src/info.h
+++ b/src/info.h
@@ -748,7 +748,6 @@ typedef enum sprite
 	SPR_BOM3, // Boss Explosion 2
 	SPR_BOM4, // Underwater Explosion
 	SPR_BMNB, // Mine Explosion
-	SPR_WDDB, // Wood Debris
 
 	// Crumbly rocks
 	SPR_ROIA,
@@ -768,8 +767,10 @@ typedef enum sprite
 	SPR_ROIO,
 	SPR_ROIP,
 
-	// Bricks
-	SPR_BRIC,
+	// Level debris
+	SPR_GFZD, // GFZ debris
+	SPR_BRIC, // Bricks
+	SPR_WDDB, // Wood Debris
 
 	// Gravity Well Objects
 	SPR_GWLG,
@@ -1273,18 +1274,10 @@ typedef enum state
 	S_MINUS_UPWARD2,
 	S_MINUS_UPWARD3,
 	S_MINUS_UPWARD4,
-	S_MINUS_UPWARD5,
-	S_MINUS_UPWARD6,
-	S_MINUS_UPWARD7,
-	S_MINUS_UPWARD8,
 	S_MINUS_DOWNWARD1,
 	S_MINUS_DOWNWARD2,
 	S_MINUS_DOWNWARD3,
 	S_MINUS_DOWNWARD4,
-	S_MINUS_DOWNWARD5,
-	S_MINUS_DOWNWARD6,
-	S_MINUS_DOWNWARD7,
-	S_MINUS_DOWNWARD8,
 
 	// Minus dirt
 	S_MINUSDIRT1,
@@ -3970,8 +3963,6 @@ typedef enum state
 	S_DUST3,
 	S_DUST4,
 
-	S_WOODDEBRIS,
-
 	S_ROCKSPAWN,
 
 	S_ROCKCRUMBLEA,
@@ -3991,8 +3982,10 @@ typedef enum state
 	S_ROCKCRUMBLEO,
 	S_ROCKCRUMBLEP,
 
-	// Bricks
+	// Level debris
+	S_GFZDEBRIS,
 	S_BRICKDEBRIS,
+	S_WOODDEBRIS,
 
 #ifdef SEENAMES
 	S_NAMECHECK,
@@ -4768,7 +4761,6 @@ typedef enum mobj_type
 	MT_EXPLODE, // Robot Explosion
 	MT_UWEXPLODE, // Underwater Explosion
 	MT_DUST,
-	MT_WOODDEBRIS,
 	MT_ROCKSPAWNER,
 	MT_FALLINGROCK,
 	MT_ROCKCRUMBLE1,
@@ -4788,8 +4780,10 @@ typedef enum mobj_type
 	MT_ROCKCRUMBLE15,
 	MT_ROCKCRUMBLE16,
 
-	// Bricks
+	// Level debris
+	MT_GFZDEBRIS,
 	MT_BRICKDEBRIS,
+	MT_WOODDEBRIS,
 
 #ifdef SEENAMES
 	MT_NAMECHECK,
diff --git a/src/m_menu.c b/src/m_menu.c
index 7a5aa8ae61ab493754df5e65427f4f31da79ac0a..7d62514e66273082a2f08e2917e6bc554b634aec 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -312,9 +312,7 @@ menu_t OP_VideoOptionsDef, OP_VideoModeDef, OP_ColorOptionsDef;
 menu_t OP_OpenGLOptionsDef, OP_OpenGLFogDef, OP_OpenGLColorDef;
 #endif
 menu_t OP_SoundOptionsDef;
-#ifdef HAVE_MIXERX
 menu_t OP_SoundAdvancedDef;
-#endif
 
 //Misc
 menu_t OP_DataOptionsDef, OP_ScreenshotOptionsDef, OP_EraseDataDef;
@@ -474,25 +472,25 @@ static consvar_t cv_dummymares = {"dummymares", "Overall", CV_HIDEN|CV_CALL, dum
 // ---------
 static menuitem_t MainMenu[] =
 {
-	{IT_STRING|IT_CALL,    NULL, "Secrets",     M_SecretsMenu,           76},
-	{IT_STRING|IT_CALL,    NULL, "1  player",   M_SinglePlayerMenu,      84},
+	{IT_STRING|IT_CALL,    NULL, "1  Player",   M_SinglePlayerMenu,      76},
 #ifndef NONET
-	{IT_STRING|IT_SUBMENU, NULL, "multiplayer", &MP_MainDef,             92},
+	{IT_STRING|IT_SUBMENU, NULL, "Multiplayer", &MP_MainDef,             84},
 #else
-	{IT_STRING|IT_CALL,    NULL, "multiplayer", M_StartSplitServerMenu,  92},
+	{IT_STRING|IT_CALL,    NULL, "Multiplayer", M_StartSplitServerMenu,  84},
 #endif
-	{IT_STRING|IT_CALL,    NULL, "options",     M_Options,              100},
-	{IT_CALL   |IT_STRING, NULL, "addons",      M_Addons,               108},
-	{IT_STRING|IT_CALL,    NULL, "quit  game",  M_QuitSRB2,             116},
+	{IT_STRING|IT_CALL,    NULL, "Extras",      M_SecretsMenu,           92},
+	{IT_CALL   |IT_STRING, NULL, "Addons",      M_Addons,               100},
+	{IT_STRING|IT_CALL,    NULL, "Options",     M_Options,              108},
+	{IT_STRING|IT_CALL,    NULL, "Quit  Game",  M_QuitSRB2,             116},
 };
 
 typedef enum
 {
-	secrets = 0,
-	singleplr,
+	singleplr = 0,
 	multiplr,
-	options,
+	secrets,
 	addons,
+	options,
 	quitdoom
 } main_e;
 
@@ -661,7 +659,7 @@ static menuitem_t SR_PandorasBox[] =
 // Sky Room Custom Unlocks
 static menuitem_t SR_MainMenu[] =
 {
-	{IT_STRING|IT_SUBMENU,NULL, "Secrets Checklist", &SR_UnlockChecklistDef, 0},
+	{IT_STRING|IT_SUBMENU,NULL, "Extras Checklist", &SR_UnlockChecklistDef, 0},
 	{IT_DISABLED,         NULL, "",   NULL,                 0}, // Custom1
 	{IT_DISABLED,         NULL, "",   NULL,                 0}, // Custom2
 	{IT_DISABLED,         NULL, "",   NULL,                 0}, // Custom3
@@ -726,19 +724,19 @@ static menuitem_t SR_EmblemHintMenu[] =
 // Single Player Main
 static menuitem_t SP_MainMenu[] =
 {
-	{IT_CALL | IT_STRING,                       NULL, "Tutorial",      M_StartTutorial,            84},
-	{IT_CALL | IT_STRING,                       NULL, "Start Game",    M_LoadGame,                 92},
-	{IT_SECRET,                                 NULL, "Record Attack", M_TimeAttack,              100},
-	{IT_SECRET,                                 NULL, "NiGHTS Mode",   M_NightsAttack,            108},
-	{IT_CALL | IT_STRING | IT_CALL_NOTMODIFIED, NULL, "Statistics",    M_Statistics,              116},
+	{IT_CALL | IT_STRING,                       NULL, "Start Game",    M_LoadGame,                 84},
+	{IT_SECRET,                                 NULL, "Record Attack", M_TimeAttack,               92},
+	{IT_SECRET,                                 NULL, "NiGHTS Mode",   M_NightsAttack,            100},
+	{IT_CALL | IT_STRING,                       NULL, "Tutorial",      M_StartTutorial,           108},
+	{IT_CALL | IT_STRING | IT_CALL_NOTMODIFIED, NULL, "Statistics",    M_Statistics,              116}
 };
 
 enum
 {
-	sptutorial,
 	sploadgame,
 	sprecordattack,
 	spnightsmode,
+	sptutorial,
 	spstatistics
 };
 
@@ -1348,29 +1346,22 @@ static menuitem_t OP_OpenGLColorMenu[] =
 static menuitem_t OP_SoundOptionsMenu[] =
 {
 	{IT_HEADER, NULL, "Game Audio", NULL, 0},
-	{IT_STRING | IT_CVAR,  NULL,  "Sound Effects", &cv_gamesounds, 6},
-	{IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Sound Volume", &cv_soundvolume, 11},
+	{IT_STRING | IT_CVAR,  NULL,  "Sound Effects", &cv_gamesounds, 12},
+	{IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Sound Volume", &cv_soundvolume, 22},
 
-	{IT_STRING | IT_CVAR,  NULL,  "Digital Music", &cv_gamedigimusic, 21},
-	{IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Digital Music Volume", &cv_digmusicvolume,  26},
+	{IT_STRING | IT_CVAR,  NULL,  "Digital Music", &cv_gamedigimusic, 42},
+	{IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "Digital Music Volume", &cv_digmusicvolume,  52},
 
-	{IT_STRING | IT_CVAR,  NULL,  "MIDI Music", &cv_gamemidimusic, 36},
-	{IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "MIDI Music Volume", &cv_midimusicvolume, 41},
+	{IT_STRING | IT_CVAR,  NULL,  "MIDI Music", &cv_gamemidimusic, 72},
+	{IT_STRING | IT_CVAR | IT_CV_SLIDER, NULL, "MIDI Music Volume", &cv_midimusicvolume, 82},
 
-	{IT_HEADER, NULL, "Accessibility", NULL, 50},
-	{IT_STRING | IT_CVAR, NULL, "Closed Captioning", &cv_closedcaptioning, 56},
-	{IT_STRING | IT_CVAR, NULL, "Reset Music Upon Dying", &cv_resetmusic, 61},
+	{IT_HEADER, NULL, "Miscellaneous", NULL, 102},
+	{IT_STRING | IT_CVAR, NULL, "Closed Captioning", &cv_closedcaptioning, 114},
+	{IT_STRING | IT_CVAR, NULL, "Reset Music Upon Dying", &cv_resetmusic, 124},
 
-	{IT_STRING | IT_CVAR, NULL, "Play Sound Effects if Unfocused", &cv_playsoundsifunfocused, 71},
-	{IT_STRING | IT_CVAR, NULL, "Play Music if Unfocused", &cv_playmusicifunfocused, 76},
-
-#ifdef HAVE_MIXERX
-	{IT_STRING | IT_SUBMENU, NULL, "Advanced Settings...", &OP_SoundAdvancedDef, 94},
-#endif
+	{IT_STRING | IT_SUBMENU, NULL, "Advanced Settings...", &OP_SoundAdvancedDef, 144},
 };
 
-#ifdef HAVE_MIXERX
-
 #ifdef HAVE_OPENMPT
 #define OPENMPT_MENUOFFSET 32
 #else
@@ -1386,24 +1377,25 @@ static menuitem_t OP_SoundOptionsMenu[] =
 static menuitem_t OP_SoundAdvancedMenu[] =
 {
 #ifdef HAVE_OPENMPT
-	{IT_HEADER, NULL, "OpenMPT Settings", NULL, 10},
-	{IT_STRING | IT_CVAR, NULL, "Instrument Filter", &cv_modfilter, 22},
+	{IT_HEADER, NULL, "OpenMPT Settings", NULL, 0},
+	{IT_STRING | IT_CVAR, NULL, "Instrument Filter", &cv_modfilter, 12},
 #endif
 
 #ifdef HAVE_MIXERX
-	{IT_HEADER, NULL, "MIDI Settings", NULL, OPENMPT_MENUOFFSET+10},
-	{IT_STRING | IT_CVAR, NULL, "MIDI Player", &cv_midiplayer, OPENMPT_MENUOFFSET+22},
-	{IT_STRING | IT_CVAR | IT_CV_STRING, NULL, "FluidSynth Sound Font File", &cv_midisoundfontpath, OPENMPT_MENUOFFSET+34},
-	{IT_STRING | IT_CVAR | IT_CV_STRING, NULL, "TiMidity++ Config Folder", &cv_miditimiditypath, OPENMPT_MENUOFFSET+61},
+	{IT_HEADER, NULL, "MIDI Settings", NULL, OPENMPT_MENUOFFSET},
+	{IT_STRING | IT_CVAR, NULL, "MIDI Player", &cv_midiplayer, OPENMPT_MENUOFFSET+12},
+	{IT_STRING | IT_CVAR | IT_CV_STRING, NULL, "FluidSynth Sound Font File", &cv_midisoundfontpath, OPENMPT_MENUOFFSET+24},
+	{IT_STRING | IT_CVAR | IT_CV_STRING, NULL, "TiMidity++ Config Folder", &cv_miditimiditypath, OPENMPT_MENUOFFSET+51},
 #endif
 
-	{IT_HEADER, NULL, "Miscellaneous", NULL, OPENMPT_MENUOFFSET+MIXERX_MENUOFFSET+10},
-	{IT_STRING | IT_CVAR, NULL, "Let Levels Force Reset Music", &cv_resetmusicbyheader, OPENMPT_MENUOFFSET+MIXERX_MENUOFFSET+22},
+	{IT_HEADER, NULL, "Miscellaneous", NULL, OPENMPT_MENUOFFSET+MIXERX_MENUOFFSET},
+	{IT_STRING | IT_CVAR, NULL, "Play Sound Effects if Unfocused", &cv_playsoundsifunfocused, OPENMPT_MENUOFFSET+MIXERX_MENUOFFSET+12},
+	{IT_STRING | IT_CVAR, NULL, "Play Music if Unfocused", &cv_playmusicifunfocused, OPENMPT_MENUOFFSET+MIXERX_MENUOFFSET+22},
+	{IT_STRING | IT_CVAR, NULL, "Let Levels Force Reset Music", &cv_resetmusicbyheader, OPENMPT_MENUOFFSET+MIXERX_MENUOFFSET+32},
 };
 
 #undef OPENMPT_MENUOFFSET
 #undef MIXERX_MENUOFFSET
-#endif
 
 static menuitem_t OP_DataOptionsMenu[] =
 {
@@ -1455,7 +1447,7 @@ enum
 static menuitem_t OP_EraseDataMenu[] =
 {
 	{IT_STRING | IT_CALL, NULL, "Erase Record Data", M_EraseData, 10},
-	{IT_STRING | IT_CALL, NULL, "Erase Secrets Data", M_EraseData, 20},
+	{IT_STRING | IT_CALL, NULL, "Erase Extras Data", M_EraseData, 20},
 
 	{IT_STRING | IT_CALL, NULL, "\x85" "Erase ALL Data", M_EraseData, 40},
 };
@@ -1666,7 +1658,7 @@ menu_t SP_MainDef = //CENTERMENUSTYLE(NULL, SP_MainMenu, &MainDef, 72);
 	SP_MainMenu,
 	M_DrawCenteredMenu,
 	BASEVIDWIDTH/2, 72,
-	1, // start at "Start Game" on first entry
+	0,
 	NULL
 };
 
@@ -1992,12 +1984,10 @@ menu_t OP_ColorOptionsDef =
 	0,
 	NULL
 };
-menu_t OP_SoundOptionsDef = DEFAULTSCROLLMENUSTYLE(
+menu_t OP_SoundOptionsDef = DEFAULTMENUSTYLE(
 	MN_OP_MAIN + (MN_OP_SOUND << 6),
 	"M_SOUND", OP_SoundOptionsMenu, &OP_MainDef, 30, 30);
-#ifdef HAVE_MIXERX
 menu_t OP_SoundAdvancedDef = DEFAULTMENUSTYLE(MN_OP_MAIN + (MN_OP_SOUND << 6), "M_SOUND", OP_SoundAdvancedMenu, &OP_SoundOptionsDef, 30, 30);
-#endif
 
 menu_t OP_ServerOptionsDef = DEFAULTSCROLLMENUSTYLE(
 	MN_OP_MAIN + (MN_OP_SERVER << 6),
@@ -3365,8 +3355,6 @@ boolean M_Responder(event_t *ev)
 void M_Drawer(void)
 {
 	boolean wipe = WipeInAction;
-	if (WipeInLevel)
-		wipe = false;
 
 	if (currentMenu == &MessageDef)
 		menuactive = true;
@@ -3436,6 +3424,8 @@ void M_StartControlPanel(void)
 	if (!Playing())
 	{
 		// Secret menu!
+		MainMenu[singleplr].alphaKey = (M_AnySecretUnlocked()) ? 76 : 84;
+		MainMenu[multiplr].alphaKey = (M_AnySecretUnlocked()) ? 84 : 92;
 		MainMenu[secrets].status = (M_AnySecretUnlocked()) ? (IT_STRING | IT_CALL) : (IT_DISABLED);
 
 		currentMenu = &MainDef;
@@ -3537,6 +3527,7 @@ void M_StartControlPanel(void)
 
 void M_EndModeAttackRun(void)
 {
+	G_ClearModeAttackRetryFlag();
 	M_ModeAttackEndGame(0);
 }
 
@@ -9245,10 +9236,7 @@ void M_DrawTimeAttackMenu(void)
 
 		V_DrawString(104-72, 73+lsheadingheight/2, V_YELLOWMAP, "RINGS:");
 
-		if (!mainrecords[cv_nextmap.value-1] || !mainrecords[cv_nextmap.value-1]->gotperfect)
-			V_DrawRightAlignedString(104+64, 73+lsheadingheight/2, V_ALLOWLOWERCASE, beststr);
-		else
-			V_DrawRightAlignedString(104+64, 73+lsheadingheight/2, V_ALLOWLOWERCASE|V_YELLOWMAP, beststr);
+		V_DrawRightAlignedString(104+64, 73+lsheadingheight/2, V_ALLOWLOWERCASE|((mapvisited[cv_nextmap.value-1] & MV_PERFECT) ? V_YELLOWMAP : 0), beststr);
 
 		V_DrawRightAlignedString(104+72, 83+lsheadingheight/2, V_ALLOWLOWERCASE, reqrings);
 	}
@@ -10910,7 +10898,7 @@ static void M_EraseData(INT32 choice)
 	if (choice == 0)
 		eschoice = M_GetText("Record Attack data");
 	else if (choice == 1)
-		eschoice = M_GetText("Secrets data");
+		eschoice = M_GetText("Extras data");
 	else
 		eschoice = M_GetText("ALL game data");
 
diff --git a/src/m_menu.h b/src/m_menu.h
index ec7915cc29ee070689fe800787658cfc097c1ecd..ce7198d7545e2462f894e76e1fe30aeced128269 100644
--- a/src/m_menu.h
+++ b/src/m_menu.h
@@ -99,7 +99,7 @@ typedef enum
 	MN_OP_SCREENSHOTS,
 	MN_OP_ERASEDATA,
 
-	// Secrets
+	// Extras
 	MN_SR_MAIN,
 	MN_SR_PANDORA,
 	MN_SR_LEVELSELECT,
diff --git a/src/p_enemy.c b/src/p_enemy.c
index ea09533dfdbaf1432c2643460f44f46e1292fd4b..26d079cb418c5eb5c4f59376fafc517872dff45f 100644
--- a/src/p_enemy.c
+++ b/src/p_enemy.c
@@ -14534,6 +14534,9 @@ void A_RolloutRock(mobj_t *actor)
 
 	actor->friction = FRACUNIT; // turns out riding on solids sucks, so let's just make it easier on ourselves
 
+	if (actor->eflags & MFE_JUSTHITFLOOR)
+		S_StartSound(actor, actor->info->painsound);
+
 	if (actor->threshold)
 		actor->threshold--;
 
diff --git a/src/p_map.c b/src/p_map.c
index 28c5ac9553012c60cf8c966548d2569aa7a75719..753ce9eb5f299f823740c928b34d9405e07a052f 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -1032,7 +1032,8 @@ static boolean PIT_CheckThing(mobj_t *thing)
 			P_SetPlayerMobjState(tmthing, S_PLAY_WALK);
 			tmthing->player->powers[pw_carry] = CR_ROLLOUT;
 			P_SetTarget(&tmthing->tracer, thing);
-			P_SetObjectMomZ(thing, tmthing->momz, true);
+			if (!P_IsObjectOnGround(thing))
+				thing->momz += tmthing->momz;
 			return true;
 		}
 	}
@@ -1063,6 +1064,7 @@ static boolean PIT_CheckThing(mobj_t *thing)
 			thing->momy = tmthing->momy;
 			tmthing->momx = tempmomx;
 			tmthing->momy = tempmomy;
+			S_StartSound(thing, thing->info->painsound);
 		}
 	}
 
@@ -3469,7 +3471,7 @@ isblocking:
 		}
 
 		// see about climbing on the wall
-		if (!(checkline->flags & ML_NOCLIMB))
+		if (!(checkline->flags & ML_NOCLIMB) && checkline->special != HORIZONSPECIAL)
 		{
 			boolean canclimb;
 			angle_t climbangle, climbline;
diff --git a/src/p_mobj.c b/src/p_mobj.c
index cbd6c3c7fb8aae5a7fc1db6ca0e098d7e3125be6..f2dd1a734f07a77b0180a2c0e2d610d49798d9a7 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -4538,23 +4538,29 @@ static void P_Boss3Thinker(mobj_t *mobj)
 	}
 	else if (mobj->movecount) // Firing mode
 	{
-		// look for a new target
-		P_BossTargetPlayer(mobj, false);
-
-		if (!mobj->target || !mobj->target->player)
-			return;
-
-		// Always face your target.
-		A_FaceTarget(mobj);
-
 		// Check if the attack animation is running. If not, play it.
 		if (mobj->state < &states[mobj->info->missilestate] || mobj->state > &states[mobj->info->raisestate])
 		{
+			// look for a new target
+			P_BossTargetPlayer(mobj, true);
+
+			if (!mobj->target || !mobj->target->player)
+				return;
+
 			if (mobj->health <= mobj->info->damage) // pinch phase
 				mobj->movecount--; // limited number of shots before diving again
 			if (mobj->movecount)
 				P_SetMobjState(mobj, mobj->info->missilestate+1);
 		}
+		else if (mobj->target && mobj->target->player)
+		{
+			angle_t diff = R_PointToAngle2(mobj->x, mobj->y, mobj->target->x, mobj->target->y) - mobj->angle;
+			if (diff > ANGLE_180)
+				diff = InvAngle(InvAngle(diff)/4);
+			else
+				diff /= 4;
+			mobj->angle += diff;
+		}
 	}
 	else if (mobj->threshold >= 0) // Traveling mode
 	{
@@ -4669,13 +4675,10 @@ static void P_Boss3Thinker(mobj_t *mobj)
 				S_StartSound(mobj, shock->info->seesound);
 
 				// look for a new target
-				P_BossTargetPlayer(mobj, false);
+				P_BossTargetPlayer(mobj, true);
 
 				if (mobj->target && mobj->target->player)
-				{
-					A_FaceTarget(mobj);
 					P_SetMobjState(mobj, mobj->info->missilestate);
-				}
 			}
 			else if (mobj->flags2 & (MF2_STRONGBOX|MF2_CLASSICPUSH)) // just hit the bottom of your tube
 			{
diff --git a/src/p_setup.c b/src/p_setup.c
index e87a088d8c45d743d5b88ae07c6c0c276df5bad6..5c792c73cec89ae2c928cf2eddc75eded1bb072d 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -2725,7 +2725,7 @@ boolean P_SetupLevel(boolean skipprecip)
 			S_FadeOutStopMusic(MUSICRATE/4); //FixedMul(FixedDiv(F_GetWipeLength(wipedefs[wipe_speclevel_towhite])*NEWTICRATERATIO, NEWTICRATE), MUSICRATE)
 
 		F_WipeStartScreen();
-		wipestyleflags |= WSF_FADEOUT|WSF_TOWHITE;
+		wipestyleflags |= (WSF_FADEOUT|WSF_TOWHITE);
 
 #ifdef HWRENDER
 		// uh..........
@@ -2757,6 +2757,13 @@ boolean P_SetupLevel(boolean skipprecip)
 		ranspecialwipe = 1;
 	}
 
+	if (G_GetModeAttackRetryFlag())
+	{
+		if (modeattacking)
+			wipestyleflags |= (WSF_FADEOUT|WSF_TOWHITE);
+		G_ClearModeAttackRetryFlag();
+	}
+
 	// Make sure all sounds are stopped before Z_FreeTags.
 	S_StopSounds();
 	S_ClearSfx();
@@ -2809,7 +2816,7 @@ boolean P_SetupLevel(boolean skipprecip)
 			snprintf(tx, 63, "%s%s%s",
 				mapheaderinfo[gamemap-1]->lvlttl,
 				(mapheaderinfo[gamemap-1]->levelflags & LF_NOZONE) ? "" : " Zone",
-				(mapheaderinfo[gamemap-1]->actnum > 0) ? va("%d",mapheaderinfo[gamemap-1]->actnum) : "");
+				(mapheaderinfo[gamemap-1]->actnum > 0) ? va(" %d",mapheaderinfo[gamemap-1]->actnum) : "");
 			V_DrawSmallString(1, 195, V_ALLOWLOWERCASE|V_TRANSLUCENT, tx);
 			I_UpdateNoVsync();
 		}
diff --git a/src/p_spec.c b/src/p_spec.c
index f1a697399c547d13094411aa9956a81d556e7439..f814e89c66e9a0892e2b94fe6ea680997442985e 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -4439,59 +4439,55 @@ void P_ProcessSpecialSector(player_t *player, sector_t *sector, sector_t *rovers
 		case 3: // Linedef executor requires all players present
 			/// \todo check continues for proper splitscreen support?
 			for (i = 0; i < MAXPLAYERS; i++)
-				if (playeringame[i] && !players[i].bot && players[i].mo && (gametype != GT_COOP || players[i].lives > 0))
+			{
+				if (!playeringame[i])
+					continue;
+				if (!players[i].mo)
+					continue;
+				if (players[i].spectator)
+					continue;
+				if (players[i].bot)
+					continue;
+				if (gametype == GT_COOP && players[i].lives <= 0)
+					continue;
+				if (roversector)
 				{
-					if (roversector)
+					if (sector->flags & SF_TRIGGERSPECIAL_TOUCH)
 					{
-						if (players[i].mo->subsector->sector == roversector)
-							;
-						else if (sector->flags & SF_TRIGGERSPECIAL_TOUCH)
+						msecnode_t *node;
+						for (node = players[i].mo->touching_sectorlist; node; node = node->m_sectorlist_next)
 						{
-							boolean insector = false;
-							msecnode_t *node;
-							for (node = players[i].mo->touching_sectorlist; node; node = node->m_sectorlist_next)
-							{
-								if (node->m_sector == roversector)
-								{
-									insector = true;
-									break;
-								}
-							}
-							if (!insector)
-								goto DoneSection2;
+							if (P_ThingIsOnThe3DFloor(players[i].mo, sector, node->m_sector))
+								break;
 						}
-						else
-							goto DoneSection2;
-
-						if (!P_ThingIsOnThe3DFloor(players[i].mo, sector, roversector))
+						if (!node)
 							goto DoneSection2;
 					}
-					else
+					else if (players[i].mo->subsector && !P_ThingIsOnThe3DFloor(players[i].mo, sector, players[i].mo->subsector->sector)) // this function handles basically everything for us lmao
+						goto DoneSection2;
+				}
+				else
+				{
+					if (players[i].mo->subsector->sector == sector)
+						;
+					else if (sector->flags & SF_TRIGGERSPECIAL_TOUCH)
 					{
-						if (players[i].mo->subsector->sector == sector)
-							;
-						else if (sector->flags & SF_TRIGGERSPECIAL_TOUCH)
+						msecnode_t *node;
+						for (node = players[i].mo->touching_sectorlist; node; node = node->m_sectorlist_next)
 						{
-							boolean insector = false;
-							msecnode_t *node;
-							for (node = players[i].mo->touching_sectorlist; node; node = node->m_sectorlist_next)
-							{
-								if (node->m_sector == sector)
-								{
-									insector = true;
-									break;
-								}
-							}
-							if (!insector)
-								goto DoneSection2;
+							if (node->m_sector == sector)
+								break;
 						}
-						else
-							goto DoneSection2;
-
-						if (special == 3 && !P_MobjReadyToTrigger(players[i].mo, sector))
+						if (!node)
 							goto DoneSection2;
 					}
+					else
+						goto DoneSection2;
+
+					if (special == 3 && !P_MobjReadyToTrigger(players[i].mo, sector))
+						goto DoneSection2;
 				}
+			}
 			/* FALLTHRU */
 		case 4: // Linedef executor that doesn't require touching floor
 		case 5: // Linedef executor
diff --git a/src/p_user.c b/src/p_user.c
index 080d51e5371c97ff923ea379c26e9f222501f6cc..0838ff80e8dd7e1850b6c689dafea5d70859e167 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -2359,11 +2359,23 @@ boolean P_PlayerHitFloor(player_t *player, boolean dorollstuff)
 				;
 			else if (player->panim != PA_IDLE && player->panim != PA_WALK && player->panim != PA_RUN && player->panim != PA_DASH)
 			{
+				fixed_t runspd = FixedMul(player->runspeed, player->mo->scale);
+
+				// See comments in P_MovePlayer for explanation of changes.
+
+				if (player->powers[pw_super])
+					runspd = FixedMul(runspd, 5*FRACUNIT/3);
+
+				runspd = FixedMul(runspd, player->mo->movefactor);
+
+				if (maptol & TOL_2D)
+					runspd = FixedMul(runspd, 2*FRACUNIT/3);
+
 				if (player->cmomx || player->cmomy)
 				{
 					if (player->charflags & SF_DASHMODE && player->dashmode >= DASHMODE_THRESHOLD && player->panim != PA_DASH)
 						P_SetPlayerMobjState(player->mo, S_PLAY_DASH);
-					else if (player->speed >= FixedMul(player->runspeed, player->mo->scale)
+					else if (player->speed >= runspd
 					&& (player->panim != PA_RUN || player->mo->state-states == S_PLAY_FLOAT_RUN))
 						P_SetPlayerMobjState(player->mo, S_PLAY_RUN);
 					else if ((player->rmomx || player->rmomy)
@@ -2376,7 +2388,7 @@ boolean P_PlayerHitFloor(player_t *player, boolean dorollstuff)
 				{
 					if (player->charflags & SF_DASHMODE && player->dashmode >= DASHMODE_THRESHOLD && player->panim != PA_DASH)
 						P_SetPlayerMobjState(player->mo, S_PLAY_DASH);
-					else if (player->speed >= FixedMul(player->runspeed, player->mo->scale)
+					else if (player->speed >= runspd
 					&& (player->panim != PA_RUN || player->mo->state-states == S_PLAY_FLOAT_RUN))
 						P_SetPlayerMobjState(player->mo, S_PLAY_RUN);
 					else if ((player->mo->momx || player->mo->momy)
@@ -4429,6 +4441,8 @@ void P_DoJump(player_t *player, boolean soundandstate)
 			player->mo->momz = 9*FRACUNIT;
 			if (P_MobjFlip(player->mo->tracer)*player->mo->tracer->momz > 0)
 				player->mo->momz += player->mo->tracer->momz;
+			if (!P_IsObjectOnGround(player->mo->tracer))
+				P_SetObjectMomZ(player->mo->tracer, -9*FRACUNIT, true);
 			player->powers[pw_carry] = CR_NONE;
 			player->mo->tracer->flags |= MF_PUSHABLE;
 			P_SetTarget(&player->mo->tracer->tracer, NULL);
@@ -7741,9 +7755,12 @@ void P_ElementalFire(player_t *player, boolean cropcircle)
 	I_Assert(!P_MobjWasRemoved(player->mo));
 
 	if (player->mo->eflags & MFE_VERTICALFLIP)
-		ground = player->mo->ceilingz - FixedMul(mobjinfo[MT_SPINFIRE].height - 1, player->mo->scale);
+		ground = player->mo->ceilingz - FixedMul(mobjinfo[MT_SPINFIRE].height, player->mo->scale);
 	else
-		ground = player->mo->floorz + 1;
+		ground = player->mo->floorz;
+
+	if (cropcircle)
+		ground += P_MobjFlip(player->mo);
 
 	if (cropcircle)
 	{
@@ -7922,6 +7939,11 @@ static void P_MovePlayer(player_t *player)
 	cmd = &player->cmd;
 	runspd = FixedMul(player->runspeed, player->mo->scale);
 
+	// This was done in Sonic 3 & Knuckles, but has been missed in Sonic Mania and the Taxman/Stealth mobile remakes. Thanks to NeoHazard for his 2017 blogpost on the matter, because this oversight otherwise almost made it all the way to 2.2's release.
+	//https://s3unlocked.blogspot.com/2017/12/over-threshold.html
+	if (player->powers[pw_super])
+		runspd = FixedMul(runspd, 5*FRACUNIT/3);
+
 	// Let's have some movement speed fun on low-friction surfaces, JUST for players... (high friction surfaces shouldn't have any adjustment, since the acceleration in this game is super high and that ends up cheesing high-friction surfaces.)
 	runspd = FixedMul(runspd, player->mo->movefactor);
 
@@ -9630,8 +9652,15 @@ 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;
+	if (player->exiting)
+	{
+		if (mo->target && mo->target->type == MT_SIGN && mo->target->spawnpoint)
+			sign = mo->target;
+		else if ((player->powers[pw_carry] == CR_NIGHTSMODE)
+		&& !(player->mo->state >= &states[S_PLAY_NIGHTS_TRANS1]
+		&& player->mo->state <= &states[S_PLAY_NIGHTS_TRANS6]))
+			return true;
+	}
 
 	cameranoclip = (player->powers[pw_carry] == CR_NIGHTSMODE || player->pflags & PF_NOCLIP) || (mo->flags & (MF_NOCLIP|MF_NOCLIPHEIGHT)); // Noclipping player camera noclips too!!
 
@@ -10162,17 +10191,6 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 	if (!camstill && !resetcalled && !paused)
 		thiscam->angle = R_PointToAngle2(thiscam->x, thiscam->y, viewpointx, viewpointy);
 
-	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))
 		thiscam->angle = angle;
@@ -10216,9 +10234,9 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 	dist = FixedHypot(f1, f2);
 
 	if (mo->eflags & MFE_VERTICALFLIP)
-		angle = R_PointToAngle2(0, thiscam->z + thiscam->height, dist, mo->z + mo->height - P_GetPlayerHeight(player));
+		angle = R_PointToAngle2(0, thiscam->z + thiscam->height, dist, (sign ? sign->ceilingz : mo->z + mo->height) - P_GetPlayerHeight(player));
 	else
-		angle = R_PointToAngle2(0, thiscam->z, dist, mo->z + P_GetPlayerHeight(player));
+		angle = R_PointToAngle2(0, thiscam->z, dist, (sign ? sign->floorz : mo->z) + P_GetPlayerHeight(player));
 	if (player->playerstate != PST_DEAD)
 		angle += (focusaiming < ANGLE_180 ? focusaiming/2 : InvAngle(InvAngle(focusaiming)/2)); // overcomplicated version of '((signed)focusaiming)/2;'
 
diff --git a/src/r_defs.h b/src/r_defs.h
index 4d41f4f706c491b985af8eee2ba0f5087a5cfd71..6d0b10b706d27897c7445c92766ad7a4f074f9e2 100644
--- a/src/r_defs.h
+++ b/src/r_defs.h
@@ -410,6 +410,8 @@ typedef enum
 	ST_NEGATIVE
 } slopetype_t;
 
+#define HORIZONSPECIAL 41
+
 typedef struct line_s
 {
 	// Vertices, from v1 to v2.
diff --git a/src/r_plane.c b/src/r_plane.c
index a3075c9d845ba4c3ddd74b3f1d9101cc74920d13..f21f49101266d1bd044d2eceedbfb31f19bcdf61 100644
--- a/src/r_plane.c
+++ b/src/r_plane.c
@@ -1007,6 +1007,8 @@ void R_DrawSinglePlane(visplane_t *pl)
 			R_CheckFlatLength(W_LumpLength(levelflat->u.flat.lumpnum));
 			// Raw flats always have dimensions that are powers-of-two numbers.
 			ds_powersoftwo = true;
+			if (spanfunc == basespanfunc)
+				spanfunc = mmxspanfunc;
 			break;
 		default:
 			switch (type)
diff --git a/src/r_segs.c b/src/r_segs.c
index 6eb81ce7a4a64a78a3cda0765bd64149fc0eb280..ee62bfc739faf207ade72cda308433522621592a 100644
--- a/src/r_segs.c
+++ b/src/r_segs.c
@@ -2694,7 +2694,7 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 	worldbottomslope >>= 4;
 #endif
 
-	if (linedef->special == 41) { // HORIZON LINES
+	if (linedef->special == HORIZONSPECIAL) { // HORIZON LINES
 		topstep = bottomstep = 0;
 		topfrac = bottomfrac = (centeryfrac>>4);
 		topfrac++; // Prevent 1px HOM
@@ -2825,7 +2825,7 @@ void R_StoreWallRange(INT32 start, INT32 stop)
 #ifdef ESLOPE
 			ffloor[i].f_pos_slope >>= 4;
 #endif
-			if (linedef->special == 41) // Horizon lines extend FOFs in contact with them too.
+			if (linedef->special == HORIZONSPECIAL) // Horizon lines extend FOFs in contact with them too.
 			{
 				ffloor[i].f_step = 0;
 				ffloor[i].f_frac = (centeryfrac>>4);
diff --git a/src/s_sound.c b/src/s_sound.c
index ef673c9afe87ee6f557951f48ec25ce0565c241a..8e4e7715d0d37dfcca1b1c774df74c446384cb36 100644
--- a/src/s_sound.c
+++ b/src/s_sound.c
@@ -1685,7 +1685,7 @@ boolean S_PrepareSoundTest(void)
 		soundtestdefs[pos++] = def;
 		if (def->soundtestcond > 0 && !(mapvisited[def->soundtestcond-1] & MV_BEATEN))
 			continue;
-		if (def->soundtestcond < 0 && !M_Achieved(1-def->soundtestcond))
+		if (def->soundtestcond < 0 && !M_Achieved(-1-def->soundtestcond))
 			continue;
 		def->allowed = true;
 	}
diff --git a/src/screen.c b/src/screen.c
index a1f6b7f55dfeee62a3ab29d1e8f39cffb03faffc..5005118b6c2a1de28d37d2f447488e8e3e362559 100644
--- a/src/screen.c
+++ b/src/screen.c
@@ -421,9 +421,9 @@ void SCR_DisplayTicRate(void)
 	else if (totaltics == TICRATE) ticcntcolor = V_GREENMAP;
 
 	V_DrawString(vid.width-(72*vid.dupx), h,
-		V_YELLOWMAP|V_NOSCALESTART|V_HUDTRANS, "FPS:");
+		V_YELLOWMAP|V_NOSCALESTART|V_USERHUDTRANS, "FPS:");
 	V_DrawString(vid.width-(40*vid.dupx), h,
-		ticcntcolor|V_NOSCALESTART|V_HUDTRANS, va("%02d/%02u", totaltics, TICRATE));
+		ticcntcolor|V_NOSCALESTART|V_USERHUDTRANS, va("%02d/%02u", totaltics, TICRATE));
 
 	lasttic = ontic;
 }
diff --git a/src/sdl/hwsym_sdl.c b/src/sdl/hwsym_sdl.c
index 82a430ee68e7b5314b93843cfbfd7f734c6423e6..5f040023a8032e1086ebec70f8a28891a0b741e1 100644
--- a/src/sdl/hwsym_sdl.c
+++ b/src/sdl/hwsym_sdl.c
@@ -97,7 +97,6 @@ void *hwSym(const char *funcName,void *handle)
 	GETFUNC(StartScreenWipe);
 	GETFUNC(EndScreenWipe);
 	GETFUNC(DoScreenWipe);
-	GETFUNC(DoScreenWipeLevel);
 	GETFUNC(DrawIntermissionBG);
 	GETFUNC(MakeScreenTexture);
 	GETFUNC(MakeScreenFinalTexture);
diff --git a/src/sdl/i_video.c b/src/sdl/i_video.c
index 5f162b00dfb525cf02abc97579964c4b3ca48c8b..95ddab3cc51615079a1a3f553ab9ac66e0baedc3 100644
--- a/src/sdl/i_video.c
+++ b/src/sdl/i_video.c
@@ -1176,14 +1176,11 @@ void I_FinishUpdate(void)
 	if (cv_closedcaptioning.value)
 		SCR_ClosedCaptions();
 
-	if (st_overlay)
-	{
-		if (cv_ticrate.value)
-			SCR_DisplayTicRate();
+	if (cv_ticrate.value)
+		SCR_DisplayTicRate();
 
-		if (cv_showping.value && netgame && consoleplayer != serverplayer)
-			SCR_DisplayLocalPing();
-	}
+	if (cv_showping.value && netgame && consoleplayer != serverplayer)
+		SCR_DisplayLocalPing();
 
 	if (rendermode == render_soft && screens[0])
 	{
@@ -1666,7 +1663,6 @@ void I_StartupGraphics(void)
 		HWD.pfnStartScreenWipe  = hwSym("StartScreenWipe",NULL);
 		HWD.pfnEndScreenWipe    = hwSym("EndScreenWipe",NULL);
 		HWD.pfnDoScreenWipe     = hwSym("DoScreenWipe",NULL);
-		HWD.pfnDoScreenWipeLevel= hwSym("DoScreenWipeLevel",NULL);
 		HWD.pfnDrawIntermissionBG=hwSym("DrawIntermissionBG",NULL);
 		HWD.pfnMakeScreenTexture= hwSym("MakeScreenTexture",NULL);
 		HWD.pfnMakeScreenFinalTexture=hwSym("MakeScreenFinalTexture",NULL);
diff --git a/src/st_stuff.c b/src/st_stuff.c
index 42a89aab6b969e991ffaed75f271591c2b34c577..6f75a25e7ed216876fb3551a5803a1833a45e4c1 100644
--- a/src/st_stuff.c
+++ b/src/st_stuff.c
@@ -198,7 +198,7 @@ void ST_Ticker(boolean run)
 
 // 0 is default, any others are special palettes.
 INT32 st_palette = 0;
-INT32 st_translucency = 0;
+INT32 st_translucency = 10;
 
 void ST_doPaletteStuff(void)
 {
@@ -1294,10 +1294,8 @@ void ST_drawTitleCard(void)
 		return;
 #endif
 
-#ifndef LEVELWIPES
 	if ((lt_ticker-lt_lasttic) > 1)
 		lt_ticker = lt_lasttic+1;
-#endif
 
 	ST_cacheLevelTitle();
 	actpat = lt_patches[0];
@@ -1351,9 +1349,7 @@ luahook:
 void ST_preLevelTitleCardDrawer(tic_t ticker, boolean update)
 {
 	V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, levelfadecol);
-#ifndef LEVELWIPES
 	if (ticker < PRELEVELTIME-1)
-#endif
 		ST_drawWipeTitleCard();
 
 	I_OsPolling();
diff --git a/src/v_video.c b/src/v_video.c
index 58244531e401490f4bd8ae5b6930921b7a8eb23b..e9237563072df7f8917dd59b446dee57cdacae97 100644
--- a/src/v_video.c
+++ b/src/v_video.c
@@ -20,6 +20,7 @@
 #include "v_video.h"
 #include "st_stuff.h"
 #include "hu_stuff.h"
+#include "f_finale.h"
 #include "r_draw.h"
 #include "console.h"
 
@@ -1861,7 +1862,9 @@ void V_DrawFadeScreen(UINT16 color, UINT8 strength)
 
 	{
 		const UINT8 *fadetable = ((color & 0xFF00) // Color is not palette index?
-		? ((UINT8 *)colormaps + strength*256) // Do COLORMAP fade.
+		? ((UINT8 *)(((color & 0x0F00) == 0x0A00) ? fadecolormap // Do fadecolormap fade.
+		: (((color & 0x0F00) == 0x0B00) ? fadecolormap + (256 * FADECOLORMAPROWS) // Do white fadecolormap fade.
+		: colormaps)) + strength*256) // Do COLORMAP fade.
 		: ((UINT8 *)transtables + ((9-strength)<<FF_TRANSSHIFT) + color*256)); // Else, do TRANSMAP** fade.
 		const UINT8 *deststop = screens[0] + vid.rowbytes * vid.height;
 		UINT8 *buf = screens[0];
diff --git a/src/v_video.h b/src/v_video.h
index cd32ac5f89ed5879f01a0e44adfae872e1f2c908..e3dbb75dc0617c79bec46b43aecadc462c6ad494 100644
--- a/src/v_video.h
+++ b/src/v_video.h
@@ -106,6 +106,10 @@ extern RGBA_t *pMasterPalette;
 #define V_HUDTRANSHALF       0x000D0000
 #define V_HUDTRANS           0x000E0000 // draw the hud translucent
 #define V_HUDTRANSDOUBLE     0x000F0000
+// Macros follow
+#define V_USERHUDTRANSHALF   ((10-(cv_translucenthud.value/2))<<V_ALPHASHIFT)
+#define V_USERHUDTRANS       ((10-cv_translucenthud.value)<<V_ALPHASHIFT)
+#define V_USERHUDTRANSDOUBLE ((10-min(cv_translucenthud.value*2, 10))<<V_ALPHASHIFT)
 
 #define V_AUTOFADEOUT        0x00100000 // used by CECHOs, automatic fade out when almost over
 #define V_RETURN8            0x00200000 // 8 pixel return instead of 12
diff --git a/src/win32/win_dll.c b/src/win32/win_dll.c
index 1311b68704929fe8063e0c9a3e9f20e3c42e0408..5378bb52f645bc10abc0c58dcf27d38f15d9b85a 100644
--- a/src/win32/win_dll.c
+++ b/src/win32/win_dll.c
@@ -121,7 +121,6 @@ static loadfunc_t hwdFuncTable[] = {
 	{"StartScreenWipe@0",   &hwdriver.pfnStartScreenWipe},
 	{"EndScreenWipe@0",     &hwdriver.pfnEndScreenWipe},
 	{"DoScreenWipe@4",      &hwdriver.pfnDoScreenWipe},
-	{"DoScreenWipeLevel@0", &hwdriver.pfnDoScreenWipeLevel},
 	{"DrawIntermissionBG@0",&hwdriver.pfnDrawIntermissionBG},
 	{"MakeScreenTexture@0", &hwdriver.pfnMakeScreenTexture},
 	{"MakeScreenFinalTexture@0", &hwdriver.pfnMakeScreenFinalTexture},
@@ -153,7 +152,6 @@ static loadfunc_t hwdFuncTable[] = {
 	{"StartScreenWipe",     &hwdriver.pfnStartScreenWipe},
 	{"EndScreenWipe",       &hwdriver.pfnEndScreenWipe},
 	{"DoScreenWipe",        &hwdriver.pfnDoScreenWipe},
-	{"DoScreenWipeLevel",   &hwdriver.pfnDoScreenWipeLevel},
 	{"DrawIntermissionBG",  &hwdriver.pfnDrawIntermissionBG},
 	{"MakeScreenTexture",   &hwdriver.pfnMakeScreenTexture},
 	{"MakeScreenFinalTexture", &hwdriver.pfnMakeScreenFinalTexture},
diff --git a/src/y_inter.c b/src/y_inter.c
index 48d08a02e59532ebb1a3c04a489122a6f328bb41..32548d26369e7c7afe1509ef5b7213045c9dbaaa 100644
--- a/src/y_inter.c
+++ b/src/y_inter.c
@@ -166,13 +166,11 @@ 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);
 static void Y_CalculateTimeRaceWinners(void);
 static void Y_CalculateMatchWinners(void);
-static void Y_FollowIntermission(void);
 static void Y_UnloadData(void);
 
 // Stuff copy+pasted from st_stuff.c
@@ -293,7 +291,7 @@ static void Y_RescaleScreenBuffer(void)
 //
 // Free all related memory.
 //
-static void Y_CleanupScreenBuffer(void)
+void Y_CleanupScreenBuffer(void)
 {
 	// Who knows?
 	if (y_buffer == NULL)
@@ -819,7 +817,7 @@ void Y_IntermissionDrawer(void)
 			}
 		}
 	}
-	else if (intertype == int_classicrace)
+	else if (intertype == int_comp)
 	{
 		INT32 x = 4;
 		INT32 y = 48;
@@ -953,7 +951,7 @@ void Y_Ticker(void)
 		if (!--timer)
 		{
 			Y_EndIntermission();
-			Y_FollowIntermission();
+			G_AfterIntermission();
 			return;
 		}
 	}
@@ -961,7 +959,7 @@ void Y_Ticker(void)
 	else if (intertic == endtic)
 	{
 		Y_EndIntermission();
-		Y_FollowIntermission();
+		G_AfterIntermission();
 		return;
 	}
 
@@ -1145,7 +1143,7 @@ void Y_Ticker(void)
 		if (data.match.numplayers != D_NumPlayers())
 			Y_CalculateMatchWinners();
 	}
-	else if (intertype == int_race || intertype == int_classicrace) // race
+	else if (intertype == int_race || intertype == int_comp) // race
 	{
 		if (!intertic) // first time only
 			S_ChangeMusicInternal("_inter", true); // loop it
@@ -1154,96 +1152,6 @@ void Y_Ticker(void)
 	}
 }
 
-//
-// Y_UpdateRecordReplays
-//
-// Update replay files/data, etc. for Record Attack
-// See G_SetNightsRecords for NiGHTS Attack.
-//
-static void Y_UpdateRecordReplays(void)
-{
-	const size_t glen = strlen(srb2home)+1+strlen("replay")+1+strlen(timeattackfolder)+1+strlen("MAPXX")+1;
-	char *gpath;
-	char lastdemo[256], bestdemo[256];
-	UINT8 earnedEmblems;
-
-	// Record new best time
-	if (!mainrecords[gamemap-1])
-		G_AllocMainRecordData(gamemap-1);
-
-	if (players[consoleplayer].score > mainrecords[gamemap-1]->score)
-		mainrecords[gamemap-1]->score = players[consoleplayer].score;
-
-	if ((mainrecords[gamemap-1]->time == 0) || (players[consoleplayer].realtime < mainrecords[gamemap-1]->time))
-		mainrecords[gamemap-1]->time = players[consoleplayer].realtime;
-
-	if ((UINT16)(players[consoleplayer].rings) > mainrecords[gamemap-1]->rings)
-		mainrecords[gamemap-1]->rings = (UINT16)(players[consoleplayer].rings);
-
-	if (data.coop.gotperfbonus)
-		mainrecords[gamemap-1]->gotperfect = true;
-
-	// Save demo!
-	bestdemo[255] = '\0';
-	lastdemo[255] = '\0';
-	G_SetDemoTime(players[consoleplayer].realtime, players[consoleplayer].score, (UINT16)(players[consoleplayer].rings));
-	G_CheckDemoStatus();
-
-	I_mkdir(va("%s"PATHSEP"replay", srb2home), 0755);
-	I_mkdir(va("%s"PATHSEP"replay"PATHSEP"%s", srb2home, timeattackfolder), 0755);
-
-	if ((gpath = malloc(glen)) == NULL)
-		I_Error("Out of memory for replay filepath\n");
-
-	sprintf(gpath,"%s"PATHSEP"replay"PATHSEP"%s"PATHSEP"%s", srb2home, timeattackfolder, G_BuildMapName(gamemap));
-	snprintf(lastdemo, 255, "%s-%s-last.lmp", gpath, skins[cv_chooseskin.value-1].name);
-
-	if (FIL_FileExists(lastdemo))
-	{
-		UINT8 *buf;
-		size_t len = FIL_ReadFile(lastdemo, &buf);
-
-		snprintf(bestdemo, 255, "%s-%s-time-best.lmp", gpath, skins[cv_chooseskin.value-1].name);
-		if (!FIL_FileExists(bestdemo) || G_CmpDemoTime(bestdemo, lastdemo) & 1)
-		{ // Better time, save this demo.
-			if (FIL_FileExists(bestdemo))
-				remove(bestdemo);
-			FIL_WriteFile(bestdemo, buf, len);
-			CONS_Printf("\x83%s\x80 %s '%s'\n", M_GetText("NEW RECORD TIME!"), M_GetText("Saved replay as"), bestdemo);
-		}
-
-		snprintf(bestdemo, 255, "%s-%s-score-best.lmp", gpath, skins[cv_chooseskin.value-1].name);
-		if (!FIL_FileExists(bestdemo) || (G_CmpDemoTime(bestdemo, lastdemo) & (1<<1)))
-		{ // Better score, save this demo.
-			if (FIL_FileExists(bestdemo))
-				remove(bestdemo);
-			FIL_WriteFile(bestdemo, buf, len);
-			CONS_Printf("\x83%s\x80 %s '%s'\n", M_GetText("NEW HIGH SCORE!"), M_GetText("Saved replay as"), bestdemo);
-		}
-
-		snprintf(bestdemo, 255, "%s-%s-rings-best.lmp", gpath, skins[cv_chooseskin.value-1].name);
-		if (!FIL_FileExists(bestdemo) || (G_CmpDemoTime(bestdemo, lastdemo) & (1<<2)))
-		{ // Better rings, save this demo.
-			if (FIL_FileExists(bestdemo))
-				remove(bestdemo);
-			FIL_WriteFile(bestdemo, buf, len);
-			CONS_Printf("\x83%s\x80 %s '%s'\n", M_GetText("NEW MOST RINGS!"), M_GetText("Saved replay as"), bestdemo);
-		}
-
-		//CONS_Printf("%s '%s'\n", M_GetText("Saved replay as"), lastdemo);
-
-		Z_Free(buf);
-	}
-	free(gpath);
-
-	// Check emblems when level data is updated
-	if ((earnedEmblems = M_CheckLevelEmblems()))
-		CONS_Printf(M_GetText("\x82" "Earned %hu emblem%s for Record Attack records.\n"), (UINT16)earnedEmblems, earnedEmblems > 1 ? "s" : "");
-
-	// Update timeattack menu's replay availability.
-	Nextmap_OnChange();
-}
-
 //
 // Y_StartIntermission
 //
@@ -1252,7 +1160,6 @@ static void Y_UpdateRecordReplays(void)
 void Y_StartIntermission(void)
 {
 	INT32 i;
-	UINT8 completionEmblems = M_CompletionEmblems();
 
 	intertic = -1;
 
@@ -1265,10 +1172,7 @@ void Y_StartIntermission(void)
 	{
 		timer = 0;
 
-		if (G_IsSpecialStage(gamemap))
-			intertype = (maptol & TOL_NIGHTS) ? int_nightsspec : int_spec;
-		else
-			intertype = (maptol & TOL_NIGHTS) ? int_nights : int_coop;
+		intertype = (G_IsSpecialStage(gamemap)) ? int_spec : int_coop;
 	}
 	else
 	{
@@ -1283,14 +1187,7 @@ void Y_StartIntermission(void)
 		}
 
 		if (gametype == GT_COOP)
-		{
-			// Nights intermission is single player only
-			// Don't add it here
-			if (G_IsSpecialStage(gamemap))
-				intertype = int_spec;
-			else
-				intertype = int_coop;
-		}
+			intertype = (G_IsSpecialStage(gamemap)) ? int_spec : int_coop;
 		else if (gametype == GT_TEAMMATCH)
 			intertype = int_teammatch;
 		else if (gametype == GT_MATCH
@@ -1300,7 +1197,7 @@ void Y_StartIntermission(void)
 		else if (gametype == GT_RACE)
 			intertype = int_race;
 		else if (gametype == GT_COMPETITION)
-			intertype = int_classicrace;
+			intertype = int_comp;
 		else if (gametype == GT_CTF)
 			intertype = int_ctf;
 	}
@@ -1315,20 +1212,6 @@ void Y_StartIntermission(void)
 
 	switch (intertype)
 	{
-		case int_nights:
-			// Can't fail
-			G_SetNightsRecords();
-
-			// Check records
-			{
-				UINT8 earnedEmblems = M_CheckLevelEmblems();
-				if (earnedEmblems)
-					CONS_Printf(M_GetText("\x82" "Earned %hu emblem%s for NiGHTS records.\n"), (UINT16)earnedEmblems, earnedEmblems > 1 ? "s" : "");
-			}
-
-			// fall back into the coop intermission for now
-			intertype = int_coop;
-			/* FALLTHRU */
 		case int_coop: // coop or single player, normal level
 		{
 			// award time and ring bonuses
@@ -1337,24 +1220,6 @@ void Y_StartIntermission(void)
 			// setup time data
 			data.coop.tics = players[consoleplayer].realtime;
 
-			if ((!modifiedgame || savemoddata) && !multiplayer && !demoplayback)
-			{
-				// Update visitation flags
-				mapvisited[gamemap-1] |= MV_BEATEN;
-				if (ALL7EMERALDS(emeralds))
-					mapvisited[gamemap-1] |= MV_ALLEMERALDS;
-				if (ultimatemode)
-					mapvisited[gamemap-1] |= MV_ULTIMATE;
-				if (data.coop.gotperfbonus)
-					mapvisited[gamemap-1] |= MV_PERFECT;
-
-				if (modeattacking == ATTACKING_RECORD)
-					Y_UpdateRecordReplays();
-
-				if (completionEmblems)
-					CONS_Printf(M_GetText("\x82" "Earned %hu emblem%s for level completion.\n"), (UINT16)completionEmblems, completionEmblems > 1 ? "s" : "");
-			}
-
 			for (i = 0; i < 4; ++i)
 				data.coop.bonuspatches[i] = W_CachePatchName(data.coop.bonuses[i].patch, PU_STATIC);
 			data.coop.ptotal = W_CachePatchName("YB_TOTAL", PU_STATIC);
@@ -1421,40 +1286,8 @@ void Y_StartIntermission(void)
 			break;
 		}
 
-		case int_nightsspec:
-			if (modeattacking && stagefailed)
-			{
-				// Nuh-uh.  Get out of here.
-				Y_EndIntermission();
-				Y_FollowIntermission();
-				break;
-			}
-			if (!stagefailed)
-				G_SetNightsRecords();
-
-			// Check records
-			{
-				UINT8 earnedEmblems = M_CheckLevelEmblems();
-				if (earnedEmblems)
-					CONS_Printf(M_GetText("\x82" "Earned %hu emblem%s for NiGHTS records.\n"), (UINT16)earnedEmblems, earnedEmblems > 1 ? "s" : "");
-			}
-
-			// fall back into the special stage intermission for now
-			intertype = int_spec;
-			/* FALLTHRU */
 		case int_spec: // coop or single player, special stage
 		{
-			// Update visitation flags?
-			if ((!modifiedgame || savemoddata) && !multiplayer && !demoplayback)
-			{
-				if (!stagefailed)
-					mapvisited[gamemap-1] |= MV_BEATEN;
-
-				// all emeralds/ultimate/perfect emblems won't be possible in ss, oh well?
-				if (completionEmblems)
-					CONS_Printf(M_GetText("\x82" "Earned %hu emblem%s for level completion.\n"), (UINT16)completionEmblems, completionEmblems > 1 ? "s" : "");
-			}
-
 			// give out ring bonuses
 			Y_AwardSpecialStageBonus();
 
@@ -1640,7 +1473,7 @@ void Y_StartIntermission(void)
 			break;
 		}
 
-		case int_classicrace: // classic (full race)
+		case int_comp: // classic (full race)
 		{
 			// find out who won
 			Y_CalculateCompetitionWinners();
@@ -2195,23 +2028,6 @@ void Y_EndIntermission(void)
 	usebuffer = false;
 }
 
-//
-// Y_FollowIntermission
-//
-static void Y_FollowIntermission(void)
-{
-	if (modeattacking)
-	{
-		M_EndModeAttackRun();
-		return;
-	}
-
-	// This handles whether to play a post-level cutscene, end the game,
-	// or simply go to the next level.
-	// No need to duplicate the code here!
-	G_AfterIntermission();
-}
-
 #define UNLOAD(x) Z_ChangeTag(x, PU_CACHE); x = NULL
 
 //
@@ -2224,8 +2040,6 @@ static void Y_UnloadData(void)
 	if (rendermode != render_soft)
 		return;
 
-	Y_CleanupScreenBuffer();
-
 	// unload the background patches
 	UNLOAD(bgpatch);
 	UNLOAD(widebgpatch);
@@ -2261,7 +2075,7 @@ static void Y_UnloadData(void)
 			break;
 		default:
 			//without this default,
-			//int_none, int_tag, int_chaos, and int_classicrace
+			//int_none, int_tag, int_chaos, and int_comp
 			//are not handled
 			break;
 	}
diff --git a/src/y_inter.h b/src/y_inter.h
index ccb48dbd4c3ed5cd43273a3971724bb9b6c897b7..b47f3b157e8d3eb0b03ca408c0818aa873b97a81 100644
--- a/src/y_inter.h
+++ b/src/y_inter.h
@@ -16,6 +16,7 @@ void Y_Ticker(void);
 void Y_StartIntermission(void);
 void Y_EndIntermission(void);
 void Y_ConsiderScreenBuffer(void);
+void Y_CleanupScreenBuffer(void);
 
 typedef enum
 {
@@ -26,9 +27,7 @@ typedef enum
 //	int_tag,      // Tag
 	int_ctf,      // CTF
 	int_spec,     // Special Stage
-	int_nights,   // NiGHTS into Dreams
-	int_nightsspec,// NiGHTS special stage
 	int_race,     // Race
-	int_classicrace, // Competition
+	int_comp,     // Competition
 } intertype_t;
 extern intertype_t intertype;