diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index f45d048d670fc8d78b17a3dd4b751bde0b946d7a..a19c4a1a48770f1d6b73bbca3341d980d9ee82ff 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -1938,7 +1938,7 @@ static void SendAskInfo(INT32 node)
 	if (node != 0 && node != BROADCASTADDR &&
 			cv_rendezvousserver.string[0])
 	{
-		I_NetRequestHolePunch();
+		I_NetRequestHolePunch(node);
 	}
 
 	asktime = I_GetTime();
@@ -4337,6 +4337,11 @@ static void HandleConnect(SINT8 node)
 	// If a server filled out, then it'd overwrite the host and turn everyone into weird husks.....
 	// It's too much effort to legimately fix right now. Just prevent it from reaching that state.
 	UINT8 maxplayers = min((dedicated ? MAXPLAYERS-1 : MAXPLAYERS), cv_maxplayers.value);
+	UINT8 connectedplayers = 0;
+
+	for (UINT8 i = dedicated ? 1 : 0; i < MAXPLAYERS; i++)
+		if (playernode[i] != UINT8_MAX) // We use this to count players because it is affected by SV_AddWaitingPlayers when more than one client joins on the same tic, unlike playeringame and D_NumPlayers. UINT8_MAX denotes no node for that player
+			connectedplayers++;
 
 	if (bannednode && bannednode[node].banid != SIZE_MAX)
 	{
@@ -4394,7 +4399,7 @@ static void HandleConnect(SINT8 node)
 	{
 		SV_SendRefuse(node, M_GetText("The server is not accepting\njoins for the moment."));
 	}
-	else if (D_NumPlayers() >= maxplayers)
+	else if (connectedplayers >= maxplayers)
 	{
 		SV_SendRefuse(node, va(M_GetText("Maximum players reached: %d"), maxplayers));
 	}
@@ -4402,7 +4407,7 @@ static void HandleConnect(SINT8 node)
 	{
 		SV_SendRefuse(node, M_GetText("Too many players from\nthis node."));
 	}
-	else if (netgame && D_NumPlayers() + netbuffer->u.clientcfg.localplayers > maxplayers)
+	else if (netgame && connectedplayers + netbuffer->u.clientcfg.localplayers > maxplayers)
 	{
 		SV_SendRefuse(node, va(M_GetText("Number of local players\nwould exceed maximum: %d"), maxplayers));
 	}
diff --git a/src/d_net.c b/src/d_net.c
index ed54d692f1f83a60ff7baf448ddc295be63d892d..8fa218cde0f2876ed94c51002d3be0f849070fd3 100644
--- a/src/d_net.c
+++ b/src/d_net.c
@@ -75,7 +75,7 @@ boolean (*I_NetCanGet)(void) = NULL;
 void (*I_NetCloseSocket)(void) = NULL;
 void (*I_NetFreeNodenum)(INT32 nodenum) = NULL;
 SINT8 (*I_NetMakeNodewPort)(const char *address, const char* port) = NULL;
-void (*I_NetRequestHolePunch)(void) = NULL;
+void (*I_NetRequestHolePunch)(INT32 node) = NULL;
 void (*I_NetRegisterHolePunch)(void) = NULL;
 boolean (*I_NetOpenSocket)(void) = NULL;
 boolean (*I_Ban) (INT32 node) = NULL;
diff --git a/src/dehacked.c b/src/dehacked.c
index 6e21e787920a7c1c64242f1b0b4db2ee0a44a3c4..9bf5f43d92b384a20d83899cc0302d3b8fc828cd 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -3397,7 +3397,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, UINT16 wad)
 
 					if (i > 0 && i <= NUMMAPS)
 					{
-						if (mapheaderinfo[i])
+						if (mapheaderinfo[i-1])
 							G_SetGameModified(multiplayer, true); // only mark as a major mod if it replaces an already-existing mapheaderinfo
 						readlevelheader(f, i);
 					}
diff --git a/src/g_input.c b/src/g_input.c
index dafbeca31920f9be52a913b4c308ce300f20c87d..798164d528135cda002d7934b8d2f156039efe13 100644
--- a/src/g_input.c
+++ b/src/g_input.c
@@ -1276,6 +1276,9 @@ INT32 G_KeyStringtoNum(const char *keystr)
 {
 	UINT32 j;
 
+	if (!keystr[0])
+		return 0;
+
 	if (!keystr[1] && keystr[0] > ' ' && keystr[0] <= 'z')
 		return keystr[0];
 
diff --git a/src/hardware/hw_cache.c b/src/hardware/hw_cache.c
index eb9511492f354d3581c9b514d307c277ecec6574..eed592af06aa3ae7266855b6723272bbe154d70c 100644
--- a/src/hardware/hw_cache.c
+++ b/src/hardware/hw_cache.c
@@ -245,7 +245,7 @@ static void HWR_ResizeBlock(INT32 originalwidth, INT32 originalheight,
 		min = blockwidth;
 	}
 
-	for (k = 2048, j = 0; k > max; j++)
+	for (k = 2048, j = 0; k > max && j < 8; j++)
 		k>>=1;
 	grInfo->smallLodLog2 = gr_lods[j];
 	grInfo->largeLodLog2 = gr_lods[j];
diff --git a/src/hardware/hw_md2.c b/src/hardware/hw_md2.c
index 28abc52f242df453e0b222ec2004de7aadd2edb8..854dbfc8e76468df08e53c76bddeeefbd4862431 100644
--- a/src/hardware/hw_md2.c
+++ b/src/hardware/hw_md2.c
@@ -474,7 +474,7 @@ void HWR_InitMD2(void)
 	size_t i;
 	INT32 s;
 	FILE *f;
-	char name[18], filename[32];
+	char name[20], filename[32];
 	float scale, offset;
 
 	CONS_Printf("InitMD2()...\n");
@@ -561,7 +561,7 @@ md2found:
 void HWR_AddPlayerMD2(int skin) // For MD2's that were added after startup
 {
 	FILE *f;
-	char name[18], filename[32];
+	char name[20], filename[32];
 	float scale, offset;
 
 	if (nomd2s)
@@ -610,7 +610,7 @@ void HWR_AddSpriteMD2(size_t spritenum) // For MD2s that were added after startu
 	FILE *f;
 	// name[18] is used to check for names in the mdls.dat file that match with sprites or player skins
 	// sprite names are always 4 characters long, and names is for player skins can be up to 19 characters long
-	char name[18], filename[32];
+	char name[20], filename[32];
 	float scale, offset;
 
 	if (nomd2s)
@@ -660,18 +660,18 @@ spritemd2found:
 // 0.0722 to blue
 // (See this same define in k_kart.c!)
 #define SETBRIGHTNESS(brightness,r,g,b) \
-	brightness = (UINT8)(((1063*(UINT16)(r))/5000) + ((3576*(UINT16)(g))/5000) + ((361*(UINT16)(b))/5000))
+	brightness = (UINT8)(((1063*(UINT32)(r))/5000) + ((3576*(UINT32)(g))/5000) + ((361*(UINT32)(b))/5000))
 
 static void HWR_CreateBlendedTexture(GLPatch_t *gpatch, GLPatch_t *blendgpatch, GLMipmap_t *grmip, INT32 skinnum, skincolors_t color)
 {
 	UINT16 w = gpatch->width, h = gpatch->height;
 	UINT32 size = w*h;
 	RGBA_t *image, *blendimage, *cur, blendcolor;
-	UINT8 translation[16]; // First the color index
-	UINT8 cutoff[16]; // Brightness cutoff before using the next color
+	UINT8 translation[17]; // First the color index
+	UINT8 cutoff[17]; // Brightness cutoff before using the next color
 	UINT8 translen = 0;
 	UINT8 i;
-	UINT8 colorbrightnesses[16];
+	UINT8 colorbrightnesses[17];
 	UINT8 color_match_lookup[256]; // optimization attempt
 
 	blendcolor = V_GetColor(0); // initialize
@@ -741,6 +741,11 @@ static void HWR_CreateBlendedTexture(GLPatch_t *gpatch, GLPatch_t *blendgpatch,
 		translen++;
 	}
 
+	if (translen > 0)
+		translation[translen] = translation[translen-1]; // extended to accomodate secondi if firsti equal to translen-1
+	if (translen > 1)
+		cutoff[translen] = cutoff[translen-1] = 0; // as above
+
 	if (skinnum == TC_RAINBOW && translen > 0)
 	{
 		UINT16 b;
@@ -756,7 +761,7 @@ static void HWR_CreateBlendedTexture(GLPatch_t *gpatch, GLPatch_t *blendgpatch,
 		{
 			UINT16 brightdif = 256;
 
-			color_match_lookup[i] = 0;
+			color_match_lookup[b] = 0;
 			for (i = 0; i < translen; i++)
 			{
 				if (b > colorbrightnesses[i]) // don't allow greater matches (because calculating a makeshift gradient for this is already a huge mess as is)
@@ -773,6 +778,9 @@ static void HWR_CreateBlendedTexture(GLPatch_t *gpatch, GLPatch_t *blendgpatch,
 		}
 	}
 
+	if (translen > 0)
+		colorbrightnesses[translen] = colorbrightnesses[translen-1];
+
 	while (size--)
 	{
 		if (skinnum == TC_BOSS)
@@ -918,6 +926,8 @@ static void HWR_CreateBlendedTexture(GLPatch_t *gpatch, GLPatch_t *blendgpatch,
 					secondi = firsti+1;
 
 					mulmax = cutoff[firsti] - cutoff[secondi];
+					if (mulmax == 0)
+						mulmax = 1; // don't divide by zero on equal cutoffs (however unlikely)
 					mul = cutoff[firsti] - brightness;
 				}
 
diff --git a/src/http-mserv.c b/src/http-mserv.c
index cb08a85bca993a40b8da083e2fcb8e8cc99bc6bd..5b7928a8f46a2ff79850ce6c47c0a88f2c33444b 100644
--- a/src/http-mserv.c
+++ b/src/http-mserv.c
@@ -52,6 +52,8 @@ consvar_t cv_masterserver_token = {
 	NULL, 0, NULL, NULL, 0, 0, NULL/* C90 moment */
 };
 
+#define HMS_QUERY_VERSION "?v=2.2"
+
 #ifdef MASTERSERVER
 
 static int hms_started;
@@ -171,7 +173,7 @@ HMS_connect (const char *format, ...)
 
 	va_start (ap, format);
 	url = malloc(seek + vsnprintf(0, 0, format, ap) +
-			sizeof "?v=2" - 1 +
+			sizeof HMS_QUERY_VERSION - 1 +
 			token_length + 1);
 	va_end (ap);
 
@@ -185,8 +187,8 @@ HMS_connect (const char *format, ...)
 	seek += vsprintf(&url[seek], format, ap);
 	va_end (ap);
 
-	strcpy(&url[seek], "?v=2.2");
-	seek += sizeof "?v=2.2" - 1;
+	strcpy(&url[seek], HMS_QUERY_VERSION);
+	seek += sizeof HMS_QUERY_VERSION - 1;
 
 	if (quack_token)
 		sprintf(&url[seek], "&token=%s", quack_token);
diff --git a/src/i_net.h b/src/i_net.h
index b496fe33f6c521ebd72d61134cd08be094a401b2..b9da94ec434ec8728e4b3e0a9f2b45bef6878b90 100644
--- a/src/i_net.h
+++ b/src/i_net.h
@@ -152,7 +152,7 @@ extern void (*I_NetCloseSocket)(void);
 
 /**	\brief send a hole punching request
 */
-extern void (*I_NetRequestHolePunch)(void);
+extern void (*I_NetRequestHolePunch)(INT32 node);
 
 /**	\brief register this machine on the hole punching server
 */
diff --git a/src/i_tcp.c b/src/i_tcp.c
index e4bf4ef049382cb1efdee4bcac47bf543ecc5194..433770d24f3fff2719ee8e566dcf69cef6bf0f6e 100644
--- a/src/i_tcp.c
+++ b/src/i_tcp.c
@@ -1545,9 +1545,9 @@ static void rendezvous(int size)
 	free(addrs);
 }
 
-static void SOCK_RequestHolePunch(void)
+static void SOCK_RequestHolePunch(INT32 node)
 {
-	mysockaddr_t * addr = &clientaddress[doomcom->remotenode];
+	mysockaddr_t * addr = &clientaddress[node];
 
 	holepunchpacket->addr = addr->ip4.sin_addr.s_addr;
 	holepunchpacket->port = addr->ip4.sin_port;
diff --git a/src/lua_hudlib_drawlist.c b/src/lua_hudlib_drawlist.c
index a867eff41d7af2e393d4dbb3c413745ce1ea7f33..cfadcc30acf6cb452b704b33e6d207ab0f6f236a 100644
--- a/src/lua_hudlib_drawlist.c
+++ b/src/lua_hudlib_drawlist.c
@@ -52,7 +52,7 @@ typedef struct drawitem_s {
 	fixed_t sy;
 	INT32 num;
 	INT32 digits;
-	const char *str;
+	size_t stroffset; // offset into strbuf to get str
 	UINT16 color;
 	UINT8 strength;
 	INT32 align;
@@ -123,6 +123,10 @@ void LUA_HUD_DestroyDrawList(huddrawlist_h list)
 	{
 		Z_Free(list->items);
 	}
+	if (list->strbuf)
+	{
+		Z_Free(list->strbuf);
+	}
 	Z_Free(list);
 }
 
@@ -150,7 +154,7 @@ static size_t AllocateDrawItem(huddrawlist_h list)
 // copy string to list's internal string buffer
 // lua can deallocate the string before we get to use it, so it's important to
 // keep our own copy
-static const char *CopyString(huddrawlist_h list, const char* str)
+static size_t CopyString(huddrawlist_h list, const char* str)
 {
 	size_t lenstr;
 
@@ -164,10 +168,10 @@ static const char *CopyString(huddrawlist_h list, const char* str)
 	}
 
 	{
-		const char *result = (const char *) &list->strbuf[list->strbuf_len];
-		strncpy(&list->strbuf[list->strbuf_len], str, lenstr + 1);
+		size_t old_len = list->strbuf_len;
+		strncpy(&list->strbuf[old_len], str, lenstr + 1);
 		list->strbuf_len += lenstr + 1;
-		return result;
+		return old_len;
 	}
 }
 
@@ -280,7 +284,7 @@ void LUA_HUD_AddDrawString(
 	item->type = DI_DrawString;
 	item->x = x;
 	item->y = y;
-	item->str = CopyString(list, str);
+	item->stroffset = CopyString(list, str);
 	item->flags = flags;
 	item->align = align;
 }
@@ -298,7 +302,7 @@ void LUA_HUD_AddDrawKartString(
 	item->type = DI_DrawKartString;
 	item->x = x;
 	item->y = y;
-	item->str = CopyString(list, str);
+	item->stroffset = CopyString(list, str);
 	item->flags = flags;
 }
 
@@ -315,7 +319,7 @@ void LUA_HUD_AddDrawLevelTitle(
 	item->type = DI_DrawLevelTitle;
 	item->x = x;
 	item->y = y;
-	item->str = CopyString(list, str);
+	item->stroffset = CopyString(list, str);
 	item->flags = flags;
 }
 
@@ -343,6 +347,7 @@ void LUA_HUD_DrawList(huddrawlist_h list)
 	for (i = 0; i < list->items_len; i++)
 	{
 		drawitem_t *item = &list->items[i];
+		const char *itemstr = &list->strbuf[item->stroffset];
 
 		switch (item->type)
 		{
@@ -366,38 +371,38 @@ void LUA_HUD_DrawList(huddrawlist_h list)
 				{
 				// hu_font
 				case align_left:
-					V_DrawString(item->x, item->y, item->flags, item->str);
+					V_DrawString(item->x, item->y, item->flags, itemstr);
 					break;
 				case align_center:
-					V_DrawCenteredString(item->x, item->y, item->flags, item->str);
+					V_DrawCenteredString(item->x, item->y, item->flags, itemstr);
 					break;
 				case align_right:
-					V_DrawRightAlignedString(item->x, item->y, item->flags, item->str);
+					V_DrawRightAlignedString(item->x, item->y, item->flags, itemstr);
 					break;
 				case align_fixed:
-					V_DrawStringAtFixed(item->x, item->y, item->flags, item->str);
+					V_DrawStringAtFixed(item->x, item->y, item->flags, itemstr);
 					break;
 				// hu_font, 0.5x scale
 				case align_small:
-					V_DrawSmallString(item->x, item->y, item->flags, item->str);
+					V_DrawSmallString(item->x, item->y, item->flags, itemstr);
 					break;
 				case align_smallright:
-					V_DrawRightAlignedSmallString(item->x, item->y, item->flags, item->str);
+					V_DrawRightAlignedSmallString(item->x, item->y, item->flags, itemstr);
 					break;
 				// tny_font
 				case align_thin:
-					V_DrawThinString(item->x, item->y, item->flags, item->str);
+					V_DrawThinString(item->x, item->y, item->flags, itemstr);
 					break;
 				case align_thinright:
-					V_DrawRightAlignedThinString(item->x, item->y, item->flags, item->str);
+					V_DrawRightAlignedThinString(item->x, item->y, item->flags, itemstr);
 					break;
 				}
 				break;
 			case DI_DrawKartString:
-				V_DrawKartString(item->x, item->y, item->flags, item->str);
+				V_DrawKartString(item->x, item->y, item->flags, itemstr);
 				break;
 			case DI_DrawLevelTitle:
-				V_DrawLevelTitle(item->x, item->y, item->flags, item->str);
+				V_DrawLevelTitle(item->x, item->y, item->flags, itemstr);
 				break;
 			case DI_FadeScreen:
 				V_DrawFadeScreen(item->color, item->strength);
diff --git a/src/m_menu.c b/src/m_menu.c
index de0e0f418e07f1be167c90e1accbbe0dca3d5cd2..ab0f20208a92417d3e896cbbe5cc320ef0bdf7bf 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -8631,6 +8631,7 @@ static void M_DrawConnectMenu(void)
 	const char *spd = "";
 	INT32 numPages = (serverlistcount+(SERVERS_PER_PAGE-1))/SERVERS_PER_PAGE;
 	int waiting;
+	int mservflags = V_ALLOWLOWERCASE;
 
 	for (i = FIRSTSERVERLINE; i < min(localservercount, SERVERS_PER_PAGE)+FIRSTSERVERLINE; i++)
 		MP_ConnectMenu[i].status = IT_STRING | IT_SPACE;
@@ -8641,9 +8642,8 @@ static void M_DrawConnectMenu(void)
 	// Page num
 	V_DrawRightAlignedString(BASEVIDWIDTH - currentMenu->x, currentMenu->y + MP_ConnectMenu[mp_connect_page].alphaKey,
 	                         highlightflags, va("%u of %d", serverlistpage+1, numPages));
-	
+
 	// Did you change the Server Browser address? Have a little reminder.
-	int mservflags = V_ALLOWLOWERCASE;
 	if (CV_IsSetToDefault(&cv_masterserver))
 		mservflags = mservflags|highlightflags|V_30TRANS;
 	else
diff --git a/src/r_things.c b/src/r_things.c
index e8086f8b6bae9bb74bf728a9c610c38f2b6206f9..1546e2cb1af265bd44c6ef1ab11762b05f4ddd82 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -80,6 +80,33 @@ static spriteframe_t sprtemp[64];
 static size_t maxframe;
 static const char *spritename;
 
+//
+// Clipping against drawsegs optimization, from prboom-plus
+//
+// TODO: This should be done with proper subsector pass through
+// sprites which would ideally remove the need to do it at all.
+// Unfortunately, SRB2's drawing loop has lots of annoying
+// changes from Doom for portals, which make it hard to implement.
+
+typedef struct drawseg_xrange_item_s
+{
+	INT16 x1, x2;
+	drawseg_t *user;
+} drawseg_xrange_item_t;
+
+typedef struct drawsegs_xrange_s
+{
+	drawseg_xrange_item_t *items;
+	INT32 count;
+} drawsegs_xrange_t;
+
+#define DS_RANGES_COUNT 3
+static drawsegs_xrange_t drawsegs_xranges[DS_RANGES_COUNT];
+
+static drawseg_xrange_item_t *drawsegs_xrange;
+static size_t drawsegs_xrange_size = 0;
+static INT32 drawsegs_xrange_count = 0;
+
 // ==========================================================================
 //
 // Sprite loading routines: support sprites in pwad, dehacked sprite renaming,
@@ -548,7 +575,7 @@ void R_DelSpriteDefs(UINT16 wadnum)
 //
 // GAME FUNCTIONS
 //
-static UINT32 visspritecount;
+UINT32 visspritecount;
 static UINT32 clippedvissprites;
 static vissprite_t *visspritechunks[MAXVISSPRITES >> VISSPRITECHUNKBITS] = {NULL};
 
@@ -2274,44 +2301,45 @@ static void R_DrawPrecipitationSprite(vissprite_t *spr)
 
 // R_ClipSprites
 // Clips vissprites without drawing, so that portals can work. -Red
-void R_ClipSprites(void)
+void R_ClipVisSprite(vissprite_t *spr, INT32 x1, INT32 x2)
 {
-	vissprite_t *spr;
-	for (;clippedvissprites < visspritecount; clippedvissprites++)
-	{
-		drawseg_t *ds;
-		INT32		x;
-		INT32		r1;
-		INT32		r2;
-		fixed_t		scale;
-		fixed_t		lowscale;
-		INT32		silhouette;
+	drawseg_t *ds;
+	INT32		x;
+	INT32		r1;
+	INT32		r2;
+	fixed_t		scale;
+	fixed_t		lowscale;
+	INT32		silhouette;
 
-		spr = R_GetVisSprite(clippedvissprites);
+	for (x = x1; x <= x2; x++)
+	{
+		spr->clipbot[x] = spr->cliptop[x] = -2;
+	}
 
-		for (x = spr->x1; x <= spr->x2; x++)
-			spr->clipbot[x] = spr->cliptop[x] = -2;
+	// Scan drawsegs from end to start for obscuring segs.
+	// The first drawseg that has a greater scale
+	//  is the clip seg.
+	//SoM: 4/8/2000:
+	// Pointer check was originally nonportable
+	// and buggy, by going past LEFT end of array:
 
-		// Scan drawsegs from end to start for obscuring segs.
-		// The first drawseg that has a greater scale
-		//  is the clip seg.
-		//SoM: 4/8/2000:
-		// Pointer check was originally nonportable
-		// and buggy, by going past LEFT end of array:
+	// e6y: optimization
+	if (drawsegs_xrange_size)
+	{
+		const drawseg_xrange_item_t *last = &drawsegs_xrange[drawsegs_xrange_count - 1];
+		drawseg_xrange_item_t *curr = &drawsegs_xrange[-1];
 
-		//    for (ds = ds_p-1; ds >= drawsegs; ds--)    old buggy code
-		for (ds = ds_p; ds-- > drawsegs ;)
+		while (++curr <= last)
 		{
 			// determine if the drawseg obscures the sprite
-			if (ds->x1 > spr->x2 ||
-			    ds->x2 < spr->x1 ||
-			    (!ds->silhouette
-			     && !ds->maskedtexturecol))
+			if (curr->x1 > spr->x2 || curr->x2 < spr->x1)
 			{
 				// does not cover sprite
 				continue;
 			}
 
+			ds = curr->user;
+
 			if (ds->portalpass > 0 && ds->portalpass <= portalrender)
 				continue; // is a portal
 
@@ -2375,87 +2403,173 @@ void R_ClipSprites(void)
 				}
 			}
 		}
-		//SoM: 3/17/2000: Clip sprites in water.
-		if (spr->heightsec != -1)  // only things in specially marked sectors
+	}
+	//SoM: 3/17/2000: Clip sprites in water.
+	if (spr->heightsec != -1)  // only things in specially marked sectors
+	{
+		fixed_t mh, h;
+		INT32 phs = viewplayer->mo->subsector->sector->heightsec;
+		if ((mh = sectors[spr->heightsec].floorheight) > spr->gz &&
+			(h = centeryfrac - FixedMul(mh -= viewz, spr->sortscale)) >= 0 &&
+			(h >>= FRACBITS) < viewheight)
 		{
-			fixed_t mh, h;
-			INT32 phs = viewplayer->mo->subsector->sector->heightsec;
-			if ((mh = sectors[spr->heightsec].floorheight) > spr->gz &&
-				(h = centeryfrac - FixedMul(mh -= viewz, spr->sortscale)) >= 0 &&
-				(h >>= FRACBITS) < viewheight)
+			if (mh <= 0 || (phs != -1 && viewz > sectors[phs].floorheight))
+			{                          // clip bottom
+				for (x = spr->x1; x <= spr->x2; x++)
+					if (spr->clipbot[x] == -2 || h < spr->clipbot[x])
+						spr->clipbot[x] = (INT16)h;
+			}
+			else						// clip top
 			{
-				if (mh <= 0 || (phs != -1 && viewz > sectors[phs].floorheight))
-				{                          // clip bottom
-					for (x = spr->x1; x <= spr->x2; x++)
-						if (spr->clipbot[x] == -2 || h < spr->clipbot[x])
-							spr->clipbot[x] = (INT16)h;
-				}
-				else						// clip top
-				{
-					for (x = spr->x1; x <= spr->x2; x++)
-						if (spr->cliptop[x] == -2 || h > spr->cliptop[x])
-							spr->cliptop[x] = (INT16)h;
-				}
+				for (x = spr->x1; x <= spr->x2; x++)
+					if (spr->cliptop[x] == -2 || h > spr->cliptop[x])
+						spr->cliptop[x] = (INT16)h;
 			}
+		}
 
-			if ((mh = sectors[spr->heightsec].ceilingheight) < spr->gzt &&
-			    (h = centeryfrac - FixedMul(mh-viewz, spr->sortscale)) >= 0 &&
-			    (h >>= FRACBITS) < viewheight)
+		if ((mh = sectors[spr->heightsec].ceilingheight) < spr->gzt &&
+		    (h = centeryfrac - FixedMul(mh-viewz, spr->sortscale)) >= 0 &&
+		    (h >>= FRACBITS) < viewheight)
+		{
+			if (phs != -1 && viewz >= sectors[phs].ceilingheight)
+			{                         // clip bottom
+				for (x = spr->x1; x <= spr->x2; x++)
+					if (spr->clipbot[x] == -2 || h < spr->clipbot[x])
+						spr->clipbot[x] = (INT16)h;
+			}
+			else                       // clip top
 			{
-				if (phs != -1 && viewz >= sectors[phs].ceilingheight)
-				{                         // clip bottom
-					for (x = spr->x1; x <= spr->x2; x++)
-						if (spr->clipbot[x] == -2 || h < spr->clipbot[x])
-							spr->clipbot[x] = (INT16)h;
-				}
-				else                       // clip top
-				{
-					for (x = spr->x1; x <= spr->x2; x++)
-						if (spr->cliptop[x] == -2 || h > spr->cliptop[x])
-							spr->cliptop[x] = (INT16)h;
-				}
+				for (x = spr->x1; x <= spr->x2; x++)
+					if (spr->cliptop[x] == -2 || h > spr->cliptop[x])
+						spr->cliptop[x] = (INT16)h;
 			}
 		}
-		if (spr->cut & SC_TOP && spr->cut & SC_BOTTOM)
+	}
+	if (spr->cut & SC_TOP && spr->cut & SC_BOTTOM)
+	{
+		for (x = spr->x1; x <= spr->x2; x++)
 		{
-			for (x = spr->x1; x <= spr->x2; x++)
-			{
-				if (spr->cliptop[x] == -2 || spr->szt > spr->cliptop[x])
-					spr->cliptop[x] = spr->szt;
+			if (spr->cliptop[x] == -2 || spr->szt > spr->cliptop[x])
+				spr->cliptop[x] = spr->szt;
 
-				if (spr->clipbot[x] == -2 || spr->sz < spr->clipbot[x])
-					spr->clipbot[x] = spr->sz;
-			}
+			if (spr->clipbot[x] == -2 || spr->sz < spr->clipbot[x])
+				spr->clipbot[x] = spr->sz;
 		}
-		else if (spr->cut & SC_TOP)
+	}
+	else if (spr->cut & SC_TOP)
+	{
+		for (x = spr->x1; x <= spr->x2; x++)
 		{
-			for (x = spr->x1; x <= spr->x2; x++)
-			{
-				if (spr->cliptop[x] == -2 || spr->szt > spr->cliptop[x])
-					spr->cliptop[x] = spr->szt;
-			}
+			if (spr->cliptop[x] == -2 || spr->szt > spr->cliptop[x])
+				spr->cliptop[x] = spr->szt;
 		}
-		else if (spr->cut & SC_BOTTOM)
+	}
+	else if (spr->cut & SC_BOTTOM)
+	{
+		for (x = spr->x1; x <= spr->x2; x++)
 		{
-			for (x = spr->x1; x <= spr->x2; x++)
+			if (spr->clipbot[x] == -2 || spr->sz < spr->clipbot[x])
+				spr->clipbot[x] = spr->sz;
+		}
+	}
+
+	// all clipping has been performed, so store the values - what, did you think we were drawing them NOW?
+
+	// check for unclipped columns
+	for (x = spr->x1; x <= spr->x2; x++)
+	{
+		if (spr->clipbot[x] == -2)
+			spr->clipbot[x] = (INT16)viewheight;
+
+		if (spr->cliptop[x] == -2)
+			//Fab : 26-04-98: was -1, now clips against console bottom
+			spr->cliptop[x] = (INT16)con_clipviewtop;
+	}
+}
+
+void R_ClipSprites(void)
+{
+	const size_t maxdrawsegs = ds_p - drawsegs;
+	const INT32 cx = viewwidth / 2;
+	drawseg_t* ds;
+	INT32 i;
+
+	// e6y
+	// Reducing of cache misses in the following R_DrawSprite()
+	// Makes sense for scenes with huge amount of drawsegs.
+	// ~12% of speed improvement on epic.wad map05
+	for (i = 0; i < DS_RANGES_COUNT; i++)
+	{
+		drawsegs_xranges[i].count = 0;
+	}
+
+	if (visspritecount - clippedvissprites <= 0)
+	{
+		return;
+	}
+
+	if (drawsegs_xrange_size < maxdrawsegs)
+	{
+		drawsegs_xrange_size = 2 * maxdrawsegs;
+
+		for (i = 0; i < DS_RANGES_COUNT; i++)
+		{
+			drawsegs_xranges[i].items = Z_Realloc(
+				drawsegs_xranges[i].items,
+				drawsegs_xrange_size * sizeof(drawsegs_xranges[i].items[0]),
+				PU_STATIC, NULL
+			);
+		}
+	}
+
+	for (ds = ds_p; ds-- > drawsegs;)
+	{
+		if (ds->silhouette || ds->maskedtexturecol)
+		{
+			drawsegs_xranges[0].items[drawsegs_xranges[0].count].x1 = ds->x1;
+			drawsegs_xranges[0].items[drawsegs_xranges[0].count].x2 = ds->x2;
+			drawsegs_xranges[0].items[drawsegs_xranges[0].count].user = ds;
+
+			// e6y: ~13% of speed improvement on sunder.wad map10
+			if (ds->x1 < cx)
 			{
-				if (spr->clipbot[x] == -2 || spr->sz < spr->clipbot[x])
-					spr->clipbot[x] = spr->sz;
+				drawsegs_xranges[1].items[drawsegs_xranges[1].count] = 
+					drawsegs_xranges[0].items[drawsegs_xranges[0].count];
+				drawsegs_xranges[1].count++;
 			}
+
+			if (ds->x2 >= cx)
+			{
+				drawsegs_xranges[2].items[drawsegs_xranges[2].count] = 
+					drawsegs_xranges[0].items[drawsegs_xranges[0].count];
+				drawsegs_xranges[2].count++;
+			}
+
+			drawsegs_xranges[0].count++;
 		}
+	}
 
-		// all clipping has been performed, so store the values - what, did you think we were drawing them NOW?
+	for (; clippedvissprites < visspritecount; clippedvissprites++)
+	{
+		vissprite_t *spr = R_GetVisSprite(clippedvissprites);
 
-		// check for unclipped columns
-		for (x = spr->x1; x <= spr->x2; x++)
+		if (spr->x2 < cx)
 		{
-			if (spr->clipbot[x] == -2)
-				spr->clipbot[x] = (INT16)viewheight;
-
-			if (spr->cliptop[x] == -2)
-				//Fab : 26-04-98: was -1, now clips against console bottom
-				spr->cliptop[x] = (INT16)con_clipviewtop;
+			drawsegs_xrange = drawsegs_xranges[1].items;
+			drawsegs_xrange_count = drawsegs_xranges[1].count;
+		}
+		else if (spr->x1 >= cx)
+		{
+			drawsegs_xrange = drawsegs_xranges[2].items;
+			drawsegs_xrange_count = drawsegs_xranges[2].count;
 		}
+		else
+		{
+			drawsegs_xrange = drawsegs_xranges[0].items;
+			drawsegs_xrange_count = drawsegs_xranges[0].count;
+		}
+
+		R_ClipVisSprite(spr, spr->x1, spr->x2);
 	}
 }
 
diff --git a/src/r_things.h b/src/r_things.h
index b80ffa714569ddafb7adc14193f3f31ebec85394..13c0f4a34cb2067d63e10798c3e10bba832fcba6 100644
--- a/src/r_things.h
+++ b/src/r_things.h
@@ -58,7 +58,6 @@ void R_DelSpriteDefs(UINT16 wadnum);
 void R_AddSprites(sector_t *sec, INT32 lightlevel);
 void R_InitSprites(void);
 void R_ClearSprites(void);
-void R_ClipSprites(void);
 void R_DrawMasked(void);
 
 // -----------
@@ -163,6 +162,17 @@ typedef struct vissprite_s
 	fixed_t thingscale;
 } vissprite_t;
 
+extern UINT32 visspritecount;
+
+void R_ClipSprites(void);
+void R_ClipVisSprite(vissprite_t *spr, INT32 x1, INT32 x2);
+
+UINT8 *R_GetSpriteTranslation(vissprite_t *vis);
+
+// ----------
+// DRAW NODES
+// ----------
+
 // A drawnode is something that points to a 3D floor, 3D side, or masked
 // middle texture. This is used for sorting with sprites.
 typedef struct drawnode_s
diff --git a/src/st_stuff.c b/src/st_stuff.c
index d63c4ed0a1264608451dd6bdbb4dfe7fc0d7d33d..4a9accf0e84612f8498138d117ce532d3c2a0ef2 100644
--- a/src/st_stuff.c
+++ b/src/st_stuff.c
@@ -2030,16 +2030,16 @@ static void ST_overlayDrawer(void)
 			// SRB2kart: changed positions & text
 			if (splitscreen)
 			{
-				INT32 splitflags = K_calcSplitFlags(0);
-				V_DrawThinString(2, (BASEVIDHEIGHT/2)-20, V_YELLOWMAP|V_HUDTRANSHALF|splitflags, M_GetText("- SPECTATING -"));
+				INT32 splitflags = K_calcSplitFlags(V_SNAPTOBOTTOM|V_SNAPTOLEFT);
+				V_DrawThinString(2, (BASEVIDHEIGHT/2)-20, V_HUDTRANSHALF|V_YELLOWMAP|splitflags, M_GetText("- SPECTATING -"));
 				V_DrawThinString(2, (BASEVIDHEIGHT/2)-10, V_HUDTRANSHALF|splitflags, itemtxt);
 			}
 			else
 			{
-				V_DrawString(2, BASEVIDHEIGHT-40, V_HUDTRANSHALF|V_YELLOWMAP, M_GetText("- SPECTATING -"));
-				V_DrawString(2, BASEVIDHEIGHT-30, V_HUDTRANSHALF, itemtxt);
-				V_DrawString(2, BASEVIDHEIGHT-20, V_HUDTRANSHALF, M_GetText("Accelerate - Float"));
-				V_DrawString(2, BASEVIDHEIGHT-10, V_HUDTRANSHALF, M_GetText("Brake - Sink"));
+				V_DrawString(2, BASEVIDHEIGHT-40, V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_HUDTRANSHALF|V_YELLOWMAP, M_GetText("- SPECTATING -"));
+				V_DrawString(2, BASEVIDHEIGHT-30, V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_HUDTRANSHALF, itemtxt);
+				V_DrawString(2, BASEVIDHEIGHT-20, V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_HUDTRANSHALF, M_GetText("Accelerate - Float"));
+				V_DrawString(2, BASEVIDHEIGHT-10, V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_HUDTRANSHALF, M_GetText("Brake - Sink"));
 			}
 		}
 	}