diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 723407d86fa17529d138634c3e6b48f2338f2557..fb1bd8afb14c03f35ef3f891dad0761d3da8c88e 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -423,7 +423,11 @@ if(${SRB2_CONFIG_HWRENDER})
 		${CMAKE_CURRENT_SOURCE_DIR}/hardware/hw_light.c
 		${CMAKE_CURRENT_SOURCE_DIR}/hardware/hw_main.c
 		${CMAKE_CURRENT_SOURCE_DIR}/hardware/hw_md2.c
+		${CMAKE_CURRENT_SOURCE_DIR}/hardware/hw_md2load.c
+		${CMAKE_CURRENT_SOURCE_DIR}/hardware/hw_md3load.c
+		${CMAKE_CURRENT_SOURCE_DIR}/hardware/hw_model.c
 		${CMAKE_CURRENT_SOURCE_DIR}/hardware/hw_trick.c
+		${CMAKE_CURRENT_SOURCE_DIR}/hardware/u_list.c
 	)
 
 	set (SRB2_HWRENDER_HEADERS
@@ -437,6 +441,10 @@ if(${SRB2_CONFIG_HWRENDER})
 		${CMAKE_CURRENT_SOURCE_DIR}/hardware/hw_light.h
 		${CMAKE_CURRENT_SOURCE_DIR}/hardware/hw_main.h
 		${CMAKE_CURRENT_SOURCE_DIR}/hardware/hw_md2.h
+		${CMAKE_CURRENT_SOURCE_DIR}/hardware/hw_md2load.h
+		${CMAKE_CURRENT_SOURCE_DIR}/hardware/hw_md3load.h
+		${CMAKE_CURRENT_SOURCE_DIR}/hardware/hw_model.h
+		${CMAKE_CURRENT_SOURCE_DIR}/hardware/u_list.h
 	)
 
 	set(SRB2_R_OPENGL_SOURCES
diff --git a/src/Makefile b/src/Makefile
index 8e9e1816d478be81b052344a471c0b835ccb52dc..c5eb8b4a3ac2e7f69cb33b40e007125f448b88d1 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -226,7 +226,8 @@ else
 	#OPTS+=-DUSE_PALETTED_TEXTURE
 	OPTS+=-DHWRENDER
 	OBJS+=$(OBJDIR)/hw_bsp.o $(OBJDIR)/hw_draw.o $(OBJDIR)/hw_light.o \
-		 $(OBJDIR)/hw_main.o $(OBJDIR)/hw_clip.o $(OBJDIR)/hw_md2.o $(OBJDIR)/hw_cache.o $(OBJDIR)/hw_trick.o
+		 $(OBJDIR)/hw_main.o $(OBJDIR)/hw_clip.o $(OBJDIR)/hw_md2.o $(OBJDIR)/hw_cache.o $(OBJDIR)/hw_trick.o \
+		 $(OBJDIR)/hw_md2load.o $(OBJDIR)/hw_md3load.o $(OBJDIR)/hw_model.o $(OBJDIR)/u_list.o
 endif
 
 ifdef NOHS
@@ -646,16 +647,18 @@ ifdef MINGW
 $(OBJDIR)/r_opengl.o: hardware/r_opengl/r_opengl.c hardware/r_opengl/r_opengl.h \
  doomdef.h doomtype.h g_state.h m_swap.h hardware/hw_drv.h screen.h \
  command.h hardware/hw_data.h hardware/hw_glide.h hardware/hw_defs.h \
- hardware/hw_md2.h hardware/hw_glob.h hardware/hw_main.h hardware/hw_clip.h am_map.h \
- d_event.h d_player.h p_pspr.h m_fixed.h tables.h info.h d_think.h \
+ hardware/hw_md2.h hardware/hw_glob.h hardware/hw_main.h hardware/hw_clip.h \
+ hardware/hw_md2load.h hardware/hw_md3load.h hardware/hw_model.h hardware/u_list.h \
+ am_map.h d_event.h d_player.h p_pspr.h m_fixed.h tables.h info.h d_think.h \
  p_mobj.h doomdata.h d_ticcmd.h r_defs.h hardware/hw_dll.h
 	$(CC) $(CFLAGS) $(WFLAGS) -c $< -o $@
 else
 $(OBJDIR)/r_opengl.o: hardware/r_opengl/r_opengl.c hardware/r_opengl/r_opengl.h \
  doomdef.h doomtype.h g_state.h m_swap.h hardware/hw_drv.h screen.h \
  command.h hardware/hw_data.h hardware/hw_glide.h hardware/hw_defs.h \
- hardware/hw_md2.h hardware/hw_glob.h hardware/hw_main.h hardware/hw_clip.h am_map.h \
- d_event.h d_player.h p_pspr.h m_fixed.h tables.h info.h d_think.h \
+ hardware/hw_md2.h hardware/hw_glob.h hardware/hw_main.h hardware/hw_clip.h \
+ hardware/hw_md2load.h hardware/hw_md3load.h hardware/hw_model.h hardware/u_list.h \
+ am_map.h d_event.h d_player.h p_pspr.h m_fixed.h tables.h info.h d_think.h \
  p_mobj.h doomdata.h d_ticcmd.h r_defs.h hardware/hw_dll.h
 	$(CC) $(CFLAGS) $(WFLAGS) -I/usr/X11R6/include -c $< -o $@
 endif
@@ -733,16 +736,18 @@ ifndef NOHW
 $(OBJDIR)/r_opengl.o: hardware/r_opengl/r_opengl.c hardware/r_opengl/r_opengl.h \
  doomdef.h doomtype.h g_state.h m_swap.h hardware/hw_drv.h screen.h \
  command.h hardware/hw_data.h hardware/hw_glide.h hardware/hw_defs.h \
- hardware/hw_md2.h hardware/hw_glob.h hardware/hw_main.h hardware/hw_clip.h am_map.h \
- d_event.h d_player.h p_pspr.h m_fixed.h tables.h info.h d_think.h \
+ hardware/hw_md2.h hardware/hw_glob.h hardware/hw_main.h hardware/hw_clip.h \
+ hardware/hw_md2load.h hardware/hw_md3load.h hardware/hw_model.h hardware/u_list.h \
+ am_map.h d_event.h d_player.h p_pspr.h m_fixed.h tables.h info.h d_think.h \
  p_mobj.h doomdata.h d_ticcmd.h r_defs.h hardware/hw_dll.h
 	$(CC) $(CFLAGS) $(WFLAGS) -D_WINDOWS -mwindows -c $< -o $@
 
 $(OBJDIR)/ogl_win.o: hardware/r_opengl/ogl_win.c hardware/r_opengl/r_opengl.h \
  doomdef.h doomtype.h g_state.h m_swap.h hardware/hw_drv.h screen.h \
  command.h hardware/hw_data.h hardware/hw_glide.h hardware/hw_defs.h \
- hardware/hw_md2.h hardware/hw_glob.h hardware/hw_main.h hardware/hw_clip.h am_map.h \
- d_event.h d_player.h p_pspr.h m_fixed.h tables.h info.h d_think.h \
+ hardware/hw_md2.h hardware/hw_glob.h hardware/hw_main.h hardware/hw_clip.h \
+ hardware/hw_md2load.h hardware/hw_md3load.h hardware/hw_model.h hardware/u_list.h \
+ am_map.h d_event.h d_player.h p_pspr.h m_fixed.h tables.h info.h d_think.h \
  p_mobj.h doomdata.h d_ticcmd.h r_defs.h hardware/hw_dll.h
 	$(CC) $(CFLAGS) $(WFLAGS) -D_WINDOWS -mwindows -c $< -o $@
 endif
diff --git a/src/d_clisrv.c b/src/d_clisrv.c
index 860bde6240f43d4b901dd7fa4edd184bb87191d4..8d6de765470f15b9c2c1c2029b2ea758347e46bd 100644
--- a/src/d_clisrv.c
+++ b/src/d_clisrv.c
@@ -4708,7 +4708,12 @@ void TryRunTics(tic_t realtics)
 	if (neededtic > gametic && !resynch_local_inprogress)
 	{
 		if (advancedemo)
-			D_StartTitle();
+		{
+			if (timedemo_quit)
+				COM_ImmedExecute("quit");
+			else
+				D_StartTitle();
+		}
 		else
 			// run the count * tics
 			while (neededtic > gametic)
diff --git a/src/d_main.c b/src/d_main.c
index ca40fcb24783aece322647b61d619e2da06beeb2..0a13c6d0b0108bc83acbfc1501e40c92305eaaa4 100644
--- a/src/d_main.c
+++ b/src/d_main.c
@@ -291,11 +291,8 @@ static void D_Display(void)
 	switch (gamestate)
 	{
 		case GS_TITLESCREEN:
-			if (!titlemapinaction || !curbghide) {
-				F_TitleScreenDrawer();
-				break;
-			}
-			/* FALLTHRU */
+			F_TitleScreenDrawer();
+			break;
 		case GS_LEVEL:
 			if (!gametic)
 				break;
@@ -366,56 +363,11 @@ static void D_Display(void)
 
 		// clean up border stuff
 		// see if the border needs to be initially drawn
-		if (gamestate == GS_LEVEL || (gamestate == GS_TITLESCREEN && titlemapinaction && curbghide && (!hidetitlemap)))
+		if (gamestate == GS_LEVEL)
 		{
 			// draw the view directly
 
-			if (!automapactive && !dedicated && cv_renderview.value)
-			{
-				if (players[displayplayer].mo || players[displayplayer].playerstate == PST_DEAD)
-				{
-					topleft = screens[0] + viewwindowy*vid.width + viewwindowx;
-					objectsdrawn = 0;
-	#ifdef HWRENDER
-					if (rendermode != render_soft)
-						HWR_RenderPlayerView(0, &players[displayplayer]);
-					else
-	#endif
-					if (rendermode != render_none)
-						R_RenderPlayerView(&players[displayplayer]);
-				}
-
-				// render the second screen
-				if (splitscreen && players[secondarydisplayplayer].mo)
-				{
-	#ifdef HWRENDER
-					if (rendermode != render_soft)
-						HWR_RenderPlayerView(1, &players[secondarydisplayplayer]);
-					else
-	#endif
-					if (rendermode != render_none)
-					{
-						viewwindowy = vid.height / 2;
-						M_Memcpy(ylookup, ylookup2, viewheight*sizeof (ylookup[0]));
-
-						topleft = screens[0] + viewwindowy*vid.width + viewwindowx;
-
-						R_RenderPlayerView(&players[secondarydisplayplayer]);
-
-						viewwindowy = 0;
-						M_Memcpy(ylookup, ylookup1, viewheight*sizeof (ylookup[0]));
-					}
-				}
-
-				// Image postprocessing effect
-				if (rendermode == render_soft)
-				{
-					if (postimgtype)
-						V_DoPostProcessor(0, postimgtype, postimgparam);
-					if (postimgtype2)
-						V_DoPostProcessor(1, postimgtype2, postimgparam2);
-				}
-			}
+			D_Render();
 
 			if (lastdraw)
 			{
@@ -428,14 +380,9 @@ static void D_Display(void)
 				lastdraw = false;
 			}
 
-			if (gamestate == GS_LEVEL)
-			{
-				ST_Drawer();
-				F_TextPromptDrawer();
-				HU_Drawer();
-			}
-			else
-				F_TitleScreenDrawer();
+			ST_Drawer();
+			F_TextPromptDrawer();
+			HU_Drawer();
 		}
 	}
 
@@ -491,6 +438,13 @@ static void D_Display(void)
 			F_RunWipe(wipetypepost, gamestate != GS_TIMEATTACK && gamestate != GS_TITLESCREEN);
 		}
 
+		// reset counters so timedemo doesn't count the wipe duration
+		if (timingdemo)
+		{
+			framecount = 0;
+			demostarttime = I_GetTime();
+		}
+		
 		wipetypepost = -1;
 	}
 	else
@@ -531,6 +485,56 @@ static void D_Display(void)
 	}
 }
 
+void D_Render(void)
+{
+	if (!automapactive && !dedicated && cv_renderview.value)
+	{
+		if (players[displayplayer].mo || players[displayplayer].playerstate == PST_DEAD)
+		{
+			topleft = screens[0] + viewwindowy*vid.width + viewwindowx;
+			objectsdrawn = 0;
+#ifdef HWRENDER
+			if (rendermode != render_soft)
+				HWR_RenderPlayerView(0, &players[displayplayer]);
+			else
+#endif
+			if (rendermode != render_none)
+				R_RenderPlayerView(&players[displayplayer]);
+		}
+
+		// render the second screen
+		if (splitscreen && players[secondarydisplayplayer].mo)
+		{
+#ifdef HWRENDER
+			if (rendermode != render_soft)
+				HWR_RenderPlayerView(1, &players[secondarydisplayplayer]);
+			else
+#endif
+			if (rendermode != render_none)
+			{
+				viewwindowy = vid.height / 2;
+				M_Memcpy(ylookup, ylookup2, viewheight*sizeof (ylookup[0]));
+
+				topleft = screens[0] + viewwindowy*vid.width + viewwindowx;
+
+				R_RenderPlayerView(&players[secondarydisplayplayer]);
+
+				viewwindowy = 0;
+				M_Memcpy(ylookup, ylookup1, viewheight*sizeof (ylookup[0]));
+			}
+		}
+
+		// Image postprocessing effect
+		if (rendermode == render_soft)
+		{
+			if (postimgtype)
+				V_DoPostProcessor(0, postimgtype, postimgparam);
+			if (postimgtype2)
+				V_DoPostProcessor(1, postimgtype2, postimgparam2);
+		}
+	}
+}
+
 // =========================================================================
 // D_SRB2Loop
 // =========================================================================
@@ -544,9 +548,6 @@ void D_SRB2Loop(void)
 	if (dedicated)
 		server = true;
 
-	if (M_CheckParm("-voodoo")) // 256x256 Texture Limiter
-		COM_BufAddText("gr_voodoocompatibility on\n");
-
 	// Pushing of + parameters is now done back in D_SRB2Main, not here.
 
 	CONS_Printf("I_StartupKeyboard()...\n");
diff --git a/src/d_main.h b/src/d_main.h
index d67a5bb498ba0f45d37b2be8c6bd15fde96cc78a..65c51802a7c644c871b862ef0ff28182204d0d6b 100644
--- a/src/d_main.h
+++ b/src/d_main.h
@@ -54,4 +54,7 @@ const char *D_Home(void);
 void D_AdvanceDemo(void);
 void D_StartTitle(void);
 
+/* Here for title maps */
+void D_Render(void);
+
 #endif //__D_MAIN__
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 5c8c327c8eae78800ded6315259a5e8dff38d0f7..62c505ee0b726569f63ae6e3a0ec10a8c6b69aa8 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -365,6 +365,11 @@ consvar_t cv_mute = {"mute", "Off", CV_NETVAR|CV_CALL, CV_OnOff, Mute_OnChange,
 
 consvar_t cv_sleep = {"cpusleep", "1", CV_SAVE, sleeping_cons_t, NULL, -1, NULL, NULL, 0, 0, NULL};
 
+char timedemo_name[256];
+boolean timedemo_csv;
+char timedemo_csv_id[256];
+boolean timedemo_quit;
+
 INT16 gametype = GT_COOP;
 boolean splitscreen = false;
 boolean circuitmap = false;
@@ -1569,11 +1574,11 @@ static void Command_Playdemo_f(void)
 
 static void Command_Timedemo_f(void)
 {
-	char name[256];
+	size_t i = 0;
 
-	if (COM_Argc() != 2)
+	if (COM_Argc() < 2)
 	{
-		CONS_Printf(M_GetText("timedemo <demoname>: time a demo\n"));
+		CONS_Printf(M_GetText("timedemo <demoname> [-csv [<trialid>]] [-quit]: time a demo\n"));
 		return;
 	}
 
@@ -1590,12 +1595,23 @@ static void Command_Timedemo_f(void)
 		G_StopMetalDemo();
 
 	// open the demo file
-	strcpy (name, COM_Argv(1));
+	strcpy (timedemo_name, COM_Argv(1));
 	// dont add .lmp so internal game demos can be played
 
-	CONS_Printf(M_GetText("Timing demo '%s'.\n"), name);
+	// print timedemo results as CSV?
+	i = COM_CheckParm("-csv");
+	timedemo_csv = (i > 0);
+	if (COM_CheckParm("-quit") != i + 1)
+		strcpy(timedemo_csv_id, COM_Argv(i + 1)); // user-defined string to identify row
+	else
+		timedemo_csv_id[0] = 0;
 
-	G_TimeDemo(name);
+	// exit after the timedemo?
+	timedemo_quit = (COM_CheckParm("-quit") > 0);
+
+	CONS_Printf(M_GetText("Timing demo '%s'.\n"), timedemo_name);
+
+	G_TimeDemo(timedemo_name);
 }
 
 // stop current demo
@@ -1646,7 +1662,31 @@ void D_MapChange(INT32 mapnum, INT32 newgametype, boolean pultmode, boolean rese
 	// The supplied data are assumed to be good.
 	I_Assert(delay >= 0 && delay <= 2);
 	if (mapnum != -1)
+	{
 		CV_SetValue(&cv_nextmap, mapnum);
+		// Kick bot from special stages
+		if (botskin)
+		{
+			if (G_IsSpecialStage(mapnum) || (mapheaderinfo[mapnum-1] && (mapheaderinfo[mapnum-1]->typeoflevel & TOL_NIGHTS)))
+			{
+				if (botingame)
+				{
+					//CL_RemoveSplitscreenPlayer();
+					botingame = false;
+					playeringame[1] = false;
+				}
+			}
+			else if (!botingame)
+			{
+				//CL_AddSplitscreenPlayer();
+				botingame = true;
+				secondarydisplayplayer = 1;
+				playeringame[1] = true;
+				players[1].bot = 1;
+				SendNameAndColor2();
+			}
+		}
+	}
 	CONS_Debug(DBG_GAMELOGIC, "Map change: mapnum=%d gametype=%d ultmode=%d resetplayers=%d delay=%d skipprecutscene=%d\n",
 	           mapnum, newgametype, pultmode, resetplayers, delay, skipprecutscene);
 	if ((netgame || multiplayer) && !((gametype == newgametype) && (newgametype == GT_COOP)))
@@ -1689,29 +1729,6 @@ void D_MapChange(INT32 mapnum, INT32 newgametype, boolean pultmode, boolean rese
 				return;
 		}
 
-		// Kick bot from special stages
-		if (botskin)
-		{
-			if (G_IsSpecialStage(mapnum) || (mapheaderinfo[mapnum-1] && (mapheaderinfo[mapnum-1]->typeoflevel & TOL_NIGHTS)))
-			{
-				if (botingame)
-				{
-					//CL_RemoveSplitscreenPlayer();
-					botingame = false;
-					playeringame[1] = false;
-				}
-			}
-			else if (!botingame)
-			{
-				//CL_AddSplitscreenPlayer();
-				botingame = true;
-				secondarydisplayplayer = 1;
-				playeringame[1] = true;
-				players[1].bot = 1;
-				SendNameAndColor2();
-			}
-		}
-
 		chmappending++;
 		if (netgame)
 			WRITEUINT32(buf_p, M_RandomizedSeed()); // random seed
diff --git a/src/d_netcmd.h b/src/d_netcmd.h
index e789e5b50f467d8dceec697ab841adcf851e0bf4..0b538890ec569dc8a2062889dc9842c502b89af3 100644
--- a/src/d_netcmd.h
+++ b/src/d_netcmd.h
@@ -113,6 +113,11 @@ extern consvar_t cv_skipmapcheck;
 
 extern consvar_t cv_sleep;
 
+extern char timedemo_name[256];
+extern boolean timedemo_csv;
+extern char timedemo_csv_id[256];
+extern boolean timedemo_quit;
+
 typedef enum
 {
 	XD_NAMEANDCOLOR = 1,
diff --git a/src/dehacked.c b/src/dehacked.c
index 6369d5d7fb14aadb6e9e693daaa56239f95a16e4..b9f766333f882dddeb5559d98e03f796fd5cb9ed 100644
--- a/src/dehacked.c
+++ b/src/dehacked.c
@@ -2089,12 +2089,59 @@ static void readmenu(MYFILE *f, INT32 num)
 				menupres[num].bgcolor = get_number(word2);
 				titlechanged = true;
 			}
-			else if (fastcmp(word, "HIDETITLEPICS") || fastcmp(word, "HIDEPICS"))
+			else if (fastcmp(word, "HIDETITLEPICS") || fastcmp(word, "HIDEPICS") || fastcmp(word, "TITLEPICSHIDE"))
 			{
 				// true by default, except MM_MAIN
 				menupres[num].hidetitlepics = (boolean)(value || word2[0] == 'T' || word2[0] == 'Y');
 				titlechanged = true;
 			}
+			else if (fastcmp(word, "TITLEPICSMODE"))
+			{
+				if (fastcmp(word2, "USER"))
+					menupres[num].ttmode = TTMODE_USER;
+				else if (fastcmp(word2, "ALACROIX"))
+					menupres[num].ttmode = TTMODE_ALACROIX;
+				else if (fastcmp(word2, "HIDE") || fastcmp(word2, "HIDDEN") || fastcmp(word2, "NONE"))
+				{
+					menupres[num].ttmode = TTMODE_USER;
+					menupres[num].ttname[0] = 0;
+					menupres[num].hidetitlepics = true;
+				}
+				else // if (fastcmp(word2, "OLD") || fastcmp(word2, "SSNTAILS"))
+					menupres[num].ttmode = TTMODE_OLD;
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "TITLEPICSSCALE"))
+			{
+				// Don't handle Alacroix special case here; see Maincfg section.
+				menupres[num].ttscale = max(1, min(8, (UINT8)get_number(word2)));
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "TITLEPICSNAME"))
+			{
+				strncpy(menupres[num].ttname, word2, 9);
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "TITLEPICSX"))
+			{
+				menupres[num].ttx = (INT16)get_number(word2);
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "TITLEPICSY"))
+			{
+				menupres[num].tty = (INT16)get_number(word2);
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "TITLEPICSLOOP"))
+			{
+				menupres[num].ttloop = (INT16)get_number(word2);
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "TITLEPICSTICS"))
+			{
+				menupres[num].tttics = (UINT16)get_number(word2);
+				titlechanged = true;
+			}
 			else if (fastcmp(word, "TITLESCROLLSPEED") || fastcmp(word, "TITLESCROLLXSPEED")
 				|| fastcmp(word, "SCROLLSPEED") || fastcmp(word, "SCROLLXSPEED"))
 			{
@@ -2308,6 +2355,7 @@ static actionpointer_t actionpointers[] =
 	{{A_ThrownRing},             "A_THROWNRING"},
 	{{A_SetSolidSteam},          "A_SETSOLIDSTEAM"},
 	{{A_UnsetSolidSteam},        "A_UNSETSOLIDSTEAM"},
+	{{A_SignSpin},               "S_SIGNSPIN"},
 	{{A_SignPlayer},             "A_SIGNPLAYER"},
 	{{A_OverlayThink},           "A_OVERLAYTHINK"},
 	{{A_JetChase},               "A_JETCHASE"},
@@ -3492,11 +3540,78 @@ static void readmaincfg(MYFILE *f)
 				titlemap = (INT16)value;
 				titlechanged = true;
 			}
-			else if (fastcmp(word, "HIDETITLEPICS"))
+			else if (fastcmp(word, "HIDETITLEPICS") || fastcmp(word, "TITLEPICSHIDE"))
 			{
 				hidetitlepics = (boolean)(value || word2[0] == 'T' || word2[0] == 'Y');
 				titlechanged = true;
 			}
+			else if (fastcmp(word, "TITLEPICSMODE"))
+			{
+				if (fastcmp(word2, "USER"))
+					ttmode = TTMODE_USER;
+				else if (fastcmp(word2, "ALACROIX"))
+					ttmode = TTMODE_ALACROIX;
+				else if (fastcmp(word2, "HIDE") || fastcmp(word2, "HIDDEN") || fastcmp(word2, "NONE"))
+				{
+					ttmode = TTMODE_USER;
+					ttname[0] = 0;
+					hidetitlepics = true;
+				}
+				else // if (fastcmp(word2, "OLD") || fastcmp(word2, "SSNTAILS"))
+					ttmode = TTMODE_OLD;
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "TITLEPICSSCALE"))
+			{
+				ttscale = max(1, min(8, (UINT8)get_number(word2)));
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "TITLEPICSSCALESAVAILABLE"))
+			{
+				// SPECIAL CASE for Alacroix: Comma-separated list of resolutions that are available
+				// for gfx loading.
+				ttavailable[0] = ttavailable[1] = ttavailable[2] = ttavailable[3] =\
+					ttavailable[4] = ttavailable[5] = false;
+
+				if (strstr(word2, "1") != NULL)
+					ttavailable[0] = true;
+				if (strstr(word2, "2") != NULL)
+					ttavailable[1] = true;
+				if (strstr(word2, "3") != NULL)
+					ttavailable[2] = true;
+				if (strstr(word2, "4") != NULL)
+					ttavailable[3] = true;
+				if (strstr(word2, "5") != NULL)
+					ttavailable[4] = true;
+				if (strstr(word2, "6") != NULL)
+					ttavailable[5] = true;
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "TITLEPICSNAME"))
+			{
+				strncpy(ttname, word2, 9);
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "TITLEPICSX"))
+			{
+				ttx = (INT16)get_number(word2);
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "TITLEPICSY"))
+			{
+				tty = (INT16)get_number(word2);
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "TITLEPICSLOOP"))
+			{
+				ttloop = (INT16)get_number(word2);
+				titlechanged = true;
+			}
+			else if (fastcmp(word, "TITLEPICSTICS"))
+			{
+				tttics = (UINT16)get_number(word2);
+				titlechanged = true;
+			}
 			else if (fastcmp(word, "TITLESCROLLSPEED") || fastcmp(word, "TITLESCROLLXSPEED"))
 			{
 				titlescrollxspeed = get_number(word2);
@@ -5367,59 +5482,18 @@ static const char *const STATE_LIST[] = { // array length left dynamic for sanit
 	"S_BUBBLES4",
 
 	// Level End Sign
-	"S_SIGN1",
-	"S_SIGN2",
-	"S_SIGN3",
-	"S_SIGN4",
-	"S_SIGN5",
-	"S_SIGN6",
-	"S_SIGN7",
-	"S_SIGN8",
-	"S_SIGN9",
-	"S_SIGN10",
-	"S_SIGN11",
-	"S_SIGN12",
-	"S_SIGN13",
-	"S_SIGN14",
-	"S_SIGN15",
-	"S_SIGN16",
-	"S_SIGN17",
-	"S_SIGN18",
-	"S_SIGN19",
-	"S_SIGN20",
-	"S_SIGN21",
-	"S_SIGN22",
-	"S_SIGN23",
-	"S_SIGN24",
-	"S_SIGN25",
-	"S_SIGN26",
-	"S_SIGN27",
-	"S_SIGN28",
-	"S_SIGN29",
-	"S_SIGN30",
-	"S_SIGN31",
-	"S_SIGN32",
-	"S_SIGN33",
-	"S_SIGN34",
-	"S_SIGN35",
-	"S_SIGN36",
-	"S_SIGN37",
-	"S_SIGN38",
-	"S_SIGN39",
-	"S_SIGN40",
-	"S_SIGN41",
-	"S_SIGN42",
-	"S_SIGN43",
-	"S_SIGN44",
-	"S_SIGN45",
-	"S_SIGN46",
-	"S_SIGN47",
-	"S_SIGN48",
-	"S_SIGN49",
-	"S_SIGN50",
-	"S_SIGN51",
-	"S_SIGN52", // Eggman
-	"S_SIGN53",
+	"S_SIGN",
+	"S_SIGNSPIN1",
+	"S_SIGNSPIN2",
+	"S_SIGNSPIN3",
+	"S_SIGNSPIN4",
+	"S_SIGNSPIN5",
+	"S_SIGNSPIN6",
+	"S_SIGNPLAYER",
+	"S_SIGNSLOW",
+	"S_SIGNSTOP",
+	"S_SIGNBOARD",
+	"S_EGGMANSIGN",
 
 	// Spike Ball
 	"S_SPIKEBALL1",
diff --git a/src/doomtype.h b/src/doomtype.h
index 7acdde966835b97f6977214e8925729bdbe14108..5f60e21b51cc6aec62c1a3427a897c47d649816d 100644
--- a/src/doomtype.h
+++ b/src/doomtype.h
@@ -326,16 +326,18 @@ size_t strlcpy(char *dst, const char *src, size_t siz);
 
 /* Miscellaneous types that don't fit anywhere else (Can this be changed?) */
 
+typedef struct
+{
+	UINT8 red;
+	UINT8 green;
+	UINT8 blue;
+	UINT8 alpha;
+} byteColor_t;
+
 union FColorRGBA
 {
 	UINT32 rgba;
-	struct
-	{
-		UINT8 red;
-		UINT8 green;
-		UINT8 blue;
-		UINT8 alpha;
-	} s;
+	byteColor_t s;
 } ATTRPACK;
 typedef union FColorRGBA RGBA_t;
 
diff --git a/src/f_finale.c b/src/f_finale.c
index c9c8cfae5af8cbb68a4a0b692ae027d17408aa37..764825e6fc2c38db5f1301bcd1fa3ec650b52d58 100644
--- a/src/f_finale.c
+++ b/src/f_finale.c
@@ -70,7 +70,6 @@ mobj_t *titlemapcameraref = NULL;
 // menu presentation state
 char curbgname[9];
 SINT8 curfadevalue;
-boolean curhidepics;
 INT32 curbgcolor;
 INT32 curbgxspeed;
 INT32 curbgyspeed;
@@ -81,6 +80,28 @@ static UINT8  curDemo = 0;
 static UINT32 demoDelayLeft;
 static UINT32 demoIdleLeft;
 
+// customizable title screen graphics
+
+ttmode_enum ttmode = TTMODE_OLD;
+UINT8 ttscale = 1; // FRACUNIT / ttscale
+// ttmode user vars
+char ttname[9];
+INT16 ttx = 0;
+INT16 tty = 0;
+INT16 ttloop = -1;
+UINT16 tttics = 1;
+
+boolean curhidepics;
+ttmode_enum curttmode;
+UINT8 curttscale;
+// ttmode user vars
+char curttname[9];
+INT16 curttx;
+INT16 curtty;
+INT16 curttloop;
+UINT16 curtttics;
+
+// ttmode old
 static patch_t *ttbanner; // white banner with "robo blast" and "2"
 static patch_t *ttwing; // wing background
 static patch_t *ttsonic; // "SONIC"
@@ -97,6 +118,78 @@ static patch_t *ttspop5;
 static patch_t *ttspop6;
 static patch_t *ttspop7;
 
+// ttmode alacroix
+static SINT8 testttscale = 0;
+static SINT8 activettscale = 0;
+boolean ttavailable[6];
+boolean ttloaded[6];
+
+static patch_t *ttribb[6][TTMAX_ALACROIX];
+static patch_t *ttsont[6][TTMAX_ALACROIX];
+static patch_t *ttrobo[6][TTMAX_ALACROIX];
+static patch_t *tttwot[6][TTMAX_ALACROIX];
+static patch_t *ttembl[6][TTMAX_ALACROIX];
+static patch_t *ttrbtx[6][TTMAX_ALACROIX];
+static patch_t *ttsoib[6][TTMAX_ALACROIX];
+static patch_t *ttsoif[6][TTMAX_ALACROIX];
+static patch_t *ttsoba[6][TTMAX_ALACROIX];
+static patch_t *ttsobk[6][TTMAX_ALACROIX];
+static patch_t *ttsodh[6][TTMAX_ALACROIX];
+static patch_t *tttaib[6][TTMAX_ALACROIX];
+static patch_t *tttaif[6][TTMAX_ALACROIX];
+static patch_t *tttaba[6][TTMAX_ALACROIX];
+static patch_t *tttabk[6][TTMAX_ALACROIX];
+static patch_t *tttabt[6][TTMAX_ALACROIX];
+static patch_t *tttaft[6][TTMAX_ALACROIX];
+static patch_t *ttknib[6][TTMAX_ALACROIX];
+static patch_t *ttknif[6][TTMAX_ALACROIX];
+static patch_t *ttknba[6][TTMAX_ALACROIX];
+static patch_t *ttknbk[6][TTMAX_ALACROIX];
+static patch_t *ttkndh[6][TTMAX_ALACROIX];
+
+#define TTEMBL (ttembl[activettscale-1])
+#define TTRIBB (ttribb[activettscale-1])
+#define TTSONT (ttsont[activettscale-1])
+#define TTROBO (ttrobo[activettscale-1])
+#define TTTWOT (tttwot[activettscale-1])
+#define TTRBTX (ttrbtx[activettscale-1])
+#define TTSOIB (ttsoib[activettscale-1])
+#define TTSOIF (ttsoif[activettscale-1])
+#define TTSOBA (ttsoba[activettscale-1])
+#define TTSOBK (ttsobk[activettscale-1])
+#define TTSODH (ttsodh[activettscale-1])
+#define TTTAIB (tttaib[activettscale-1])
+#define TTTAIF (tttaif[activettscale-1])
+#define TTTABA (tttaba[activettscale-1])
+#define TTTABK (tttabk[activettscale-1])
+#define TTTABT (tttabt[activettscale-1])
+#define TTTAFT (tttaft[activettscale-1])
+#define TTKNIB (ttknib[activettscale-1])
+#define TTKNIF (ttknif[activettscale-1])
+#define TTKNBA (ttknba[activettscale-1])
+#define TTKNBK (ttknbk[activettscale-1])
+#define TTKNDH (ttkndh[activettscale-1])
+
+static boolean sonic_blink = false;
+static boolean sonic_blink_twice = false;
+static boolean sonic_blinked_already = false;
+static INT32 sonic_idle_start = 0;
+static INT32 sonic_idle_end = 0;
+static boolean tails_blink = false;
+static boolean tails_blink_twice = false;
+static boolean tails_blinked_already = false;
+static INT32 tails_idle_start = 0;
+static INT32 tails_idle_end = 0;
+static boolean knux_blink = false;
+static boolean knux_blink_twice = false;
+static boolean knux_blinked_already = false;
+static INT32 knux_idle_start = 0;
+static INT32 knux_idle_end = 0;
+
+// ttmode user
+static patch_t *ttuser[TTMAX_USER];
+static INT32 ttuser_count = 0;
+
 static boolean goodending;
 static patch_t *endbrdr[2]; // border - blue, white, pink - where have i seen those colours before?
 static patch_t *endbgsp[3]; // nebula, sun, planet
@@ -2097,16 +2190,24 @@ void F_InitMenuPresValues(void)
 	// Set defaults for presentation values
 	strncpy(curbgname, "TITLESKY", 9);
 	curfadevalue = 16;
-	curhidepics = hidetitlepics;
 	curbgcolor = -1;
 	curbgxspeed = (gamestate == GS_TIMEATTACK) ? 0 : titlescrollxspeed;
 	curbgyspeed = (gamestate == GS_TIMEATTACK) ? 22 : titlescrollyspeed;
 	curbghide = (gamestate == GS_TIMEATTACK) ? false : true;
 
+	curhidepics = hidetitlepics;
+	curttmode = ttmode;
+	curttscale = ttscale;
+	strncpy(curttname, ttname, 9);
+	curttx = ttx;
+	curtty = tty;
+	curttloop = ttloop;
+	curtttics = tttics;
+
 	// Find current presentation values
 	M_SetMenuCurBackground((gamestate == GS_TIMEATTACK) ? "RECATTBG" : "TITLESKY");
 	M_SetMenuCurFadeValue(16);
-	M_SetMenuCurHideTitlePics();
+	M_SetMenuCurTitlePics();
 }
 
 //
@@ -2173,6 +2274,32 @@ void F_SkyScroll(INT32 scrollxspeed, INT32 scrollyspeed, const char *patchname)
 	W_UnlockCachedPatch(pat);
 }
 
+#define LOADTTGFX(arr, name, maxf) \
+lumpnum = W_CheckNumForName(name); \
+if (lumpnum != LUMPERROR) \
+{ \
+	arr[0] = W_CachePatchName(name, PU_LEVEL); \
+	arr[min(1, maxf-1)] = 0; \
+} \
+else if (strlen(name) <= 6) \
+{ \
+	fixed_t cnt = strlen(name); \
+	strncpy(lumpname, name, 7); \
+	for (i = 0; i < maxf-1; i++) \
+	{ \
+		sprintf(&lumpname[cnt], "%.2hu", (UINT16)(i+1)); \
+		lumpname[8] = 0; \
+		lumpnum = W_CheckNumForName(lumpname); \
+		if (lumpnum != LUMPERROR) \
+			arr[i] = W_CachePatchName(lumpname, PU_LEVEL); \
+		else \
+			break; \
+	} \
+	arr[min(i, maxf-1)] = 0; \
+} \
+else \
+	arr[0] = 0;
+
 void F_StartTitleScreen(void)
 {
 	if (menupres[MN_MAIN].musname[0])
@@ -2182,7 +2309,19 @@ void F_StartTitleScreen(void)
 
 	if (gamestate != GS_TITLESCREEN && gamestate != GS_WAITINGPLAYERS)
 	{
-		finalecount = 0;
+		ttuser_count =\
+		 ttloaded[0] = ttloaded[1] = ttloaded[2] = ttloaded[3] = ttloaded[4] = ttloaded[5] =\
+		 testttscale = activettscale =\
+		 sonic_blink = sonic_blink_twice = sonic_idle_start = sonic_idle_end =\
+		 tails_blink = tails_blink_twice = tails_idle_start = tails_idle_end =\
+		 knux_blink  = knux_blink_twice  = knux_idle_start  = knux_idle_end  = 0;
+
+		sonic_blinked_already = tails_blinked_already = knux_blinked_already = 1; // don't blink on the first idle cycle
+
+		if (curttmode == TTMODE_ALACROIX)
+			finalecount = -3; // hack so that frames don't advance during the entry wipe
+		else
+			finalecount = 0;
 		wipetypepost = menupres[MN_MAIN].enterwipe;
 	}
 	else
@@ -2257,27 +2396,183 @@ void F_StartTitleScreen(void)
 	demoDelayLeft = demoDelayTime;
 	demoIdleLeft = demoIdleTime;
 
-	ttbanner = W_CachePatchName("TTBANNER", PU_LEVEL);
-	ttwing = W_CachePatchName("TTWING", PU_LEVEL);
-	ttsonic = W_CachePatchName("TTSONIC", PU_LEVEL);
-	ttswave1 = W_CachePatchName("TTSWAVE1", PU_LEVEL);
-	ttswave2 = W_CachePatchName("TTSWAVE2", PU_LEVEL);
-	ttswip1 = W_CachePatchName("TTSWIP1", PU_LEVEL);
-	ttsprep1 = W_CachePatchName("TTSPREP1", PU_LEVEL);
-	ttsprep2 = W_CachePatchName("TTSPREP2", PU_LEVEL);
-	ttspop1 = W_CachePatchName("TTSPOP1", PU_LEVEL);
-	ttspop2 = W_CachePatchName("TTSPOP2", PU_LEVEL);
-	ttspop3 = W_CachePatchName("TTSPOP3", PU_LEVEL);
-	ttspop4 = W_CachePatchName("TTSPOP4", PU_LEVEL);
-	ttspop5 = W_CachePatchName("TTSPOP5", PU_LEVEL);
-	ttspop6 = W_CachePatchName("TTSPOP6", PU_LEVEL);
-	ttspop7 = W_CachePatchName("TTSPOP7", PU_LEVEL);
+	switch(curttmode)
+	{
+		case TTMODE_OLD:
+		case TTMODE_NONE:
+			ttbanner = W_CachePatchName("TTBANNER", PU_LEVEL);
+			ttwing = W_CachePatchName("TTWING", PU_LEVEL);
+			ttsonic = W_CachePatchName("TTSONIC", PU_LEVEL);
+			ttswave1 = W_CachePatchName("TTSWAVE1", PU_LEVEL);
+			ttswave2 = W_CachePatchName("TTSWAVE2", PU_LEVEL);
+			ttswip1 = W_CachePatchName("TTSWIP1", PU_LEVEL);
+			ttsprep1 = W_CachePatchName("TTSPREP1", PU_LEVEL);
+			ttsprep2 = W_CachePatchName("TTSPREP2", PU_LEVEL);
+			ttspop1 = W_CachePatchName("TTSPOP1", PU_LEVEL);
+			ttspop2 = W_CachePatchName("TTSPOP2", PU_LEVEL);
+			ttspop3 = W_CachePatchName("TTSPOP3", PU_LEVEL);
+			ttspop4 = W_CachePatchName("TTSPOP4", PU_LEVEL);
+			ttspop5 = W_CachePatchName("TTSPOP5", PU_LEVEL);
+			ttspop6 = W_CachePatchName("TTSPOP6", PU_LEVEL);
+			ttspop7 = W_CachePatchName("TTSPOP7", PU_LEVEL);
+			break;
+
+		// don't load alacroix gfx yet; we do that upon first draw.
+		case TTMODE_ALACROIX:
+			break;
+
+		case TTMODE_USER:
+		{
+			UINT16 i;
+			lumpnum_t lumpnum;
+			char lumpname[9];
+
+			LOADTTGFX(ttuser, curttname, TTMAX_USER)
+			break;
+		}
+	}
+}
+
+static void F_UnloadAlacroixGraphics(SINT8 oldttscale)
+{
+	// This all gets freed by PU_LEVEL when exiting the menus.
+	// When re-visiting the menus (e.g., from exiting in-game), the gfx are force-reloaded.
+	// So leftover addresses here should not be a problem.
+
+	UINT16 i;
+	oldttscale--; // zero-based index
+	for (i = 0; i < TTMAX_ALACROIX; i++)
+	{
+		if(ttembl[oldttscale][i]) { Z_Free(ttembl[oldttscale][i]); ttembl[oldttscale][i] = 0; }
+		if(ttribb[oldttscale][i]) { Z_Free(ttribb[oldttscale][i]); ttribb[oldttscale][i] = 0; }
+		if(ttsont[oldttscale][i]) { Z_Free(ttsont[oldttscale][i]); ttsont[oldttscale][i] = 0; }
+		if(ttrobo[oldttscale][i]) { Z_Free(ttrobo[oldttscale][i]); ttrobo[oldttscale][i] = 0; }
+		if(tttwot[oldttscale][i]) { Z_Free(tttwot[oldttscale][i]); tttwot[oldttscale][i] = 0; }
+		if(ttrbtx[oldttscale][i]) { Z_Free(ttrbtx[oldttscale][i]); ttrbtx[oldttscale][i] = 0; }
+		if(ttsoib[oldttscale][i]) { Z_Free(ttsoib[oldttscale][i]); ttsoib[oldttscale][i] = 0; }
+		if(ttsoif[oldttscale][i]) { Z_Free(ttsoif[oldttscale][i]); ttsoif[oldttscale][i] = 0; }
+		if(ttsoba[oldttscale][i]) { Z_Free(ttsoba[oldttscale][i]); ttsoba[oldttscale][i] = 0; }
+		if(ttsobk[oldttscale][i]) { Z_Free(ttsobk[oldttscale][i]); ttsobk[oldttscale][i] = 0; }
+		if(ttsodh[oldttscale][i]) { Z_Free(ttsodh[oldttscale][i]); ttsodh[oldttscale][i] = 0; }
+		if(tttaib[oldttscale][i]) { Z_Free(tttaib[oldttscale][i]); tttaib[oldttscale][i] = 0; }
+		if(tttaif[oldttscale][i]) { Z_Free(tttaif[oldttscale][i]); tttaif[oldttscale][i] = 0; }
+		if(tttaba[oldttscale][i]) { Z_Free(tttaba[oldttscale][i]); tttaba[oldttscale][i] = 0; }
+		if(tttabk[oldttscale][i]) { Z_Free(tttabk[oldttscale][i]); tttabk[oldttscale][i] = 0; }
+		if(tttabt[oldttscale][i]) { Z_Free(tttabt[oldttscale][i]); tttabt[oldttscale][i] = 0; }
+		if(tttaft[oldttscale][i]) { Z_Free(tttaft[oldttscale][i]); tttaft[oldttscale][i] = 0; }
+		if(ttknib[oldttscale][i]) { Z_Free(ttknib[oldttscale][i]); ttknib[oldttscale][i] = 0; }
+		if(ttknif[oldttscale][i]) { Z_Free(ttknif[oldttscale][i]); ttknif[oldttscale][i] = 0; }
+		if(ttknba[oldttscale][i]) { Z_Free(ttknba[oldttscale][i]); ttknba[oldttscale][i] = 0; }
+		if(ttknbk[oldttscale][i]) { Z_Free(ttknbk[oldttscale][i]); ttknbk[oldttscale][i] = 0; }
+		if(ttkndh[oldttscale][i]) { Z_Free(ttkndh[oldttscale][i]); ttkndh[oldttscale][i] = 0; }
+	}
+	ttloaded[oldttscale] = false;
+}
+
+static void F_LoadAlacroixGraphics(SINT8 newttscale)
+{
+	UINT16 i, j;
+	lumpnum_t lumpnum;
+	char lumpname[9];
+	char names[22][5] = {
+		"EMBL",
+		"RIBB",
+		"SONT",
+		"ROBO",
+		"TWOT",
+		"RBTX",
+		"SOIB",
+		"SOIF",
+		"SOBA",
+		"SOBK",
+		"SODH",
+		"TAIB",
+		"TAIF",
+		"TABA",
+		"TABK",
+		"TABT",
+		"TAFT",
+		"KNIB",
+		"KNIF",
+		"KNBA",
+		"KNBK",
+		"KNDH"
+	};
+	char lumpnames[22][7];
+
+	newttscale--; // 0-based index
+
+	if (!ttloaded[newttscale])
+	{
+		for (j = 0; j < 22; j++)
+			sprintf(&lumpnames[j][0], "T%.1hu%s", (UINT8)newttscale+1, names[j]);
+
+		LOADTTGFX(ttembl[newttscale], lumpnames[0], TTMAX_ALACROIX)
+		LOADTTGFX(ttribb[newttscale], lumpnames[1], TTMAX_ALACROIX)
+		LOADTTGFX(ttsont[newttscale], lumpnames[2], TTMAX_ALACROIX)
+		LOADTTGFX(ttrobo[newttscale], lumpnames[3], TTMAX_ALACROIX)
+		LOADTTGFX(tttwot[newttscale], lumpnames[4], TTMAX_ALACROIX)
+		LOADTTGFX(ttrbtx[newttscale], lumpnames[5], TTMAX_ALACROIX)
+		LOADTTGFX(ttsoib[newttscale], lumpnames[6], TTMAX_ALACROIX)
+		LOADTTGFX(ttsoif[newttscale], lumpnames[7], TTMAX_ALACROIX)
+		LOADTTGFX(ttsoba[newttscale], lumpnames[8], TTMAX_ALACROIX)
+		LOADTTGFX(ttsobk[newttscale], lumpnames[9], TTMAX_ALACROIX)
+		LOADTTGFX(ttsodh[newttscale], lumpnames[10], TTMAX_ALACROIX)
+		LOADTTGFX(tttaib[newttscale], lumpnames[11], TTMAX_ALACROIX)
+		LOADTTGFX(tttaif[newttscale], lumpnames[12], TTMAX_ALACROIX)
+		LOADTTGFX(tttaba[newttscale], lumpnames[13], TTMAX_ALACROIX)
+		LOADTTGFX(tttabk[newttscale], lumpnames[14], TTMAX_ALACROIX)
+		LOADTTGFX(tttabt[newttscale], lumpnames[15], TTMAX_ALACROIX)
+		LOADTTGFX(tttaft[newttscale], lumpnames[16], TTMAX_ALACROIX)
+		LOADTTGFX(ttknib[newttscale], lumpnames[17], TTMAX_ALACROIX)
+		LOADTTGFX(ttknif[newttscale], lumpnames[18], TTMAX_ALACROIX)
+		LOADTTGFX(ttknba[newttscale], lumpnames[19], TTMAX_ALACROIX)
+		LOADTTGFX(ttknbk[newttscale], lumpnames[20], TTMAX_ALACROIX)
+		LOADTTGFX(ttkndh[newttscale], lumpnames[21], TTMAX_ALACROIX)
+
+		ttloaded[newttscale] = true;
+	}
+}
+
+#undef LOADTTGFX
+
+static void F_FigureActiveTtScale(void)
+{
+	SINT8 newttscale = max(1, min(6, vid.dupx));
+	SINT8 oldttscale = activettscale;
+
+	if (newttscale == testttscale)
+		return;
+	testttscale = newttscale;
+
+	// If ttscale is unavailable: look for lower scales, then higher scales.
+	for (; newttscale >= 1; newttscale--)
+	{
+		if (ttavailable[newttscale-1])
+			break;
+	}
+
+	for (; newttscale <= 6; newttscale++)
+	{
+		if (ttavailable[newttscale-1])
+			break;
+	}
+
+	activettscale = (newttscale >= 1 && newttscale <= 6) ? newttscale : 0;
+
+	// We have a new ttscale, so load gfx
+	if(oldttscale > 0)
+		F_UnloadAlacroixGraphics(oldttscale);
+
+	if(activettscale > 0)
+		F_LoadAlacroixGraphics(activettscale);
 }
 
 // (no longer) De-Demo'd Title Screen
 void F_TitleScreenDrawer(void)
 {
 	boolean hidepics;
+	fixed_t sc = FRACUNIT / max(1, curttscale);
 
 	if (modeattacking)
 		return; // We likely came here from retrying. Don't do a damn thing.
@@ -2285,11 +2580,13 @@ void F_TitleScreenDrawer(void)
 	// Draw that sky!
 	if (curbgcolor >= 0)
 		V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, curbgcolor);
-	else if (!curbghide || !titlemapinaction || gamestate == GS_WAITINGPLAYERS)
+	else if (titlemapinaction && curbghide && ! hidetitlemap)
+		D_Render();
+	else
 		F_SkyScroll(curbgxspeed, curbgyspeed, curbgname);
 
 	// Don't draw outside of the title screen, or if the patch isn't there.
-	if (!ttwing || (gamestate != GS_TITLESCREEN && gamestate != GS_WAITINGPLAYERS))
+	if (gamestate != GS_TITLESCREEN && gamestate != GS_WAITINGPLAYERS)
 		return;
 
 	// rei|miru: use title pics?
@@ -2301,42 +2598,701 @@ void F_TitleScreenDrawer(void)
 		return;
 #endif
 
-	V_DrawScaledPatch(30, 14, 0, ttwing);
-
-	if (finalecount < 57)
-	{
-		if (finalecount == 35)
-			V_DrawScaledPatch(115, 15, 0, ttspop1);
-		else if (finalecount == 36)
-			V_DrawScaledPatch(114, 15, 0,ttspop2);
-		else if (finalecount == 37)
-			V_DrawScaledPatch(113, 15, 0,ttspop3);
-		else if (finalecount == 38)
-			V_DrawScaledPatch(112, 15, 0,ttspop4);
-		else if (finalecount == 39)
-			V_DrawScaledPatch(111, 15, 0,ttspop5);
-		else if (finalecount == 40)
-			V_DrawScaledPatch(110, 15, 0, ttspop6);
-		else if (finalecount >= 41 && finalecount <= 44)
-			V_DrawScaledPatch(109, 15, 0, ttspop7);
-		else if (finalecount >= 45 && finalecount <= 48)
-			V_DrawScaledPatch(108, 12, 0, ttsprep1);
-		else if (finalecount >= 49 && finalecount <= 52)
-			V_DrawScaledPatch(107, 9, 0, ttsprep2);
-		else if (finalecount >= 53 && finalecount <= 56)
-			V_DrawScaledPatch(106, 6, 0, ttswip1);
-		V_DrawScaledPatch(93, 106, 0, ttsonic);
-	}
-	else
+	switch(curttmode)
 	{
-		V_DrawScaledPatch(93, 106, 0,ttsonic);
-		if (finalecount/5 & 1)
-			V_DrawScaledPatch(100, 3, 0,ttswave1);
-		else
-			V_DrawScaledPatch(100,3, 0,ttswave2);
-	}
+		case TTMODE_OLD:
+		case TTMODE_NONE:
+			V_DrawSciencePatch(30<<FRACBITS, 14<<FRACBITS, 0, ttwing, sc);
+
+			if (finalecount < 57)
+			{
+				if (finalecount == 35)
+					V_DrawSciencePatch(115<<FRACBITS, 15<<FRACBITS, 0, ttspop1, sc);
+				else if (finalecount == 36)
+					V_DrawSciencePatch(114<<FRACBITS, 15<<FRACBITS, 0,ttspop2, sc);
+				else if (finalecount == 37)
+					V_DrawSciencePatch(113<<FRACBITS, 15<<FRACBITS, 0,ttspop3, sc);
+				else if (finalecount == 38)
+					V_DrawSciencePatch(112<<FRACBITS, 15<<FRACBITS, 0,ttspop4, sc);
+				else if (finalecount == 39)
+					V_DrawSciencePatch(111<<FRACBITS, 15<<FRACBITS, 0,ttspop5, sc);
+				else if (finalecount == 40)
+					V_DrawSciencePatch(110<<FRACBITS, 15<<FRACBITS, 0, ttspop6, sc);
+				else if (finalecount >= 41 && finalecount <= 44)
+					V_DrawSciencePatch(109<<FRACBITS, 15<<FRACBITS, 0, ttspop7, sc);
+				else if (finalecount >= 45 && finalecount <= 48)
+					V_DrawSciencePatch(108<<FRACBITS, 12<<FRACBITS, 0, ttsprep1, sc);
+				else if (finalecount >= 49 && finalecount <= 52)
+					V_DrawSciencePatch(107<<FRACBITS, 9<<FRACBITS, 0, ttsprep2, sc);
+				else if (finalecount >= 53 && finalecount <= 56)
+					V_DrawSciencePatch(106<<FRACBITS, 6<<FRACBITS, 0, ttswip1, sc);
+				V_DrawSciencePatch(93<<FRACBITS, 106<<FRACBITS, 0, ttsonic, sc);
+			}
+			else
+			{
+				V_DrawSciencePatch(93<<FRACBITS, 106<<FRACBITS, 0,ttsonic, sc);
+				if (finalecount/5 & 1)
+					V_DrawSciencePatch(100<<FRACBITS, 3<<FRACBITS, 0,ttswave1, sc);
+				else
+					V_DrawSciencePatch(100<<FRACBITS, 3<<FRACBITS, 0,ttswave2, sc);
+			}
+
+			V_DrawSciencePatch(48<<FRACBITS, 142<<FRACBITS, 0,ttbanner, sc);
+			break;
+
+		case TTMODE_ALACROIX:
+			//
+			// PRE-INTRO: WING ON BLACK BACKGROUND
+			//
+
+			// Figure the gfx scale and load gfx if necessary
+			F_FigureActiveTtScale();
+
+			if (!activettscale) // invalid scale, draw nothing
+				break;
+			sc = FRACUNIT / activettscale;
+
+			// Start at black background. Draw it until tic 30, where we replace with a white flash.
+			//
+			// TODO: How to NOT draw the titlemap while this background is drawn?
+			//
+			if (finalecount <= 29)
+				V_DrawFill(0, 0, BASEVIDWIDTH, BASEVIDHEIGHT, 31);
+
+			// Draw emblem
+			V_DrawSciencePatch(40<<FRACBITS, 20<<FRACBITS, 0, TTEMBL[0], sc);
+
+			// Animate SONIC ROBO BLAST 2 before the white flash at tic 30.
+			if (finalecount <= 29)
+			{
+				// Ribbon unfurls, revealing SONIC text, from tic 0 to tic 24. SONIC text is pre-baked into this ribbon graphic.
+				V_DrawSciencePatch(39<<FRACBITS, 88<<FRACBITS, 0, TTRIBB[min(max(0, finalecount), 24)], sc);
+
+				// Animate SONIC text while the ribbon unfurls, from tic 0 to tic 28.
+				if(finalecount >= 0)
+					V_DrawSciencePatch(89<<FRACBITS, 92<<FRACBITS, 0, TTSONT[min(finalecount, 28)], sc);
+
+				// Fade in ROBO BLAST 2 starting at tic 10.
+				if (finalecount > 9)
+				{
+					INT32 fadeval = 0;
+
+					// Fade between tic 10 and tic 29.
+					if (finalecount < 30)
+					{
+						UINT8 fadecounter = 30-finalecount;
+						switch(fadecounter)
+						{
+							case 20: case 19: fadeval = V_90TRANS; break;
+							case 18: case 17: fadeval = V_80TRANS; break;
+							case 16: case 15: fadeval = V_70TRANS; break;
+							case 14: case 13: fadeval = V_60TRANS; break;
+							case 12: case 11: fadeval = V_TRANSLUCENT; break;
+							case 10: case 9: fadeval = V_40TRANS; break;
+							case 8: case 7: fadeval = V_30TRANS; break;
+							case 6: case 5: fadeval = V_20TRANS; break;
+							case 4: case 3: fadeval = V_10TRANS; break;
+						}
+					}
+					V_DrawSciencePatch(79<<FRACBITS, 132<<FRACBITS, fadeval, TTROBO[0], sc);
+
+					// Draw the TWO from tic 16 to tic 31, so the TWO lands right when the screen flashes white.
+					if(finalecount > 15)
+						V_DrawSciencePatch(106<<FRACBITS, 118<<FRACBITS, fadeval, TTTWOT[min(finalecount-16, 15)], sc);
+				}
+			}
+
+			//
+			// ALACROIX CHARACTER FRAMES
+			//
+			// Start all animation from tic 34 (or whenever the white flash begins to fade; see below.)
+			// Delay the start a bit for better music timing.
+			//
+
+#define CHARSTART 41
+#define SONICSTART (CHARSTART+0)
+#define SONICIDLE (SONICSTART+57)
+#define SONICX 89
+#define SONICY 13
+#define TAILSSTART (CHARSTART+27)
+#define TAILSIDLE (TAILSSTART+60)
+#define TAILSX 35
+#define TAILSY 19
+#define KNUXSTART (CHARSTART+44)
+#define KNUXIDLE (KNUXSTART+70)
+#define KNUXX 167
+#define KNUXY 7
+
+			// Decide who gets to blink or not.
+			// Make this decision at the END of an idle/blink cycle.
+			// Upon first idle, both idle_start and idle_end will be 0.
+
+			if (finalecount >= KNUXIDLE)
+			{
+				if (!knux_idle_start || finalecount - knux_idle_start >= knux_idle_end)
+				{
+					if (knux_blink)
+					{
+						knux_blink = false; // don't run the cycle twice in a row
+						knux_blinked_already = true;
+					}
+					else if (knux_blinked_already) // or after the first non-blink cycle, either.
+						knux_blinked_already = false;
+					else
+					{
+						// make this chance higher than Sonic/Tails because Knux's idle cycle is longer
+						knux_blink = !(M_RandomKey(100) % 2);
+						knux_blink_twice = knux_blink ? !(M_RandomKey(100) % 5) : false;
+					}
+					knux_idle_start = finalecount;
+				}
+
+				knux_idle_end = knux_blink ? (knux_blink_twice ? 17 : 7) : 46;
+			}
+
+			if (finalecount >= TAILSIDLE)
+			{
+				if (!tails_idle_start || finalecount - tails_idle_start >= tails_idle_end)
+				{
+					if (tails_blink)
+					{
+						tails_blink = false; // don't run the cycle twice in a row
+						tails_blinked_already = true;
+					}
+					else if (tails_blinked_already) // or after the first non-blink cycle, either.
+						tails_blinked_already = false;
+					else
+					{
+						tails_blink = !(M_RandomKey(100) % 3);
+						tails_blink_twice = tails_blink ? !(M_RandomKey(100) % 5) : false;
+					}
+					tails_idle_start = finalecount;
+				}
+
+				// Tails does not actually have a non-blink idle cycle, but make up a number
+				// so he can still blink.
+				tails_idle_end = tails_blink ? (tails_blink_twice ? 17 : 7) : 30;
+			}
+
+			if (finalecount >= SONICIDLE)
+			{
+				if (!sonic_idle_start || finalecount - sonic_idle_start >= sonic_idle_end)
+				{
+					if (sonic_blink)
+					{
+						sonic_blink = false; // don't run the cycle twice in a row
+						sonic_blinked_already = true;
+					}
+					else if (sonic_blinked_already) // or after the first non-blink cycle, either.
+						sonic_blinked_already = false;
+					else
+					{
+						sonic_blink = !(M_RandomKey(100) % 3);
+						sonic_blink_twice = sonic_blink ? !(M_RandomKey(100) % 5) : false;
+					}
+					sonic_idle_start = finalecount;
+				}
+
+				sonic_idle_end = sonic_blink ? (sonic_blink_twice ? 17 : 7) : 25;
+			}
+
+
+			//
+			// BACK TAIL LAYER
+			//
+
+			if (finalecount >= TAILSSTART)
+			{
+				if (finalecount >= TAILSIDLE)
+				{
+					//
+					// Tails Back Tail Layer Idle
+					//
+					SINT8 taftcount = (finalecount - (TAILSIDLE)) % 41;
+					if      (taftcount >= 0   && taftcount < 5  )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABT[0 ], sc);
+					else if (taftcount >= 5   && taftcount < 9 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABT[1 ], sc);
+					else if (taftcount >= 9   && taftcount < 12 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABT[2 ], sc);
+					else if (taftcount >= 12  && taftcount < 14 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABT[3 ], sc);
+					else if (taftcount >= 14  && taftcount < 17 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABT[4 ], sc);
+					else if (taftcount >= 17  && taftcount < 21 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABT[5 ], sc);
+					else if (taftcount >= 21  && taftcount < 24 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABT[6 ], sc);
+					else if (taftcount >= 24  && taftcount < 25 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABT[7 ], sc);
+					else if (taftcount >= 25  && taftcount < 28 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABT[8 ], sc);
+					else if (taftcount >= 28  && taftcount < 31 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABT[9 ], sc);
+					else if (taftcount >= 31  && taftcount < 35 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABT[10], sc);
+					else if (taftcount >= 35  && taftcount < 41 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABT[11], sc);
+				}
+			}
+
+			//
+			// FRONT TAIL LAYER
+			//
+
+			if (finalecount >= TAILSSTART)
+			{
+				if (finalecount >= TAILSIDLE)
+				{
+					//
+					// Tails Front Tail Layer Idle
+					//
+					SINT8 tabtcount = (finalecount - (TAILSIDLE)) % 41;
+					if      (tabtcount >= 0   && tabtcount < 6  )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAFT[0 ], sc);
+					else if (tabtcount >= 6   && tabtcount < 11 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAFT[1 ], sc);
+					else if (tabtcount >= 11  && tabtcount < 15 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAFT[2 ], sc);
+					else if (tabtcount >= 15  && tabtcount < 18 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAFT[3 ], sc);
+					else if (tabtcount >= 18  && tabtcount < 19 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAFT[4 ], sc);
+					else if (tabtcount >= 19  && tabtcount < 22 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAFT[5 ], sc);
+					else if (tabtcount >= 22  && tabtcount < 27 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAFT[6 ], sc);
+					else if (tabtcount >= 27  && tabtcount < 30 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAFT[7 ], sc);
+					else if (tabtcount >= 30  && tabtcount < 31 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAFT[8 ], sc);
+					else if (tabtcount >= 31  && tabtcount < 34 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAFT[9 ], sc);
+					else if (tabtcount >= 34  && tabtcount < 37 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAFT[10], sc);
+					else if (tabtcount >= 37  && tabtcount < 41 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAFT[11], sc);
+				}
+			}
+
+			//
+			// BACK LAYER CHARACTERS
+			//
+
+			if (finalecount >= KNUXSTART)
+			{
+				if (finalecount < KNUXIDLE)
+				{
+					//
+					// Knux Back Layer Intro
+					//
+					if      (finalecount >= KNUXSTART+0   && finalecount < KNUXSTART+6  )
+						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIB[0 ], sc);
+					else if (finalecount >= KNUXSTART+6   && finalecount < KNUXSTART+10 )
+						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIB[1 ], sc);
+					else if (finalecount >= KNUXSTART+10  && finalecount < KNUXSTART+13 )
+						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIB[2 ], sc);
+					else if (finalecount >= KNUXSTART+13  && finalecount < KNUXSTART+15 )
+						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIB[3 ], sc);
+					else if (finalecount >= KNUXSTART+15  && finalecount < KNUXSTART+18 )
+						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIB[4 ], sc);
+					else if (finalecount >= KNUXSTART+18  && finalecount < KNUXSTART+22 )
+						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIB[5 ], sc);
+					else if (finalecount >= KNUXSTART+22  && finalecount < KNUXSTART+28 )
+						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIB[6 ], sc);
+					else if (finalecount >= KNUXSTART+28  && finalecount < KNUXSTART+32 )
+						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIB[7 ], sc);
+					else if (finalecount >= KNUXSTART+32  && finalecount < KNUXSTART+35 )
+						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIB[8 ], sc);
+					else if (finalecount >= KNUXSTART+35  && finalecount < KNUXSTART+40 )
+						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIB[9 ], sc);
+					else if (finalecount >= KNUXSTART+40  && finalecount < KNUXSTART+41 )
+						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIB[10], sc);
+					else if (finalecount >= KNUXSTART+41  && finalecount < KNUXSTART+44 )
+						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIB[11], sc);
+					else if (finalecount >= KNUXSTART+44  && finalecount < KNUXSTART+50 )
+						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIB[12], sc);
+					else if (finalecount >= KNUXSTART+50  && finalecount < KNUXSTART+56 )
+						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIB[13], sc);
+					else if (finalecount >= KNUXSTART+56  && finalecount < KNUXSTART+57 )
+						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIB[14], sc);
+					else if (finalecount >= KNUXSTART+57  && finalecount < KNUXSTART+60 )
+						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIB[15], sc);
+					else if (finalecount >= KNUXSTART+60  && finalecount < KNUXSTART+63 )
+						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIB[16], sc);
+					else if (finalecount >= KNUXSTART+63  && finalecount < KNUXSTART+67 )
+						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIB[17], sc);
+					else if (finalecount >= KNUXSTART+67  && finalecount < KNUXSTART+70 )
+						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIB[18], sc);
+					// Start idle animation (frame K20-B)
+				}
+				else
+				{
+					//
+					// Knux Back Layer Idle
+					//
+					if (!knux_blink)
+						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNBA[0], sc);
+					else
+					{
+						//
+						// Knux Blinking
+						//
+						SINT8 idlecount = finalecount - knux_idle_start;
+						if      (idlecount >= 0  && idlecount < 2 )
+							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNBK[0], sc);
+						else if (idlecount >= 2  && idlecount < 6 )
+							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNBK[1], sc);
+						else if (idlecount >= 6  && idlecount < 7 )
+							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNBK[2], sc);
+						// We reach this point if knux_blink_twice == true
+						else if (idlecount >= 7  && idlecount < 10)
+							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNBA[0], sc);
+						else if (idlecount >= 10 && idlecount < 12)
+							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNBK[0], sc);
+						else if (idlecount >= 12 && idlecount < 16)
+							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNBK[1], sc);
+						else if (idlecount >= 16 && idlecount < 17)
+							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNBK[2], sc);
+					}
+				}
+			}
 
-	V_DrawScaledPatch(48, 142, 0,ttbanner);
+			if (finalecount >= TAILSSTART)
+			{
+				if (finalecount < TAILSIDLE)
+				{
+					//
+					// Tails Back Layer Intro
+					//
+					if      (finalecount >= TAILSSTART+0   && finalecount < TAILSSTART+6  )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIB[0 ], sc);
+					else if (finalecount >= TAILSSTART+6   && finalecount < TAILSSTART+10 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIB[1 ], sc);
+					else if (finalecount >= TAILSSTART+10  && finalecount < TAILSSTART+12 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIB[2 ], sc);
+					else if (finalecount >= TAILSSTART+12  && finalecount < TAILSSTART+16 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIB[3 ], sc);
+					else if (finalecount >= TAILSSTART+16  && finalecount < TAILSSTART+22 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIB[4 ], sc);
+					else if (finalecount >= TAILSSTART+22  && finalecount < TAILSSTART+23 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIB[5 ], sc);
+					else if (finalecount >= TAILSSTART+23  && finalecount < TAILSSTART+26 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIB[6 ], sc);
+					else if (finalecount >= TAILSSTART+26  && finalecount < TAILSSTART+30 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIB[7 ], sc);
+					else if (finalecount >= TAILSSTART+30  && finalecount < TAILSSTART+35 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIB[8 ], sc);
+					else if (finalecount >= TAILSSTART+35  && finalecount < TAILSSTART+41 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIB[9 ], sc);
+					else if (finalecount >= TAILSSTART+41  && finalecount < TAILSSTART+43 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIB[10], sc);
+					else if (finalecount >= TAILSSTART+43  && finalecount < TAILSSTART+47 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIB[11], sc);
+					else if (finalecount >= TAILSSTART+47  && finalecount < TAILSSTART+51 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIB[12], sc);
+					else if (finalecount >= TAILSSTART+51  && finalecount < TAILSSTART+53 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIB[13], sc);
+					else if (finalecount >= TAILSSTART+53  && finalecount < TAILSSTART+56 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIB[14], sc);
+					else if (finalecount >= TAILSSTART+56  && finalecount < TAILSSTART+60 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIB[15], sc);
+					// Start idle animation (frame T17-B)
+				}
+				else
+				{
+					//
+					// Tails Back Layer Idle
+					//
+					if (!tails_blink)
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABA[0], sc);
+					else
+					{
+						//
+						// Tails Blinking
+						//
+						SINT8 idlecount = finalecount - tails_idle_start;
+						if      (idlecount >= +0  && idlecount < +2 )
+							V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABK[0], sc);
+						else if (idlecount >= +2  && idlecount < +6 )
+							V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABK[1], sc);
+						else if (idlecount >= +6  && idlecount < +7 )
+							V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABK[2], sc);
+						// We reach this point if tails_blink_twice == true
+						else if (idlecount >= +7  && idlecount < +10)
+							V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABA[0], sc);
+						else if (idlecount >= +10 && idlecount < +12)
+							V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABK[0], sc);
+						else if (idlecount >= +12 && idlecount < +16)
+							V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABK[1], sc);
+						else if (idlecount >= +16 && idlecount < +17)
+							V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTABK[2], sc);
+					}
+				}
+			}
+
+			if (finalecount >= SONICSTART)
+			{
+				if (finalecount < SONICIDLE)
+				{
+					//
+					// Sonic Back Layer Intro
+					//
+					if      (finalecount >= SONICSTART+0   && finalecount < SONICSTART+6  )
+						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIB[0 ], sc);
+					else if (finalecount >= SONICSTART+6   && finalecount < SONICSTART+11 )
+						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIB[1 ], sc);
+					else if (finalecount >= SONICSTART+11  && finalecount < SONICSTART+14 )
+						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIB[2 ], sc);
+					else if (finalecount >= SONICSTART+14  && finalecount < SONICSTART+18 )
+						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIB[3 ], sc);
+					else if (finalecount >= SONICSTART+18  && finalecount < SONICSTART+19 )
+						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIB[4 ], sc);
+					else if (finalecount >= SONICSTART+19  && finalecount < SONICSTART+27 )
+						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIB[5 ], sc);
+					else if (finalecount >= SONICSTART+27  && finalecount < SONICSTART+31 )
+						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIB[6 ], sc);
+					//else if (finalecount >= SONICSTART+31  && finalecount < SONICSTART+33 )
+					//  Frame is blank
+					//	V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIB[7 ], sc);
+					else if (finalecount >= SONICSTART+33  && finalecount < SONICSTART+36 )
+						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIB[8 ], sc);
+					else if (finalecount >= SONICSTART+36  && finalecount < SONICSTART+40 )
+						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIB[9 ], sc);
+					else if (finalecount >= SONICSTART+40  && finalecount < SONICSTART+44 )
+						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIB[10], sc);
+					else if (finalecount >= SONICSTART+44  && finalecount < SONICSTART+47 )
+						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIB[11], sc);
+					else if (finalecount >= SONICSTART+47  && finalecount < SONICSTART+49 )
+						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIB[12], sc);
+					else if (finalecount >= SONICSTART+49  && finalecount < SONICSTART+50 )
+						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIB[13], sc);
+					else if (finalecount >= SONICSTART+50  && finalecount < SONICSTART+53 )
+						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIB[14], sc);
+					else if (finalecount >= SONICSTART+53  && finalecount < SONICSTART+57 )
+						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIB[15], sc);
+					// Start idle animation (frame S17-B)
+				}
+				else
+				{
+					//
+					// Sonic Back Layer Idle
+					//
+					if (!sonic_blink)
+						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOBA[0], sc);
+					else
+					{
+						//
+						// Sonic Blinking
+						//
+						SINT8 idlecount = finalecount - sonic_idle_start;
+						if      (idlecount >= 0  && idlecount < 2 )
+							V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOBK[0], sc);
+						else if (idlecount >= 2  && idlecount < 6 )
+							V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOBK[1], sc);
+						else if (idlecount >= 6  && idlecount < 7 )
+							V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOBK[2], sc);
+						// We reach this point if sonic_blink_twice == true
+						else if (idlecount >= 7  && idlecount < 10)
+							V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOBA[0], sc);
+						else if (idlecount >= 10 && idlecount < 12)
+							V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOBK[0], sc);
+						else if (idlecount >= 12 && idlecount < 16)
+							V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOBK[1], sc);
+						else if (idlecount >= 16 && idlecount < 17)
+							V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOBK[2], sc);
+					}
+				}
+			}
+
+			//
+			// LOGO LAYER
+			//
+
+			// After tic 34, starting when the flash fades,
+			// draw the combined ribbon and SONIC ROBO BLAST 2 logo. Note the different Y value, because this
+			// graphic is cropped differently from the unfurling ribbon.
+			if (finalecount > 34)
+				V_DrawSciencePatch(39<<FRACBITS, 93<<FRACBITS, 0, TTRBTX[0], sc);
+
+			//
+			// FRONT LAYER CHARACTERS
+			//
+
+			if (finalecount >= KNUXSTART)
+			{
+				if (finalecount < KNUXIDLE)
+				{
+					//
+					// Knux Front Layer Intro
+					//
+					if      (finalecount >= KNUXSTART+22  && finalecount < KNUXSTART+28 )
+						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIF[6 ], sc);
+					else if (finalecount >= KNUXSTART+28  && finalecount < KNUXSTART+32 )
+						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIF[7 ], sc);
+					else if (finalecount >= KNUXSTART+32  && finalecount < KNUXSTART+35 )
+						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNIF[8 ], sc);
+				}
+				else
+				{
+					//
+					// Knux Front Layer Idle
+					//
+					if (!knux_blink)
+					{
+						SINT8 idlecount = finalecount - knux_idle_start;
+						if      (idlecount >= 0  && idlecount < 5 )
+							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNDH[0 ], sc);
+						else if (idlecount >= 5  && idlecount < 10)
+							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNDH[1 ], sc);
+						else if (idlecount >= 10 && idlecount < 13)
+							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNDH[2 ], sc);
+						else if (idlecount >= 13 && idlecount < 14)
+							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNDH[3 ], sc);
+						else if (idlecount >= 14 && idlecount < 17)
+							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNDH[4 ], sc);
+						else if (idlecount >= 17 && idlecount < 21)
+							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNDH[5 ], sc);
+						else if (idlecount >= 21 && idlecount < 27)
+							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNDH[6 ], sc);
+						else if (idlecount >= 27 && idlecount < 32)
+							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNDH[7 ], sc);
+						else if (idlecount >= 32 && idlecount < 34)
+							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNDH[8 ], sc);
+						else if (idlecount >= 34 && idlecount < 37)
+							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNDH[9 ], sc);
+						else if (idlecount >= 37 && idlecount < 39)
+							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNDH[10], sc);
+						else if (idlecount >= 39 && idlecount < 42)
+							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNDH[11], sc);
+						else if (idlecount >= 42 && idlecount < 46)
+							V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNDH[12], sc);
+					}
+					else
+						V_DrawSciencePatch(KNUXX<<FRACBITS, KNUXY<<FRACBITS, 0, TTKNDH[0 ], sc);
+				}
+			}
+
+			if (finalecount >= TAILSSTART)
+			{
+				if (finalecount < TAILSIDLE)
+				{
+					//
+					// Tails Front Layer Intro
+					//
+					if      (finalecount >= TAILSSTART+26  && finalecount < TAILSSTART+30 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIF[7 ], sc);
+					else if (finalecount >= TAILSSTART+30  && finalecount < TAILSSTART+35 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIF[8 ], sc);
+					else if (finalecount >= TAILSSTART+35  && finalecount < TAILSSTART+41 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIF[9 ], sc);
+					else if (finalecount >= TAILSSTART+41  && finalecount < TAILSSTART+43 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIF[10], sc);
+					else if (finalecount >= TAILSSTART+43  && finalecount < TAILSSTART+47 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIF[11], sc);
+					else if (finalecount >= TAILSSTART+47  && finalecount < TAILSSTART+51 )
+						V_DrawSciencePatch(TAILSX<<FRACBITS, TAILSY<<FRACBITS, 0, TTTAIF[12], sc);
+				}
+				// No Tails Front Layer Idle
+			}
+
+			if (finalecount >= SONICSTART)
+			{
+				if (finalecount < SONICIDLE)
+				{
+					//
+					// Sonic Front Layer Intro
+					//
+					if      (finalecount >= SONICSTART+19  && finalecount < SONICSTART+27 )
+						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIF[5 ], sc);
+					else if (finalecount >= SONICSTART+27  && finalecount < SONICSTART+31 )
+						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIF[6 ], sc);
+					else if (finalecount >= SONICSTART+31  && finalecount < SONICSTART+33 )
+						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIF[7 ], sc);
+					else if (finalecount >= SONICSTART+33  && finalecount < SONICSTART+36 )
+						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIF[8 ], sc);
+					else if (finalecount >= SONICSTART+36  && finalecount < SONICSTART+40 )
+						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIF[9 ], sc);
+					else if (finalecount >= SONICSTART+40  && finalecount < SONICSTART+44 )
+						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIF[10], sc);
+					else if (finalecount >= SONICSTART+44  && finalecount < SONICSTART+47 )
+						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIF[11], sc);
+					// ...
+					else if (finalecount >= SONICSTART+53  && finalecount < SONICSTART+57 )
+						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSOIF[15], sc);
+				}
+				else
+				{
+					//
+					// Sonic Front Layer Idle
+					//
+					if (!sonic_blink)
+					{
+						SINT8 idlecount = finalecount - sonic_idle_start;
+						if      (idlecount >= 0  && idlecount < 5 )
+							V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSODH[0], sc);
+						else if (idlecount >= 5  && idlecount < 8 )
+							V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSODH[1], sc);
+						else if (idlecount >= 8  && idlecount < 9 )
+							V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSODH[2], sc);
+						else if (idlecount >= 9  && idlecount < 12)
+							V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSODH[3], sc);
+						else if (idlecount >= 12 && idlecount < 17)
+							V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSODH[4], sc);
+						else if (idlecount >= 17 && idlecount < 19)
+							V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSODH[5], sc);
+						else if (idlecount >= 19 && idlecount < 21)
+							V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSODH[6], sc);
+						else if (idlecount >= 21 && idlecount < 22)
+							V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSODH[7], sc);
+						else if (idlecount >= 22 && idlecount < 25)
+							V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSODH[8], sc);
+					}
+					else
+						V_DrawSciencePatch(SONICX<<FRACBITS, SONICY<<FRACBITS, 0, TTSODH[0], sc);
+				}
+			}
+
+			// Flash at tic 30, timed to O__TITLE percussion. Hold the flash until tic 34.
+			// After tic 34, fade the flash until tic 44.
+			if (finalecount > 29 && finalecount < 35)
+				V_DrawFadeScreen(0, 9);
+			else if (finalecount > 34 && 44-finalecount > 0 && 44-finalecount < 10)
+				V_DrawFadeScreen(0, 44-finalecount);
+
+#undef CHARSTART
+#undef SONICSTART
+#undef SONICIDLE
+#undef SONICX
+#undef SONICY
+#undef TAILSSTART
+#undef TAILSIDLE
+#undef TAILSX
+#undef TAILSY
+#undef KNUXSTART
+#undef KNUXIDLE
+#undef KNUXX
+#undef KNUXY
+
+			break;
+
+		case TTMODE_USER:
+			if (!ttuser[max(0, ttuser_count)])
+			{
+				if(curttloop > -1 && ttuser[curttloop])
+					ttuser_count = curttloop;
+				else if (ttuser[max(0, ttuser_count-1)])
+					ttuser_count = max(0, ttuser_count-1);
+				else
+					break; // draw nothing
+			}
+
+			V_DrawSciencePatch(curttx<<FRACBITS, curtty<<FRACBITS, 0, ttuser[ttuser_count], sc);
+
+			if (!(finalecount % max(1, curtttics)))
+				ttuser_count++;
+			break;
+	}
 
 #ifdef HAVE_BLUA
 luahook:
@@ -2358,10 +3314,6 @@ void F_TitleScreenTicker(boolean run)
 	if (run)
 		finalecount++;
 
-	// don't trigger if doing anything besides idling on title
-	if (gameaction != ga_nothing || gamestate != GS_TITLESCREEN)
-		return;
-
 	// Execute the titlemap camera settings
 	if (titlemapinaction)
 	{
@@ -2408,6 +3360,10 @@ void F_TitleScreenTicker(boolean run)
 		}
 	}
 
+	// don't trigger if doing anything besides idling on title
+	if (gameaction != ga_nothing || gamestate != GS_TITLESCREEN)
+		return;
+
 	// no demos to play? or, are they disabled?
 	if (!cv_rollingdemos.value || !numDemos)
 		return;
diff --git a/src/f_finale.h b/src/f_finale.h
index 58c492c3d9578725a59df4049b78dc027810a2c6..f75f93c7745057f6b845054070af3a792bae6a3b 100644
--- a/src/f_finale.h
+++ b/src/f_finale.h
@@ -77,6 +77,28 @@ void F_ContinueDrawer(void);
 extern INT32 titlescrollxspeed;
 extern INT32 titlescrollyspeed;
 
+typedef enum
+{
+	TTMODE_NONE = 0,
+	TTMODE_OLD,
+	TTMODE_ALACROIX,
+	TTMODE_USER
+} ttmode_enum;
+
+#define TTMAX_ALACROIX 30 // max frames for SONIC typeface, plus one for NULL terminating entry
+#define TTMAX_USER 100
+
+extern ttmode_enum ttmode;
+extern UINT8 ttscale;
+// ttmode user vars
+extern char ttname[9];
+extern INT16 ttx;
+extern INT16 tty;
+extern INT16 ttloop;
+extern UINT16 tttics;
+extern boolean ttavailable[6];
+
+
 typedef enum
 {
 	TITLEMAP_OFF = 0,
@@ -89,13 +111,22 @@ typedef enum
 extern mobj_t *titlemapcameraref;
 extern char curbgname[9];
 extern SINT8 curfadevalue;
-extern boolean curhidepics;
 extern INT32 curbgcolor;
 extern INT32 curbgxspeed;
 extern INT32 curbgyspeed;
 extern boolean curbghide;
 extern boolean hidetitlemap;
 
+extern boolean curhidepics;
+extern ttmode_enum curttmode;
+extern UINT8 curttscale;
+// ttmode user vars
+extern char curttname[9];
+extern INT16 curttx;
+extern INT16 curtty;
+extern INT16 curttloop;
+extern UINT16 curtttics;
+
 #define TITLEBACKGROUNDACTIVE (curfadevalue >= 0 || curbgname[0])
 
 void F_InitMenuPresValues(void);
diff --git a/src/g_game.c b/src/g_game.c
index 44aaf7b1267df0de75f77011bce1176b59fec853..18d0cdfe849027a034deae84f95c760294534e70 100644
--- a/src/g_game.c
+++ b/src/g_game.c
@@ -104,7 +104,7 @@ UINT32 demoIdleTime  = 3*TICRATE;
 boolean timingdemo; // if true, exit with report on completion
 boolean nodrawers; // for comparative timing purposes
 boolean noblit; // for comparative timing purposes
-static tic_t demostarttime; // for comparative timing purposes
+tic_t demostarttime; // for comparative timing purposes
 
 boolean netgame; // only true if packets are broadcast
 boolean multiplayer;
@@ -2805,7 +2805,7 @@ void G_AddPlayer(INT32 playernum)
 
 			countplayers++;
 
-			if (!players->exiting)
+			if (!players[i].exiting)
 				notexiting++;
 
 			if (!(cv_coopstarposts.value && (gametype == GT_COOP) && (p->starpostnum < players[i].starpostnum)))
@@ -3360,6 +3360,11 @@ void G_LoadGameData(void)
 	// Allow saving of gamedata beyond this point
 	gamedataloaded = true;
 
+	if (M_CheckParm("-gamedata") && M_IsNextParm())
+	{
+		strlcpy(gamedatafilename, M_GetNextParm(), sizeof gamedatafilename);
+	}
+
 	if (M_CheckParm("-resetdata"))
 		return; // Don't load (essentially, reset).
 
@@ -6378,7 +6383,46 @@ boolean G_CheckDemoStatus(void)
 		timingdemo = false;
 		f1 = (double)demotime;
 		f2 = (double)framecount*TICRATE;
-		CONS_Printf(M_GetText("timed %u gametics in %d realtics\n%f seconds, %f avg fps\n"), leveltime,demotime,f1/TICRATE,f2/f1);
+
+		CONS_Printf(M_GetText("timed %u gametics in %d realtics - %u frames\n%f seconds, %f avg fps\n"),
+			leveltime,demotime,(UINT32)framecount,f1/TICRATE,f2/f1);
+
+		// CSV-readable timedemo results, for external parsing
+		if (timedemo_csv)
+		{
+			FILE *f;
+			const char *csvpath = va("%s"PATHSEP"%s", srb2home, "timedemo.csv");
+			const char *header = "id,demoname,seconds,avgfps,leveltime,demotime,framecount,ticrate,rendermode,vidmode,vidwidth,vidheight,procbits\n";
+			const char *rowformat = "\"%s\",\"%s\",%f,%f,%u,%d,%u,%u,%u,%u,%u,%u,%u\n";
+			boolean headerrow = !FIL_FileExists(csvpath);
+			UINT8 procbits = 0;
+
+			// Bitness
+			if (sizeof(void*) == 4)
+				procbits = 32;
+			else if (sizeof(void*) == 8)
+				procbits = 64;
+
+			f = fopen(csvpath, "a+");
+
+			if (f)
+			{
+				if (headerrow)
+					fputs(header, f);
+				fprintf(f, rowformat,
+					timedemo_csv_id,timedemo_name,f1/TICRATE,f2/f1,leveltime,demotime,(UINT32)framecount,TICRATE,rendermode,vid.modenum,vid.width,vid.height,procbits);
+				fclose(f);
+				CONS_Printf("Timedemo results saved to '%s'\n", csvpath);
+			}
+			else
+			{
+				// Just print the CSV output to console
+				CON_LogMessage(header);
+				CONS_Printf(rowformat,
+					timedemo_csv_id,timedemo_name,f1/TICRATE,f2/f1,leveltime,demotime,(UINT32)framecount,TICRATE,rendermode,vid.modenum,vid.width,vid.height,procbits);
+			}
+		}
+
 		if (restorecv_vidwait != cv_vidwait.value)
 			CV_SetValue(&cv_vidwait, restorecv_vidwait);
 		D_AdvanceDemo();
diff --git a/src/g_game.h b/src/g_game.h
index 8c775c238d9a5ed804b659efd1297e6d91f1d0eb..22abae17dea2c66d116fc109fe08101161d3c646 100644
--- a/src/g_game.h
+++ b/src/g_game.h
@@ -37,6 +37,7 @@ extern boolean playeringame[MAXPLAYERS];
 
 // demoplaying back and demo recording
 extern boolean demoplayback, titledemo, demorecording, timingdemo;
+extern tic_t demostarttime;
 
 // Quit after playing a demo from cmdline.
 extern boolean singledemo;
diff --git a/src/hardware/hw_cache.c b/src/hardware/hw_cache.c
index 5cfd0f61c52b989ccf368fe1338e2b2f856b311f..65f1002136742d79610deab135d92a7dace02721 100644
--- a/src/hardware/hw_cache.c
+++ b/src/hardware/hw_cache.c
@@ -505,43 +505,6 @@ static void HWR_ResizeBlock(INT32 originalwidth, INT32 originalheight,
 		if (blockheight < 1)
 			I_Error("3D GenerateTexture : too small");
 	}
-	else if (cv_voodoocompatibility.value)
-	{
-		if (originalwidth > 256 || originalheight > 256)
-		{
-			blockwidth = 256;
-			while (originalwidth < blockwidth)
-				blockwidth >>= 1;
-			if (blockwidth < 1)
-				I_Error("3D GenerateTexture : too small");
-
-			blockheight = 256;
-			while (originalheight < blockheight)
-				blockheight >>= 1;
-			if (blockheight < 1)
-				I_Error("3D GenerateTexture : too small");
-		}
-		else
-		{
-			//size up to nearest power of 2
-			blockwidth = 1;
-			while (blockwidth < originalwidth)
-				blockwidth <<= 1;
-			// scale down the original graphics to fit in 256
-			if (blockwidth > 256)
-				blockwidth = 256;
-				//I_Error("3D GenerateTexture : too big");
-
-			//size up to nearest power of 2
-			blockheight = 1;
-			while (blockheight < originalheight)
-				blockheight <<= 1;
-			// scale down the original graphics to fit in 256
-			if (blockheight > 256)
-				blockheight = 255;
-				//I_Error("3D GenerateTexture : too big");
-		}
-	}
 	else
 	{
 #ifdef GLIDE_API_COMPATIBILITY
@@ -770,18 +733,6 @@ void HWR_MakePatch (const patch_t *patch, GLPatch_t *grPatch, GLMipmap_t *grMipm
 		newwidth = blockwidth;
 		newheight = blockheight;
 	}
-	else if (cv_voodoocompatibility.value) // Only scales down textures that exceed 256x256.
-	{
-		// no rounddown, do not size up patches, so they don't look 'scaled'
-		newwidth  = min(grPatch->width, blockwidth);
-		newheight = min(grPatch->height, blockheight);
-
-		if (newwidth > 256 || newheight > 256)
-		{
-			newwidth = blockwidth;
-			newheight = blockheight;
-		}
-	}
 	else
 	{
 		// no rounddown, do not size up patches, so they don't look 'scaled'
@@ -825,10 +776,10 @@ static void FreeMipmapColormap(INT32 patchnum, void *patch)
 {
 	GLPatch_t* const grpatch = patch;
 	(void)patchnum; //unused
-	while (grpatch->mipmap.nextcolormap)
+	while (grpatch->mipmap->nextcolormap)
 	{
-		GLMipmap_t *grmip = grpatch->mipmap.nextcolormap;
-		grpatch->mipmap.nextcolormap = grmip->nextcolormap;
+		GLMipmap_t *grmip = grpatch->mipmap->nextcolormap;
+		grpatch->mipmap->nextcolormap = grmip->nextcolormap;
 		if (grmip->grInfo.data) Z_Free(grmip->grInfo.data);
 		free(grmip);
 	}
@@ -924,29 +875,6 @@ GLTexture_t *HWR_GetTexture(INT32 tex)
 	return grtex;
 }
 
-// HWR_RenderPlane and HWR_RenderPolyObjectPlane need this to get the flat dimensions from a patch.
-lumpnum_t gr_patchflat;
-
-static void HWR_LoadPatchFlat(GLMipmap_t *grMipmap, lumpnum_t flatlumpnum)
-{
-	UINT8 *flat;
-	patch_t *patch = (patch_t *)W_CacheLumpNum(flatlumpnum, PU_STATIC);
-#ifndef NO_PNG_LUMPS
-	size_t lumplength = W_LumpLength(flatlumpnum);
-
-	if (R_IsLumpPNG((UINT8 *)patch, lumplength))
-		patch = R_PNGToPatch((UINT8 *)patch, lumplength, NULL, false);
-#endif
-
-	grMipmap->width  = (UINT16)SHORT(patch->width);
-	grMipmap->height = (UINT16)SHORT(patch->height);
-
-	flat = Z_Malloc(grMipmap->width * grMipmap->height, PU_HWRCACHE, &grMipmap->grInfo.data);
-	memset(flat, TRANSPARENTPIXEL, grMipmap->width * grMipmap->height);
-
-	R_PatchToFlat(patch, flat);
-}
-
 static void HWR_CacheFlat(GLMipmap_t *grMipmap, lumpnum_t flatlumpnum)
 {
 	size_t size, pflatsize;
@@ -987,40 +915,15 @@ static void HWR_CacheFlat(GLMipmap_t *grMipmap, lumpnum_t flatlumpnum)
 			break;
 	}
 
-	if (R_CheckIfPatch(flatlumpnum))
-		HWR_LoadPatchFlat(grMipmap, flatlumpnum);
-	else
-	{
-		grMipmap->width  = (UINT16)pflatsize;
-		grMipmap->height = (UINT16)pflatsize;
+	grMipmap->width  = (UINT16)pflatsize;
+	grMipmap->height = (UINT16)pflatsize;
 
-		// the flat raw data needn't be converted with palettized textures
-		W_ReadLump(flatlumpnum, Z_Malloc(W_LumpLength(flatlumpnum),
-			PU_HWRCACHE, &grMipmap->grInfo.data));
-	}
+	// the flat raw data needn't be converted with palettized textures
+	W_ReadLump(flatlumpnum, Z_Malloc(W_LumpLength(flatlumpnum),
+		PU_HWRCACHE, &grMipmap->grInfo.data));
 }
 
-// Download a Doom 'flat' to the hardware cache and make it ready for use
-void HWR_GetFlat(lumpnum_t flatlumpnum)
-{
-	GLMipmap_t *grmip;
-
-	grmip = &HWR_GetCachedGLPatch(flatlumpnum)->mipmap;
-
-	if (!grmip->downloaded && !grmip->grInfo.data)
-		HWR_CacheFlat(grmip, flatlumpnum);
-
-	HWD.pfnSetTexture(grmip);
-
-	// The system-memory data can be purged now.
-	Z_ChangeTag(grmip->grInfo.data, PU_HWRCACHE_UNLOCKED);
-
-	gr_patchflat = 0;
-	if (R_CheckIfPatch(flatlumpnum))
-		gr_patchflat = flatlumpnum;
-}
-
-static void HWR_LoadTextureFlat(GLMipmap_t *grMipmap, INT32 texturenum)
+static void HWR_CacheTextureAsFlat(GLMipmap_t *grMipmap, INT32 texturenum)
 {
 	UINT8 *flat;
 
@@ -1042,24 +945,53 @@ static void HWR_LoadTextureFlat(GLMipmap_t *grMipmap, INT32 texturenum)
 	R_TextureToFlat(texturenum, flat);
 }
 
-void HWR_GetTextureFlat(INT32 texturenum)
+// Download a Doom 'flat' to the hardware cache and make it ready for use
+void HWR_LiterallyGetFlat(lumpnum_t flatlumpnum)
 {
-	GLTexture_t *grtex;
-#ifdef PARANOIA
-	if ((unsigned)texturenum >= gr_numtextures)
-		I_Error("HWR_GetTextureFlat: texturenum >= numtextures\n");
-#endif
-	if (texturenum == 0 || texturenum == -1)
+	GLMipmap_t *grmip;
+	if (flatlumpnum == LUMPERROR)
 		return;
-	grtex = &gr_textures2[texturenum];
 
-	if (!grtex->mipmap.grInfo.data && !grtex->mipmap.downloaded)
-		HWR_LoadTextureFlat(&grtex->mipmap, texturenum);
+	grmip = HWR_GetCachedGLPatch(flatlumpnum)->mipmap;
+	if (!grmip->downloaded && !grmip->grInfo.data)
+		HWR_CacheFlat(grmip, flatlumpnum);
 
-	HWD.pfnSetTexture(&grtex->mipmap);
+	HWD.pfnSetTexture(grmip);
 
 	// The system-memory data can be purged now.
-	Z_ChangeTag(grtex->mipmap.grInfo.data, PU_HWRCACHE_UNLOCKED);
+	Z_ChangeTag(grmip->grInfo.data, PU_HWRCACHE_UNLOCKED);
+}
+
+void HWR_GetLevelFlat(levelflat_t *levelflat)
+{
+	// Who knows?
+	if (levelflat == NULL)
+		return;
+
+	if (levelflat->type == LEVELFLAT_FLAT)
+		HWR_LiterallyGetFlat(levelflat->u.flat.lumpnum);
+	else if (levelflat->type == LEVELFLAT_TEXTURE)
+	{
+		GLTexture_t *grtex;
+		INT32 texturenum = levelflat->u.texture.num;
+#ifdef PARANOIA
+		if ((unsigned)texturenum >= gr_numtextures)
+			I_Error("HWR_GetLevelFlat: texturenum >= numtextures\n");
+#endif
+		if (texturenum == 0 || texturenum == -1)
+			return;
+		grtex = &gr_textures2[texturenum];
+
+		if (!grtex->mipmap.grInfo.data && !grtex->mipmap.downloaded)
+			HWR_CacheTextureAsFlat(&grtex->mipmap, texturenum);
+
+		HWD.pfnSetTexture(&grtex->mipmap);
+
+		// The system-memory data can be purged now.
+		Z_ChangeTag(grtex->mipmap.grInfo.data, PU_HWRCACHE_UNLOCKED);
+	}
+	else // set no texture
+		HWD.pfnSetTexture(NULL);
 }
 
 //
@@ -1088,22 +1020,22 @@ static void HWR_LoadMappedPatch(GLMipmap_t *grmip, GLPatch_t *gpatch)
 void HWR_GetPatch(GLPatch_t *gpatch)
 {
 	// is it in hardware cache
-	if (!gpatch->mipmap.downloaded && !gpatch->mipmap.grInfo.data)
+	if (!gpatch->mipmap->downloaded && !gpatch->mipmap->grInfo.data)
 	{
 		// load the software patch, PU_STATIC or the Z_Malloc for hardware patch will
 		// flush the software patch before the conversion! oh yeah I suffered
 		patch_t *ptr = W_CacheLumpNumPwad(gpatch->wadnum, gpatch->lumpnum, PU_STATIC);
-		HWR_MakePatch(ptr, gpatch, &gpatch->mipmap, true);
+		HWR_MakePatch(ptr, gpatch, gpatch->mipmap, true);
 
 		// this is inefficient.. but the hardware patch in heap is purgeable so it should
 		// not fragment memory, and besides the REAL cache here is the hardware memory
 		Z_Free(ptr);
 	}
 
-	HWD.pfnSetTexture(&gpatch->mipmap);
+	HWD.pfnSetTexture(gpatch->mipmap);
 
 	// The system-memory patch data can be purged now.
-	Z_ChangeTag(gpatch->mipmap.grInfo.data, PU_HWRCACHE_UNLOCKED);
+	Z_ChangeTag(gpatch->mipmap->grInfo.data, PU_HWRCACHE_UNLOCKED);
 }
 
 
@@ -1123,7 +1055,7 @@ void HWR_GetMappedPatch(GLPatch_t *gpatch, const UINT8 *colormap)
 
 	// search for the mimmap
 	// skip the first (no colormap translated)
-	for (grmip = &gpatch->mipmap; grmip->nextcolormap; )
+	for (grmip = gpatch->mipmap; grmip->nextcolormap; )
 	{
 		grmip = grmip->nextcolormap;
 		if (grmip->colormap == colormap)
@@ -1153,7 +1085,7 @@ void HWR_UnlockCachedPatch(GLPatch_t *gpatch)
 	if (!gpatch)
 		return;
 
-	Z_ChangeTag(gpatch->mipmap.grInfo.data, PU_HWRCACHE_UNLOCKED);
+	Z_ChangeTag(gpatch->mipmap->grInfo.data, PU_HWRCACHE_UNLOCKED);
 	Z_ChangeTag(gpatch, PU_HWRPATCHINFO_UNLOCKED);
 }
 
@@ -1241,7 +1173,7 @@ GLPatch_t *HWR_GetPic(lumpnum_t lumpnum)
 
 	grpatch = HWR_GetCachedGLPatch(lumpnum);
 
-	if (!grpatch->mipmap.downloaded && !grpatch->mipmap.grInfo.data)
+	if (!grpatch->mipmap->downloaded && !grpatch->mipmap->grInfo.data)
 	{
 		pic_t *pic;
 		UINT8 *block;
@@ -1257,19 +1189,19 @@ GLPatch_t *HWR_GetPic(lumpnum_t lumpnum)
 		grpatch->topoffset = 0;
 
 		// find the good 3dfx size (boring spec)
-		HWR_ResizeBlock (grpatch->width, grpatch->height, &grpatch->mipmap.grInfo);
-		grpatch->mipmap.width = (UINT16)blockwidth;
-		grpatch->mipmap.height = (UINT16)blockheight;
+		HWR_ResizeBlock (grpatch->width, grpatch->height, &grpatch->mipmap->grInfo);
+		grpatch->mipmap->width = (UINT16)blockwidth;
+		grpatch->mipmap->height = (UINT16)blockheight;
 
 		if (pic->mode == PALETTE)
-			grpatch->mipmap.grInfo.format = textureformat; // can be set by driver
+			grpatch->mipmap->grInfo.format = textureformat; // can be set by driver
 		else
-			grpatch->mipmap.grInfo.format = picmode2GR[pic->mode];
+			grpatch->mipmap->grInfo.format = picmode2GR[pic->mode];
 
-		Z_Free(grpatch->mipmap.grInfo.data);
+		Z_Free(grpatch->mipmap->grInfo.data);
 
 		// allocate block
-		block = MakeBlock(&grpatch->mipmap);
+		block = MakeBlock(grpatch->mipmap);
 
 		// if rounddown, rounddown patches as well as textures
 		if (cv_grrounddown.value)
@@ -1277,18 +1209,6 @@ GLPatch_t *HWR_GetPic(lumpnum_t lumpnum)
 			newwidth = blockwidth;
 			newheight = blockheight;
 		}
-		else if (cv_voodoocompatibility.value) // Only scales down textures that exceed 256x256.
-		{
-			// no rounddown, do not size up patches, so they don't look 'scaled'
-			newwidth  = min(SHORT(pic->width),blockwidth);
-			newheight = min(SHORT(pic->height),blockheight);
-
-			if (newwidth > 256 || newheight > 256)
-			{
-				newwidth = blockwidth;
-				newheight = blockheight;
-			}
-		}
 		else
 		{
 			// no rounddown, do not size up patches, so they don't look 'scaled'
@@ -1299,25 +1219,25 @@ GLPatch_t *HWR_GetPic(lumpnum_t lumpnum)
 
 		if (grpatch->width  == blockwidth &&
 			grpatch->height == blockheight &&
-			format2bpp[grpatch->mipmap.grInfo.format] == format2bpp[picmode2GR[pic->mode]])
+			format2bpp[grpatch->mipmap->grInfo.format] == format2bpp[picmode2GR[pic->mode]])
 		{
 			// no conversion needed
-			M_Memcpy(grpatch->mipmap.grInfo.data, pic->data,len);
+			M_Memcpy(grpatch->mipmap->grInfo.data, pic->data,len);
 		}
 		else
 			HWR_DrawPicInCache(block, newwidth, newheight,
-			                   blockwidth*format2bpp[grpatch->mipmap.grInfo.format],
+			                   blockwidth*format2bpp[grpatch->mipmap->grInfo.format],
 			                   pic,
-			                   format2bpp[grpatch->mipmap.grInfo.format]);
+			                   format2bpp[grpatch->mipmap->grInfo.format]);
 
 		Z_Unlock(pic);
 		Z_ChangeTag(block, PU_HWRCACHE_UNLOCKED);
 
-		grpatch->mipmap.flags = 0;
+		grpatch->mipmap->flags = 0;
 		grpatch->max_s = (float)newwidth  / (float)blockwidth;
 		grpatch->max_t = (float)newheight / (float)blockheight;
 	}
-	HWD.pfnSetTexture(&grpatch->mipmap);
+	HWD.pfnSetTexture(grpatch->mipmap);
 	//CONS_Debug(DBG_RENDER, "picloaded at %x as texture %d\n",grpatch->mipmap.grInfo.data, grpatch->mipmap.downloaded);
 
 	return grpatch;
@@ -1333,6 +1253,7 @@ GLPatch_t *HWR_GetCachedGLPatchPwad(UINT16 wadnum, UINT16 lumpnum)
 		grpatch = Z_Calloc(sizeof(GLPatch_t), PU_HWRPATCHINFO, NULL);
 		grpatch->wadnum = wadnum;
 		grpatch->lumpnum = lumpnum;
+		grpatch->mipmap = Z_Calloc(sizeof(GLMipmap_t), PU_HWRPATCHINFO, NULL);
 		M_AATreeSet(hwrcache, lumpnum, grpatch);
 	}
 
@@ -1436,7 +1357,7 @@ void HWR_GetFadeMask(lumpnum_t fademasklumpnum)
 {
 	GLMipmap_t *grmip;
 
-	grmip = &HWR_GetCachedGLPatch(fademasklumpnum)->mipmap;
+	grmip = HWR_GetCachedGLPatch(fademasklumpnum)->mipmap;
 
 	if (!grmip->downloaded && !grmip->grInfo.data)
 		HWR_CacheFadeMask(grmip, fademasklumpnum);
diff --git a/src/hardware/hw_clip.c b/src/hardware/hw_clip.c
index 6d120efe72c4ca251b61dfa5c68be1b206719d31..4bdc753ec870e750c1f9fc3768d46defbc0a788a 100644
--- a/src/hardware/hw_clip.c
+++ b/src/hardware/hw_clip.c
@@ -77,8 +77,8 @@
 #include "r_opengl/r_opengl.h"
 
 #ifdef HAVE_SPHEREFRUSTRUM
-static GLdouble viewMatrix[16];
-static GLdouble projMatrix[16];
+static GLfloat viewMatrix[16];
+static GLfloat projMatrix[16];
 float frustum[6][4];
 #endif
 
@@ -380,8 +380,8 @@ void gld_FrustrumSetup(void)
 	float t;
 	float clip[16];
 
-	pglGetDoublev(GL_PROJECTION_MATRIX, projMatrix);
-	pglGetDoublev(GL_MODELVIEW_MATRIX, viewMatrix);
+	pglGeFloatv(GL_PROJECTION_MATRIX, projMatrix);
+	pglGetFloatv(GL_MODELVIEW_MATRIX, viewMatrix);
 
 	clip[0]  = CALCMATRIX(0, 0, 1, 4, 2, 8, 3, 12);
 	clip[1]  = CALCMATRIX(0, 1, 1, 5, 2, 9, 3, 13);
diff --git a/src/hardware/hw_data.h b/src/hardware/hw_data.h
index 44929dd67ba00267173afde6f4fa55a50440c107..629861c23309b6bfd6db289cc0b6ad80ce88b8d2 100644
--- a/src/hardware/hw_data.h
+++ b/src/hardware/hw_data.h
@@ -83,8 +83,8 @@ struct GLPatch_s
 	float               max_s,max_t;
 	UINT16              wadnum;      // the software patch lump num for when the hardware patch
 	UINT16              lumpnum;     // was flushed, and we need to re-create it
-	GLMipmap_t          mipmap;
-};
+	GLMipmap_t         *mipmap;
+} ATTRPACK;
 typedef struct GLPatch_s GLPatch_t;
 
 #endif //_HWR_DATA_
diff --git a/src/hardware/hw_defs.h b/src/hardware/hw_defs.h
index 5dcead77cb9c59ed4d192feadf64ecde878802ad..83d601b8745e979003ea7ac8832dafdb7527fae9 100644
--- a/src/hardware/hw_defs.h
+++ b/src/hardware/hw_defs.h
@@ -95,14 +95,29 @@ typedef struct
 
 //Hurdler: Transform (coords + angles)
 //BP: transform order : scale(rotation_x(rotation_y(translation(v))))
+
+// Kart features
+//#define USE_FTRANSFORM_ANGLEZ
+//#define USE_FTRANSFORM_MIRROR
+
+// Vanilla features
+#define USE_MODEL_NEXTFRAME
+
 typedef struct
 {
 	FLOAT       x,y,z;           // position
+#ifdef USE_FTRANSFORM_ANGLEZ
+	FLOAT       anglex,angley,anglez;   // aimingangle / viewangle
+#else
 	FLOAT       anglex,angley;   // aimingangle / viewangle
+#endif
 	FLOAT       scalex,scaley,scalez;
 	FLOAT       fovxangle, fovyangle;
-	INT32       splitscreen;
+	UINT8       splitscreen;
 	boolean     flip;            // screenflip
+#ifdef USE_FTRANSFORM_MIRROR
+	boolean     mirror;          // SRB2Kart: Encore Mode
+#endif
 } FTransform;
 
 // Transformed vector, as passed to HWR API
@@ -145,7 +160,7 @@ enum EPolyFlags
 	                                    // When set, pass the color constant into the FSurfaceInfo -> FlatColor
 	PF_NoTexture        = 0x00002000,   // Use the small white texture
 	PF_Corona           = 0x00004000,   // Tell the rendrer we are drawing a corona
-	PF_MD2              = 0x00008000,   // Tell the rendrer we are drawing an MD2
+	PF_Unused           = 0x00008000,   // Unused
 	PF_RemoveYWrap      = 0x00010000,   // Force clamp texture on Y
 	PF_ForceWrapX       = 0x00020000,   // Force repeat texture on X
 	PF_ForceWrapY       = 0x00040000,   // Force repeat texture on Y
@@ -203,8 +218,6 @@ enum hwdsetspecialstate
 	HWD_SET_FOG_COLOR,
 	HWD_SET_FOG_DENSITY,
 	HWD_SET_FOV,
-	HWD_SET_POLYGON_SMOOTH,
-	HWD_SET_PALETTECOLOR,
 	HWD_SET_TEXTUREFILTERMODE,
 	HWD_SET_TEXTUREANISOTROPICMODE,
 	HWD_NUMSTATE
diff --git a/src/hardware/hw_draw.c b/src/hardware/hw_draw.c
index d9e688c0aaa5d8745754c662f50472b621ae47ba..3b0620cc134c96c480aade16dce049e4d443e71c 100644
--- a/src/hardware/hw_draw.c
+++ b/src/hardware/hw_draw.c
@@ -647,7 +647,7 @@ void HWR_DrawFlatFill (INT32 x, INT32 y, INT32 w, INT32 h, lumpnum_t flatlumpnum
 	v[0].tow = v[1].tow = (float)((y & flatflag)/dflatsize);
 	v[2].tow = v[3].tow = (float)(v[0].tow + h/dflatsize);
 
-	HWR_GetFlat(flatlumpnum);
+	HWR_LiterallyGetFlat(flatlumpnum);
 
 	//Hurdler: Boris, the same comment as above... but maybe for pics
 	// it not a problem since they don't have any transparent pixel
diff --git a/src/hardware/hw_drv.h b/src/hardware/hw_drv.h
index e0507bc7083f9f54cebe7b3ab6952fa5a932ee03..aed1611f111176ba0ab9c907362e154421894825 100644
--- a/src/hardware/hw_drv.h
+++ b/src/hardware/hw_drv.h
@@ -59,20 +59,18 @@ EXPORT void HWRAPI(ClearMipMapCache) (void);
 EXPORT void HWRAPI(SetSpecialState) (hwdspecialstate_t IdState, INT32 Value);
 
 //Hurdler: added for new development
-EXPORT void HWRAPI(DrawMD2) (INT32 *gl_cmd_buffer, md2_frame_t *frame, FTransform *pos, float scale);
-EXPORT void HWRAPI(DrawMD2i) (INT32 *gl_cmd_buffer, md2_frame_t *frame, INT32 duration, INT32 tics, md2_frame_t *nextframe, FTransform *pos, float scale, UINT8 flipped, UINT8 *color);
+EXPORT void HWRAPI(DrawModel) (model_t *model, INT32 frameIndex, INT32 duration, INT32 tics, INT32 nextFrameIndex, FTransform *pos, float scale, UINT8 flipped, UINT8 *color);
+EXPORT void HWRAPI(CreateModelVBOs) (model_t *model);
 EXPORT void HWRAPI(SetTransform) (FTransform *ptransform);
 EXPORT INT32 HWRAPI(GetTextureUsed) (void);
 EXPORT INT32 HWRAPI(GetRenderVersion) (void);
 
-#ifdef SHUFFLE
 #define SCREENVERTS 10
 EXPORT void HWRAPI(PostImgRedraw) (float points[SCREENVERTS][SCREENVERTS][2]);
-#endif
 EXPORT void HWRAPI(FlushScreenTextures) (void);
 EXPORT void HWRAPI(StartScreenWipe) (void);
 EXPORT void HWRAPI(EndScreenWipe) (void);
-EXPORT void HWRAPI(DoScreenWipe) (float alpha);
+EXPORT void HWRAPI(DoScreenWipe) (void);
 EXPORT void HWRAPI(DrawIntermissionBG) (void);
 EXPORT void HWRAPI(MakeScreenTexture) (void);
 EXPORT void HWRAPI(MakeScreenFinalTexture) (void);
@@ -98,8 +96,8 @@ struct hwdriver_s
 	GClipRect           pfnGClipRect;
 	ClearMipMapCache    pfnClearMipMapCache;
 	SetSpecialState     pfnSetSpecialState;//Hurdler: added for backward compatibility
-	DrawMD2             pfnDrawMD2;
-	DrawMD2i            pfnDrawMD2i;
+	DrawModel           pfnDrawModel;
+	CreateModelVBOs     pfnCreateModelVBOs;
 	SetTransform        pfnSetTransform;
 	GetTextureUsed      pfnGetTextureUsed;
 	GetRenderVersion    pfnGetRenderVersion;
@@ -109,9 +107,7 @@ struct hwdriver_s
 #ifndef HAVE_SDL
 	Shutdown            pfnShutdown;
 #endif
-#ifdef SHUFFLE
 	PostImgRedraw       pfnPostImgRedraw;
-#endif
 	FlushScreenTextures pfnFlushScreenTextures;
 	StartScreenWipe     pfnStartScreenWipe;
 	EndScreenWipe       pfnEndScreenWipe;
diff --git a/src/hardware/hw_glob.h b/src/hardware/hw_glob.h
index c7b06edfd1f1455e5d399a0a51e58771fce88786..72365013d2d2e866439cc90b453c828617db1172 100644
--- a/src/hardware/hw_glob.h
+++ b/src/hardware/hw_glob.h
@@ -23,6 +23,7 @@
 #include "hw_defs.h"
 #include "hw_main.h"
 #include "../m_misc.h"
+#include "../p_setup.h"
 
 // the original aspect ratio of Doom graphics isn't square
 #define ORIGINAL_ASPECT (320.0f/200.0f)
@@ -100,8 +101,8 @@ void HWR_InitTextureCache(void);
 void HWR_FreeTextureCache(void);
 void HWR_FreeExtraSubsectors(void);
 
-void HWR_GetFlat(lumpnum_t flatlumpnum);
-void HWR_GetTextureFlat(INT32 texturenum);
+void HWR_GetLevelFlat(levelflat_t *levelflat);
+void HWR_LiterallyGetFlat(lumpnum_t flatlumpnum);
 GLTexture_t *HWR_GetTexture(INT32 tex);
 void HWR_GetPatch(GLPatch_t *gpatch);
 void HWR_GetMappedPatch(GLPatch_t *gpatch, const UINT8 *colormap);
@@ -115,8 +116,6 @@ void HWR_GetFadeMask(lumpnum_t fademasklumpnum);
 // --------
 // hw_draw.c
 // --------
-extern lumpnum_t gr_patchflat;
-
 extern float gr_patch_scalex;
 extern float gr_patch_scaley;
 
diff --git a/src/hardware/hw_light.c b/src/hardware/hw_light.c
index 8f62f7763da6dfd98b89c4efe112d0479f4cc49b..491cb739f999a82c8013c68fcbe45e7ad3798568 100644
--- a/src/hardware/hw_light.c
+++ b/src/hardware/hw_light.c
@@ -1205,7 +1205,8 @@ void HWR_DL_AddLight(gr_vissprite_t *spr, GLPatch_t *patch)
 	dynlights->nb++;
 }
 
-static GLPatch_t lightmappatch;
+static GLMipmap_t lightmappatchmipmap;
+static GLPatch_t lightmappatch = { .mipmap = &lightmappatchmipmap };
 
 void HWR_InitLight(void)
 {
@@ -1215,7 +1216,7 @@ void HWR_InitLight(void)
 	for (i = 0;i < NUMLIGHTS;i++)
 		lspr[i].dynamic_sqrradius = lspr[i].dynamic_radius*lspr[i].dynamic_radius;
 
-	lightmappatch.mipmap.downloaded = false;
+	lightmappatch.mipmap->downloaded = false;
 	coronalumpnum = W_CheckNumForName("CORONA");
 }
 
@@ -1226,10 +1227,10 @@ static void HWR_SetLight(void)
 {
 	int    i, j;
 
-	if (!lightmappatch.mipmap.downloaded && !lightmappatch.mipmap.grInfo.data)
+	if (!lightmappatch.mipmap->downloaded && !lightmappatch.mipmap->grInfo.data)
 	{
 
-		UINT16 *Data = Z_Malloc(129*128*sizeof (UINT16), PU_HWRCACHE, &lightmappatch.mipmap.grInfo.data);
+		UINT16 *Data = Z_Malloc(129*128*sizeof (UINT16), PU_HWRCACHE, &lightmappatch.mipmap->grInfo.data);
 
 		for (i = 0; i < 128; i++)
 		{
@@ -1242,23 +1243,23 @@ static void HWR_SetLight(void)
 					Data[i*128+j] = 0;
 			}
 		}
-		lightmappatch.mipmap.grInfo.format = GR_TEXFMT_ALPHA_INTENSITY_88;
+		lightmappatch.mipmap->grInfo.format = GR_TEXFMT_ALPHA_INTENSITY_88;
 
 		lightmappatch.width = 128;
 		lightmappatch.height = 128;
-		lightmappatch.mipmap.width = 128;
-		lightmappatch.mipmap.height = 128;
+		lightmappatch.mipmap->width = 128;
+		lightmappatch.mipmap->height = 128;
 #ifdef GLIDE_API_COMPATIBILITY
-		lightmappatch.mipmap.grInfo.smallLodLog2 = GR_LOD_LOG2_128;
-		lightmappatch.mipmap.grInfo.largeLodLog2 = GR_LOD_LOG2_128;
-		lightmappatch.mipmap.grInfo.aspectRatioLog2 = GR_ASPECT_LOG2_1x1;
+		lightmappatch.mipmap->grInfo.smallLodLog2 = GR_LOD_LOG2_128;
+		lightmappatch.mipmap->grInfo.largeLodLog2 = GR_LOD_LOG2_128;
+		lightmappatch.mipmap->grInfo.aspectRatioLog2 = GR_ASPECT_LOG2_1x1;
 #endif
-		lightmappatch.mipmap.flags = 0; //TF_WRAPXY; // DEBUG: view the overdraw !
+		lightmappatch.mipmap->flags = 0; //TF_WRAPXY; // DEBUG: view the overdraw !
 	}
-	HWD.pfnSetTexture(&lightmappatch.mipmap);
+	HWD.pfnSetTexture(lightmappatch.mipmap);
 
 	// The system-memory data can be purged now.
-	Z_ChangeTag(lightmappatch.mipmap.grInfo.data, PU_HWRCACHE_UNLOCKED);
+	Z_ChangeTag(lightmappatch.mipmap->grInfo.data, PU_HWRCACHE_UNLOCKED);
 }
 
 //**********************************************************
diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index f2658707dba7c5f84ee7c35f3aaaf0cd7415ea33..79cc9e8e53bbc1209d55fe4088b1212bdbe148bb 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -70,12 +70,12 @@ static void HWR_ProjectPrecipitationSprite(precipmobj_t *thing);
 #endif
 
 #ifdef SORTING
-void HWR_AddTransparentFloor(lumpnum_t lumpnum, INT32 texturenum, extrasubsector_t *xsub, boolean isceiling, fixed_t fixedheight,
+void HWR_AddTransparentFloor(levelflat_t *levelflat, extrasubsector_t *xsub, boolean isceiling, fixed_t fixedheight,
                              INT32 lightlevel, INT32 alpha, sector_t *FOFSector, FBITFIELD blend, boolean fogplane, extracolormap_t *planecolormap);
-void HWR_AddTransparentPolyobjectFloor(lumpnum_t lumpnum, INT32 texturenum, polyobj_t *polysector, boolean isceiling, fixed_t fixedheight,
+void HWR_AddTransparentPolyobjectFloor(levelflat_t *levelflat, polyobj_t *polysector, boolean isceiling, fixed_t fixedheight,
                              INT32 lightlevel, INT32 alpha, sector_t *FOFSector, FBITFIELD blend, extracolormap_t *planecolormap);
 #else
-static void HWR_Add3DWater(lumpnum_t lumpnum, extrasubsector_t *xsub, fixed_t fixedheight,
+static void HWR_Add3DWater(levelflat_t *levelflat, extrasubsector_t *xsub, fixed_t fixedheight,
                            INT32 lightlevel, INT32 alpha, sector_t *FOFSector);
 static void HWR_Render3DWater(void);
 static void HWR_RenderTransparentWalls(void);
@@ -522,7 +522,7 @@ static UINT8 HWR_FogBlockAlpha(INT32 light, UINT32 color) // Let's see if this c
 // HWR_RenderPlane  : Render a floor or ceiling convex polygon
 // -----------------+
 static void HWR_RenderPlane(sector_t *sector, extrasubsector_t *xsub, boolean isceiling, fixed_t fixedheight,
-                           FBITFIELD PolyFlags, INT32 lightlevel, lumpnum_t lumpnum, INT32 texturenum, sector_t *FOFsector, UINT8 alpha, boolean fogplane, extracolormap_t *planecolormap)
+                           FBITFIELD PolyFlags, INT32 lightlevel, levelflat_t *levelflat, sector_t *FOFsector, UINT8 alpha, boolean fogplane, extracolormap_t *planecolormap)
 {
 	polyvertex_t *  pv;
 	float           height; //constant y for all points on the convex flat polygon
@@ -530,9 +530,9 @@ static void HWR_RenderPlane(sector_t *sector, extrasubsector_t *xsub, boolean is
 	INT32             nrPlaneVerts;   //verts original define of convex flat polygon
 	INT32             i;
 	float           flatxref,flatyref;
-	float fflatwidth, fflatheight;
-	INT32 flatflag;
-	boolean texflat = true;
+	float fflatwidth = 64.0f, fflatheight = 64.0f;
+	INT32 flatflag = 63;
+	boolean texflat = false;
 	size_t len;
 	float scrollx = 0.0f, scrolly = 0.0f;
 	angle_t angle = 0;
@@ -541,7 +541,6 @@ static void HWR_RenderPlane(sector_t *sector, extrasubsector_t *xsub, boolean is
 #ifdef ESLOPE
 	pslope_t *slope = NULL;
 #endif
-	patch_t *patch;
 
 	static FOutVector *planeVerts = NULL;
 	static UINT16 numAllocedPlaneVerts = 0;
@@ -597,48 +596,49 @@ static void HWR_RenderPlane(sector_t *sector, extrasubsector_t *xsub, boolean is
 		Z_Malloc(numAllocedPlaneVerts * sizeof (FOutVector), PU_LEVEL, &planeVerts);
 	}
 
-	len = W_LumpLength(lumpnum);
-
-	switch (len)
+	// set texture for polygon
+	if (levelflat != NULL)
 	{
-		case 4194304: // 2048x2048 lump
-			fflatwidth = fflatheight = 2048.0f;
-			break;
-		case 1048576: // 1024x1024 lump
-			fflatwidth = fflatheight = 1024.0f;
-			break;
-		case 262144:// 512x512 lump
-			fflatwidth = fflatheight = 512.0f;
-			break;
-		case 65536: // 256x256 lump
-			fflatwidth = fflatheight = 256.0f;
-			break;
-		case 16384: // 128x128 lump
-			fflatwidth = fflatheight = 128.0f;
-			break;
-		case 1024: // 32x32 lump
-			fflatwidth = fflatheight = 32.0f;
-			break;
-		default: // 64x64 lump
-			fflatwidth = fflatheight = 64.0f;
-			break;
-	}
+		if (levelflat->type == LEVELFLAT_TEXTURE)
+		{
+			fflatwidth = textures[levelflat->u.texture.num]->width;
+			fflatheight = textures[levelflat->u.texture.num]->height;
+			texflat = true;
+		}
+		else if (levelflat->type == LEVELFLAT_FLAT)
+		{
+			len = W_LumpLength(levelflat->u.flat.lumpnum);
 
-	flatflag = ((INT32)fflatwidth)-1;
+			switch (len)
+			{
+				case 4194304: // 2048x2048 lump
+					fflatwidth = fflatheight = 2048.0f;
+					break;
+				case 1048576: // 1024x1024 lump
+					fflatwidth = fflatheight = 1024.0f;
+					break;
+				case 262144:// 512x512 lump
+					fflatwidth = fflatheight = 512.0f;
+					break;
+				case 65536: // 256x256 lump
+					fflatwidth = fflatheight = 256.0f;
+					break;
+				case 16384: // 128x128 lump
+					fflatwidth = fflatheight = 128.0f;
+					break;
+				case 1024: // 32x32 lump
+					fflatwidth = fflatheight = 32.0f;
+					break;
+				default: // 64x64 lump
+					fflatwidth = fflatheight = 64.0f;
+					break;
+			}
 
-	if (texturenum != 0 && texturenum != -1)
-	{
-		fflatwidth = textures[texturenum]->width;
-		fflatheight = textures[texturenum]->height;
-	}
-	else if (gr_patchflat && R_CheckIfPatch(gr_patchflat))		// Just in case?
-	{
-		patch = (patch_t *)W_CacheLumpNum(gr_patchflat, PU_STATIC);
-		fflatwidth = SHORT(patch->width);
-		fflatheight = SHORT(patch->height);
+			flatflag = ((INT32)fflatwidth)-1;
+		}
 	}
-	else
-		texflat = false;
+	else // set no texture
+		HWD.pfnSetTexture(NULL);
 
 	// reference point for flat texture coord for each vertex around the polygon
 	flatxref = (float)(((fixed_t)pv->x & (~flatflag)) / fflatwidth);
@@ -1972,7 +1972,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)
+		if (gr_midtexture && gr_linedef->special != 41) // Ignore horizon line for OGL
 		{
 			{
 				fixed_t     texturevpeg;
@@ -3183,23 +3183,22 @@ static inline void HWR_AddPolyObjectSegs(void)
 
 #ifdef POLYOBJECTS_PLANES
 static void HWR_RenderPolyObjectPlane(polyobj_t *polysector, boolean isceiling, fixed_t fixedheight,
-									FBITFIELD blendmode, UINT8 lightlevel, lumpnum_t lumpnum, INT32 texturenum, sector_t *FOFsector,
+									FBITFIELD blendmode, UINT8 lightlevel, levelflat_t *levelflat, sector_t *FOFsector,
 									UINT8 alpha, extracolormap_t *planecolormap)
 {
 	float           height; //constant y for all points on the convex flat polygon
 	FOutVector      *v3d;
 	INT32             i;
 	float           flatxref,flatyref;
-	float fflatwidth, fflatheight;
-	INT32 flatflag;
-	boolean texflat = true;
+	float fflatwidth = 64.0f, fflatheight = 64.0f;
+	INT32 flatflag = 63;
+	boolean texflat = false;
 	size_t len;
 	float scrollx = 0.0f, scrolly = 0.0f;
 	angle_t angle = 0;
 	FSurfaceInfo    Surf;
 	fixed_t tempxsow, tempytow;
 	size_t nrPlaneVerts;
-	patch_t *patch;
 
 	static FOutVector *planeVerts = NULL;
 	static UINT16 numAllocedPlaneVerts = 0;
@@ -3225,48 +3224,49 @@ static void HWR_RenderPolyObjectPlane(polyobj_t *polysector, boolean isceiling,
 		Z_Malloc(numAllocedPlaneVerts * sizeof (FOutVector), PU_LEVEL, &planeVerts);
 	}
 
-	len = W_LumpLength(lumpnum);
-
-	switch (len)
+	// set texture for polygon
+	if (levelflat != NULL)
 	{
-		case 4194304: // 2048x2048 lump
-			fflatwidth = fflatheight = 2048.0f;
-			break;
-		case 1048576: // 1024x1024 lump
-			fflatwidth = fflatheight = 1024.0f;
-			break;
-		case 262144:// 512x512 lump
-			fflatwidth = fflatheight = 512.0f;
-			break;
-		case 65536: // 256x256 lump
-			fflatwidth = fflatheight = 256.0f;
-			break;
-		case 16384: // 128x128 lump
-			fflatwidth = fflatheight = 128.0f;
-			break;
-		case 1024: // 32x32 lump
-			fflatwidth = fflatheight = 32.0f;
-			break;
-		default: // 64x64 lump
-			fflatwidth = fflatheight = 64.0f;
-			break;
-	}
+		if (levelflat->type == LEVELFLAT_TEXTURE)
+		{
+			fflatwidth = textures[levelflat->u.texture.num]->width;
+			fflatheight = textures[levelflat->u.texture.num]->height;
+			texflat = true;
+		}
+		else if (levelflat->type == LEVELFLAT_FLAT)
+		{
+			len = W_LumpLength(levelflat->u.flat.lumpnum);
 
-	flatflag = ((INT32)fflatwidth)-1;
+			switch (len)
+			{
+				case 4194304: // 2048x2048 lump
+					fflatwidth = fflatheight = 2048.0f;
+					break;
+				case 1048576: // 1024x1024 lump
+					fflatwidth = fflatheight = 1024.0f;
+					break;
+				case 262144:// 512x512 lump
+					fflatwidth = fflatheight = 512.0f;
+					break;
+				case 65536: // 256x256 lump
+					fflatwidth = fflatheight = 256.0f;
+					break;
+				case 16384: // 128x128 lump
+					fflatwidth = fflatheight = 128.0f;
+					break;
+				case 1024: // 32x32 lump
+					fflatwidth = fflatheight = 32.0f;
+					break;
+				default: // 64x64 lump
+					fflatwidth = fflatheight = 64.0f;
+					break;
+			}
 
-	if (texturenum != 0 && texturenum != -1)
-	{
-		fflatwidth = textures[texturenum]->width;
-		fflatheight = textures[texturenum]->height;
-	}
-	else if (gr_patchflat && R_CheckIfPatch(gr_patchflat))		// Just in case?
-	{
-		patch = (patch_t *)W_CacheLumpNum(gr_patchflat, PU_STATIC);
-		fflatwidth = SHORT(patch->width);
-		fflatheight = SHORT(patch->height);
+			flatflag = ((INT32)fflatwidth)-1;
+		}
 	}
-	else
-		texflat = false;
+	else // set no texture
+		HWD.pfnSetTexture(NULL);
 
 	// reference point for flat texture coord for each vertex around the polygon
 	flatxref = (float)((polysector->origVerts[0].x & (~flatflag)) / fflatwidth);
@@ -3400,15 +3400,14 @@ static void HWR_AddPolyObjectPlanes(void)
 				FBITFIELD blendmode;
 				memset(&Surf, 0x00, sizeof(Surf));
 				blendmode = HWR_TranstableToAlpha(po_ptrs[i]->translucency, &Surf);
-				HWR_AddTransparentPolyobjectFloor(levelflats[polyobjsector->floorpic].lumpnum, levelflats[polyobjsector->floorpic].texturenum, po_ptrs[i], false, polyobjsector->floorheight,
+				HWR_AddTransparentPolyobjectFloor(&levelflats[polyobjsector->floorpic], po_ptrs[i], false, polyobjsector->floorheight,
 													(light == -1 ? gr_frontsector->lightlevel : *gr_frontsector->lightlist[light].lightlevel), Surf.FlatColor.s.alpha, polyobjsector, blendmode, (light == -1 ? gr_frontsector->extra_colormap : *gr_frontsector->lightlist[light].extra_colormap));
 			}
 			else
 			{
-				HWR_GetFlat(levelflats[polyobjsector->floorpic].lumpnum);
-				HWR_GetTextureFlat(levelflats[polyobjsector->floorpic].texturenum);
+				HWR_GetLevelFlat(&levelflats[polyobjsector->floorpic]);
 				HWR_RenderPolyObjectPlane(po_ptrs[i], false, polyobjsector->floorheight, PF_Occlude,
-										(light == -1 ? gr_frontsector->lightlevel : *gr_frontsector->lightlist[light].lightlevel), levelflats[polyobjsector->floorpic].lumpnum, levelflats[polyobjsector->floorpic].texturenum,
+										(light == -1 ? gr_frontsector->lightlevel : *gr_frontsector->lightlist[light].lightlevel), &levelflats[polyobjsector->floorpic],
 										polyobjsector, 255, (light == -1 ? gr_frontsector->extra_colormap : *gr_frontsector->lightlist[light].extra_colormap));
 			}
 		}
@@ -3424,15 +3423,14 @@ static void HWR_AddPolyObjectPlanes(void)
 				FBITFIELD blendmode;
 				memset(&Surf, 0x00, sizeof(Surf));
 				blendmode = HWR_TranstableToAlpha(po_ptrs[i]->translucency, &Surf);
-				HWR_AddTransparentPolyobjectFloor(levelflats[polyobjsector->ceilingpic].lumpnum, levelflats[polyobjsector->floorpic].texturenum, po_ptrs[i], true, polyobjsector->ceilingheight,
+				HWR_AddTransparentPolyobjectFloor(&levelflats[polyobjsector->ceilingpic], po_ptrs[i], true, polyobjsector->ceilingheight,
 				                                  (light == -1 ? gr_frontsector->lightlevel : *gr_frontsector->lightlist[light].lightlevel), Surf.FlatColor.s.alpha, polyobjsector, blendmode, (light == -1 ? gr_frontsector->extra_colormap : *gr_frontsector->lightlist[light].extra_colormap));
 			}
 			else
 			{
-				HWR_GetFlat(levelflats[polyobjsector->ceilingpic].lumpnum);
-				HWR_GetTextureFlat(levelflats[polyobjsector->ceilingpic].texturenum);
+				HWR_GetLevelFlat(&levelflats[polyobjsector->ceilingpic]);
 				HWR_RenderPolyObjectPlane(po_ptrs[i], true, polyobjsector->ceilingheight, PF_Occlude,
-				                          (light == -1 ? gr_frontsector->lightlevel : *gr_frontsector->lightlist[light].lightlevel), levelflats[polyobjsector->floorpic].lumpnum, levelflats[polyobjsector->floorpic].texturenum,
+				                          (light == -1 ? gr_frontsector->lightlevel : *gr_frontsector->lightlist[light].lightlevel), &levelflats[polyobjsector->floorpic],
 				                          polyobjsector, 255, (light == -1 ? gr_frontsector->extra_colormap : *gr_frontsector->lightlist[light].extra_colormap));
 			}
 		}
@@ -3583,13 +3581,12 @@ static void HWR_Subsector(size_t num)
 		{
 			if (sub->validcount != validcount)
 			{
-				HWR_GetFlat(levelflats[gr_frontsector->floorpic].lumpnum);
-				HWR_GetTextureFlat(levelflats[gr_frontsector->floorpic].texturenum);
+				HWR_GetLevelFlat(&levelflats[gr_frontsector->floorpic]);
 				HWR_RenderPlane(gr_frontsector, &extrasubsectors[num], false,
 					// Hack to make things continue to work around slopes.
 					locFloorHeight == cullFloorHeight ? locFloorHeight : gr_frontsector->floorheight,
 					// We now return you to your regularly scheduled rendering.
-					PF_Occlude, floorlightlevel, levelflats[gr_frontsector->floorpic].lumpnum, levelflats[gr_frontsector->floorpic].texturenum, NULL, 255, false, floorcolormap);
+					PF_Occlude, floorlightlevel, &levelflats[gr_frontsector->floorpic], NULL, 255, false, floorcolormap);
 			}
 		}
 		else
@@ -3606,13 +3603,12 @@ static void HWR_Subsector(size_t num)
 		{
 			if (sub->validcount != validcount)
 			{
-				HWR_GetFlat(levelflats[gr_frontsector->ceilingpic].lumpnum);
-				HWR_GetTextureFlat(levelflats[gr_frontsector->ceilingpic].texturenum);
+				HWR_GetLevelFlat(&levelflats[gr_frontsector->ceilingpic]);
 				HWR_RenderPlane(NULL, &extrasubsectors[num], true,
 					// Hack to make things continue to work around slopes.
 					locCeilingHeight == cullCeilingHeight ? locCeilingHeight : gr_frontsector->ceilingheight,
 					// We now return you to your regularly scheduled rendering.
-					PF_Occlude, ceilinglightlevel, levelflats[gr_frontsector->ceilingpic].lumpnum, levelflats[gr_frontsector->ceilingpic].texturenum, NULL, 255, false, ceilingcolormap);
+					PF_Occlude, ceilinglightlevel, &levelflats[gr_frontsector->ceilingpic], NULL, 255, false, ceilingcolormap);
 			}
 		}
 		else
@@ -3671,7 +3667,7 @@ static void HWR_Subsector(size_t num)
 					else
 						alpha = HWR_FogBlockAlpha(*gr_frontsector->lightlist[light].lightlevel, NORMALFOG);
 
-					HWR_AddTransparentFloor(0, 0,
+					HWR_AddTransparentFloor(NULL,
 					                       &extrasubsectors[num],
 										   false,
 					                       *rover->bottomheight,
@@ -3683,14 +3679,13 @@ static void HWR_Subsector(size_t num)
 				{
 					light = R_GetPlaneLight(gr_frontsector, centerHeight, dup_viewz < cullHeight ? true : false);
 #ifndef SORTING
-					HWR_Add3DWater(levelflats[*rover->bottompic].lumpnum,
+					HWR_Add3DWater(&levelflats[*rover->bottompic],
 					               &extrasubsectors[num],
 					               *rover->bottomheight,
 					               *gr_frontsector->lightlist[light].lightlevel,
 					               rover->alpha-1, rover->master->frontsector);
 #else
-					HWR_AddTransparentFloor(levelflats[*rover->bottompic].lumpnum,
-											levelflats[*rover->bottompic].texturenum,
+					HWR_AddTransparentFloor(&levelflats[*rover->bottompic],
 					                       &extrasubsectors[num],
 										   false,
 					                       *rover->bottomheight,
@@ -3701,10 +3696,9 @@ static void HWR_Subsector(size_t num)
 				}
 				else
 				{
-					HWR_GetFlat(levelflats[*rover->bottompic].lumpnum);
-					HWR_GetTextureFlat(levelflats[*rover->bottompic].texturenum);
+					HWR_GetLevelFlat(&levelflats[*rover->bottompic]);
 					light = R_GetPlaneLight(gr_frontsector, centerHeight, dup_viewz < cullHeight ? true : false);
-					HWR_RenderPlane(NULL, &extrasubsectors[num], false, *rover->bottomheight, PF_Occlude, *gr_frontsector->lightlist[light].lightlevel, levelflats[*rover->bottompic].lumpnum, levelflats[*rover->bottompic].texturenum,
+					HWR_RenderPlane(NULL, &extrasubsectors[num], false, *rover->bottomheight, PF_Occlude, *gr_frontsector->lightlist[light].lightlevel, &levelflats[*rover->bottompic],
 					                rover->master->frontsector, 255, false, *gr_frontsector->lightlist[light].extra_colormap);
 				}
 			}
@@ -3736,7 +3730,7 @@ static void HWR_Subsector(size_t num)
 					else
 						alpha = HWR_FogBlockAlpha(*gr_frontsector->lightlist[light].lightlevel, NORMALFOG);
 
-					HWR_AddTransparentFloor(0, 0,
+					HWR_AddTransparentFloor(NULL,
 					                       &extrasubsectors[num],
 										   true,
 					                       *rover->topheight,
@@ -3748,14 +3742,13 @@ static void HWR_Subsector(size_t num)
 				{
 					light = R_GetPlaneLight(gr_frontsector, centerHeight, dup_viewz < cullHeight ? true : false);
 #ifndef SORTING
-					HWR_Add3DWater(levelflats[*rover->toppic].lumpnum,
+					HWR_Add3DWater(&levelflats[*rover->toppic],
 					                          &extrasubsectors[num],
 					                          *rover->topheight,
 					                          *gr_frontsector->lightlist[light].lightlevel,
 					                          rover->alpha-1, rover->master->frontsector);
 #else
-					HWR_AddTransparentFloor(levelflats[*rover->toppic].lumpnum,
-											levelflats[*rover->bottompic].texturenum,
+					HWR_AddTransparentFloor(&levelflats[*rover->toppic],
 					                        &extrasubsectors[num],
 											true,
 					                        *rover->topheight,
@@ -3767,10 +3760,9 @@ static void HWR_Subsector(size_t num)
 				}
 				else
 				{
-					HWR_GetFlat(levelflats[*rover->toppic].lumpnum);
-					HWR_GetTextureFlat(levelflats[*rover->toppic].texturenum);
+					HWR_GetLevelFlat(&levelflats[*rover->toppic]);
 					light = R_GetPlaneLight(gr_frontsector, centerHeight, dup_viewz < cullHeight ? true : false);
-					HWR_RenderPlane(NULL, &extrasubsectors[num], true, *rover->topheight, PF_Occlude, *gr_frontsector->lightlist[light].lightlevel, levelflats[*rover->toppic].lumpnum, levelflats[*rover->toppic].texturenum,
+					HWR_RenderPlane(NULL, &extrasubsectors[num], true, *rover->topheight, PF_Occlude, *gr_frontsector->lightlist[light].lightlevel, &levelflats[*rover->toppic],
 					                  rover->master->frontsector, 255, false, *gr_frontsector->lightlist[light].extra_colormap);
 				}
 			}
@@ -5098,8 +5090,7 @@ typedef struct
 	boolean isceiling;
 	fixed_t fixedheight;
 	INT32 lightlevel;
-	lumpnum_t lumpnum;
-	INT32 texturenum;
+	levelflat_t *levelflat;
 	INT32 alpha;
 	sector_t *FOFSector;
 	FBITFIELD blend;
@@ -5117,8 +5108,7 @@ typedef struct
 	boolean isceiling;
 	fixed_t fixedheight;
 	INT32 lightlevel;
-	lumpnum_t lumpnum;
-	INT32 texturenum;
+	levelflat_t *levelflat;
 	INT32 alpha;
 	sector_t *FOFSector;
 	FBITFIELD blend;
@@ -5149,7 +5139,7 @@ static INT32 drawcount = 0;
 #define MAX_TRANSPARENTFLOOR 512
 
 // This will likely turn into a copy of HWR_Add3DWater and replace it.
-void HWR_AddTransparentFloor(lumpnum_t lumpnum, INT32 texturenum, extrasubsector_t *xsub, boolean isceiling,
+void HWR_AddTransparentFloor(levelflat_t *levelflat, extrasubsector_t *xsub, boolean isceiling,
 	fixed_t fixedheight, INT32 lightlevel, INT32 alpha, sector_t *FOFSector, FBITFIELD blend, boolean fogplane, extracolormap_t *planecolormap)
 {
 	static size_t allocedplanes = 0;
@@ -5167,8 +5157,7 @@ void HWR_AddTransparentFloor(lumpnum_t lumpnum, INT32 texturenum, extrasubsector
 	planeinfo[numplanes].isceiling = isceiling;
 	planeinfo[numplanes].fixedheight = fixedheight;
 	planeinfo[numplanes].lightlevel = lightlevel;
-	planeinfo[numplanes].lumpnum = lumpnum;
-	planeinfo[numplanes].texturenum = texturenum;
+	planeinfo[numplanes].levelflat = levelflat;
 	planeinfo[numplanes].xsub = xsub;
 	planeinfo[numplanes].alpha = alpha;
 	planeinfo[numplanes].FOFSector = FOFSector;
@@ -5182,7 +5171,7 @@ void HWR_AddTransparentFloor(lumpnum_t lumpnum, INT32 texturenum, extrasubsector
 
 // Adding this for now until I can create extrasubsector info for polyobjects
 // When that happens it'll just be done through HWR_AddTransparentFloor and HWR_RenderPlane
-void HWR_AddTransparentPolyobjectFloor(lumpnum_t lumpnum, INT32 texturenum, polyobj_t *polysector, boolean isceiling,
+void HWR_AddTransparentPolyobjectFloor(levelflat_t *levelflat, polyobj_t *polysector, boolean isceiling,
 	fixed_t fixedheight, INT32 lightlevel, INT32 alpha, sector_t *FOFSector, FBITFIELD blend, extracolormap_t *planecolormap)
 {
 	static size_t allocedpolyplanes = 0;
@@ -5200,8 +5189,7 @@ void HWR_AddTransparentPolyobjectFloor(lumpnum_t lumpnum, INT32 texturenum, poly
 	polyplaneinfo[numpolyplanes].isceiling = isceiling;
 	polyplaneinfo[numpolyplanes].fixedheight = fixedheight;
 	polyplaneinfo[numpolyplanes].lightlevel = lightlevel;
-	polyplaneinfo[numpolyplanes].lumpnum = lumpnum;
-	polyplaneinfo[numpolyplanes].texturenum = texturenum;
+	polyplaneinfo[numpolyplanes].levelflat = levelflat;
 	polyplaneinfo[numpolyplanes].polysector = polysector;
 	polyplaneinfo[numpolyplanes].alpha = alpha;
 	polyplaneinfo[numpolyplanes].FOFSector = FOFSector;
@@ -5363,12 +5351,9 @@ static void HWR_CreateDrawNodes(void)
 			gr_frontsector = NULL;
 
 			if (!(sortnode[sortindex[i]].plane->blend & PF_NoTexture))
-			{
-				HWR_GetFlat(sortnode[sortindex[i]].plane->lumpnum);
-				HWR_GetTextureFlat(sortnode[sortindex[i]].plane->texturenum);
-			}
+				HWR_GetLevelFlat(sortnode[sortindex[i]].plane->levelflat);
 			HWR_RenderPlane(NULL, sortnode[sortindex[i]].plane->xsub, sortnode[sortindex[i]].plane->isceiling, sortnode[sortindex[i]].plane->fixedheight, sortnode[sortindex[i]].plane->blend, sortnode[sortindex[i]].plane->lightlevel,
-				sortnode[sortindex[i]].plane->lumpnum, sortnode[sortindex[i]].plane->texturenum, sortnode[sortindex[i]].plane->FOFSector, sortnode[sortindex[i]].plane->alpha, sortnode[sortindex[i]].plane->fogplane, sortnode[sortindex[i]].plane->planecolormap);
+				sortnode[sortindex[i]].plane->levelflat, sortnode[sortindex[i]].plane->FOFSector, sortnode[sortindex[i]].plane->alpha, sortnode[sortindex[i]].plane->fogplane, sortnode[sortindex[i]].plane->planecolormap);
 		}
 		else if (sortnode[sortindex[i]].polyplane)
 		{
@@ -5376,12 +5361,9 @@ static void HWR_CreateDrawNodes(void)
 			gr_frontsector = NULL;
 
 			if (!(sortnode[sortindex[i]].polyplane->blend & PF_NoTexture))
-			{
-				HWR_GetFlat(sortnode[sortindex[i]].polyplane->lumpnum);
-				HWR_GetTextureFlat(sortnode[sortindex[i]].polyplane->texturenum);
-			}
+				HWR_GetLevelFlat(sortnode[sortindex[i]].polyplane->levelflat);
 			HWR_RenderPolyObjectPlane(sortnode[sortindex[i]].polyplane->polysector, sortnode[sortindex[i]].polyplane->isceiling, sortnode[sortindex[i]].polyplane->fixedheight, sortnode[sortindex[i]].polyplane->blend, sortnode[sortindex[i]].polyplane->lightlevel,
-				sortnode[sortindex[i]].polyplane->lumpnum, sortnode[sortindex[i]].polyplane->texturenum, sortnode[sortindex[i]].polyplane->FOFSector, sortnode[sortindex[i]].polyplane->alpha, sortnode[sortindex[i]].polyplane->planecolormap);
+				sortnode[sortindex[i]].polyplane->levelflat, sortnode[sortindex[i]].polyplane->FOFSector, sortnode[sortindex[i]].polyplane->alpha, sortnode[sortindex[i]].polyplane->planecolormap);
 		}
 		else if (sortnode[sortindex[i]].wall)
 		{
@@ -5426,17 +5408,17 @@ static void HWR_DrawSprites(void)
 #endif
 				if (spr->mobj && spr->mobj->skin && spr->mobj->sprite == SPR_PLAY)
 				{
-					if (!cv_grmd2.value || md2_playermodels[(skin_t*)spr->mobj->skin-skins].notfound || md2_playermodels[(skin_t*)spr->mobj->skin-skins].scale < 0.0f)
+					if (!cv_grmodels.value || md2_playermodels[(skin_t*)spr->mobj->skin-skins].notfound || md2_playermodels[(skin_t*)spr->mobj->skin-skins].scale < 0.0f)
 						HWR_DrawSprite(spr);
 					else
-						HWR_DrawMD2(spr);
+						HWR_DrawModel(spr);
 				}
 				else
 				{
-					if (!cv_grmd2.value || md2_models[spr->mobj->sprite].notfound || md2_models[spr->mobj->sprite].scale < 0.0f)
+					if (!cv_grmodels.value || md2_models[spr->mobj->sprite].notfound || md2_models[spr->mobj->sprite].scale < 0.0f)
 						HWR_DrawSprite(spr);
 					else
-						HWR_DrawMD2(spr);
+						HWR_DrawModel(spr);
 				}
 		}
 	}
@@ -5564,7 +5546,7 @@ static void HWR_ProjectSprite(mobj_t *thing)
 	tz = (tr_x * gr_viewcos) + (tr_y * gr_viewsin);
 
 	// thing is behind view plane?
-	if (tz < ZCLIP_PLANE && !papersprite && (!cv_grmd2.value || md2_models[thing->sprite].notfound == true)) //Yellow: Only MD2's dont disappear
+	if (tz < ZCLIP_PLANE && !papersprite && (!cv_grmodels.value || md2_models[thing->sprite].notfound == true)) //Yellow: Only MD2's dont disappear
 		return;
 
 	// The above can stay as it works for cutting sprites that are too close
@@ -5755,6 +5737,15 @@ static void HWR_ProjectSprite(mobj_t *thing)
 		// New colormap stuff for skins Tails 06-07-2002
 		if (thing->colorized)
 			vis->colormap = R_GetTranslationColormap(TC_RAINBOW, thing->color, GTC_CACHE);
+		else if (thing->player && thing->player->dashmode >= DASHMODE_THRESHOLD
+			&& (thing->player->charflags & SF_DASHMODE)
+			&& ((leveltime/2) & 1))
+		{
+			if (thing->player->charflags & SF_MACHINE)
+				vis->colormap = R_GetTranslationColormap(TC_DASHMODE, 0, GTC_CACHE);
+			else
+				vis->colormap = R_GetTranslationColormap(TC_RAINBOW, thing->color, GTC_CACHE);
+		}
 		else if (thing->skin && thing->sprite == SPR_PLAY) // This thing is a player!
 		{
 			size_t skinnum = (skin_t*)thing->skin-skins;
@@ -6307,6 +6298,7 @@ void HWR_RenderPlayerView(INT32 viewnumber, player_t *player)
 
 	// note: sets viewangle, viewx, viewy, viewz
 	R_SetupFrame(player);
+	framecount++; // timedemo
 
 	// copy view cam position for local use
 	dup_viewx = viewx;
@@ -6603,13 +6595,13 @@ void HWR_Startup(void)
 	// do this once
 	if (!startupdone)
 	{
-		CONS_Printf("HWR_Startup()\n");
+		CONS_Printf("HWR_Startup()...\n");
 		HWR_InitPolyPool();
 		// add console cmds & vars
 		HWR_AddEngineCommands();
 		HWR_InitTextureCache();
 
-		HWR_InitMD2();
+		HWR_InitModels();
 
 #ifdef ALAM_LIGHTING
 		HWR_InitLight();
@@ -6660,10 +6652,11 @@ void transform(float *cx, float *cy, float *cz)
 
 
 //Hurdler: 3D Water stuff
+#ifndef SORTING
+
 #define MAX_3DWATER 512
 
-#ifndef SORTING
-static void HWR_Add3DWater(lumpnum_t lumpnum, extrasubsector_t *xsub,
+static void HWR_Add3DWater(levelflat_t *levelflat, extrasubsector_t *xsub,
 	fixed_t fixedheight, INT32 lightlevel, INT32 alpha, sector_t *FOFSector)
 {
 	static size_t allocedplanes = 0;
@@ -6679,17 +6672,15 @@ static void HWR_Add3DWater(lumpnum_t lumpnum, extrasubsector_t *xsub,
 	}
 	planeinfo[numfloors].fixedheight = fixedheight;
 	planeinfo[numfloors].lightlevel = lightlevel;
-	planeinfo[numfloors].lumpnum = lumpnum;
+	planeinfo[numfloors].levelflat = levelflat;
 	planeinfo[numfloors].xsub = xsub;
 	planeinfo[numfloors].alpha = alpha;
 	planeinfo[numfloors].FOFSector = FOFSector;
 	numfloors++;
 }
-#endif
 
 #define DIST_PLANE(i) ABS(planeinfo[(i)].fixedheight-dup_viewz)
 
-#if 0
 static void HWR_QuickSortPlane(INT32 start, INT32 finish)
 {
 	INT32 left = start;
@@ -6719,9 +6710,7 @@ static void HWR_QuickSortPlane(INT32 start, INT32 finish)
 	if (start < right) HWR_QuickSortPlane(start, right);
 	if (left < finish) HWR_QuickSortPlane(left, finish);
 }
-#endif
 
-#ifndef SORTING
 static void HWR_Render3DWater(void)
 {
 	size_t i;
@@ -6752,8 +6741,8 @@ static void HWR_Render3DWater(void)
 	gr_frontsector = NULL; //Hurdler: gr_fronsector is no longer valid
 	for (i = 0; i < numfloors; i++)
 	{
-		HWR_GetFlat(planeinfo[i].lumpnum);
-		HWR_RenderPlane(NULL, planeinfo[i].xsub, planeinfo[i].isceiling, planeinfo[i].fixedheight, PF_Translucent, planeinfo[i].lightlevel, planeinfo[i].lumpnum,
+		HWR_GetLevelFlat(planeinfo[i].levelflat);
+		HWR_RenderPlane(NULL, planeinfo[i].xsub, planeinfo[i].isceiling, planeinfo[i].fixedheight, PF_Translucent, planeinfo[i].lightlevel, planeinfo[i].levelflat,
 			planeinfo[i].FOFSector, planeinfo[i].alpha, planeinfo[i].fogplane, planeinfo[i].planecolormap);
 	}
 	numfloors = 0;
@@ -6786,6 +6775,7 @@ static void HWR_AddTransparentWall(wallVert3D *wallVerts, FSurfaceInfo *pSurf, I
 	wallinfo[numwalls].wallcolormap = wallcolormap;
 	numwalls++;
 }
+
 #ifndef SORTING
 static void HWR_RenderTransparentWalls(void)
 {
@@ -6818,6 +6808,7 @@ static void HWR_RenderTransparentWalls(void)
 	numwalls = 0;
 }
 #endif
+
 static void HWR_RenderWall(wallVert3D   *wallVerts, FSurfaceInfo *pSurf, FBITFIELD blend, boolean fogwall, INT32 lightlevel, extracolormap_t *wallcolormap)
 {
 	FOutVector  trVerts[4];
@@ -6876,11 +6867,6 @@ static void HWR_RenderWall(wallVert3D   *wallVerts, FSurfaceInfo *pSurf, FBITFIE
 #endif
 }
 
-void HWR_SetPaletteColor(INT32 palcolor)
-{
-	HWD.pfnSetSpecialState(HWD_SET_PALETTECOLOR, palcolor);
-}
-
 INT32 HWR_GetTextureUsed(void)
 {
 	return HWD.pfnGetTextureUsed();
@@ -6927,7 +6913,6 @@ void HWR_DoPostProcessor(player_t *player)
 	if (splitscreen) // Not supported in splitscreen - someone want to add support?
 		return;
 
-#ifdef SHUFFLE
 	// Drunken vision! WooOOooo~
 	if (*type == postimg_water || *type == postimg_heat)
 	{
@@ -6970,7 +6955,6 @@ void HWR_DoPostProcessor(player_t *player)
 			HWD.pfnMakeScreenTexture();
 	}
 	// Flipping of the screen isn't done here anymore
-#endif // SHUFFLE
 }
 
 void HWR_StartScreenWipe(void)
@@ -7017,7 +7001,7 @@ void HWR_DoWipe(UINT8 wipenum, UINT8 scrnnum)
 
 	HWR_GetFadeMask(lumpnum);
 
-	HWD.pfnDoScreenWipe(HWRWipeCounter); // Still send in wipecounter since old stuff might not support multitexturing
+	HWD.pfnDoScreenWipe();
 
 	HWRWipeCounter += 0.05f; // increase opacity of end screen
 
diff --git a/src/hardware/hw_main.h b/src/hardware/hw_main.h
index 31e97cc130fb9ce740194a704a8525905bdc0369..3a0a58427fbb1d2d8bed6173cb04430b32c45541 100644
--- a/src/hardware/hw_main.h
+++ b/src/hardware/hw_main.h
@@ -60,7 +60,6 @@ void HWR_AddCommands(void);
 void HWR_CorrectSWTricks(void);
 void transform(float *cx, float *cy, float *cz);
 FBITFIELD HWR_TranstableToAlpha(INT32 transtablenum, FSurfaceInfo *pSurf);
-void HWR_SetPaletteColor(INT32 palcolor);
 INT32 HWR_GetTextureUsed(void);
 void HWR_DoPostProcessor(player_t *player);
 void HWR_StartScreenWipe(void);
@@ -83,7 +82,8 @@ extern consvar_t cv_grcoronas;
 extern consvar_t cv_grcoronasize;
 #endif
 extern consvar_t cv_grfov;
-extern consvar_t cv_grmd2;
+extern consvar_t cv_grmodels;
+extern consvar_t cv_grmodelinterpolation;
 extern consvar_t cv_grfog;
 extern consvar_t cv_grfogcolor;
 extern consvar_t cv_grfogdensity;
@@ -94,7 +94,6 @@ extern consvar_t cv_grgammablue;
 extern consvar_t cv_grfiltermode;
 extern consvar_t cv_granisotropicmode;
 extern consvar_t cv_grcorrecttricks;
-extern consvar_t cv_voodoocompatibility;
 extern consvar_t cv_grfovchange;
 extern consvar_t cv_grsolvetjoin;
 extern consvar_t cv_grspritebillboarding;
diff --git a/src/hardware/hw_md2.c b/src/hardware/hw_md2.c
index b847fdbc359c3f5d44d589705c1ce96ecfcdda3e..e02d2ce4673080eea9b8d92690c2722b1d8b9f9c 100644
--- a/src/hardware/hw_md2.c
+++ b/src/hardware/hw_md2.c
@@ -35,6 +35,7 @@
 #include "hw_drv.h"
 #include "hw_light.h"
 #include "hw_md2.h"
+#include "../d_main.h"
 #include "../r_bsp.h"
 #include "../r_main.h"
 #include "../m_misc.h"
@@ -43,6 +44,7 @@
 #include "../r_things.h"
 #include "../r_draw.h"
 #include "../p_tick.h"
+#include "hw_model.h"
 
 #include "hw_main.h"
 #include "../v_video.h"
@@ -75,172 +77,6 @@
 #include "errno.h"
 #endif
 
-#define NUMVERTEXNORMALS 162
-float avertexnormals[NUMVERTEXNORMALS][3] = {
-{-0.525731f, 0.000000f, 0.850651f},
-{-0.442863f, 0.238856f, 0.864188f},
-{-0.295242f, 0.000000f, 0.955423f},
-{-0.309017f, 0.500000f, 0.809017f},
-{-0.162460f, 0.262866f, 0.951056f},
-{0.000000f, 0.000000f, 1.000000f},
-{0.000000f, 0.850651f, 0.525731f},
-{-0.147621f, 0.716567f, 0.681718f},
-{0.147621f, 0.716567f, 0.681718f},
-{0.000000f, 0.525731f, 0.850651f},
-{0.309017f, 0.500000f, 0.809017f},
-{0.525731f, 0.000000f, 0.850651f},
-{0.295242f, 0.000000f, 0.955423f},
-{0.442863f, 0.238856f, 0.864188f},
-{0.162460f, 0.262866f, 0.951056f},
-{-0.681718f, 0.147621f, 0.716567f},
-{-0.809017f, 0.309017f, 0.500000f},
-{-0.587785f, 0.425325f, 0.688191f},
-{-0.850651f, 0.525731f, 0.000000f},
-{-0.864188f, 0.442863f, 0.238856f},
-{-0.716567f, 0.681718f, 0.147621f},
-{-0.688191f, 0.587785f, 0.425325f},
-{-0.500000f, 0.809017f, 0.309017f},
-{-0.238856f, 0.864188f, 0.442863f},
-{-0.425325f, 0.688191f, 0.587785f},
-{-0.716567f, 0.681718f, -0.147621f},
-{-0.500000f, 0.809017f, -0.309017f},
-{-0.525731f, 0.850651f, 0.000000f},
-{0.000000f, 0.850651f, -0.525731f},
-{-0.238856f, 0.864188f, -0.442863f},
-{0.000000f, 0.955423f, -0.295242f},
-{-0.262866f, 0.951056f, -0.162460f},
-{0.000000f, 1.000000f, 0.000000f},
-{0.000000f, 0.955423f, 0.295242f},
-{-0.262866f, 0.951056f, 0.162460f},
-{0.238856f, 0.864188f, 0.442863f},
-{0.262866f, 0.951056f, 0.162460f},
-{0.500000f, 0.809017f, 0.309017f},
-{0.238856f, 0.864188f, -0.442863f},
-{0.262866f, 0.951056f, -0.162460f},
-{0.500000f, 0.809017f, -0.309017f},
-{0.850651f, 0.525731f, 0.000000f},
-{0.716567f, 0.681718f, 0.147621f},
-{0.716567f, 0.681718f, -0.147621f},
-{0.525731f, 0.850651f, 0.000000f},
-{0.425325f, 0.688191f, 0.587785f},
-{0.864188f, 0.442863f, 0.238856f},
-{0.688191f, 0.587785f, 0.425325f},
-{0.809017f, 0.309017f, 0.500000f},
-{0.681718f, 0.147621f, 0.716567f},
-{0.587785f, 0.425325f, 0.688191f},
-{0.955423f, 0.295242f, 0.000000f},
-{1.000000f, 0.000000f, 0.000000f},
-{0.951056f, 0.162460f, 0.262866f},
-{0.850651f, -0.525731f, 0.000000f},
-{0.955423f, -0.295242f, 0.000000f},
-{0.864188f, -0.442863f, 0.238856f},
-{0.951056f, -0.162460f, 0.262866f},
-{0.809017f, -0.309017f, 0.500000f},
-{0.681718f, -0.147621f, 0.716567f},
-{0.850651f, 0.000000f, 0.525731f},
-{0.864188f, 0.442863f, -0.238856f},
-{0.809017f, 0.309017f, -0.500000f},
-{0.951056f, 0.162460f, -0.262866f},
-{0.525731f, 0.000000f, -0.850651f},
-{0.681718f, 0.147621f, -0.716567f},
-{0.681718f, -0.147621f, -0.716567f},
-{0.850651f, 0.000000f, -0.525731f},
-{0.809017f, -0.309017f, -0.500000f},
-{0.864188f, -0.442863f, -0.238856f},
-{0.951056f, -0.162460f, -0.262866f},
-{0.147621f, 0.716567f, -0.681718f},
-{0.309017f, 0.500000f, -0.809017f},
-{0.425325f, 0.688191f, -0.587785f},
-{0.442863f, 0.238856f, -0.864188f},
-{0.587785f, 0.425325f, -0.688191f},
-{0.688191f, 0.587785f, -0.425325f},
-{-0.147621f, 0.716567f, -0.681718f},
-{-0.309017f, 0.500000f, -0.809017f},
-{0.000000f, 0.525731f, -0.850651f},
-{-0.525731f, 0.000000f, -0.850651f},
-{-0.442863f, 0.238856f, -0.864188f},
-{-0.295242f, 0.000000f, -0.955423f},
-{-0.162460f, 0.262866f, -0.951056f},
-{0.000000f, 0.000000f, -1.000000f},
-{0.295242f, 0.000000f, -0.955423f},
-{0.162460f, 0.262866f, -0.951056f},
-{-0.442863f, -0.238856f, -0.864188f},
-{-0.309017f, -0.500000f, -0.809017f},
-{-0.162460f, -0.262866f, -0.951056f},
-{0.000000f, -0.850651f, -0.525731f},
-{-0.147621f, -0.716567f, -0.681718f},
-{0.147621f, -0.716567f, -0.681718f},
-{0.000000f, -0.525731f, -0.850651f},
-{0.309017f, -0.500000f, -0.809017f},
-{0.442863f, -0.238856f, -0.864188f},
-{0.162460f, -0.262866f, -0.951056f},
-{0.238856f, -0.864188f, -0.442863f},
-{0.500000f, -0.809017f, -0.309017f},
-{0.425325f, -0.688191f, -0.587785f},
-{0.716567f, -0.681718f, -0.147621f},
-{0.688191f, -0.587785f, -0.425325f},
-{0.587785f, -0.425325f, -0.688191f},
-{0.000000f, -0.955423f, -0.295242f},
-{0.000000f, -1.000000f, 0.000000f},
-{0.262866f, -0.951056f, -0.162460f},
-{0.000000f, -0.850651f, 0.525731f},
-{0.000000f, -0.955423f, 0.295242f},
-{0.238856f, -0.864188f, 0.442863f},
-{0.262866f, -0.951056f, 0.162460f},
-{0.500000f, -0.809017f, 0.309017f},
-{0.716567f, -0.681718f, 0.147621f},
-{0.525731f, -0.850651f, 0.000000f},
-{-0.238856f, -0.864188f, -0.442863f},
-{-0.500000f, -0.809017f, -0.309017f},
-{-0.262866f, -0.951056f, -0.162460f},
-{-0.850651f, -0.525731f, 0.000000f},
-{-0.716567f, -0.681718f, -0.147621f},
-{-0.716567f, -0.681718f, 0.147621f},
-{-0.525731f, -0.850651f, 0.000000f},
-{-0.500000f, -0.809017f, 0.309017f},
-{-0.238856f, -0.864188f, 0.442863f},
-{-0.262866f, -0.951056f, 0.162460f},
-{-0.864188f, -0.442863f, 0.238856f},
-{-0.809017f, -0.309017f, 0.500000f},
-{-0.688191f, -0.587785f, 0.425325f},
-{-0.681718f, -0.147621f, 0.716567f},
-{-0.442863f, -0.238856f, 0.864188f},
-{-0.587785f, -0.425325f, 0.688191f},
-{-0.309017f, -0.500000f, 0.809017f},
-{-0.147621f, -0.716567f, 0.681718f},
-{-0.425325f, -0.688191f, 0.587785f},
-{-0.162460f, -0.262866f, 0.951056f},
-{0.442863f, -0.238856f, 0.864188f},
-{0.162460f, -0.262866f, 0.951056f},
-{0.309017f, -0.500000f, 0.809017f},
-{0.147621f, -0.716567f, 0.681718f},
-{0.000000f, -0.525731f, 0.850651f},
-{0.425325f, -0.688191f, 0.587785f},
-{0.587785f, -0.425325f, 0.688191f},
-{0.688191f, -0.587785f, 0.425325f},
-{-0.955423f, 0.295242f, 0.000000f},
-{-0.951056f, 0.162460f, 0.262866f},
-{-1.000000f, 0.000000f, 0.000000f},
-{-0.850651f, 0.000000f, 0.525731f},
-{-0.955423f, -0.295242f, 0.000000f},
-{-0.951056f, -0.162460f, 0.262866f},
-{-0.864188f, 0.442863f, -0.238856f},
-{-0.951056f, 0.162460f, -0.262866f},
-{-0.809017f, 0.309017f, -0.500000f},
-{-0.864188f, -0.442863f, -0.238856f},
-{-0.951056f, -0.162460f, -0.262866f},
-{-0.809017f, -0.309017f, -0.500000f},
-{-0.681718f, 0.147621f, -0.716567f},
-{-0.681718f, -0.147621f, -0.716567f},
-{-0.850651f, 0.000000f, -0.525731f},
-{-0.688191f, 0.587785f, -0.425325f},
-{-0.587785f, 0.425325f, -0.688191f},
-{-0.425325f, 0.688191f, -0.587785f},
-{-0.425325f, -0.688191f, -0.587785f},
-{-0.587785f, -0.425325f, -0.688191f},
-{-0.688191f, -0.587785f, -0.425325f},
-};
-
 md2_t md2_models[NUMSPRITES];
 md2_t md2_playermodels[MAXSKINS];
 
@@ -248,230 +84,25 @@ md2_t md2_playermodels[MAXSKINS];
 /*
  * free model
  */
-static void md2_freeModel (md2_model_t *model)
+#if 0
+static void md2_freeModel (model_t *model)
 {
-	if (model)
-	{
-		if (model->skins)
-			free(model->skins);
-
-		if (model->texCoords)
-			free(model->texCoords);
-
-		if (model->triangles)
-			free(model->triangles);
-
-		if (model->frames)
-		{
-			size_t i;
-
-			for (i = 0; i < model->header.numFrames; i++)
-			{
-				if (model->frames[i].vertices)
-					free(model->frames[i].vertices);
-			}
-			free(model->frames);
-		}
-
-		if (model->spr2frames)
-			free(model->spr2frames);
-
-		if (model->glCommandBuffer)
-			free(model->glCommandBuffer);
-
-		free(model);
-	}
+	UnloadModel(model);
 }
+#endif
 
 
 //
 // load model
 //
 // Hurdler: the current path is the Legacy.exe path
-static md2_model_t *md2_readModel(const char *filename)
+static model_t *md2_readModel(const char *filename)
 {
-	FILE *file;
-	md2_model_t *model;
-	UINT8 buffer[MD2_MAX_FRAMESIZE];
-	size_t i;
-
-	model = calloc(1, sizeof (*model));
-	if (model == NULL)
-		return 0;
-
 	//Filename checking fixed ~Monster Iestyn and Golden
-	file = fopen(va("%s"PATHSEP"%s", srb2home, filename), "rb");
-	if (!file)
-	{
-		free(model);
-		return 0;
-	}
-
-	// initialize model and read header
-
-	if (fread(&model->header, sizeof (model->header), 1, file) != 1
-		|| model->header.magic != MD2_IDENT
-		|| model->header.version != MD2_VERSION)
-	{
-		fclose(file);
-		free(model);
-		return 0;
-	}
-
-	model->header.numSkins = 1;
-
-#define MD2LIMITCHECK(field, max, msgname) \
-	if (field > max) \
-	{ \
-		CONS_Alert(CONS_ERROR, "md2_readModel: %s has too many " msgname " (# found: %d, maximum: %d)\n", filename, field, max); \
-		md2_freeModel (model); \
-		fclose(file); \
-		return 0; \
-	}
-
-	// Uncomment if these are actually needed
-//	MD2LIMITCHECK(model->header.numSkins,     MD2_MAX_SKINS,     "skins")
-//	MD2LIMITCHECK(model->header.numTexCoords, MD2_MAX_TEXCOORDS, "texture coordinates")
-	MD2LIMITCHECK(model->header.numTriangles, MD2_MAX_TRIANGLES, "triangles")
-	MD2LIMITCHECK(model->header.numFrames,    MD2_MAX_FRAMES,    "frames")
-	MD2LIMITCHECK(model->header.numVertices,  MD2_MAX_VERTICES,  "vertices")
-
-#undef MD2LIMITCHECK
-
-	// read skins
-	fseek(file, model->header.offsetSkins, SEEK_SET);
-	if (model->header.numSkins > 0)
-	{
-		model->skins = calloc(sizeof (md2_skin_t), model->header.numSkins);
-		if (!model->skins || model->header.numSkins !=
-			fread(model->skins, sizeof (md2_skin_t), model->header.numSkins, file))
-		{
-			md2_freeModel (model);
-			fclose(file);
-			return 0;
-		}
-	}
-
-	// read texture coordinates
-	fseek(file, model->header.offsetTexCoords, SEEK_SET);
-	if (model->header.numTexCoords > 0)
-	{
-		model->texCoords = calloc(sizeof (md2_textureCoordinate_t), model->header.numTexCoords);
-		if (!model->texCoords || model->header.numTexCoords !=
-			fread(model->texCoords, sizeof (md2_textureCoordinate_t), model->header.numTexCoords, file))
-		{
-			md2_freeModel (model);
-			fclose(file);
-			return 0;
-		}
-	}
-
-	// read triangles
-	fseek(file, model->header.offsetTriangles, SEEK_SET);
-	if (model->header.numTriangles > 0)
-	{
-		model->triangles = calloc(sizeof (md2_triangle_t), model->header.numTriangles);
-		if (!model->triangles || model->header.numTriangles !=
-			fread(model->triangles, sizeof (md2_triangle_t), model->header.numTriangles, file))
-		{
-			md2_freeModel (model);
-			fclose(file);
-			return 0;
-		}
-	}
-
-	// read alias frames
-	fseek(file, model->header.offsetFrames, SEEK_SET);
-	if (model->header.numFrames > 0)
-	{
-		model->frames = calloc(sizeof (md2_frame_t), model->header.numFrames);
-		if (!model->frames)
-		{
-			md2_freeModel (model);
-			fclose(file);
-			return 0;
-		}
-
-		for (i = 0; i < model->header.numFrames; i++)
-		{
-			md2_alias_frame_t *frame = (md2_alias_frame_t *)(void *)buffer;
-			size_t j;
-
-			model->frames[i].vertices = calloc(sizeof (md2_triangleVertex_t), model->header.numVertices);
-			if (!model->frames[i].vertices || model->header.frameSize !=
-				fread(frame, 1, model->header.frameSize, file))
-			{
-				md2_freeModel (model);
-				fclose(file);
-				return 0;
-			}
-
-			strcpy(model->frames[i].name, frame->name);
-			if (frame->name[0] == 'S')
-			{
-				boolean super;
-				if ((super = (fastncmp("UPER", frame->name+1, 4))) // SUPER
-					|| fastncmp("PR2_", frame->name+1, 4)) // SPR2_
-				{
-					UINT8 spr2;
-					for (spr2 = 0; spr2 < free_spr2; spr2++)
-						if (fastncmp(frame->name+5,spr2names[spr2],3)
-						&& ((frame->name[8] == spr2names[spr2][3])
-							|| (frame->name[8] == '.' && spr2names[spr2][3] == '_')))
-							break;
-
-					if (spr2 < free_spr2)
-					{
-						if (!model->spr2frames)
-						{
-							model->spr2frames = calloc(sizeof (size_t), 2*NUMPLAYERSPRITES*2);
-							if (!model->spr2frames)
-							{
-								md2_freeModel (model);
-								fclose(file);
-								return 0;
-							}
-						}
-						if (super)
-							spr2 |= FF_SPR2SUPER;
-						if (model->spr2frames[spr2*2 + 1]++ == 0) // numspr2frames
-							model->spr2frames[spr2*2] = i; // starting frame
-						CONS_Debug(DBG_RENDER, "frame %s, sprite2 %s - starting frame %s, number of frames %s\n", frame->name, spr2names[spr2 & ~FF_SPR2SUPER], sizeu1(model->spr2frames[spr2*2]), sizeu2(model->spr2frames[spr2*2 + 1]));
-					}
-				}
-			}
-			for (j = 0; j < model->header.numVertices; j++)
-			{
-				model->frames[i].vertices[j].vertex[0] = (float) ((INT32) frame->alias_vertices[j].vertex[0]) * frame->scale[0] + frame->translate[0];
-				model->frames[i].vertices[j].vertex[2] = -1* ((float) ((INT32) frame->alias_vertices[j].vertex[1]) * frame->scale[1] + frame->translate[1]);
-				model->frames[i].vertices[j].vertex[1] = (float) ((INT32) frame->alias_vertices[j].vertex[2]) * frame->scale[2] + frame->translate[2];
-				model->frames[i].vertices[j].normal[0] = avertexnormals[frame->alias_vertices[j].lightNormalIndex][0];
-				model->frames[i].vertices[j].normal[1] = avertexnormals[frame->alias_vertices[j].lightNormalIndex][1];
-				model->frames[i].vertices[j].normal[2] = avertexnormals[frame->alias_vertices[j].lightNormalIndex][2];
-			}
-		}
-	}
-
-	// read gl commands
-	fseek(file, model->header.offsetGlCommands, SEEK_SET);
-	if (model->header.numGlCommands)
-	{
-		model->glCommandBuffer = calloc(sizeof (INT32), model->header.numGlCommands);
-		if (!model->glCommandBuffer || model->header.numGlCommands !=
-			fread(model->glCommandBuffer, sizeof (INT32), model->header.numGlCommands, file))
-		{
-			md2_freeModel (model);
-			fclose(file);
-			return 0;
-		}
-	}
-
-	fclose(file);
-
-	return model;
+	return LoadModel(va("%s"PATHSEP"%s", srb2home, filename), PU_STATIC);
 }
 
-static inline void md2_printModelInfo (md2_model_t *model)
+static inline void md2_printModelInfo (model_t *model)
 {
 #if 0
 	INT32 i;
@@ -530,7 +161,7 @@ static GrTextureFormat_t PNG_Load(const char *filename, int *w, int *h, GLPatch_
 #endif
 	png_FILE_p png_FILE;
 	//Filename checking fixed ~Monster Iestyn and Golden
-	char *pngfilename = va("%s"PATHSEP"md2"PATHSEP"%s", srb2home, filename);
+	char *pngfilename = va("%s"PATHSEP"models"PATHSEP"%s", srb2home, filename);
 
 	FIL_ForceExtension(pngfilename, ".png");
 	png_FILE = fopen(pngfilename, "rb");
@@ -567,7 +198,7 @@ static GrTextureFormat_t PNG_Load(const char *filename, int *w, int *h, GLPatch_
 		//CONS_Debug(DBG_RENDER, "libpng load error on %s\n", filename);
 		png_destroy_read_struct(&png_ptr, &png_info_ptr, NULL);
 		fclose(png_FILE);
-		Z_Free(grpatch->mipmap.grInfo.data);
+		Z_Free(grpatch->mipmap->grInfo.data);
 		return 0;
 	}
 #ifdef USE_FAR_KEYWORD
@@ -608,7 +239,7 @@ static GrTextureFormat_t PNG_Load(const char *filename, int *w, int *h, GLPatch_
 
 	{
 		png_uint_32 i, pitch = png_get_rowbytes(png_ptr, png_info_ptr);
-		png_bytep PNG_image = Z_Malloc(pitch*height, PU_HWRCACHE, &grpatch->mipmap.grInfo.data);
+		png_bytep PNG_image = Z_Malloc(pitch*height, PU_HWRCACHE, &grpatch->mipmap->grInfo.data);
 		png_bytepp row_pointers = png_malloc(png_ptr, height * sizeof (png_bytep));
 		for (i = 0; i < height; i++)
 			row_pointers[i] = PNG_image + i*pitch;
@@ -659,7 +290,7 @@ static GrTextureFormat_t PCX_Load(const char *filename, int *w, int *h,
 	INT32 ch, rep;
 	FILE *file;
 	//Filename checking fixed ~Monster Iestyn and Golden
-	char *pcxfilename = va("%s"PATHSEP"md2"PATHSEP"%s", srb2home, filename);
+	char *pcxfilename = va("%s"PATHSEP"models"PATHSEP"%s", srb2home, filename);
 
 	FIL_ForceExtension(pcxfilename, ".pcx");
 	file = fopen(pcxfilename, "rb");
@@ -682,7 +313,7 @@ static GrTextureFormat_t PCX_Load(const char *filename, int *w, int *h,
 
 	pw = *w = header.xmax - header.xmin + 1;
 	ph = *h = header.ymax - header.ymin + 1;
-	image = Z_Malloc(pw*ph*4, PU_HWRCACHE, &grpatch->mipmap.grInfo.data);
+	image = Z_Malloc(pw*ph*4, PU_HWRCACHE, &grpatch->mipmap->grInfo.data);
 
 	if (fread(palette, sizeof (UINT8), PALSIZE, file) != PALSIZE)
 	{
@@ -720,7 +351,7 @@ static GrTextureFormat_t PCX_Load(const char *filename, int *w, int *h,
 }
 
 // -----------------+
-// md2_loadTexture  : Download a pcx or png texture for MD2 models
+// md2_loadTexture  : Download a pcx or png texture for models
 // -----------------+
 static void md2_loadTexture(md2_t *model)
 {
@@ -730,39 +361,42 @@ static void md2_loadTexture(md2_t *model)
 	if (model->grpatch)
 	{
 		grpatch = model->grpatch;
-		Z_Free(grpatch->mipmap.grInfo.data);
+		Z_Free(grpatch->mipmap->grInfo.data);
 	}
 	else
+	{
 		grpatch = Z_Calloc(sizeof *grpatch, PU_HWRPATCHINFO,
 		                   &(model->grpatch));
+		grpatch->mipmap = Z_Calloc(sizeof (GLMipmap_t), PU_HWRPATCHINFO, NULL);
+	}
 
-	if (!grpatch->mipmap.downloaded && !grpatch->mipmap.grInfo.data)
+	if (!grpatch->mipmap->downloaded && !grpatch->mipmap->grInfo.data)
 	{
 		int w = 0, h = 0;
 #ifdef HAVE_PNG
-		grpatch->mipmap.grInfo.format = PNG_Load(filename, &w, &h, grpatch);
-		if (grpatch->mipmap.grInfo.format == 0)
+		grpatch->mipmap->grInfo.format = PNG_Load(filename, &w, &h, grpatch);
+		if (grpatch->mipmap->grInfo.format == 0)
 #endif
-		grpatch->mipmap.grInfo.format = PCX_Load(filename, &w, &h, grpatch);
-		if (grpatch->mipmap.grInfo.format == 0)
+		grpatch->mipmap->grInfo.format = PCX_Load(filename, &w, &h, grpatch);
+		if (grpatch->mipmap->grInfo.format == 0)
 			return;
 
-		grpatch->mipmap.downloaded = 0;
-		grpatch->mipmap.flags = 0;
+		grpatch->mipmap->downloaded = 0;
+		grpatch->mipmap->flags = 0;
 
 		grpatch->width = (INT16)w;
 		grpatch->height = (INT16)h;
-		grpatch->mipmap.width = (UINT16)w;
-		grpatch->mipmap.height = (UINT16)h;
+		grpatch->mipmap->width = (UINT16)w;
+		grpatch->mipmap->height = (UINT16)h;
 
 #ifdef GLIDE_API_COMPATIBILITY
 		// not correct!
-		grpatch->mipmap.grInfo.smallLodLog2 = GR_LOD_LOG2_256;
-		grpatch->mipmap.grInfo.largeLodLog2 = GR_LOD_LOG2_256;
-		grpatch->mipmap.grInfo.aspectRatioLog2 = GR_ASPECT_LOG2_1x1;
+		grpatch->mipmap->grInfo.smallLodLog2 = GR_LOD_LOG2_256;
+		grpatch->mipmap->grInfo.largeLodLog2 = GR_LOD_LOG2_256;
+		grpatch->mipmap->grInfo.aspectRatioLog2 = GR_ASPECT_LOG2_1x1;
 #endif
 	}
-	HWD.pfnSetTexture(&grpatch->mipmap);
+	HWD.pfnSetTexture(grpatch->mipmap);
 	HWR_UnlockCachedPatch(grpatch);
 }
 
@@ -780,42 +414,45 @@ static void md2_loadBlendTexture(md2_t *model)
 	if (model->blendgrpatch)
 	{
 		grpatch = model->blendgrpatch;
-		Z_Free(grpatch->mipmap.grInfo.data);
+		Z_Free(grpatch->mipmap->grInfo.data);
 	}
 	else
+	{
 		grpatch = Z_Calloc(sizeof *grpatch, PU_HWRPATCHINFO,
 		                   &(model->blendgrpatch));
+		grpatch->mipmap = Z_Calloc(sizeof (GLMipmap_t), PU_HWRPATCHINFO, NULL);
+	}
 
-	if (!grpatch->mipmap.downloaded && !grpatch->mipmap.grInfo.data)
+	if (!grpatch->mipmap->downloaded && !grpatch->mipmap->grInfo.data)
 	{
 		int w = 0, h = 0;
 #ifdef HAVE_PNG
-		grpatch->mipmap.grInfo.format = PNG_Load(filename, &w, &h, grpatch);
-		if (grpatch->mipmap.grInfo.format == 0)
+		grpatch->mipmap->grInfo.format = PNG_Load(filename, &w, &h, grpatch);
+		if (grpatch->mipmap->grInfo.format == 0)
 #endif
-		grpatch->mipmap.grInfo.format = PCX_Load(filename, &w, &h, grpatch);
-		if (grpatch->mipmap.grInfo.format == 0)
+		grpatch->mipmap->grInfo.format = PCX_Load(filename, &w, &h, grpatch);
+		if (grpatch->mipmap->grInfo.format == 0)
 		{
 			Z_Free(filename);
 			return;
 		}
 
-		grpatch->mipmap.downloaded = 0;
-		grpatch->mipmap.flags = 0;
+		grpatch->mipmap->downloaded = 0;
+		grpatch->mipmap->flags = 0;
 
 		grpatch->width = (INT16)w;
 		grpatch->height = (INT16)h;
-		grpatch->mipmap.width = (UINT16)w;
-		grpatch->mipmap.height = (UINT16)h;
+		grpatch->mipmap->width = (UINT16)w;
+		grpatch->mipmap->height = (UINT16)h;
 
 #ifdef GLIDE_API_COMPATIBILITY
 		// not correct!
-		grpatch->mipmap.grInfo.smallLodLog2 = GR_LOD_LOG2_256;
-		grpatch->mipmap.grInfo.largeLodLog2 = GR_LOD_LOG2_256;
-		grpatch->mipmap.grInfo.aspectRatioLog2 = GR_ASPECT_LOG2_1x1;
+		grpatch->mipmap->grInfo.smallLodLog2 = GR_LOD_LOG2_256;
+		grpatch->mipmap->grInfo.largeLodLog2 = GR_LOD_LOG2_256;
+		grpatch->mipmap->grInfo.aspectRatioLog2 = GR_ASPECT_LOG2_1x1;
 #endif
 	}
-	HWD.pfnSetTexture(&grpatch->mipmap); // We do need to do this so that it can be cleared and knows to recreate it when necessary
+	HWD.pfnSetTexture(grpatch->mipmap); // We do need to do this so that it can be cleared and knows to recreate it when necessary
 	HWR_UnlockCachedPatch(grpatch);
 
 	Z_Free(filename);
@@ -824,7 +461,7 @@ static void md2_loadBlendTexture(md2_t *model)
 // Don't spam the console, or the OS with fopen requests!
 static boolean nomd2s = false;
 
-void HWR_InitMD2(void)
+void HWR_InitModels(void)
 {
 	size_t i;
 	INT32 s;
@@ -832,7 +469,7 @@ void HWR_InitMD2(void)
 	char name[18], filename[32];
 	float scale, offset;
 
-	CONS_Printf("InitMD2()...\n");
+	CONS_Printf("HWR_InitModels()...\n");
 	for (s = 0; s < MAXSKINS; s++)
 	{
 		md2_playermodels[s].scale = -1.0f;
@@ -852,13 +489,13 @@ void HWR_InitMD2(void)
 		md2_models[i].error = false;
 	}
 
-	// read the md2.dat file
+	// read the models.dat file
 	//Filename checking fixed ~Monster Iestyn and Golden
-	f = fopen(va("%s"PATHSEP"%s", srb2home, "md2.dat"), "rt");
+	f = fopen(va("%s"PATHSEP"%s", srb2home, "models.dat"), "rt");
 
 	if (!f)
 	{
-		CONS_Printf("%s %s\n", M_GetText("Error while loading md2.dat:"), strerror(errno));
+		CONS_Printf("%s %s\n", M_GetText("Error while loading models.dat:"), strerror(errno));
 		nomd2s = true;
 		return;
 	}
@@ -866,7 +503,7 @@ void HWR_InitMD2(void)
 	{
 		if (stricmp(name, "PLAY") == 0)
 		{
-			CONS_Printf("MD2 for sprite PLAY detected in md2.dat, use a player skin instead!\n");
+			CONS_Printf("Model for sprite PLAY detected in models.dat, use a player skin instead!\n");
 			continue;
 		}
 
@@ -900,7 +537,7 @@ void HWR_InitMD2(void)
 			}
 		}
 		// no sprite/player skin name found?!?
-		CONS_Printf("Unknown sprite/player skin %s detected in md2.dat\n", name);
+		//CONS_Printf("Unknown sprite/player skin %s detected in models.dat\n", name);
 md2found:
 		// move on to next line...
 		continue;
@@ -908,7 +545,7 @@ md2found:
 	fclose(f);
 }
 
-void HWR_AddPlayerMD2(int skin) // For MD2's that were added after startup
+void HWR_AddPlayerModel(int skin) // For skins that were added after startup
 {
 	FILE *f;
 	char name[18], filename[32];
@@ -917,20 +554,20 @@ void HWR_AddPlayerMD2(int skin) // For MD2's that were added after startup
 	if (nomd2s)
 		return;
 
-	CONS_Printf("AddPlayerMD2()...\n");
+	//CONS_Printf("HWR_AddPlayerModel()...\n");
 
-	// read the md2.dat file
+	// read the models.dat file
 	//Filename checking fixed ~Monster Iestyn and Golden
-	f = fopen(va("%s"PATHSEP"%s", srb2home, "md2.dat"), "rt");
+	f = fopen(va("%s"PATHSEP"%s", srb2home, "models.dat"), "rt");
 
 	if (!f)
 	{
-		CONS_Printf("Error while loading md2.dat\n");
+		CONS_Printf("Error while loading models.dat\n");
 		nomd2s = true;
 		return;
 	}
 
-	// Check for any MD2s that match the names of player skins!
+	// Check for any model that match the names of player skins!
 	while (fscanf(f, "%19s %31s %f %f", name, filename, &scale, &offset) == 4)
 	{
 		if (stricmp(name, skins[skin].name) == 0)
@@ -944,17 +581,16 @@ void HWR_AddPlayerMD2(int skin) // For MD2's that were added after startup
 		}
 	}
 
-	//CONS_Printf("MD2 for player skin %s not found\n", skins[skin].name);
+	//CONS_Printf("Model for player skin %s not found\n", skins[skin].name);
 	md2_playermodels[skin].notfound = true;
 playermd2found:
 	fclose(f);
 }
 
-
-void HWR_AddSpriteMD2(size_t spritenum) // For MD2s that were added after startup
+void HWR_AddSpriteModel(size_t spritenum) // For sprites that were added after startup
 {
 	FILE *f;
-	// name[18] is used to check for names in the md2.dat file that match with sprites or player skins
+	// name[18] is used to check for names in the models.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];
 	float scale, offset;
@@ -965,18 +601,18 @@ void HWR_AddSpriteMD2(size_t spritenum) // For MD2s that were added after startu
 	if (spritenum == SPR_PLAY) // Handled already NEWMD2: Per sprite, per-skin check
 		return;
 
-	// Read the md2.dat file
+	// Read the models.dat file
 	//Filename checking fixed ~Monster Iestyn and Golden
-	f = fopen(va("%s"PATHSEP"%s", srb2home, "md2.dat"), "rt");
+	f = fopen(va("%s"PATHSEP"%s", srb2home, "models.dat"), "rt");
 
 	if (!f)
 	{
-		CONS_Printf("Error while loading md2.dat\n");
+		CONS_Printf("Error while loading models.dat\n");
 		nomd2s = true;
 		return;
 	}
 
-	// Check for any MD2s that match the names of player skins!
+	// Check for any MD2s that match the names of sprite names!
 	while (fscanf(f, "%19s %31s %f %f", name, filename, &scale, &offset) == 4)
 	{
 		if (stricmp(name, sprnames[spritenum]) == 0)
@@ -1000,7 +636,6 @@ spritemd2found:
 // 0.2126 to red
 // 0.7152 to green
 // 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)) / 3)
 
@@ -1029,8 +664,8 @@ static void HWR_CreateBlendedTexture(GLPatch_t *gpatch, GLPatch_t *blendgpatch,
 	cur = Z_Malloc(size*4, PU_HWRCACHE, &grmip->grInfo.data);
 	memset(cur, 0x00, size*4);
 
-	image = gpatch->mipmap.grInfo.data;
-	blendimage = blendgpatch->mipmap.grInfo.data;
+	image = gpatch->mipmap->grInfo.data;
+	blendimage = blendgpatch->mipmap->grInfo.data;
 
 	// Average all of the translation's colors
 	if (color == SKINCOLOR_NONE || color >= MAXTRANSLATIONS)
@@ -1145,13 +780,13 @@ static void HWR_GetBlendedTexture(GLPatch_t *gpatch, GLPatch_t *blendgpatch, INT
 	if (colormap == colormaps || colormap == NULL)
 	{
 		// Don't do any blending
-		HWD.pfnSetTexture(&gpatch->mipmap);
+		HWD.pfnSetTexture(gpatch->mipmap);
 		return;
 	}
 
 	// search for the mimmap
 	// skip the first (no colormap translated)
-	for (grmip = &gpatch->mipmap; grmip->nextcolormap; )
+	for (grmip = gpatch->mipmap; grmip->nextcolormap; )
 	{
 		grmip = grmip->nextcolormap;
 		if (grmip->colormap == colormap)
@@ -1184,39 +819,40 @@ static void HWR_GetBlendedTexture(GLPatch_t *gpatch, GLPatch_t *blendgpatch, INT
 	Z_ChangeTag(newmip->grInfo.data, PU_HWRCACHE_UNLOCKED);
 }
 
+#define NORMALFOG 0x00000000
+#define FADEFOG 0x19000000
 
+static boolean HWR_CanInterpolateModel(mobj_t *mobj, model_t *model)
+{
+	if (cv_grmodelinterpolation.value == 2) // Always interpolate
+		return true;
+	return model->interpolate[(mobj->frame & FF_FRAMEMASK)];
+}
 
-// -----------------+
-// HWR_DrawMD2      : Draw MD2
-//                  : (monsters, bonuses, weapons, lights, ...)
-// Returns          :
-// -----------------+
-	/*
-	wait/stand
-	death
-	pain
-	walk
-	shoot/fire
-
-	die?
-	atka?
-	atkb?
-	attacka/b/c/d?
-	res?
-	run?
-	*/
-
-static UINT8 P_GetModelSprite2(md2_t *md2, skin_t *skin, UINT8 spr2, player_t *player)
+static boolean HWR_CanInterpolateSprite2(modelspr2frames_t *spr2frame)
+{
+	if (cv_grmodelinterpolation.value == 2) // Always interpolate
+		return true;
+	return spr2frame->interpolate;
+}
+
+//
+// HWR_GetModelSprite2 (see P_GetSkinSprite2)
+// For non-super players, tries each sprite2's immediate predecessor until it finds one with a number of frames or ends up at standing.
+// For super players, does the same as above - but tries the super equivalent for each sprite2 before the non-super version.
+//
+
+static UINT8 HWR_GetModelSprite2(md2_t *md2, skin_t *skin, UINT8 spr2, player_t *player)
 {
 	UINT8 super = 0, i = 0;
 
-	if (!md2 || !skin)
+	if (!md2 || !md2->model || !md2->model->spr2frames || !skin)
 		return 0;
 
 	if ((playersprite_t)(spr2 & ~FF_SPR2SUPER) >= free_spr2)
 		return 0;
 
-	while (!(md2->model->spr2frames[spr2*2 + 1])
+	while (!md2->model->spr2frames[spr2].numframes
 		&& spr2 != SPR2_STND
 		&& ++i != 32) // recursion limiter
 	{
@@ -1257,19 +893,23 @@ static UINT8 P_GetModelSprite2(md2_t *md2, skin_t *skin, UINT8 spr2, player_t *p
 	return spr2;
 }
 
-#define NORMALFOG 0x00000000
-#define FADEFOG 0x19000000
-void HWR_DrawMD2(gr_vissprite_t *spr)
+//
+// HWR_DrawModel
+//
+
+void HWR_DrawModel(gr_vissprite_t *spr)
 {
 	FSurfaceInfo Surf;
 
 	char filename[64];
-	INT32 frame;
+	INT32 frame = 0;
+	INT32 nextFrame = -1;
+	UINT8 spr2 = 0;
 	FTransform p;
 	md2_t *md2;
 	UINT8 color[4];
 
-	if (!cv_grmd2.value)
+	if (!cv_grmodels.value)
 		return;
 
 	if (spr->precip)
@@ -1315,13 +955,13 @@ void HWR_DrawMD2(gr_vissprite_t *spr)
 	// Look at HWR_ProjectSprite for more
 	{
 		GLPatch_t *gpatch;
-		INT32 *buff;
 		INT32 durs = spr->mobj->state->tics;
 		INT32 tics = spr->mobj->tics;
-		md2_frame_t *curr, *next = NULL;
+		//mdlframe_t *next = NULL;
 		const UINT8 flip = (UINT8)(!(spr->mobj->eflags & MFE_VERTICALFLIP) != !(spr->mobj->frame & FF_VERTICALFLIP));
 		spritedef_t *sprdef;
 		spriteframe_t *sprframe;
+		INT32 mod;
 		float finalscale;
 
 		// Apparently people don't like jump frames like that, so back it goes
@@ -1352,13 +992,14 @@ void HWR_DrawMD2(gr_vissprite_t *spr)
 			return; // we already failed loading this before :(
 		if (!md2->model)
 		{
-			//CONS_Debug(DBG_RENDER, "Loading MD2... (%s)", sprnames[spr->mobj->sprite]);
-			sprintf(filename, "md2/%s", md2->filename);
+			//CONS_Debug(DBG_RENDER, "Loading model... (%s)", sprnames[spr->mobj->sprite]);
+			sprintf(filename, "models/%s", md2->filename);
 			md2->model = md2_readModel(filename);
 
 			if (md2->model)
 			{
 				md2_printModelInfo(md2->model);
+				HWD.pfnCreateModelVBOs(md2->model);
 			}
 			else
 			{
@@ -1371,18 +1012,18 @@ void HWR_DrawMD2(gr_vissprite_t *spr)
 		finalscale = md2->scale;
 		//Hurdler: arf, I don't like that implementation at all... too much crappy
 		gpatch = md2->grpatch;
-		if (!gpatch || !gpatch->mipmap.grInfo.format || !gpatch->mipmap.downloaded)
+		if (!gpatch || !gpatch->mipmap->grInfo.format || !gpatch->mipmap->downloaded)
 			md2_loadTexture(md2);
 		gpatch = md2->grpatch; // Load it again, because it isn't being loaded into gpatch after md2_loadtexture...
 
-		if ((gpatch && gpatch->mipmap.grInfo.format) // don't load the blend texture if the base texture isn't available
-			&& (!md2->blendgrpatch || !((GLPatch_t *)md2->blendgrpatch)->mipmap.grInfo.format || !((GLPatch_t *)md2->blendgrpatch)->mipmap.downloaded))
+		if ((gpatch && gpatch->mipmap->grInfo.format) // don't load the blend texture if the base texture isn't available
+			&& (!md2->blendgrpatch || !((GLPatch_t *)md2->blendgrpatch)->mipmap->grInfo.format || !((GLPatch_t *)md2->blendgrpatch)->mipmap->downloaded))
 			md2_loadBlendTexture(md2);
 
-		if (gpatch && gpatch->mipmap.grInfo.format) // else if meant that if a texture couldn't be loaded, it would just end up using something else's texture
+		if (gpatch && gpatch->mipmap->grInfo.format) // else if meant that if a texture couldn't be loaded, it would just end up using something else's texture
 		{
 			if ((skincolors_t)spr->mobj->color != SKINCOLOR_NONE &&
-				md2->blendgrpatch && ((GLPatch_t *)md2->blendgrpatch)->mipmap.grInfo.format
+				md2->blendgrpatch && ((GLPatch_t *)md2->blendgrpatch)->mipmap->grInfo.format
 				&& gpatch->width == ((GLPatch_t *)md2->blendgrpatch)->width && gpatch->height == ((GLPatch_t *)md2->blendgrpatch)->height)
 			{
 				INT32 skinnum = TC_DEFAULT;
@@ -1397,23 +1038,28 @@ void HWR_DrawMD2(gr_vissprite_t *spr)
 				}
 				else if (spr->mobj->color)
 				{
-					if (spr->mobj->skin && spr->mobj->sprite == SPR_PLAY)
+					if (spr->mobj->colorized)
+						skinnum = TC_RAINBOW;
+					else if (spr->mobj->player && spr->mobj->player->dashmode >= DASHMODE_THRESHOLD
+						&& (spr->mobj->player->charflags & SF_DASHMODE)
+						&& ((leveltime/2) & 1))
 					{
-						if (spr->mobj->colorized)
-							skinnum = TC_RAINBOW;
+						if (spr->mobj->player->charflags & SF_MACHINE)
+							skinnum = TC_DASHMODE;
 						else
-						{
-							skinnum = (INT32)((skin_t*)spr->mobj->skin-skins);
-						}
+							skinnum = TC_RAINBOW;
 					}
-					else skinnum = TC_DEFAULT;
+					else if (spr->mobj->skin && spr->mobj->sprite == SPR_PLAY)
+						skinnum = (INT32)((skin_t*)spr->mobj->skin-skins);
+					else
+						skinnum = TC_DEFAULT;
 				}
 				HWR_GetBlendedTexture(gpatch, (GLPatch_t *)md2->blendgrpatch, skinnum, spr->colormap, (skincolors_t)spr->mobj->color);
 			}
 			else
 			{
 				// This is safe, since we know the texture has been downloaded
-				HWD.pfnSetTexture(&gpatch->mipmap);
+				HWD.pfnSetTexture(gpatch->mipmap);
 			}
 		}
 		else
@@ -1430,70 +1076,69 @@ void HWR_DrawMD2(gr_vissprite_t *spr)
 			tics = spr->mobj->anim_duration;
 		}
 
-#define INTERPOLERATION_LIMIT TICRATE/4
-
+		frame = (spr->mobj->frame & FF_FRAMEMASK);
 		if (spr->mobj->skin && spr->mobj->sprite == SPR_PLAY && md2->model->spr2frames)
 		{
-			UINT8 spr2 = P_GetModelSprite2(md2, spr->mobj->skin, spr->mobj->sprite2, spr->mobj->player);
-			UINT8 mod = md2->model->spr2frames[spr2*2 + 1] ? md2->model->spr2frames[spr2*2 + 1] : md2->model->header.numFrames;
-			if (mod > ((skin_t *)spr->mobj->skin)->sprites[spr2].numframes)
+			spr2 = HWR_GetModelSprite2(md2, spr->mobj->skin, spr->mobj->sprite2, spr->mobj->player);
+			mod = md2->model->spr2frames[spr2].numframes;
+#ifndef DONTHIDEDIFFANIMLENGTH // by default, different anim length is masked by the mod
+			if (mod > (INT32)((skin_t *)spr->mobj->skin)->sprites[spr2].numframes)
 				mod = ((skin_t *)spr->mobj->skin)->sprites[spr2].numframes;
-			//FIXME: this is not yet correct
-			frame = (spr->mobj->frame & FF_FRAMEMASK);
-			if (frame >= mod)
-				frame = 0;
-			buff = md2->model->glCommandBuffer;
-			curr = &md2->model->frames[md2->model->spr2frames[spr2*2] + frame];
-			if (cv_grmd2.value == 1 && tics <= durs && tics <= INTERPOLERATION_LIMIT)
-			{
-				if (durs > INTERPOLERATION_LIMIT)
-					durs = INTERPOLERATION_LIMIT;
+#endif
+			if (!mod)
+				mod = 1;
+			frame = md2->model->spr2frames[spr2].frames[frame%mod];
+		}
+		else
+		{
+			mod = md2->model->meshes[0].numFrames;
+			if (!mod)
+				mod = 1;
+		}
 
-				if (spr->mobj->frame & FF_ANIMATE
+#ifdef USE_MODEL_NEXTFRAME
+#define INTERPOLERATION_LIMIT TICRATE/4
+		if (cv_grmodelinterpolation.value && tics <= durs && tics <= INTERPOLERATION_LIMIT)
+		{
+			if (durs > INTERPOLERATION_LIMIT)
+				durs = INTERPOLERATION_LIMIT;
+
+			if (spr->mobj->skin && spr->mobj->sprite == SPR_PLAY && md2->model->spr2frames)
+			{
+				if (HWR_CanInterpolateSprite2(&md2->model->spr2frames[spr2])
+					&& (spr->mobj->frame & FF_ANIMATE
 					|| (spr->mobj->state->nextstate != S_NULL
-					&& states[spr->mobj->state->nextstate].sprite == spr->mobj->sprite
-					&& (states[spr->mobj->state->nextstate].frame & FF_FRAMEMASK) == spr->mobj->sprite2))
+					&& states[spr->mobj->state->nextstate].sprite == SPR_PLAY
+					&& ((P_GetSkinSprite2(spr->mobj->skin, (((spr->mobj->player && spr->mobj->player->powers[pw_super]) ? FF_SPR2SUPER : 0)|states[spr->mobj->state->nextstate].frame) & FF_FRAMEMASK, spr->mobj->player) == spr->mobj->sprite2)))))
 				{
-					if (++frame >= mod)
-						frame = 0;
+					nextFrame = (spr->mobj->frame & FF_FRAMEMASK) + 1;
+					if (nextFrame >= mod)
+						nextFrame = 0;
 					if (frame || !(spr->mobj->state->frame & FF_SPR2ENDSTATE))
-						next = &md2->model->frames[md2->model->spr2frames[spr2*2] + frame];
+						nextFrame = md2->model->spr2frames[spr2].frames[nextFrame];
+					else
+						nextFrame = -1;
 				}
 			}
-		}
-		else
-		{
-			//FIXME: this is not yet correct
-			frame = (spr->mobj->frame & FF_FRAMEMASK) % md2->model->header.numFrames;
-			buff = md2->model->glCommandBuffer;
-			curr = &md2->model->frames[frame];
-			if (cv_grmd2.value == 1 && tics <= durs && tics <= INTERPOLERATION_LIMIT)
+			else if (HWR_CanInterpolateModel(spr->mobj, md2->model))
 			{
-				if (durs > INTERPOLERATION_LIMIT)
-					durs = INTERPOLERATION_LIMIT;
-
 				// frames are handled differently for states with FF_ANIMATE, so get the next frame differently for the interpolation
 				if (spr->mobj->frame & FF_ANIMATE)
 				{
-					UINT32 nextframe = (spr->mobj->frame & FF_FRAMEMASK) + 1;
-					if (nextframe >= (UINT32)spr->mobj->state->var1)
-						nextframe = (spr->mobj->state->frame & FF_FRAMEMASK);
-					nextframe %= md2->model->header.numFrames;
-					next = &md2->model->frames[nextframe];
+					nextFrame = (spr->mobj->frame & FF_FRAMEMASK) + 1;
+					if (nextFrame >= (INT32)(spr->mobj->state->var1 + (spr->mobj->state->frame & FF_FRAMEMASK)))
+						nextFrame = (spr->mobj->state->frame & FF_FRAMEMASK) % mod;
 				}
 				else
 				{
-					if (spr->mobj->state->nextstate != S_NULL
-					&& states[spr->mobj->state->nextstate].sprite == spr->mobj->sprite)
-					{
-						const UINT32 nextframe = (states[spr->mobj->state->nextstate].frame & FF_FRAMEMASK) % md2->model->header.numFrames;
-						next = &md2->model->frames[nextframe];
-					}
+					if (spr->mobj->state->nextstate != S_NULL && states[spr->mobj->state->nextstate].sprite != SPR_NULL
+					&& !(spr->mobj->player && (spr->mobj->state->nextstate == S_PLAY_WAIT) && spr->mobj->state == &states[S_PLAY_STND]))
+						nextFrame = (states[spr->mobj->state->nextstate].frame & FF_FRAMEMASK) % mod;
 				}
 			}
 		}
-
 #undef INTERPOLERATION_LIMIT
+#endif
 
 		//Hurdler: it seems there is still a small problem with mobj angle
 		p.x = FIXED_TO_FLOAT(spr->mobj->x);
@@ -1513,7 +1158,13 @@ void HWR_DrawMD2(gr_vissprite_t *spr)
 
 		if (sprframe->rotate)
 		{
-			const fixed_t anglef = AngleFixed((spr->mobj->player ? spr->mobj->player->drawangle : spr->mobj->angle));
+			fixed_t anglef = AngleFixed(spr->mobj->angle);
+
+			if (spr->mobj->player)
+				anglef = AngleFixed(spr->mobj->player->drawangle);
+			else
+				anglef = AngleFixed(spr->mobj->angle);
+
 			p.angley = FIXED_TO_FLOAT(anglef);
 		}
 		else
@@ -1522,6 +1173,20 @@ void HWR_DrawMD2(gr_vissprite_t *spr)
 			p.angley = FIXED_TO_FLOAT(anglef);
 		}
 		p.anglex = 0.0f;
+#ifdef USE_FTRANSFORM_ANGLEZ
+		// Slope rotation from Kart
+		p.anglez = 0.0f;
+		if (spr->mobj->standingslope)
+		{
+			fixed_t tempz = spr->mobj->standingslope->normal.z;
+			fixed_t tempy = spr->mobj->standingslope->normal.y;
+			fixed_t tempx = spr->mobj->standingslope->normal.x;
+			fixed_t tempangle = AngleFixed(R_PointToAngle2(0, 0, FixedSqrt(FixedMul(tempy, tempy) + FixedMul(tempz, tempz)), tempx));
+			p.anglez = FIXED_TO_FLOAT(tempangle);
+			tempangle = -AngleFixed(R_PointToAngle2(0, 0, tempz, tempy));
+			p.anglex = FIXED_TO_FLOAT(tempangle);
+		}
+#endif
 
 		color[0] = Surf.FlatColor.s.red;
 		color[1] = Surf.FlatColor.s.green;
@@ -1532,8 +1197,11 @@ void HWR_DrawMD2(gr_vissprite_t *spr)
 		finalscale *= FIXED_TO_FLOAT(spr->mobj->scale);
 
 		p.flip = atransform.flip;
+#ifdef USE_FTRANSFORM_MIRROR
+		p.mirror = atransform.mirror; // from Kart
+#endif
 
-		HWD.pfnDrawMD2i(buff, curr, durs, tics, next, &p, finalscale, flip, color);
+		HWD.pfnDrawModel(md2->model, frame, durs, tics, nextFrame, &p, finalscale, flip, color);
 	}
 }
 
diff --git a/src/hardware/hw_md2.h b/src/hardware/hw_md2.h
index 24a5639330c12fe59ed1aae43ace60d54139c0f3..a5f5fc1174b1e0bc1e9a69fa8e5b75f0d78b9234 100644
--- a/src/hardware/hw_md2.h
+++ b/src/hardware/hw_md2.h
@@ -22,104 +22,7 @@
 #define _HW_MD2_H_
 
 #include "hw_glob.h"
-#include "../info.h"
-
-// magic number "IDP2" or 844121161
-#define MD2_IDENT                       (INT32)(('2' << 24) + ('P' << 16) + ('D' << 8) + 'I')
-// model version
-#define MD2_VERSION                     8
-
-// magic number "IDP2" or 844121161
-#define MD2_IDENT                       (INT32)(('2' << 24) + ('P' << 16) + ('D' << 8) + 'I')
-// model version
-#define MD2_VERSION                     8
-
-#define MD2_MAX_TRIANGLES               8192
-#define MD2_MAX_VERTICES                4096
-#define MD2_MAX_TEXCOORDS               4096
-#define MD2_MAX_FRAMES                  512
-#define MD2_MAX_SKINS                   32
-#define MD2_MAX_FRAMESIZE               (MD2_MAX_VERTICES * 4 + 128)
-
-#if defined(_MSC_VER)
-#pragma pack(1)
-#endif
-typedef struct
-{
-	UINT32 magic;
-	UINT32 version;
-	UINT32 skinWidth;
-	UINT32 skinHeight;
-	UINT32 frameSize;
-	UINT32 numSkins;
-	UINT32 numVertices;
-	UINT32 numTexCoords;
-	UINT32 numTriangles;
-	UINT32 numGlCommands;
-	UINT32 numFrames;
-	UINT32 offsetSkins;
-	UINT32 offsetTexCoords;
-	UINT32 offsetTriangles;
-	UINT32 offsetFrames;
-	UINT32 offsetGlCommands;
-	UINT32 offsetEnd;
-} ATTRPACK md2_header_t; //NOTE: each of md2_header's members are 4 unsigned bytes
-
-typedef struct
-{
-	UINT8 vertex[3];
-	UINT8 lightNormalIndex;
-} ATTRPACK md2_alias_triangleVertex_t;
-
-typedef struct
-{
-	float vertex[3];
-	float normal[3];
-} ATTRPACK md2_triangleVertex_t;
-
-typedef struct
-{
-	INT16 vertexIndices[3];
-	INT16 textureIndices[3];
-} ATTRPACK md2_triangle_t;
-
-typedef struct
-{
-	INT16 s, t;
-} ATTRPACK md2_textureCoordinate_t;
-
-typedef struct
-{
-	float scale[3];
-	float translate[3];
-	char name[16];
-	md2_alias_triangleVertex_t alias_vertices[1];
-} ATTRPACK md2_alias_frame_t;
-
-typedef struct
-{
-	char name[16];
-	md2_triangleVertex_t *vertices;
-} ATTRPACK md2_frame_t;
-
-typedef char md2_skin_t[64];
-
-typedef struct
-{
-	float s, t;
-	INT32 vertexIndex;
-} ATTRPACK md2_glCommandVertex_t;
-
-typedef struct
-{
-	md2_header_t            header;
-	md2_skin_t              *skins;
-	md2_textureCoordinate_t *texCoords;
-	md2_triangle_t          *triangles;
-	md2_frame_t             *frames;
-	size_t                  *spr2frames; // size_t spr2frames[2*NUMPLAYERSPRITES][2];
-	INT32                   *glCommandBuffer;
-} ATTRPACK md2_model_t;
+#include "hw_model.h"
 
 #if defined(_MSC_VER)
 #pragma pack()
@@ -130,7 +33,7 @@ typedef struct
 	char        filename[32];
 	float       scale;
 	float       offset;
-	md2_model_t *model;
+	model_t     *model;
 	void        *grpatch;
 	void        *blendgrpatch;
 	boolean     notfound;
@@ -141,9 +44,9 @@ typedef struct
 extern md2_t md2_models[NUMSPRITES];
 extern md2_t md2_playermodels[MAXSKINS];
 
-void HWR_InitMD2(void);
-void HWR_DrawMD2(gr_vissprite_t *spr);
-void HWR_AddPlayerMD2(INT32 skin);
-void HWR_AddSpriteMD2(size_t spritenum);
+void HWR_InitModels(void);
+void HWR_DrawModel(gr_vissprite_t *spr);
+void HWR_AddPlayerModel(INT32 skin);
+void HWR_AddSpriteModel(size_t spritenum);
 
 #endif // _HW_MD2_H_
diff --git a/src/hardware/hw_md2load.c b/src/hardware/hw_md2load.c
new file mode 100644
index 0000000000000000000000000000000000000000..fed81e411dacc6b3f87ccb985cb49c0331381770
--- /dev/null
+++ b/src/hardware/hw_md2load.c
@@ -0,0 +1,576 @@
+/*
+	From the 'Wizard2' engine by Spaddlewit Inc. ( http://www.spaddlewit.com )
+	An experimental work-in-progress.
+
+	Donated to Sonic Team Junior and adapted to work with
+	Sonic Robo Blast 2. The license of this code matches whatever
+	the licensing is for Sonic Robo Blast 2.
+*/
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include "../doomdef.h"
+#include "hw_md2load.h"
+#include "hw_model.h"
+#include "../z_zone.h"
+
+#define NUMVERTEXNORMALS 162
+
+// Quake 2 normals are indexed. Use avertexnormals[normalindex][x/y/z] and
+// you'll have your normals.
+float avertexnormals[NUMVERTEXNORMALS][3] = {
+{-0.525731f, 0.000000f, 0.850651f},
+{-0.442863f, 0.238856f, 0.864188f},
+{-0.295242f, 0.000000f, 0.955423f},
+{-0.309017f, 0.500000f, 0.809017f},
+{-0.162460f, 0.262866f, 0.951056f},
+{0.000000f, 0.000000f, 1.000000f},
+{0.000000f, 0.850651f, 0.525731f},
+{-0.147621f, 0.716567f, 0.681718f},
+{0.147621f, 0.716567f, 0.681718f},
+{0.000000f, 0.525731f, 0.850651f},
+{0.309017f, 0.500000f, 0.809017f},
+{0.525731f, 0.000000f, 0.850651f},
+{0.295242f, 0.000000f, 0.955423f},
+{0.442863f, 0.238856f, 0.864188f},
+{0.162460f, 0.262866f, 0.951056f},
+{-0.681718f, 0.147621f, 0.716567f},
+{-0.809017f, 0.309017f, 0.500000f},
+{-0.587785f, 0.425325f, 0.688191f},
+{-0.850651f, 0.525731f, 0.000000f},
+{-0.864188f, 0.442863f, 0.238856f},
+{-0.716567f, 0.681718f, 0.147621f},
+{-0.688191f, 0.587785f, 0.425325f},
+{-0.500000f, 0.809017f, 0.309017f},
+{-0.238856f, 0.864188f, 0.442863f},
+{-0.425325f, 0.688191f, 0.587785f},
+{-0.716567f, 0.681718f, -0.147621f},
+{-0.500000f, 0.809017f, -0.309017f},
+{-0.525731f, 0.850651f, 0.000000f},
+{0.000000f, 0.850651f, -0.525731f},
+{-0.238856f, 0.864188f, -0.442863f},
+{0.000000f, 0.955423f, -0.295242f},
+{-0.262866f, 0.951056f, -0.162460f},
+{0.000000f, 1.000000f, 0.000000f},
+{0.000000f, 0.955423f, 0.295242f},
+{-0.262866f, 0.951056f, 0.162460f},
+{0.238856f, 0.864188f, 0.442863f},
+{0.262866f, 0.951056f, 0.162460f},
+{0.500000f, 0.809017f, 0.309017f},
+{0.238856f, 0.864188f, -0.442863f},
+{0.262866f, 0.951056f, -0.162460f},
+{0.500000f, 0.809017f, -0.309017f},
+{0.850651f, 0.525731f, 0.000000f},
+{0.716567f, 0.681718f, 0.147621f},
+{0.716567f, 0.681718f, -0.147621f},
+{0.525731f, 0.850651f, 0.000000f},
+{0.425325f, 0.688191f, 0.587785f},
+{0.864188f, 0.442863f, 0.238856f},
+{0.688191f, 0.587785f, 0.425325f},
+{0.809017f, 0.309017f, 0.500000f},
+{0.681718f, 0.147621f, 0.716567f},
+{0.587785f, 0.425325f, 0.688191f},
+{0.955423f, 0.295242f, 0.000000f},
+{1.000000f, 0.000000f, 0.000000f},
+{0.951056f, 0.162460f, 0.262866f},
+{0.850651f, -0.525731f, 0.000000f},
+{0.955423f, -0.295242f, 0.000000f},
+{0.864188f, -0.442863f, 0.238856f},
+{0.951056f, -0.162460f, 0.262866f},
+{0.809017f, -0.309017f, 0.500000f},
+{0.681718f, -0.147621f, 0.716567f},
+{0.850651f, 0.000000f, 0.525731f},
+{0.864188f, 0.442863f, -0.238856f},
+{0.809017f, 0.309017f, -0.500000f},
+{0.951056f, 0.162460f, -0.262866f},
+{0.525731f, 0.000000f, -0.850651f},
+{0.681718f, 0.147621f, -0.716567f},
+{0.681718f, -0.147621f, -0.716567f},
+{0.850651f, 0.000000f, -0.525731f},
+{0.809017f, -0.309017f, -0.500000f},
+{0.864188f, -0.442863f, -0.238856f},
+{0.951056f, -0.162460f, -0.262866f},
+{0.147621f, 0.716567f, -0.681718f},
+{0.309017f, 0.500000f, -0.809017f},
+{0.425325f, 0.688191f, -0.587785f},
+{0.442863f, 0.238856f, -0.864188f},
+{0.587785f, 0.425325f, -0.688191f},
+{0.688191f, 0.587785f, -0.425325f},
+{-0.147621f, 0.716567f, -0.681718f},
+{-0.309017f, 0.500000f, -0.809017f},
+{0.000000f, 0.525731f, -0.850651f},
+{-0.525731f, 0.000000f, -0.850651f},
+{-0.442863f, 0.238856f, -0.864188f},
+{-0.295242f, 0.000000f, -0.955423f},
+{-0.162460f, 0.262866f, -0.951056f},
+{0.000000f, 0.000000f, -1.000000f},
+{0.295242f, 0.000000f, -0.955423f},
+{0.162460f, 0.262866f, -0.951056f},
+{-0.442863f, -0.238856f, -0.864188f},
+{-0.309017f, -0.500000f, -0.809017f},
+{-0.162460f, -0.262866f, -0.951056f},
+{0.000000f, -0.850651f, -0.525731f},
+{-0.147621f, -0.716567f, -0.681718f},
+{0.147621f, -0.716567f, -0.681718f},
+{0.000000f, -0.525731f, -0.850651f},
+{0.309017f, -0.500000f, -0.809017f},
+{0.442863f, -0.238856f, -0.864188f},
+{0.162460f, -0.262866f, -0.951056f},
+{0.238856f, -0.864188f, -0.442863f},
+{0.500000f, -0.809017f, -0.309017f},
+{0.425325f, -0.688191f, -0.587785f},
+{0.716567f, -0.681718f, -0.147621f},
+{0.688191f, -0.587785f, -0.425325f},
+{0.587785f, -0.425325f, -0.688191f},
+{0.000000f, -0.955423f, -0.295242f},
+{0.000000f, -1.000000f, 0.000000f},
+{0.262866f, -0.951056f, -0.162460f},
+{0.000000f, -0.850651f, 0.525731f},
+{0.000000f, -0.955423f, 0.295242f},
+{0.238856f, -0.864188f, 0.442863f},
+{0.262866f, -0.951056f, 0.162460f},
+{0.500000f, -0.809017f, 0.309017f},
+{0.716567f, -0.681718f, 0.147621f},
+{0.525731f, -0.850651f, 0.000000f},
+{-0.238856f, -0.864188f, -0.442863f},
+{-0.500000f, -0.809017f, -0.309017f},
+{-0.262866f, -0.951056f, -0.162460f},
+{-0.850651f, -0.525731f, 0.000000f},
+{-0.716567f, -0.681718f, -0.147621f},
+{-0.716567f, -0.681718f, 0.147621f},
+{-0.525731f, -0.850651f, 0.000000f},
+{-0.500000f, -0.809017f, 0.309017f},
+{-0.238856f, -0.864188f, 0.442863f},
+{-0.262866f, -0.951056f, 0.162460f},
+{-0.864188f, -0.442863f, 0.238856f},
+{-0.809017f, -0.309017f, 0.500000f},
+{-0.688191f, -0.587785f, 0.425325f},
+{-0.681718f, -0.147621f, 0.716567f},
+{-0.442863f, -0.238856f, 0.864188f},
+{-0.587785f, -0.425325f, 0.688191f},
+{-0.309017f, -0.500000f, 0.809017f},
+{-0.147621f, -0.716567f, 0.681718f},
+{-0.425325f, -0.688191f, 0.587785f},
+{-0.162460f, -0.262866f, 0.951056f},
+{0.442863f, -0.238856f, 0.864188f},
+{0.162460f, -0.262866f, 0.951056f},
+{0.309017f, -0.500000f, 0.809017f},
+{0.147621f, -0.716567f, 0.681718f},
+{0.000000f, -0.525731f, 0.850651f},
+{0.425325f, -0.688191f, 0.587785f},
+{0.587785f, -0.425325f, 0.688191f},
+{0.688191f, -0.587785f, 0.425325f},
+{-0.955423f, 0.295242f, 0.000000f},
+{-0.951056f, 0.162460f, 0.262866f},
+{-1.000000f, 0.000000f, 0.000000f},
+{-0.850651f, 0.000000f, 0.525731f},
+{-0.955423f, -0.295242f, 0.000000f},
+{-0.951056f, -0.162460f, 0.262866f},
+{-0.864188f, 0.442863f, -0.238856f},
+{-0.951056f, 0.162460f, -0.262866f},
+{-0.809017f, 0.309017f, -0.500000f},
+{-0.864188f, -0.442863f, -0.238856f},
+{-0.951056f, -0.162460f, -0.262866f},
+{-0.809017f, -0.309017f, -0.500000f},
+{-0.681718f, 0.147621f, -0.716567f},
+{-0.681718f, -0.147621f, -0.716567f},
+{-0.850651f, 0.000000f, -0.525731f},
+{-0.688191f, 0.587785f, -0.425325f},
+{-0.587785f, 0.425325f, -0.688191f},
+{-0.425325f, 0.688191f, -0.587785f},
+{-0.425325f, -0.688191f, -0.587785f},
+{-0.587785f, -0.425325f, -0.688191f},
+{-0.688191f, -0.587785f, -0.425325f},
+};
+
+typedef struct
+{
+	int ident;        // A "magic number" that's used to identify the .md2 file
+	int version;      // The version of the file, always 8
+	int skinwidth;    // Width of the skin(s) in pixels
+	int skinheight;   // Height of the skin(s) in pixels
+	int framesize;    // Size of each frame in bytes
+	int numSkins;     // Number of skins with the model
+	int numXYZ;       // Number of vertices in each frame
+	int numST;        // Number of texture coordinates in each frame.
+	int numTris;      // Number of triangles in each frame
+	int numGLcmds;    // Number of dwords (4 bytes) in the gl command list.
+	int numFrames;    // Number of frames
+	int offsetSkins;  // Offset, in bytes from the start of the file, to the list of skin names.
+	int offsetST;     // Offset, in bytes from the start of the file, to the list of texture coordinates
+	int offsetTris;   // Offset, in bytes from the start of the file, to the list of triangles
+	int offsetFrames; // Offset, in bytes from the start of the file, to the list of frames
+	int offsetGLcmds; // Offset, in bytes from the start of the file, to the list of gl commands
+	int offsetEnd;    // Offset, in bytes from the start of the file, to the end of the file (filesize)
+} md2header_t;
+
+typedef struct
+{
+	unsigned short meshIndex[3]; // indices into the array of vertices in each frames
+	unsigned short stIndex[3];   // indices into the array of texture coordinates
+} md2triangle_t;
+
+typedef struct
+{
+	short s;
+	short t;
+} md2texcoord_t;
+
+typedef struct
+{
+	unsigned char v[3];             // Scaled vertices. You'll need to multiply them with scale[x] to make them normal.
+	unsigned char lightNormalIndex; // Index to the array of normals
+} md2vertex_t;
+
+typedef struct
+{
+	float scale[3];      // Used by the v member in the md2framePoint structure
+	float translate[3];  // Used by the v member in the md2framePoint structure
+	char name[16];       // Name of the frame
+} md2frame_t;
+
+// Load the model
+model_t *MD2_LoadModel(const char *fileName, int ztag, boolean useFloat)
+{
+	FILE *f;
+
+	model_t *retModel = NULL;
+	md2header_t *header;
+
+	size_t fileLen;
+	int i, j;
+	size_t namelen;
+	char *texturefilename;
+	const char *texPos;
+
+	char *buffer;
+
+	const float WUNITS = 1.0f;
+	float dataScale = WUNITS;
+
+	md2triangle_t *tris;
+	md2texcoord_t *texcoords;
+	md2frame_t *frames;
+	char *fname = NULL;
+	int foffset = 0;
+
+	int t;
+
+	// MD2 currently does not work with tinyframes, so force useFloat = true
+	//
+	// <SSNTails>
+	// the UV coordinates in MD2 are not compatible with glDrawElements like MD3 is. So they need to be loaded as full float.
+	//
+	// MD2 is intended to be draw in triangle strips and fans
+	// not very compatible with a modern GL implementation, either
+	// so the idea would be to full float expand it, and put it in a vertex buffer object
+	// I'm sure there's a way to convert the UVs to 'tinyframes', but maybe that's a job for someone else.
+	// You'd have to decompress the model, then recompress, reindexing the triangles and weeding out duplicate coordinates
+	// I already have the decompression work done
+
+	useFloat = true;
+
+	f = fopen(fileName, "rb");
+
+	if (!f)
+		return NULL;
+
+	retModel = (model_t*)Z_Calloc(sizeof(model_t), ztag, 0);
+
+	//size_t fileLen;
+
+	//int i, j;
+
+	//size_t namelen;
+	//char *texturefilename;
+	texPos = strchr(fileName, '/');
+
+	if (texPos)
+	{
+		texPos++;
+		namelen = strlen(texPos) + 1;
+		texturefilename = (char*)Z_Malloc(namelen, PU_CACHE, 0);
+		strcpy(texturefilename, texPos);
+	}
+	else
+	{
+		namelen = strlen(fileName) + 1;
+		texturefilename = (char*)Z_Malloc(namelen, PU_CACHE, 0);
+		strcpy(texturefilename, fileName);
+	}
+
+	texturefilename[namelen - 2] = 'z';
+	texturefilename[namelen - 3] = 'u';
+	texturefilename[namelen - 4] = 'b';
+
+	// find length of file
+	fseek(f, 0, SEEK_END);
+	fileLen = ftell(f);
+	fseek(f, 0, SEEK_SET);
+
+	// read in file
+	buffer = malloc(fileLen);
+	if (fread(buffer, fileLen, 1, f)) { } // squash ignored fread error
+	fclose(f);
+
+	// get pointer to file header
+	header = (md2header_t*)buffer;
+
+	retModel->numMeshes = 1; // MD2 only has one mesh
+	retModel->meshes = (mesh_t*)Z_Calloc(sizeof(mesh_t) * retModel->numMeshes, ztag, 0);
+	retModel->meshes[0].numFrames = header->numFrames;
+	// const float WUNITS = 1.0f;
+	// float dataScale = WUNITS;
+
+	// Tris and ST are simple structures that can be straight-copied
+	tris = (md2triangle_t*)&buffer[header->offsetTris];
+	texcoords = (md2texcoord_t*)&buffer[header->offsetST];
+	frames = (md2frame_t*)&buffer[header->offsetFrames];
+
+	retModel->framenames = (char*)Z_Calloc(header->numFrames*16, ztag, 0);
+	fname = retModel->framenames;
+	for (i = 0; i < header->numFrames; i++)
+	{
+		md2frame_t *fr = (md2frame_t*)&buffer[header->offsetFrames + foffset];
+		memcpy(fname, fr->name, 16);
+		foffset += sizeof(md2frame_t) + (sizeof(md2vertex_t) * header->numXYZ);
+		fname += 16;
+	}
+
+	// Read in textures
+	retModel->numMaterials = header->numSkins;
+
+	if (retModel->numMaterials <= 0) // Always at least one skin, duh
+		retModel->numMaterials = 1;
+
+	retModel->materials = (material_t*)Z_Calloc(sizeof(material_t)*retModel->numMaterials, ztag, 0);
+
+	// int t;
+	for (t = 0; t < retModel->numMaterials; t++)
+	{
+		retModel->materials[t].ambient[0] = 0.8f;
+		retModel->materials[t].ambient[1] = 0.8f;
+		retModel->materials[t].ambient[2] = 0.8f;
+		retModel->materials[t].ambient[3] = 1.0f;
+		retModel->materials[t].diffuse[0] = 0.8f;
+		retModel->materials[t].diffuse[1] = 0.8f;
+		retModel->materials[t].diffuse[2] = 0.8f;
+		retModel->materials[t].diffuse[3] = 1.0f;
+		retModel->materials[t].emissive[0] = 0.0f;
+		retModel->materials[t].emissive[1] = 0.0f;
+		retModel->materials[t].emissive[2] = 0.0f;
+		retModel->materials[t].emissive[3] = 1.0f;
+		retModel->materials[t].specular[0] = 0.0f;
+		retModel->materials[t].specular[1] = 0.0f;
+		retModel->materials[t].specular[2] = 0.0f;
+		retModel->materials[t].specular[3] = 1.0f;
+		retModel->materials[t].shininess = 0.0f;
+		retModel->materials[t].spheremap = false;
+
+		/*		retModel->materials[t].texture = Texture::ReadTexture((char*)texturefilename, ZT_TEXTURE);
+
+				if (!systemSucks)
+				{
+					// Check for a normal map...??
+					char openfilename[1024];
+					char normalMapName[1024];
+					strcpy(normalMapName, texturefilename);
+					size_t len = strlen(normalMapName);
+					char *ptr = &normalMapName[len];
+					ptr--; // z
+					ptr--; // u
+					ptr--; // b
+					ptr--; // .
+					*ptr++ = '_';
+					*ptr++ = 'n';
+					*ptr++ = '.';
+					*ptr++ = 'b';
+					*ptr++ = 'u';
+					*ptr++ = 'z';
+					*ptr++ = '\0';
+
+					sprintf(openfilename, "%s/%s", "textures", normalMapName);
+					// Convert backslashes to forward slashes
+					for (int k = 0; k < 1024; k++)
+					{
+						if (openfilename[k] == '\0')
+							break;
+
+						if (openfilename[k] == '\\')
+							openfilename[k] = '/';
+					}
+
+					Resource::resource_t *res = Resource::Open(openfilename);
+					if (res)
+					{
+						Resource::Close(res);
+						retModel->materials[t].lightmap = Texture::ReadTexture(normalMapName, ZT_TEXTURE);
+					}
+				}*/
+	}
+
+	retModel->meshes[0].numTriangles = header->numTris;
+
+	if (!useFloat) // Decompress to MD3 'tinyframe' space
+	{
+		char *ptr;
+
+		md2triangle_t *trisPtr;
+		unsigned short *indexptr;
+		float *uvptr;
+
+		dataScale = 0.015624f; // 1 / 64.0f
+		retModel->meshes[0].tinyframes = (tinyframe_t*)Z_Calloc(sizeof(tinyframe_t)*header->numFrames, ztag, 0);
+		retModel->meshes[0].numVertices = header->numXYZ;
+		retModel->meshes[0].uvs = (float*)Z_Malloc(sizeof(float) * 2 * retModel->meshes[0].numVertices, ztag, 0);
+
+		ptr = (char*)frames;
+		for (i = 0; i < header->numFrames; i++, ptr += header->framesize)
+		{
+			short *vertptr;
+			char *normptr;
+			// char *tanptr;
+
+			md2vertex_t *vertex;
+
+			md2frame_t *framePtr = (md2frame_t*)ptr;
+			retModel->meshes[0].tinyframes[i].vertices = (short*)Z_Malloc(sizeof(short) * 3 * header->numXYZ, ztag, 0);
+			retModel->meshes[0].tinyframes[i].normals = (char*)Z_Malloc(sizeof(char) * 3 * header->numXYZ, ztag, 0);
+
+			//			if (retModel->materials[0].lightmap)
+			//				retModel->meshes[0].tinyframes[i].tangents = (char*)malloc(sizeof(char));//(char*)Z_Malloc(sizeof(char)*3*header->numVerts, ztag);
+			retModel->meshes[0].indices = (unsigned short*)Z_Malloc(sizeof(unsigned short) * 3 * header->numTris, ztag, 0);
+
+			vertptr = retModel->meshes[0].tinyframes[i].vertices;
+			normptr = retModel->meshes[0].tinyframes[i].normals;
+
+			//			tanptr = retModel->meshes[0].tinyframes[i].tangents;
+			retModel->meshes[0].tinyframes[i].material = &retModel->materials[0];
+
+			framePtr++; // Advance to vertex list
+			vertex = (md2vertex_t*)framePtr;
+			framePtr--;
+			for (j = 0; j < header->numXYZ; j++, vertex++)
+			{
+				*vertptr = (short)(((vertex->v[0] * framePtr->scale[0]) + framePtr->translate[0]) / dataScale);
+				vertptr++;
+				*vertptr = (short)(((vertex->v[2] * framePtr->scale[2]) + framePtr->translate[2]) / dataScale);
+				vertptr++;
+				*vertptr = -1.0f * (short)(((vertex->v[1] * framePtr->scale[1]) + framePtr->translate[1]) / dataScale);
+				vertptr++;
+
+				// Normal
+				*normptr++ = (char)(avertexnormals[vertex->lightNormalIndex][0] * 127);
+				*normptr++ = (char)(avertexnormals[vertex->lightNormalIndex][2] * 127);
+				*normptr++ = (char)(avertexnormals[vertex->lightNormalIndex][1] * 127);
+			}
+		}
+
+		// This doesn't need to be done every frame!
+		trisPtr = tris;
+		indexptr = retModel->meshes[0].indices;
+		uvptr = (float*)retModel->meshes[0].uvs;
+		for (j = 0; j < header->numTris; j++, trisPtr++)
+		{
+			*indexptr = trisPtr->meshIndex[0];
+			indexptr++;
+			*indexptr = trisPtr->meshIndex[1];
+			indexptr++;
+			*indexptr = trisPtr->meshIndex[2];
+			indexptr++;
+
+			uvptr[trisPtr->meshIndex[0] * 2] = texcoords[trisPtr->stIndex[0]].s / (float)header->skinwidth;
+			uvptr[trisPtr->meshIndex[0] * 2 + 1] = (texcoords[trisPtr->stIndex[0]].t / (float)header->skinheight);
+			uvptr[trisPtr->meshIndex[1] * 2] = texcoords[trisPtr->stIndex[1]].s / (float)header->skinwidth;
+			uvptr[trisPtr->meshIndex[1] * 2 + 1] = (texcoords[trisPtr->stIndex[1]].t / (float)header->skinheight);
+			uvptr[trisPtr->meshIndex[2] * 2] = texcoords[trisPtr->stIndex[2]].s / (float)header->skinwidth;
+			uvptr[trisPtr->meshIndex[2] * 2 + 1] = (texcoords[trisPtr->stIndex[2]].t / (float)header->skinheight);
+		}
+	}
+	else // Full float loading method
+	{
+		md2triangle_t *trisPtr;
+		float *uvptr;
+
+		char *ptr;
+
+		retModel->meshes[0].numVertices = header->numTris * 3;
+		retModel->meshes[0].frames = (mdlframe_t*)Z_Calloc(sizeof(mdlframe_t)*header->numFrames, ztag, 0);
+		retModel->meshes[0].uvs = (float*)Z_Malloc(sizeof(float) * 2 * retModel->meshes[0].numVertices, ztag, 0);
+
+		trisPtr = tris;
+		uvptr = retModel->meshes[0].uvs;
+		for (i = 0; i < retModel->meshes[0].numTriangles; i++, trisPtr++)
+		{
+			*uvptr++ = texcoords[trisPtr->stIndex[0]].s / (float)header->skinwidth;
+			*uvptr++ = (texcoords[trisPtr->stIndex[0]].t / (float)header->skinheight);
+			*uvptr++ = texcoords[trisPtr->stIndex[1]].s / (float)header->skinwidth;
+			*uvptr++ = (texcoords[trisPtr->stIndex[1]].t / (float)header->skinheight);
+			*uvptr++ = texcoords[trisPtr->stIndex[2]].s / (float)header->skinwidth;
+			*uvptr++ = (texcoords[trisPtr->stIndex[2]].t / (float)header->skinheight);
+		}
+
+		ptr = (char*)frames;
+		for (i = 0; i < header->numFrames; i++, ptr += header->framesize)
+		{
+			float *vertptr, *normptr;
+
+			md2vertex_t *vertex;
+
+			md2frame_t *framePtr = (md2frame_t*)ptr;
+			retModel->meshes[0].frames[i].normals = (float*)Z_Malloc(sizeof(float) * 3 * header->numTris * 3, ztag, 0);
+			retModel->meshes[0].frames[i].vertices = (float*)Z_Malloc(sizeof(float) * 3 * header->numTris * 3, ztag, 0);
+			//			if (retModel->materials[0].lightmap)
+			//				retModel->meshes[0].frames[i].tangents = (float*)malloc(sizeof(float));//(float*)Z_Malloc(sizeof(float)*3*header->numTris*3, ztag);
+			//float *vertptr, *normptr;
+			normptr = (float*)retModel->meshes[0].frames[i].normals;
+			vertptr = (float*)retModel->meshes[0].frames[i].vertices;
+			trisPtr = tris;
+
+			retModel->meshes[0].frames[i].material = &retModel->materials[0];
+
+			framePtr++; // Advance to vertex list
+			vertex = (md2vertex_t*)framePtr;
+			framePtr--;
+			for (j = 0; j < header->numTris; j++, trisPtr++)
+			{
+				*vertptr = ((vertex[trisPtr->meshIndex[0]].v[0] * framePtr->scale[0]) + framePtr->translate[0]) * WUNITS;
+				vertptr++;
+				*vertptr = ((vertex[trisPtr->meshIndex[0]].v[2] * framePtr->scale[2]) + framePtr->translate[2]) * WUNITS;
+				vertptr++;
+				*vertptr = -1.0f * ((vertex[trisPtr->meshIndex[0]].v[1] * framePtr->scale[1]) + framePtr->translate[1]) * WUNITS;
+				vertptr++;
+
+				*vertptr = ((vertex[trisPtr->meshIndex[1]].v[0] * framePtr->scale[0]) + framePtr->translate[0]) * WUNITS;
+				vertptr++;
+				*vertptr = ((vertex[trisPtr->meshIndex[1]].v[2] * framePtr->scale[2]) + framePtr->translate[2]) * WUNITS;
+				vertptr++;
+				*vertptr = -1.0f * ((vertex[trisPtr->meshIndex[1]].v[1] * framePtr->scale[1]) + framePtr->translate[1]) * WUNITS;
+				vertptr++;
+
+				*vertptr = ((vertex[trisPtr->meshIndex[2]].v[0] * framePtr->scale[0]) + framePtr->translate[0]) * WUNITS;
+				vertptr++;
+				*vertptr = ((vertex[trisPtr->meshIndex[2]].v[2] * framePtr->scale[2]) + framePtr->translate[2]) * WUNITS;
+				vertptr++;
+				*vertptr = -1.0f * ((vertex[trisPtr->meshIndex[2]].v[1] * framePtr->scale[1]) + framePtr->translate[1]) * WUNITS;
+				vertptr++;
+
+				*normptr++ = avertexnormals[vertex[trisPtr->meshIndex[0]].lightNormalIndex][0];
+				*normptr++ = avertexnormals[vertex[trisPtr->meshIndex[0]].lightNormalIndex][2];
+				*normptr++ = avertexnormals[vertex[trisPtr->meshIndex[0]].lightNormalIndex][1];
+
+				*normptr++ = avertexnormals[vertex[trisPtr->meshIndex[1]].lightNormalIndex][0];
+				*normptr++ = avertexnormals[vertex[trisPtr->meshIndex[1]].lightNormalIndex][2];
+				*normptr++ = avertexnormals[vertex[trisPtr->meshIndex[1]].lightNormalIndex][1];
+
+				*normptr++ = avertexnormals[vertex[trisPtr->meshIndex[2]].lightNormalIndex][0];
+				*normptr++ = avertexnormals[vertex[trisPtr->meshIndex[2]].lightNormalIndex][2];
+				*normptr++ = avertexnormals[vertex[trisPtr->meshIndex[2]].lightNormalIndex][1];
+			}
+		}
+	}
+
+	free(buffer);
+	return retModel;
+}
diff --git a/src/hardware/hw_md2load.h b/src/hardware/hw_md2load.h
new file mode 100644
index 0000000000000000000000000000000000000000..1662d6471eca6780ebd0213595b8985e4968da20
--- /dev/null
+++ b/src/hardware/hw_md2load.h
@@ -0,0 +1,19 @@
+/*
+	From the 'Wizard2' engine by Spaddlewit Inc. ( http://www.spaddlewit.com )
+	An experimental work-in-progress.
+
+	Donated to Sonic Team Junior and adapted to work with
+	Sonic Robo Blast 2. The license of this code matches whatever
+	the licensing is for Sonic Robo Blast 2.
+*/
+
+#ifndef _HW_MD2LOAD_H_
+#define _HW_MD2LOAD_H_
+
+#include "hw_model.h"
+#include "../doomtype.h"
+
+// Load the Model
+model_t *MD2_LoadModel(const char *fileName, int ztag, boolean useFloat);
+
+#endif
diff --git a/src/hardware/hw_md3load.c b/src/hardware/hw_md3load.c
new file mode 100644
index 0000000000000000000000000000000000000000..87931d27ba104e1f9a4109515f6497ad19bae453
--- /dev/null
+++ b/src/hardware/hw_md3load.c
@@ -0,0 +1,522 @@
+/*
+	From the 'Wizard2' engine by Spaddlewit Inc. ( http://www.spaddlewit.com )
+	An experimental work-in-progress.
+
+	Donated to Sonic Team Junior and adapted to work with
+	Sonic Robo Blast 2. The license of this code matches whatever
+	the licensing is for Sonic Robo Blast 2.
+*/
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include "../doomdef.h"
+#include "hw_md3load.h"
+#include "hw_model.h"
+#include "../z_zone.h"
+
+typedef struct
+{
+	int ident;			// A "magic number" that's used to identify the .md3 file
+	int version;		// The version of the file, always 15
+	char name[64];
+	int flags;
+	int numFrames;		// Number of frames
+	int numTags;
+	int numSurfaces;
+	int numSkins;		// Number of skins with the model
+	int offsetFrames;
+	int offsetTags;
+	int offsetSurfaces;
+	int offsetEnd;		// Offset, in bytes from the start of the file, to the end of the file (filesize)
+} md3modelHeader;
+
+typedef struct
+{
+	float minBounds[3];		// First corner of the bounding box
+	float maxBounds[3];		// Second corner of the bounding box
+	float localOrigin[3];	// Local origin, usually (0, 0, 0)
+	float radius;			// Radius of bounding sphere
+	char name[16];			// Name of frame
+} md3Frame;
+
+typedef struct
+{
+	char name[64];		// Name of tag
+	float origin[3];	// Coordinates of tag
+	float axis[9];		// Orientation of tag object
+} md3Tag;
+
+typedef struct
+{
+	int ident;
+	char name[64];			// Name of this surface
+	int flags;
+	int numFrames;			// # of keyframes
+	int numShaders;			// # of shaders
+	int numVerts;			// # of vertices
+	int numTriangles;		// # of triangles
+	int offsetTriangles;	// Relative offset from start of this struct to where the list of Triangles start
+	int offsetShaders;		// Relative offset from start of this struct to where the list of Shaders start
+	int offsetST;			// Relative offset from start of this struct to where the list of tex coords start
+	int offsetXYZNormal;	// Relative offset from start of this struct to where the list of vertices start
+	int offsetEnd;			// Relative offset from start of this struct to where this surface ends
+} md3Surface;
+
+typedef struct
+{
+	char name[64]; // Name of this shader
+	int shaderIndex; // Shader index number
+} md3Shader;
+
+typedef struct
+{
+	int index[3]; // List of offset values into the list of Vertex objects that constitute the corners of the Triangle object.
+} md3Triangle;
+
+typedef struct
+{
+	float st[2];
+} md3TexCoord;
+
+typedef struct
+{
+	short x, y, z, n;
+} md3Vertex;
+
+static float latlnglookup[256][256][3];
+
+static void GetNormalFromLatLong(short latlng, float *out)
+{
+	float *lookup = latlnglookup[(unsigned char)(latlng >> 8)][(unsigned char)(latlng & 255)];
+
+	out[0] = *lookup++;
+	out[1] = *lookup++;
+	out[2] = *lookup++;
+}
+
+#if 0
+static void NormalToLatLng(float *n, short *out)
+{
+	// Special cases
+	if (0.0f == n[0] && 0.0f == n[1])
+	{
+		if (n[2] > 0.0f)
+			*out = 0;
+		else
+			*out = 128;
+	}
+	else
+	{
+		char x, y;
+
+		x = (char)(57.2957795f * (atan2(n[1], n[0])) * (255.0f / 360.0f));
+		y = (char)(57.2957795f * (acos(n[2])) * (255.0f / 360.0f));
+
+		*out = (x << 8) + y;
+	}
+}
+#endif
+
+static inline void LatLngToNormal(short n, float *out)
+{
+	const float PI = (3.1415926535897932384626433832795f);
+	float lat = (float)(n >> 8);
+	float lng = (float)(n & 255);
+
+	lat *= PI / 128.0f;
+	lng *= PI / 128.0f;
+
+	out[0] = cosf(lat) * sinf(lng);
+	out[1] = sinf(lat) * sinf(lng);
+	out[2] = cosf(lng);
+}
+
+static void LatLngInit(void)
+{
+	int i, j;
+	for (i = 0; i < 256; i++)
+	{
+		for (j = 0; j < 256; j++)
+			LatLngToNormal((short)((i << 8) + j), latlnglookup[i][j]);
+	}
+}
+
+static boolean latlnginit = false;
+
+model_t *MD3_LoadModel(const char *fileName, int ztag, boolean useFloat)
+{
+	const float WUNITS = 1.0f;
+	model_t *retModel = NULL;
+	md3Frame *frames = NULL;
+	char *fname = NULL;
+	md3modelHeader *mdh;
+	long fileLen;
+	long fileReadLen;
+	char *buffer;
+	int surfEnd;
+	int i, t;
+	int matCount;
+	FILE *f;
+
+	if (!latlnginit)
+	{
+		LatLngInit();
+		latlnginit = true;
+	}
+
+	f = fopen(fileName, "rb");
+
+	if (!f)
+		return NULL;
+
+	retModel = (model_t*)Z_Calloc(sizeof(model_t), ztag, 0);
+
+	// find length of file
+	fseek(f, 0, SEEK_END);
+	fileLen = ftell(f);
+	fseek(f, 0, SEEK_SET);
+
+	// read in file
+	buffer = malloc(fileLen);
+	fileReadLen = fread(buffer, fileLen, 1, f);
+	fclose(f);
+
+	(void)fileReadLen; // intentionally ignore return value, per buildbot
+
+	// get pointer to file header
+	mdh = (md3modelHeader*)buffer;
+
+	retModel->numMeshes = mdh->numSurfaces;
+
+	retModel->numMaterials = 0;
+	surfEnd = 0;
+	for (i = 0; i < mdh->numSurfaces; i++)
+	{
+		md3Surface *mdS = (md3Surface*)&buffer[mdh->offsetSurfaces];
+		surfEnd += mdS->offsetEnd;
+
+		retModel->numMaterials += mdS->numShaders;
+	}
+
+	// Initialize materials
+	if (retModel->numMaterials <= 0) // Always at least one skin, duh
+		retModel->numMaterials = 1;
+
+	retModel->materials = (material_t*)Z_Calloc(sizeof(material_t)*retModel->numMaterials, ztag, 0);
+
+	for (t = 0; t < retModel->numMaterials; t++)
+	{
+		retModel->materials[t].ambient[0] = 0.3686f;
+		retModel->materials[t].ambient[1] = 0.3684f;
+		retModel->materials[t].ambient[2] = 0.3684f;
+		retModel->materials[t].ambient[3] = 1.0f;
+		retModel->materials[t].diffuse[0] = 0.8863f;
+		retModel->materials[t].diffuse[1] = 0.8850f;
+		retModel->materials[t].diffuse[2] = 0.8850f;
+		retModel->materials[t].diffuse[3] = 1.0f;
+		retModel->materials[t].emissive[0] = 0.0f;
+		retModel->materials[t].emissive[1] = 0.0f;
+		retModel->materials[t].emissive[2] = 0.0f;
+		retModel->materials[t].emissive[3] = 1.0f;
+		retModel->materials[t].specular[0] = 0.4902f;
+		retModel->materials[t].specular[1] = 0.4887f;
+		retModel->materials[t].specular[2] = 0.4887f;
+		retModel->materials[t].specular[3] = 1.0f;
+		retModel->materials[t].shininess = 25.0f;
+		retModel->materials[t].spheremap = false;
+	}
+
+	retModel->meshes = (mesh_t*)Z_Calloc(sizeof(mesh_t)*retModel->numMeshes, ztag, 0);
+
+	frames = (md3Frame*)&buffer[mdh->offsetFrames];
+	retModel->framenames = (char*)Z_Calloc(mdh->numFrames*16, ztag, 0);
+	fname = retModel->framenames;
+	for (i = 0; i < mdh->numFrames; i++)
+	{
+		memcpy(fname, frames->name, 16);
+		fname += 16;
+		frames++;
+	}
+
+	matCount = 0;
+	for (i = 0, surfEnd = 0; i < mdh->numSurfaces; i++)
+	{
+		int j;
+		md3Shader *mdShader;
+		md3Surface *mdS = (md3Surface*)&buffer[mdh->offsetSurfaces + surfEnd];
+		surfEnd += mdS->offsetEnd;
+
+		mdShader = (md3Shader*)((char*)mdS + mdS->offsetShaders);
+
+		for (j = 0; j < mdS->numShaders; j++, matCount++)
+		{
+			size_t len = strlen(mdShader[j].name);
+			mdShader[j].name[len-1] = 'z';
+			mdShader[j].name[len-2] = 'u';
+			mdShader[j].name[len-3] = 'b';
+
+			// Load material
+/*			retModel->materials[matCount].texture = Texture::ReadTexture(mdShader[j].name, ZT_TEXTURE);
+
+			if (!systemSucks)
+			{
+				// Check for a normal map...??
+				char openfilename[1024];
+				char normalMapName[1024];
+				strcpy(normalMapName, mdShader[j].name);
+				len = strlen(normalMapName);
+				char *ptr = &normalMapName[len];
+				ptr--; // z
+				ptr--; // u
+				ptr--; // b
+				ptr--; // .
+				*ptr++ = '_';
+				*ptr++ = 'n';
+				*ptr++ = '.';
+				*ptr++ = 'b';
+				*ptr++ = 'u';
+				*ptr++ = 'z';
+				*ptr++ = '\0';
+
+				sprintf(openfilename, "%s/%s", "textures", normalMapName);
+				// Convert backslashes to forward slashes
+				for (int k = 0; k < 1024; k++)
+				{
+					if (openfilename[k] == '\0')
+						break;
+
+					if (openfilename[k] == '\\')
+						openfilename[k] = '/';
+				}
+
+				Resource::resource_t *res = Resource::Open(openfilename);
+				if (res)
+				{
+					Resource::Close(res);
+					retModel->materials[matCount].lightmap = Texture::ReadTexture(normalMapName, ZT_TEXTURE);
+				}
+			}*/
+		}
+
+		retModel->meshes[i].numFrames = mdS->numFrames;
+		retModel->meshes[i].numTriangles = mdS->numTriangles;
+
+		if (!useFloat) // 'tinyframe' mode with indices
+		{
+			float tempNormal[3];
+			float *uvptr;
+			md3TexCoord *mdST;
+			unsigned short *indexptr;
+			md3Triangle *mdT;
+
+			retModel->meshes[i].tinyframes = (tinyframe_t*)Z_Calloc(sizeof(tinyframe_t)*mdS->numFrames, ztag, 0);
+			retModel->meshes[i].numVertices = mdS->numVerts;
+			retModel->meshes[i].uvs = (float*)Z_Malloc(sizeof(float)*2*mdS->numVerts, ztag, 0);
+			for (j = 0; j < mdS->numFrames; j++)
+			{
+				short *vertptr;
+				char *normptr;
+				// char *tanptr;
+				int k;
+				md3Vertex *mdV = (md3Vertex*)((char*)mdS + mdS->offsetXYZNormal + (mdS->numVerts*j*sizeof(md3Vertex)));
+				retModel->meshes[i].tinyframes[j].vertices = (short*)Z_Malloc(sizeof(short)*3*mdS->numVerts, ztag, 0);
+				retModel->meshes[i].tinyframes[j].normals = (char*)Z_Malloc(sizeof(char)*3*mdS->numVerts, ztag, 0);
+
+//				if (retModel->materials[0].lightmap)
+//					retModel->meshes[i].tinyframes[j].tangents = (char*)malloc(sizeof(char));//(char*)Z_Malloc(sizeof(char)*3*mdS->numVerts, ztag);
+				retModel->meshes[i].indices = (unsigned short*)Z_Malloc(sizeof(unsigned short) * 3 * mdS->numTriangles, ztag, 0);
+				vertptr = retModel->meshes[i].tinyframes[j].vertices;
+				normptr = retModel->meshes[i].tinyframes[j].normals;
+
+//				tanptr = retModel->meshes[i].tinyframes[j].tangents;
+				retModel->meshes[i].tinyframes[j].material = &retModel->materials[i];
+
+				for (k = 0; k < mdS->numVerts; k++)
+				{
+					// Vertex
+					*vertptr = mdV[k].x;
+					vertptr++;
+					*vertptr = mdV[k].z;
+					vertptr++;
+					*vertptr = 1.0f - mdV[k].y;
+					vertptr++;
+
+					// Normal
+					GetNormalFromLatLong(mdV[k].n, tempNormal);
+					*normptr = (char)(tempNormal[0] * 127);
+					normptr++;
+					*normptr = (char)(tempNormal[2] * 127);
+					normptr++;
+					*normptr = (char)(tempNormal[1] * 127);
+					normptr++;
+				}
+			}
+
+			uvptr = (float*)retModel->meshes[i].uvs;
+			mdST = (md3TexCoord*)((char*)mdS + mdS->offsetST);
+			for (j = 0; j < mdS->numVerts; j++)
+			{
+				*uvptr = mdST[j].st[0];
+				uvptr++;
+				*uvptr = mdST[j].st[1];
+				uvptr++;
+			}
+
+			indexptr = retModel->meshes[i].indices;
+			mdT = (md3Triangle*)((char*)mdS + mdS->offsetTriangles);
+			for (j = 0; j < mdS->numTriangles; j++, mdT++)
+			{
+				// Indices
+				*indexptr = (unsigned short)mdT->index[0];
+				indexptr++;
+				*indexptr = (unsigned short)mdT->index[1];
+				indexptr++;
+				*indexptr = (unsigned short)mdT->index[2];
+				indexptr++;
+			}
+		}
+		else // Traditional full-float loading method
+		{
+			float dataScale = 0.015624f * WUNITS;
+			float tempNormal[3];
+			md3TexCoord *mdST;
+			md3Triangle *mdT;
+			float *uvptr;
+			int k;
+
+			retModel->meshes[i].numVertices = mdS->numTriangles * 3;//mdS->numVerts;
+			retModel->meshes[i].frames = (mdlframe_t*)Z_Calloc(sizeof(mdlframe_t)*mdS->numFrames, ztag, 0);
+			retModel->meshes[i].uvs = (float*)Z_Malloc(sizeof(float)*2*mdS->numTriangles*3, ztag, 0);
+
+			for (j = 0; j < mdS->numFrames; j++)
+			{
+				float *vertptr;
+				float *normptr;
+				md3Vertex *mdV = (md3Vertex*)((char*)mdS + mdS->offsetXYZNormal + (mdS->numVerts*j*sizeof(md3Vertex)));
+				retModel->meshes[i].frames[j].vertices = (float*)Z_Malloc(sizeof(float)*3*mdS->numTriangles*3, ztag, 0);
+				retModel->meshes[i].frames[j].normals = (float*)Z_Malloc(sizeof(float)*3*mdS->numTriangles*3, ztag, 0);
+//				if (retModel->materials[i].lightmap)
+//					retModel->meshes[i].frames[j].tangents = (float*)malloc(sizeof(float));//(float*)Z_Malloc(sizeof(float)*3*mdS->numTriangles*3, ztag);
+				vertptr = retModel->meshes[i].frames[j].vertices;
+				normptr = retModel->meshes[i].frames[j].normals;
+				retModel->meshes[i].frames[j].material = &retModel->materials[i];
+
+				mdT = (md3Triangle*)((char*)mdS + mdS->offsetTriangles);
+
+				for (k = 0; k < mdS->numTriangles; k++)
+				{
+					// Vertex 1
+					*vertptr = mdV[mdT->index[0]].x * dataScale;
+					vertptr++;
+					*vertptr = mdV[mdT->index[0]].z * dataScale;
+					vertptr++;
+					*vertptr = 1.0f - mdV[mdT->index[0]].y * dataScale;
+					vertptr++;
+
+					GetNormalFromLatLong(mdV[mdT->index[0]].n, tempNormal);
+					*normptr = tempNormal[0];
+					normptr++;
+					*normptr = tempNormal[2];
+					normptr++;
+					*normptr = tempNormal[1];
+					normptr++;
+
+					// Vertex 2
+					*vertptr = mdV[mdT->index[1]].x * dataScale;
+					vertptr++;
+					*vertptr = mdV[mdT->index[1]].z * dataScale;
+					vertptr++;
+					*vertptr = 1.0f - mdV[mdT->index[1]].y * dataScale;
+					vertptr++;
+
+					GetNormalFromLatLong(mdV[mdT->index[1]].n, tempNormal);
+					*normptr = tempNormal[0];
+					normptr++;
+					*normptr = tempNormal[2];
+					normptr++;
+					*normptr = tempNormal[1];
+					normptr++;
+
+					// Vertex 3
+					*vertptr = mdV[mdT->index[2]].x * dataScale;
+					vertptr++;
+					*vertptr = mdV[mdT->index[2]].z * dataScale;
+					vertptr++;
+					*vertptr = 1.0f - mdV[mdT->index[2]].y * dataScale;
+					vertptr++;
+
+					GetNormalFromLatLong(mdV[mdT->index[2]].n, tempNormal);
+					*normptr = tempNormal[0];
+					normptr++;
+					*normptr = tempNormal[2];
+					normptr++;
+					*normptr = tempNormal[1];
+					normptr++;
+
+					mdT++; // Advance to next triangle
+				}
+			}
+
+			mdST = (md3TexCoord*)((char*)mdS + mdS->offsetST);
+			uvptr = (float*)retModel->meshes[i].uvs;
+			mdT = (md3Triangle*)((char*)mdS + mdS->offsetTriangles);
+
+			for (k = 0; k < mdS->numTriangles; k++)
+			{
+				*uvptr = mdST[mdT->index[0]].st[0];
+				uvptr++;
+				*uvptr = mdST[mdT->index[0]].st[1];
+				uvptr++;
+
+				*uvptr = mdST[mdT->index[1]].st[0];
+				uvptr++;
+				*uvptr = mdST[mdT->index[1]].st[1];
+				uvptr++;
+
+				*uvptr = mdST[mdT->index[2]].st[0];
+				uvptr++;
+				*uvptr = mdST[mdT->index[2]].st[1];
+				uvptr++;
+
+				mdT++; // Advance to next triangle
+			}
+		}
+	}
+	/*
+	// Tags?
+	retModel->numTags = mdh->numTags;
+	retModel->maxNumFrames = mdh->numFrames;
+	retModel->tags = (tag_t*)Z_Calloc(sizeof(tag_t) * retModel->numTags * mdh->numFrames, ztag);
+	md3Tag *mdTag = (md3Tag*)&buffer[mdh->offsetTags];
+	tag_t *curTag = retModel->tags;
+	for (i = 0; i < mdh->numFrames; i++)
+	{
+		int j;
+		for (j = 0; j < retModel->numTags; j++, mdTag++)
+		{
+			strcpys(curTag->name, mdTag->name, sizeof(curTag->name) / sizeof(char));
+			curTag->transform.m[0][0] = mdTag->axis[0];
+			curTag->transform.m[0][1] = mdTag->axis[1];
+			curTag->transform.m[0][2] = mdTag->axis[2];
+			curTag->transform.m[1][0] = mdTag->axis[3];
+			curTag->transform.m[1][1] = mdTag->axis[4];
+			curTag->transform.m[1][2] = mdTag->axis[5];
+			curTag->transform.m[2][0] = mdTag->axis[6];
+			curTag->transform.m[2][1] = mdTag->axis[7];
+			curTag->transform.m[2][2] = mdTag->axis[8];
+			curTag->transform.m[3][0] = mdTag->origin[0] * WUNITS;
+			curTag->transform.m[3][1] = mdTag->origin[1] * WUNITS;
+			curTag->transform.m[3][2] = mdTag->origin[2] * WUNITS;
+			curTag->transform.m[3][3] = 1.0f;
+
+			Matrix::Rotate(&curTag->transform, 90.0f, &Vector::Xaxis);
+			curTag++;
+		}
+	}*/
+
+
+	free(buffer);
+
+	return retModel;
+}
diff --git a/src/hardware/hw_md3load.h b/src/hardware/hw_md3load.h
new file mode 100644
index 0000000000000000000000000000000000000000..c0e0522ff6bf2e6e43c96021ab758ca64ae7b45b
--- /dev/null
+++ b/src/hardware/hw_md3load.h
@@ -0,0 +1,19 @@
+/*
+	From the 'Wizard2' engine by Spaddlewit Inc. ( http://www.spaddlewit.com )
+	An experimental work-in-progress.
+
+	Donated to Sonic Team Junior and adapted to work with
+	Sonic Robo Blast 2. The license of this code matches whatever
+	the licensing is for Sonic Robo Blast 2.
+*/
+
+#ifndef _HW_MD3LOAD_H_
+#define _HW_MD3LOAD_H_
+
+#include "hw_model.h"
+#include "../doomtype.h"
+
+// Load the Model
+model_t *MD3_LoadModel(const char *fileName, int ztag, boolean useFloat);
+
+#endif
diff --git a/src/hardware/hw_model.c b/src/hardware/hw_model.c
new file mode 100644
index 0000000000000000000000000000000000000000..ac73f8acac4b577f1ca18bd077e8bba837f320d8
--- /dev/null
+++ b/src/hardware/hw_model.c
@@ -0,0 +1,737 @@
+/*
+	From the 'Wizard2' engine by Spaddlewit Inc. ( http://www.spaddlewit.com )
+	An experimental work-in-progress.
+
+	Donated to Sonic Team Junior and adapted to work with
+	Sonic Robo Blast 2. The license of this code matches whatever
+	the licensing is for Sonic Robo Blast 2.
+*/
+
+#include "../doomdef.h"
+#include "../doomtype.h"
+#include "../info.h"
+#include "../z_zone.h"
+#include "hw_model.h"
+#include "hw_md2load.h"
+#include "hw_md3load.h"
+#include "hw_md2.h"
+#include "u_list.h"
+#include <string.h>
+
+static float PI = (3.1415926535897932384626433832795f);
+static float U_Deg2Rad(float deg)
+{
+	return deg * ((float)PI / 180.0f);
+}
+
+vector_t vectorXaxis = { 1.0f, 0.0f, 0.0f };
+vector_t vectorYaxis = { 0.0f, 1.0f, 0.0f };
+vector_t vectorZaxis = { 0.0f, 0.0f, 1.0f };
+
+void VectorRotate(vector_t *rotVec, const vector_t *axisVec, float angle)
+{
+	float ux, uy, uz, vx, vy, vz, wx, wy, wz, sa, ca;
+
+	angle = U_Deg2Rad(angle);
+
+	// Rotate the point (x,y,z) around the vector (u,v,w)
+	ux = axisVec->x * rotVec->x;
+	uy = axisVec->x * rotVec->y;
+	uz = axisVec->x * rotVec->z;
+	vx = axisVec->y * rotVec->x;
+	vy = axisVec->y * rotVec->y;
+	vz = axisVec->y * rotVec->z;
+	wx = axisVec->z * rotVec->x;
+	wy = axisVec->z * rotVec->y;
+	wz = axisVec->z * rotVec->z;
+	sa = sinf(angle);
+	ca = cosf(angle);
+
+	rotVec->x = axisVec->x*(ux + vy + wz) + (rotVec->x*(axisVec->y*axisVec->y + axisVec->z*axisVec->z) - axisVec->x*(vy + wz))*ca + (-wy + vz)*sa;
+	rotVec->y = axisVec->y*(ux + vy + wz) + (rotVec->y*(axisVec->x*axisVec->x + axisVec->z*axisVec->z) - axisVec->y*(ux + wz))*ca + (wx - uz)*sa;
+	rotVec->z = axisVec->z*(ux + vy + wz) + (rotVec->z*(axisVec->x*axisVec->x + axisVec->y*axisVec->y) - axisVec->z*(ux + vy))*ca + (-vx + uy)*sa;
+}
+
+void UnloadModel(model_t *model)
+{
+	// Wouldn't it be great if C just had destructors?
+	int i;
+	for (i = 0; i < model->numMeshes; i++)
+	{
+		mesh_t *mesh = &model->meshes[i];
+
+		if (mesh->frames)
+		{
+			int j;
+			for (j = 0; j < mesh->numFrames; j++)
+			{
+				if (mesh->frames[j].normals)
+					Z_Free(mesh->frames[j].normals);
+
+				if (mesh->frames[j].tangents)
+					Z_Free(mesh->frames[j].tangents);
+
+				if (mesh->frames[j].vertices)
+					Z_Free(mesh->frames[j].vertices);
+
+				if (mesh->frames[j].colors)
+					Z_Free(mesh->frames[j].colors);
+			}
+
+			Z_Free(mesh->frames);
+		}
+		else if (mesh->tinyframes)
+		{
+			int j;
+			for (j = 0; j < mesh->numFrames; j++)
+			{
+				if (mesh->tinyframes[j].normals)
+					Z_Free(mesh->tinyframes[j].normals);
+
+				if (mesh->tinyframes[j].tangents)
+					Z_Free(mesh->tinyframes[j].tangents);
+
+				if (mesh->tinyframes[j].vertices)
+					Z_Free(mesh->tinyframes[j].vertices);
+			}
+
+			if (mesh->indices)
+				Z_Free(mesh->indices);
+
+			Z_Free(mesh->tinyframes);
+		}
+
+		if (mesh->uvs)
+			Z_Free(mesh->uvs);
+
+		if (mesh->lightuvs)
+			Z_Free(mesh->lightuvs);
+	}
+
+	if (model->meshes)
+		Z_Free(model->meshes);
+
+	if (model->tags)
+		Z_Free(model->tags);
+
+	if (model->materials)
+		Z_Free(model->materials);
+
+	DeleteVBOs(model);
+	Z_Free(model);
+}
+
+tag_t *GetTagByName(model_t *model, char *name, int frame)
+{
+	if (frame < model->maxNumFrames)
+	{
+		tag_t *iterator = &model->tags[frame * model->numTags];
+
+		int i;
+		for (i = 0; i < model->numTags; i++)
+		{
+			if (!stricmp(iterator[i].name, name))
+				return &iterator[i];
+		}
+	}
+
+	return NULL;
+}
+
+//
+// LoadModel
+//
+// Load a model and
+// convert it to the
+// internal format.
+//
+model_t *LoadModel(const char *filename, int ztag)
+{
+	model_t *model;
+
+	// What type of file?
+	const char *extension = NULL;
+	int i;
+	for (i = (int)strlen(filename)-1; i >= 0; i--)
+	{
+		if (filename[i] != '.')
+			continue;
+
+		extension = &filename[i];
+		break;
+	}
+
+	if (!extension)
+	{
+		CONS_Printf("Model %s is lacking a file extension, unable to determine type!\n", filename);
+		return NULL;
+	}
+
+	if (!strcmp(extension, ".md3"))
+	{
+		if (!(model = MD3_LoadModel(filename, ztag, false)))
+			return NULL;
+	}
+	else if (!strcmp(extension, ".md3s")) // MD3 that will be converted in memory to use full floats
+	{
+		if (!(model = MD3_LoadModel(filename, ztag, true)))
+			return NULL;
+	}
+	else if (!strcmp(extension, ".md2"))
+	{
+		if (!(model = MD2_LoadModel(filename, ztag, false)))
+			return NULL;
+	}
+	else if (!strcmp(extension, ".md2s"))
+	{
+		if (!(model = MD2_LoadModel(filename, ztag, true)))
+			return NULL;
+	}
+	else
+	{
+		CONS_Printf("Unknown model format: %s\n", extension);
+		return NULL;
+	}
+
+	model->mdlFilename = (char*)Z_Malloc(strlen(filename)+1, ztag, 0);
+	strcpy(model->mdlFilename, filename);
+
+	Optimize(model);
+	GeneratePolygonNormals(model, ztag);
+	LoadModelSprite2(model);
+	if (!model->spr2frames)
+		LoadModelInterpolationSettings(model);
+
+	// Default material properties
+	for (i = 0 ; i < model->numMaterials; i++)
+	{
+		material_t *material = &model->materials[i];
+		material->ambient[0] = 0.7686f;
+		material->ambient[1] = 0.7686f;
+		material->ambient[2] = 0.7686f;
+		material->ambient[3] = 1.0f;
+		material->diffuse[0] = 0.5863f;
+		material->diffuse[1] = 0.5863f;
+		material->diffuse[2] = 0.5863f;
+		material->diffuse[3] = 1.0f;
+		material->specular[0] = 0.4902f;
+		material->specular[1] = 0.4902f;
+		material->specular[2] = 0.4902f;
+		material->specular[3] = 1.0f;
+		material->shininess = 25.0f;
+	}
+
+	return model;
+}
+
+void HWR_ReloadModels(void)
+{
+	size_t i;
+	INT32 s;
+
+	for (s = 0; s < MAXSKINS; s++)
+	{
+		if (md2_playermodels[s].model)
+			LoadModelSprite2(md2_playermodels[s].model);
+	}
+
+	for (i = 0; i < NUMSPRITES; i++)
+	{
+		if (md2_models[i].model)
+			LoadModelInterpolationSettings(md2_models[i].model);
+	}
+}
+
+void LoadModelInterpolationSettings(model_t *model)
+{
+	INT32 i;
+	INT32 numframes = model->meshes[0].numFrames;
+	char *framename = model->framenames;
+
+	if (!framename)
+		return;
+
+	#define GET_OFFSET \
+		memcpy(&interpolation_flag, framename + offset, 2); \
+		model->interpolate[i] = (!memcmp(interpolation_flag, MODEL_INTERPOLATION_FLAG, 2));
+
+	for (i = 0; i < numframes; i++)
+	{
+		int offset = (strlen(framename) - 4);
+		char interpolation_flag[3];
+		memset(&interpolation_flag, 0x00, 3);
+
+		// find the +i on the frame name
+		// ANIM+i00
+		// so the offset is (frame name length - 4)
+		GET_OFFSET;
+
+		// maybe the frame had three digits?
+		// ANIM+i000
+		// so the offset is (frame name length - 5)
+		if (!model->interpolate[i])
+		{
+			offset--;
+			GET_OFFSET;
+		}
+
+		framename += 16;
+	}
+
+	#undef GET_OFFSET
+}
+
+void LoadModelSprite2(model_t *model)
+{
+	INT32 i;
+	modelspr2frames_t *spr2frames = NULL;
+	INT32 numframes = model->meshes[0].numFrames;
+	char *framename = model->framenames;
+
+	if (!framename)
+		return;
+
+	for (i = 0; i < numframes; i++)
+	{
+		char prefix[6];
+		char name[5];
+		char interpolation_flag[3];
+		char framechars[4];
+		UINT8 frame = 0;
+		UINT8 spr2idx;
+		boolean interpolate = false;
+
+		memset(&prefix, 0x00, 6);
+		memset(&name, 0x00, 5);
+		memset(&interpolation_flag, 0x00, 3);
+		memset(&framechars, 0x00, 4);
+
+		if (strlen(framename) >= 9)
+		{
+			boolean super;
+			char *modelframename = framename;
+			memcpy(&prefix, modelframename, 5);
+			modelframename += 5;
+			memcpy(&name, modelframename, 4);
+			modelframename += 4;
+			// Oh look
+			memcpy(&interpolation_flag, modelframename, 2);
+			if (!memcmp(interpolation_flag, MODEL_INTERPOLATION_FLAG, 2))
+			{
+				interpolate = true;
+				modelframename += 2;
+			}
+			memcpy(&framechars, modelframename, 3);
+
+			if ((super = (!memcmp(prefix, "SUPER", 5))) || (!memcmp(prefix, "SPR2_", 5)))
+			{
+				spr2idx = 0;
+				while (spr2idx < free_spr2)
+				{
+					if (!memcmp(spr2names[spr2idx], name, 4))
+					{
+						if (!spr2frames)
+							spr2frames = (modelspr2frames_t*)Z_Calloc(sizeof(modelspr2frames_t)*NUMPLAYERSPRITES*2, PU_STATIC, NULL);
+						if (super)
+							spr2idx |= FF_SPR2SUPER;
+						if (framechars[0])
+						{
+							frame = atoi(framechars);
+							if (spr2frames[spr2idx].numframes < frame+1)
+								spr2frames[spr2idx].numframes = frame+1;
+						}
+						else
+						{
+							frame = spr2frames[spr2idx].numframes;
+							spr2frames[spr2idx].numframes++;
+						}
+						spr2frames[spr2idx].frames[frame] = i;
+						spr2frames[spr2idx].interpolate = interpolate;
+						break;
+					}
+					spr2idx++;
+				}
+			}
+		}
+
+		framename += 16;
+	}
+
+	if (model->spr2frames)
+		Z_Free(model->spr2frames);
+	model->spr2frames = spr2frames;
+}
+
+//
+// GenerateVertexNormals
+//
+// Creates a new normal for a vertex using the average of all of the polygons it belongs to.
+//
+void GenerateVertexNormals(model_t *model)
+{
+	int i;
+	for (i = 0; i < model->numMeshes; i++)
+	{
+		int j;
+
+		mesh_t *mesh = &model->meshes[i];
+
+		if (!mesh->frames)
+			continue;
+
+		for (j = 0; j < mesh->numFrames; j++)
+		{
+			mdlframe_t *frame = &mesh->frames[j];
+			int memTag = PU_STATIC;
+			float *newNormals = (float*)Z_Malloc(sizeof(float)*3*mesh->numTriangles*3, memTag, 0);
+			int k;
+			float *vertPtr = frame->vertices;
+			float *oldNormals;
+
+			M_Memcpy(newNormals, frame->normals, sizeof(float)*3*mesh->numTriangles*3);
+
+/*			if (!systemSucks)
+			{
+				memTag = Z_GetTag(frame->tangents);
+				float *newTangents = (float*)Z_Malloc(sizeof(float)*3*mesh->numTriangles*3, memTag);
+				M_Memcpy(newTangents, frame->tangents, sizeof(float)*3*mesh->numTriangles*3);
+			}*/
+
+			for (k = 0; k < mesh->numVertices; k++)
+			{
+				float x, y, z;
+				int vCount = 0;
+				vector_t normal;
+				int l;
+				float *testPtr = frame->vertices;
+
+				x = *vertPtr++;
+				y = *vertPtr++;
+				z = *vertPtr++;
+
+				normal.x = normal.y = normal.z = 0;
+
+				for (l = 0; l < mesh->numVertices; l++)
+				{
+					float testX, testY, testZ;
+					testX = *testPtr++;
+					testY = *testPtr++;
+					testZ = *testPtr++;
+
+					if (fabsf(x - testX) > FLT_EPSILON
+						|| fabsf(y - testY) > FLT_EPSILON
+						|| fabsf(z - testZ) > FLT_EPSILON)
+						continue;
+
+					// Found a vertex match! Add it...
+					normal.x += frame->normals[3 * l + 0];
+					normal.y += frame->normals[3 * l + 1];
+					normal.z += frame->normals[3 * l + 2];
+					vCount++;
+				}
+
+				if (vCount > 1)
+				{
+//					Vector::Normalize(&normal);
+					newNormals[3 * k + 0] = (float)normal.x;
+					newNormals[3 * k + 1] = (float)normal.y;
+					newNormals[3 * k + 2] = (float)normal.z;
+
+/*					if (!systemSucks)
+					{
+						Vector::vector_t tangent;
+						Vector::Tangent(&normal, &tangent);
+						newTangents[3 * k + 0] = tangent.x;
+						newTangents[3 * k + 1] = tangent.y;
+						newTangents[3 * k + 2] = tangent.z;
+					}*/
+				}
+			}
+
+			oldNormals = frame->normals;
+			frame->normals = newNormals;
+			Z_Free(oldNormals);
+
+/*			if (!systemSucks)
+			{
+				float *oldTangents = frame->tangents;
+				frame->tangents = newTangents;
+				Z_Free(oldTangents);
+			}*/
+		}
+	}
+}
+
+typedef struct materiallist_s
+{
+	struct materiallist_s *next;
+	struct materiallist_s *prev;
+	material_t *material;
+} materiallist_t;
+
+static boolean AddMaterialToList(materiallist_t **head, material_t *material)
+{
+	materiallist_t *node, *newMatNode;
+	for (node = *head; node; node = node->next)
+	{
+		if (node->material == material)
+			return false;
+	}
+
+	// Didn't find it, so add to the list
+	newMatNode = (materiallist_t*)Z_Malloc(sizeof(materiallist_t), PU_CACHE, 0);
+	newMatNode->material = material;
+	ListAdd(newMatNode, (listitem_t**)head);
+	return true;
+}
+
+//
+// Optimize
+//
+// Groups triangles from meshes in the model
+// Only works for models with 1 frame
+//
+void Optimize(model_t *model)
+{
+	int numMeshes = 0;
+	int i;
+	materiallist_t *matListHead = NULL;
+	int memTag;
+	mesh_t *newMeshes;
+	materiallist_t *node;
+
+	if (model->numMeshes <= 1)
+		return; // No need
+
+	for (i = 0; i < model->numMeshes; i++)
+	{
+		mesh_t *curMesh = &model->meshes[i];
+
+		if (curMesh->numFrames > 1)
+			return; // Can't optimize models with > 1 frame
+
+		if (!curMesh->frames)
+			return; // Don't optimize tinyframe models (no need)
+
+		// We are condensing to 1 mesh per material, so
+		// the # of materials we use will be the new
+		// # of meshes
+		if (AddMaterialToList(&matListHead, curMesh->frames[0].material))
+			numMeshes++;
+	}
+
+	memTag = PU_STATIC;
+	newMeshes = (mesh_t*)Z_Calloc(sizeof(mesh_t) * numMeshes, memTag, 0);
+
+	i = 0;
+	for (node = matListHead; node; node = node->next)
+	{
+		material_t *curMat = node->material;
+		mesh_t *newMesh = &newMeshes[i];
+		mdlframe_t *curFrame;
+		int uvCount;
+		int vertCount;
+		int colorCount;
+
+		// Find all triangles with this material and count them
+		int numTriangles = 0;
+		int j;
+		for (j = 0; j < model->numMeshes; j++)
+		{
+			mesh_t *curMesh = &model->meshes[j];
+
+			if (curMesh->frames[0].material == curMat)
+				numTriangles += curMesh->numTriangles;
+		}
+
+		newMesh->numFrames = 1;
+		newMesh->numTriangles = numTriangles;
+		newMesh->numVertices = numTriangles * 3;
+		newMesh->uvs = (float*)Z_Malloc(sizeof(float)*2*numTriangles*3, memTag, 0);
+//		if (node->material->lightmap)
+//			newMesh->lightuvs = (float*)Z_Malloc(sizeof(float)*2*numTriangles*3, memTag, 0);
+		newMesh->frames = (mdlframe_t*)Z_Calloc(sizeof(mdlframe_t), memTag, 0);
+		curFrame = &newMesh->frames[0];
+
+		curFrame->material = curMat;
+		curFrame->normals = (float*)Z_Malloc(sizeof(float)*3*numTriangles*3, memTag, 0);
+//		if (!systemSucks)
+//			curFrame->tangents = (float*)Z_Malloc(sizeof(float)*3*numTriangles*3, memTag, 0);
+		curFrame->vertices = (float*)Z_Malloc(sizeof(float)*3*numTriangles*3, memTag, 0);
+		curFrame->colors = (char*)Z_Malloc(sizeof(char)*4*numTriangles*3, memTag, 0);
+
+		// Now traverse the meshes of the model, adding in
+		// vertices/normals/uvs that match the current material
+		uvCount = 0;
+		vertCount = 0;
+		colorCount = 0;
+		for (j = 0; j < model->numMeshes; j++)
+		{
+			mesh_t *curMesh = &model->meshes[j];
+
+			if (curMesh->frames[0].material == curMat)
+			{
+				float *dest;
+				float *src;
+				char *destByte;
+				char *srcByte;
+
+				M_Memcpy(&newMesh->uvs[uvCount],
+					curMesh->uvs,
+					sizeof(float)*2*curMesh->numTriangles*3);
+
+/*				if (node->material->lightmap)
+				{
+					M_Memcpy(&newMesh->lightuvs[uvCount],
+						curMesh->lightuvs,
+						sizeof(float)*2*curMesh->numTriangles*3);
+				}*/
+				uvCount += 2*curMesh->numTriangles*3;
+
+				dest = (float*)newMesh->frames[0].vertices;
+				src = (float*)curMesh->frames[0].vertices;
+				M_Memcpy(&dest[vertCount],
+					src,
+					sizeof(float)*3*curMesh->numTriangles*3);
+
+				dest = (float*)newMesh->frames[0].normals;
+				src = (float*)curMesh->frames[0].normals;
+				M_Memcpy(&dest[vertCount],
+					src,
+					sizeof(float)*3*curMesh->numTriangles*3);
+
+/*				if (!systemSucks)
+				{
+					dest = (float*)newMesh->frames[0].tangents;
+					src = (float*)curMesh->frames[0].tangents;
+					M_Memcpy(&dest[vertCount],
+						src,
+						sizeof(float)*3*curMesh->numTriangles*3);
+				}*/
+
+				vertCount += 3 * curMesh->numTriangles * 3;
+
+				destByte = (char*)newMesh->frames[0].colors;
+				srcByte = (char*)curMesh->frames[0].colors;
+
+				if (srcByte)
+				{
+					M_Memcpy(&destByte[colorCount],
+						srcByte,
+						sizeof(char)*4*curMesh->numTriangles*3);
+				}
+				else
+				{
+					memset(&destByte[colorCount],
+						255,
+						sizeof(char)*4*curMesh->numTriangles*3);
+				}
+
+				colorCount += 4 * curMesh->numTriangles * 3;
+			}
+		}
+
+		i++;
+	}
+
+	CONS_Printf("Model::Optimize(): Model reduced from %d to %d meshes.\n", model->numMeshes, numMeshes);
+	model->meshes = newMeshes;
+	model->numMeshes = numMeshes;
+}
+
+void GeneratePolygonNormals(model_t *model, int ztag)
+{
+	int i;
+	for (i = 0; i < model->numMeshes; i++)
+	{
+		int j;
+		mesh_t *mesh = &model->meshes[i];
+
+		if (!mesh->frames)
+			continue;
+
+		for (j = 0; j < mesh->numFrames; j++)
+		{
+			int k;
+			mdlframe_t *frame = &mesh->frames[j];
+			const float *vertices = frame->vertices;
+			vector_t *polyNormals;
+
+			frame->polyNormals = (vector_t*)Z_Malloc(sizeof(vector_t) * mesh->numTriangles, ztag, 0);
+
+			polyNormals = frame->polyNormals;
+
+			for (k = 0; k < mesh->numTriangles; k++)
+			{
+//				Vector::Normal(vertices, polyNormals);
+				vertices += 3 * 3;
+				polyNormals++;
+			}
+		}
+	}
+}
+
+//
+// Reload
+//
+// Reload VBOs
+//
+#if 0
+static void Reload(void)
+{
+/*	model_t *node;
+	for (node = modelHead; node; node = node->next)
+	{
+		int i;
+		for (i = 0; i < node->numMeshes; i++)
+		{
+			mesh_t *mesh = &node->meshes[i];
+
+			if (mesh->frames)
+			{
+				int j;
+				for (j = 0; j < mesh->numFrames; j++)
+					CreateVBO(mesh, &mesh->frames[j]);
+			}
+			else if (mesh->tinyframes)
+			{
+				int j;
+				for (j = 0; j < mesh->numFrames; j++)
+					CreateVBO(mesh, &mesh->tinyframes[j]);
+			}
+		}
+	}*/
+}
+#endif
+
+void DeleteVBOs(model_t *model)
+{
+	(void)model;
+/*	for (int i = 0; i < model->numMeshes; i++)
+	{
+		mesh_t *mesh = &model->meshes[i];
+
+		if (mesh->frames)
+		{
+			for (int j = 0; j < mesh->numFrames; j++)
+			{
+				mdlframe_t *frame = &mesh->frames[j];
+				if (!frame->vboID)
+					continue;
+				bglDeleteBuffers(1, &frame->vboID);
+				frame->vboID = 0;
+			}
+		}
+		else if (mesh->tinyframes)
+		{
+			for (int j = 0; j < mesh->numFrames; j++)
+			{
+				tinyframe_t *frame = &mesh->tinyframes[j];
+				if (!frame->vboID)
+					continue;
+				bglDeleteBuffers(1, &frame->vboID);
+				frame->vboID = 0;
+			}
+		}
+	}*/
+}
diff --git a/src/hardware/hw_model.h b/src/hardware/hw_model.h
new file mode 100644
index 0000000000000000000000000000000000000000..2a5240bdefbda6320fa2411b451a5bf86408124e
--- /dev/null
+++ b/src/hardware/hw_model.h
@@ -0,0 +1,121 @@
+/*
+	From the 'Wizard2' engine by Spaddlewit Inc. ( http://www.spaddlewit.com )
+	An experimental work-in-progress.
+
+	Donated to Sonic Team Junior and adapted to work with
+	Sonic Robo Blast 2. The license of this code matches whatever
+	the licensing is for Sonic Robo Blast 2.
+*/
+
+#ifndef _HW_MODEL_H_
+#define _HW_MODEL_H_
+
+#include "../doomtype.h"
+
+typedef struct
+{
+	float x, y, z;
+} vector_t;
+
+extern vector_t vectorXaxis;
+extern vector_t vectorYaxis;
+extern vector_t vectorZaxis;
+
+void VectorRotate(vector_t *rotVec, const vector_t *axisVec, float angle);
+
+typedef struct
+{
+	float ambient[4], diffuse[4], specular[4], emissive[4];
+	float shininess;
+	boolean spheremap;
+//	Texture::texture_t *texture;
+//	Texture::texture_t *lightmap;
+} material_t;
+
+typedef struct
+{
+	material_t *material; // Pointer to the allocated 'materials' list in model_t
+	float *vertices;
+	float *normals;
+	float *tangents;
+	char *colors;
+	unsigned int vboID;
+	vector_t *polyNormals;
+} mdlframe_t;
+
+typedef struct
+{
+	material_t *material;
+	short *vertices;
+	char *normals;
+	char *tangents;
+	unsigned int vboID;
+} tinyframe_t;
+
+// Equivalent to MD3's many 'surfaces'
+typedef struct mesh_s
+{
+	int numVertices;
+	int numTriangles;
+
+	float *uvs;
+	float *lightuvs;
+
+	int numFrames;
+	mdlframe_t *frames;
+	tinyframe_t *tinyframes;
+	unsigned short *indices;
+} mesh_t;
+
+typedef struct tag_s
+{
+	char name[64];
+//	matrix_t transform;
+} tag_t;
+
+#define MODEL_INTERPOLATION_FLAG "+i"
+
+typedef struct
+{
+	INT32 frames[256];
+	UINT8 numframes;
+	boolean interpolate;
+} modelspr2frames_t;
+
+typedef struct model_s
+{
+	int maxNumFrames;
+
+	int numMaterials;
+	material_t *materials;
+	int numMeshes;
+	mesh_t *meshes;
+	int numTags;
+	tag_t *tags;
+
+	char *mdlFilename;
+	boolean unloaded;
+
+	char *framenames;
+	boolean interpolate[256];
+	modelspr2frames_t *spr2frames;
+} model_t;
+
+extern int numModels;
+extern model_t *modelHead;
+
+void HWR_ReloadModels(void);
+
+tag_t *GetTagByName(model_t *model, char *name, int frame);
+model_t *LoadModel(const char *filename, int ztag);
+void UnloadModel(model_t *model);
+void Optimize(model_t *model);
+void LoadModelInterpolationSettings(model_t *model);
+void LoadModelSprite2(model_t *model);
+void GenerateVertexNormals(model_t *model);
+void GeneratePolygonNormals(model_t *model, int ztag);
+void CreateVBOTiny(mesh_t *mesh, tinyframe_t *frame);
+void CreateVBO(mesh_t *mesh, mdlframe_t *frame);
+void DeleteVBOs(model_t *model);
+
+#endif
diff --git a/src/hardware/r_opengl/ogl_win.c b/src/hardware/r_opengl/ogl_win.c
index eb9a31a7d7f1e909d72e88cc5bc3c166471b73ab..562afe9989e0908f046ece44abe2fcf22d2fa439 100644
--- a/src/hardware/r_opengl/ogl_win.c
+++ b/src/hardware/r_opengl/ogl_win.c
@@ -347,13 +347,6 @@ static INT32 WINAPI SetRes(viddef_t *lvid, vmode_t *pcurrentmode)
 	if (strstr(renderer, "810"))   oglflags |= GLF_NOZBUFREAD;
 	DBG_Printf("oglflags   : 0x%X\n", oglflags);
 
-#ifdef USE_PALETTED_TEXTURE
-	if (isExtAvailable("GL_EXT_paletted_texture",gl_extensions))
-		glColorTableEXT = GetGLFunc("glColorTableEXT");
-	else
-		glColorTableEXT = NULL;
-#endif
-
 #ifdef USE_WGL_SWAP
 	if (isExtAvailable("WGL_EXT_swap_control",gl_extensions))
 		wglSwapIntervalEXT = GetGLFunc("wglSwapIntervalEXT");
@@ -582,19 +575,8 @@ EXPORT void HWRAPI(SetPalette) (RGBA_t *pal, RGBA_t *gamma)
 		myPaletteData[i].s.blue  = (UINT8)MIN((pal[i].s.blue*gamma->s.blue)/127,   255);
 		myPaletteData[i].s.alpha = pal[i].s.alpha;
 	}
-#ifdef USE_PALETTED_TEXTURE
-	if (glColorTableEXT)
-	{
-		for (i = 0; i < 256; i++)
-		{
-			palette_tex[3*i+0] = pal[i].s.red;
-			palette_tex[3*i+1] = pal[i].s.green;
-			palette_tex[3*i+2] = pal[i].s.blue;
-		}
-		glColorTableEXT(GL_TEXTURE_2D, GL_RGB8, 256, GL_RGB, GL_UNSIGNED_BYTE, palette_tex);
-	}
-#endif
-	// on a chang� de palette, il faut recharger toutes les textures
+
+	// on a palette change, you have to reload all of the textures
 	Flush();
 }
 
diff --git a/src/hardware/r_opengl/r_opengl.c b/src/hardware/r_opengl/r_opengl.c
index ac989613a3be866ede5596ec0f7df44ab494fa54..1a50854c7e21cb949e839c1b8fbf0b9263832074 100644
--- a/src/hardware/r_opengl/r_opengl.c
+++ b/src/hardware/r_opengl/r_opengl.c
@@ -29,13 +29,10 @@
 
 #include <stdarg.h>
 #include <math.h>
-#ifndef SHUFFLE
-#define SHUFFLE
-#endif
 #include "r_opengl.h"
+#include "r_vbo.h"
 
 #if defined (HWRENDER) && !defined (NOROPENGL)
-// for KOS: GL_TEXTURE_ENV, glAlphaFunc, glColorMask, glPolygonOffset, glReadPixels, GL_ALPHA_TEST, GL_POLYGON_OFFSET_FILL
 
 struct GLRGBAFloat
 {
@@ -45,6 +42,7 @@ struct GLRGBAFloat
 	GLfloat alpha;
 };
 typedef struct GLRGBAFloat GLRGBAFloat;
+static const GLubyte white[4] = { 255, 255, 255, 255 };
 
 // ==========================================================================
 //                                                                  CONSTANTS
@@ -68,8 +66,10 @@ static float NEAR_CLIPPING_PLANE =   NZCLIP_PLANE;
 static  GLuint      NextTexAvail    = FIRST_TEX_AVAIL;
 static  GLuint      tex_downloaded  = 0;
 static  GLfloat     fov             = 90.0f;
+#if 0
 static  GLuint      pal_col         = 0;
 static  FRGBAFloat  const_pal_col;
+#endif
 static  FBITFIELD   CurrentPolyFlags;
 
 static  FTextureInfo*  gr_cachetail = NULL;
@@ -90,26 +90,20 @@ static FTransform  md2_transform;
 const GLubyte *gl_extensions = NULL;
 
 //Hurdler: 04/10/2000: added for the kick ass coronas as Boris wanted;-)
-static GLdouble    modelMatrix[16];
-static GLdouble    projMatrix[16];
+static GLfloat    modelMatrix[16];
+static GLfloat    projMatrix[16];
 static GLint       viewport[4];
 
-
-#ifdef USE_PALETTED_TEXTURE
-	PFNGLCOLORTABLEEXTPROC  glColorTableEXT = NULL;
-	GLubyte                 palette_tex[256*3];
-#endif
-
 // Yay for arbitrary  numbers! NextTexAvail is buggy for some reason.
 // Sryder:	NextTexAvail is broken for these because palette changes or changes to the texture filter or antialiasing
 //			flush all of the stored textures, leaving them unavailable at times such as between levels
 //			These need to start at 0 and be set to their number, and be reset to 0 when deleted so that intel GPUs
 //			can know when the textures aren't there, as textures are always considered resident in their virtual memory
 // TODO:	Store them in a more normal way
-#define SCRTEX_SCREENTEXTURE 65535
-#define SCRTEX_STARTSCREENWIPE 65534
-#define SCRTEX_ENDSCREENWIPE 65533
-#define SCRTEX_FINALSCREENTEXTURE 65532
+#define SCRTEX_SCREENTEXTURE 4294967295U
+#define SCRTEX_STARTSCREENWIPE 4294967294U
+#define SCRTEX_ENDSCREENWIPE 4294967293U
+#define SCRTEX_FINALSCREENTEXTURE 4294967292U
 static GLuint screentexture = 0;
 static GLuint startScreenWipe = 0;
 static GLuint endScreenWipe = 0;
@@ -161,9 +155,6 @@ float byteasfloat(UINT8 fbyte)
 
 static I_Error_t I_Error_GL = NULL;
 
-static boolean gl13 = false; // whether we can use opengl 1.3 functions
-
-
 // -----------------+
 // DBG_Printf       : Output error messages to debug log if DEBUG_TO_FILE is defined,
 //                  : else do nothing
@@ -194,14 +185,14 @@ FUNCPRINTF void DBG_Printf(const char *lpFmt, ...)
 #define pglAlphaFunc glAlphaFunc
 #define pglBlendFunc glBlendFunc
 #define pglCullFace glCullFace
-#define pglPolygonMode glPolygonMode
 #define pglPolygonOffset glPolygonOffset
 #define pglScissor glScissor
 #define pglEnable glEnable
 #define pglDisable glDisable
-#define pglGetDoublev glGetDoublev
+#define pglGetFloatv glGetFloatv
 //glGetIntegerv
 //glGetString
+#define pglHint glHint
 
 /* Depth Buffer */
 #define pglClearDepth glClearDepth
@@ -215,19 +206,26 @@ FUNCPRINTF void DBG_Printf(const char *lpFmt, ...)
 #define pglPushMatrix glPushMatrix
 #define pglPopMatrix glPopMatrix
 #define pglLoadIdentity glLoadIdentity
-#define pglMultMatrixd glMultMatrixd
+#define pglMultMatrixf glMultMatrixf
 #define pglRotatef glRotatef
 #define pglScalef glScalef
 #define pglTranslatef glTranslatef
 
 /* Drawing Functions */
-#define pglBegin glBegin
-#define pglEnd glEnd
-#define pglVertex3f glVertex3f
-#define pglNormal3f glNormal3f
-#define pglColor4f glColor4f
-#define pglColor4fv glColor4fv
-#define pglTexCoord2f glTexCoord2f
+#define pglColor4ubv glColor4ubv
+#define pglVertexPointer glVertexPointer
+#define pglNormalPointer glNormalPointer
+#define pglTexCoordPointer glTexCoordPointer
+#define pglColorPointer glColorPointer
+#define pglDrawArrays glDrawArrays
+#define pglDrawElements glDrawElements
+#define pglEnableClientState glEnableClientState
+#define pglDisableClientState glDisableClientState
+#define pglClientActiveTexture glClientActiveTexture
+#define pglGenBuffers glGenBuffers
+#define pglBindBuffer glBindBuffer
+#define pglBufferData glBufferData
+#define pglDeleteBuffers glDeleteBuffers
 
 /* Lighting */
 #define pglShadeModel glShadeModel
@@ -271,8 +269,6 @@ typedef void (APIENTRY * PFNglBlendFunc) (GLenum sfactor, GLenum dfactor);
 static PFNglBlendFunc pglBlendFunc;
 typedef void (APIENTRY * PFNglCullFace) (GLenum mode);
 static PFNglCullFace pglCullFace;
-typedef void (APIENTRY * PFNglPolygonMode) (GLenum face, GLenum mode);
-static PFNglPolygonMode pglPolygonMode;
 typedef void (APIENTRY * PFNglPolygonOffset) (GLfloat factor, GLfloat units);
 static PFNglPolygonOffset pglPolygonOffset;
 typedef void (APIENTRY * PFNglScissor) (GLint x, GLint y, GLsizei width, GLsizei height);
@@ -281,8 +277,8 @@ typedef void (APIENTRY * PFNglEnable) (GLenum cap);
 static PFNglEnable pglEnable;
 typedef void (APIENTRY * PFNglDisable) (GLenum cap);
 static PFNglDisable pglDisable;
-typedef void (APIENTRY * PFNglGetDoublev) (GLenum pname, GLdouble *params);
-static PFNglGetDoublev pglGetDoublev;
+typedef void (APIENTRY * PFNglGetFloatv) (GLenum pname, GLfloat *params);
+static PFNglGetFloatv pglGetFloatv;
 //glGetIntegerv
 //glGetString
 
@@ -307,8 +303,8 @@ typedef void (APIENTRY * PFNglPopMatrix) (void);
 static PFNglPopMatrix pglPopMatrix;
 typedef void (APIENTRY * PFNglLoadIdentity) (void);
 static PFNglLoadIdentity pglLoadIdentity;
-typedef void (APIENTRY * PFNglMultMatrixd) (const GLdouble *m);
-static PFNglMultMatrixd pglMultMatrixd;
+typedef void (APIENTRY * PFNglMultMatrixf) (const GLfloat *m);
+static PFNglMultMatrixf pglMultMatrixf;
 typedef void (APIENTRY * PFNglRotatef) (GLfloat angle, GLfloat x, GLfloat y, GLfloat z);
 static PFNglRotatef pglRotatef;
 typedef void (APIENTRY * PFNglScalef) (GLfloat x, GLfloat y, GLfloat z);
@@ -317,20 +313,33 @@ typedef void (APIENTRY * PFNglTranslatef) (GLfloat x, GLfloat y, GLfloat z);
 static PFNglTranslatef pglTranslatef;
 
 /* Drawing Functions */
-typedef void (APIENTRY * PFNglBegin) (GLenum mode);
-static PFNglBegin pglBegin;
-typedef void (APIENTRY * PFNglEnd) (void);
-static PFNglEnd pglEnd;
-typedef void (APIENTRY * PFNglVertex3f) (GLfloat x, GLfloat y, GLfloat z);
-static PFNglVertex3f pglVertex3f;
-typedef void (APIENTRY * PFNglNormal3f) (GLfloat x, GLfloat y, GLfloat z);
-static PFNglNormal3f pglNormal3f;
-typedef void (APIENTRY * PFNglColor4f) (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha);
-static PFNglColor4f pglColor4f;
-typedef void (APIENTRY * PFNglColor4fv) (const GLfloat *v);
-static PFNglColor4fv pglColor4fv;
-typedef void (APIENTRY * PFNglTexCoord2f) (GLfloat s, GLfloat t);
-static PFNglTexCoord2f pglTexCoord2f;
+typedef void (APIENTRY * PFNglColor4ubv) (const GLubyte *v);
+static PFNglColor4ubv pglColor4ubv;
+typedef void (APIENTRY * PFNglVertexPointer) (GLint size, GLenum type, GLsizei stride, const GLvoid *pointer);
+static PFNglVertexPointer pglVertexPointer;
+typedef void (APIENTRY * PFNglNormalPointer) (GLenum type, GLsizei stride, const GLvoid *pointer);
+static PFNglNormalPointer pglNormalPointer;
+typedef void (APIENTRY * PFNglTexCoordPointer) (GLint size, GLenum type, GLsizei stride, const GLvoid *pointer);
+static PFNglTexCoordPointer pglTexCoordPointer;
+typedef void (APIENTRY * PFNglColorPointer) (GLint size, GLenum type, GLsizei stride, const GLvoid *pointer);
+static PFNglColorPointer pglColorPointer;
+typedef void (APIENTRY * PFNglDrawArrays) (GLenum mode, GLint first, GLsizei count);
+static PFNglDrawArrays pglDrawArrays;
+typedef void (APIENTRY * PFNglDrawElements) (GLenum mode, GLsizei count, GLenum type, const GLvoid *indices);
+static PFNglDrawElements pglDrawElements;
+typedef void (APIENTRY * PFNglEnableClientState) (GLenum cap);
+static PFNglEnableClientState pglEnableClientState;
+typedef void (APIENTRY * PFNglDisableClientState) (GLenum cap);
+static PFNglDisableClientState pglDisableClientState;
+typedef void (APIENTRY * PFNglGenBuffers) (GLsizei n, GLuint *buffers);
+static PFNglGenBuffers pglGenBuffers;
+typedef void (APIENTRY * PFNglBindBuffer) (GLenum target, GLuint buffer);
+static PFNglBindBuffer pglBindBuffer;
+typedef void (APIENTRY * PFNglBufferData) (GLenum target, GLsizei size, const GLvoid *data, GLenum usage);
+static PFNglBufferData pglBufferData;
+typedef void (APIENTRY * PFNglDeleteBuffers) (GLsizei n, const GLuint *buffers);
+static PFNglDeleteBuffers pglDeleteBuffers;
+
 
 /* Lighting */
 typedef void (APIENTRY * PFNglShadeModel) (GLenum mode);
@@ -383,6 +392,10 @@ typedef void (APIENTRY *PFNglActiveTexture) (GLenum);
 static PFNglActiveTexture pglActiveTexture;
 typedef void (APIENTRY *PFNglMultiTexCoord2f) (GLenum, GLfloat, GLfloat);
 static PFNglMultiTexCoord2f pglMultiTexCoord2f;
+typedef void (APIENTRY *PFNglMultiTexCoord2fv) (GLenum target, const GLfloat *v);
+static PFNglMultiTexCoord2fv pglMultiTexCoord2fv;
+typedef void (APIENTRY *PFNglClientActiveTexture) (GLenum);
+static PFNglClientActiveTexture pglClientActiveTexture;
 
 /* 1.2 Parms */
 /* GL_CLAMP_TO_EDGE_EXT */
@@ -416,19 +429,18 @@ boolean SetupGLfunc(void)
 
 	GETOPENGLFUNC(pglClearColor, glClearColor)
 
-	GETOPENGLFUNC(pglClear , glClear)
-	GETOPENGLFUNC(pglColorMask , glColorMask)
-	GETOPENGLFUNC(pglAlphaFunc , glAlphaFunc)
-	GETOPENGLFUNC(pglBlendFunc , glBlendFunc)
-	GETOPENGLFUNC(pglCullFace , glCullFace)
-	GETOPENGLFUNC(pglPolygonMode , glPolygonMode)
-	GETOPENGLFUNC(pglPolygonOffset , glPolygonOffset)
-	GETOPENGLFUNC(pglScissor , glScissor)
-	GETOPENGLFUNC(pglEnable , glEnable)
-	GETOPENGLFUNC(pglDisable , glDisable)
-	GETOPENGLFUNC(pglGetDoublev , glGetDoublev)
-	GETOPENGLFUNC(pglGetIntegerv , glGetIntegerv)
-	GETOPENGLFUNC(pglGetString , glGetString)
+	GETOPENGLFUNC(pglClear, glClear)
+	GETOPENGLFUNC(pglColorMask, glColorMask)
+	GETOPENGLFUNC(pglAlphaFunc, glAlphaFunc)
+	GETOPENGLFUNC(pglBlendFunc, glBlendFunc)
+	GETOPENGLFUNC(pglCullFace, glCullFace)
+	GETOPENGLFUNC(pglPolygonOffset, glPolygonOffset)
+	GETOPENGLFUNC(pglScissor, glScissor)
+	GETOPENGLFUNC(pglEnable, glEnable)
+	GETOPENGLFUNC(pglDisable, glDisable)
+	GETOPENGLFUNC(pglGetFloatv, glGetFloatv)
+	GETOPENGLFUNC(pglGetIntegerv, glGetIntegerv)
+	GETOPENGLFUNC(pglGetString, glGetString)
 
 	GETOPENGLFUNC(pglClearDepth , glClearDepth)
 	GETOPENGLFUNC(pglDepthFunc , glDepthFunc)
@@ -440,18 +452,20 @@ boolean SetupGLfunc(void)
 	GETOPENGLFUNC(pglPushMatrix , glPushMatrix)
 	GETOPENGLFUNC(pglPopMatrix , glPopMatrix)
 	GETOPENGLFUNC(pglLoadIdentity , glLoadIdentity)
-	GETOPENGLFUNC(pglMultMatrixd , glMultMatrixd)
+	GETOPENGLFUNC(pglMultMatrixf , glMultMatrixf)
 	GETOPENGLFUNC(pglRotatef , glRotatef)
 	GETOPENGLFUNC(pglScalef , glScalef)
 	GETOPENGLFUNC(pglTranslatef , glTranslatef)
 
-	GETOPENGLFUNC(pglBegin , glBegin)
-	GETOPENGLFUNC(pglEnd , glEnd)
-	GETOPENGLFUNC(pglVertex3f , glVertex3f)
-	GETOPENGLFUNC(pglNormal3f , glNormal3f)
-	GETOPENGLFUNC(pglColor4f , glColor4f)
-	GETOPENGLFUNC(pglColor4fv , glColor4fv)
-	GETOPENGLFUNC(pglTexCoord2f , glTexCoord2f)
+	GETOPENGLFUNC(pglColor4ubv, glColor4ubv)
+	GETOPENGLFUNC(pglVertexPointer, glVertexPointer)
+	GETOPENGLFUNC(pglNormalPointer, glNormalPointer)
+	GETOPENGLFUNC(pglTexCoordPointer, glTexCoordPointer)
+	GETOPENGLFUNC(pglColorPointer, glColorPointer)
+	GETOPENGLFUNC(pglDrawArrays, glDrawArrays)
+	GETOPENGLFUNC(pglDrawElements, glDrawElements)
+	GETOPENGLFUNC(pglEnableClientState, glEnableClientState)
+	GETOPENGLFUNC(pglDisableClientState, glDisableClientState)
 
 	GETOPENGLFUNC(pglShadeModel , glShadeModel)
 	GETOPENGLFUNC(pglLightfv, glLightfv)
@@ -483,42 +497,18 @@ boolean SetupGLfunc(void)
 }
 
 // This has to be done after the context is created so the version number can be obtained
+// This is stupid -- even some of the oldest usable OpenGL hardware today supports 1.3-level featureset.
 boolean SetupGLFunc13(void)
 {
-	const GLubyte *version = pglGetString(GL_VERSION);
-	int glmajor, glminor;
-
-	gl13 = false;
-	// Parse the GL version
-	if (version != NULL)
-	{
-		if (sscanf((const char*)version, "%d.%d", &glmajor, &glminor) == 2)
-		{
-			// Look, we gotta prepare for the inevitable arrival of GL 2.0 code...
-			if (glmajor == 1 && glminor >= 3)
-				gl13 = true;
-			else if (glmajor > 1)
-				gl13 = true;
-		}
-	}
-
-	if (gl13)
-	{
-		pglActiveTexture = GetGLFunc("glActiveTexture");
-		pglMultiTexCoord2f = GetGLFunc("glMultiTexCoord2f");
-	}
-	else if (isExtAvailable("GL_ARB_multitexture", gl_extensions))
-	{
-		// Get the functions
-		pglActiveTexture  = GetGLFunc("glActiveTextureARB");
-		pglMultiTexCoord2f  = GetGLFunc("glMultiTexCoord2fARB");
+	pglActiveTexture = GetGLFunc("glActiveTexture");
+	pglMultiTexCoord2f = GetGLFunc("glMultiTexCoord2f");
+	pglClientActiveTexture = GetGLFunc("glClientActiveTexture");
+	pglMultiTexCoord2fv = GetGLFunc("glMultiTexCoord2fv");
+	pglGenBuffers = GetGLFunc("glGenBuffers");
+	pglBindBuffer = GetGLFunc("glBindBuffer");
+	pglBufferData = GetGLFunc("glBufferData");
+	pglDeleteBuffers = GetGLFunc("glDeleteBuffers");
 
-		gl13 = true; // This is now true, so the new fade mask stuff can be done, if OpenGL version is less than 1.3, it still uses the old fade stuff.
-		DBG_Printf("GL_ARB_multitexture support: enabled\n");
-
-	}
-	else
-		DBG_Printf("GL_ARB_multitexture support: disabled\n");
 	return true;
 }
 
@@ -535,39 +525,40 @@ static void SetNoTexture(void)
 	}
 }
 
-static void GLPerspective(GLdouble fovy, GLdouble aspect)
+static void GLPerspective(GLfloat fovy, GLfloat aspect)
 {
-	GLdouble m[4][4] =
+	GLfloat m[4][4] =
 	{
 		{ 1.0f, 0.0f, 0.0f, 0.0f},
 		{ 0.0f, 1.0f, 0.0f, 0.0f},
 		{ 0.0f, 0.0f, 1.0f,-1.0f},
 		{ 0.0f, 0.0f, 0.0f, 0.0f},
 	};
-	const GLdouble zNear = NEAR_CLIPPING_PLANE;
-	const GLdouble zFar = FAR_CLIPPING_PLANE;
-	const GLdouble radians = (GLdouble)(fovy / 2.0f * M_PIl / 180.0f);
-	const GLdouble sine = sin(radians);
-	const GLdouble deltaZ = zFar - zNear;
-	GLdouble cotangent;
+	const GLfloat zNear = NEAR_CLIPPING_PLANE;
+	const GLfloat zFar = FAR_CLIPPING_PLANE;
+	const GLfloat radians = (GLfloat)(fovy / 2.0f * M_PIl / 180.0f);
+	const GLfloat sine = sinf(radians);
+	const GLfloat deltaZ = zFar - zNear;
+	GLfloat cotangent;
 
 	if ((fabsf((float)deltaZ) < 1.0E-36f) || fpclassify(sine) == FP_ZERO || fpclassify(aspect) == FP_ZERO)
 	{
 		return;
 	}
-	cotangent = cos(radians) / sine;
+	cotangent = cosf(radians) / sine;
 
 	m[0][0] = cotangent / aspect;
 	m[1][1] = cotangent;
 	m[2][2] = -(zFar + zNear) / deltaZ;
 	m[3][2] = -2.0f * zNear * zFar / deltaZ;
-	pglMultMatrixd(&m[0][0]);
+
+	pglMultMatrixf(&m[0][0]);
 }
 
-static void GLProject(GLdouble objX, GLdouble objY, GLdouble objZ,
-                      GLdouble* winX, GLdouble* winY, GLdouble* winZ)
+static void GLProject(GLfloat objX, GLfloat objY, GLfloat objZ,
+                      GLfloat* winX, GLfloat* winY, GLfloat* winZ)
 {
-	GLdouble in[4], out[4];
+	GLfloat in[4], out[4];
 	int i;
 
 	for (i=0; i<4; i++)
@@ -634,7 +625,7 @@ void SetModelView(GLint w, GLint h)
 
 	// added for new coronas' code (without depth buffer)
 	pglGetIntegerv(GL_VIEWPORT, viewport);
-	pglGetDoublev(GL_PROJECTION_MATRIX, projMatrix);
+	pglGetFloatv(GL_PROJECTION_MATRIX, projMatrix);
 }
 
 
@@ -659,9 +650,11 @@ void SetStates(void)
 	//pglShadeModel(GL_FLAT);
 
 	pglEnable(GL_TEXTURE_2D);      // two-dimensional texturing
+
 	pglTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
 
 	pglAlphaFunc(GL_NOTEQUAL, 0.0f);
+
 	//pglBlendFunc(GL_ONE, GL_ZERO); // copy pixel to frame buffer (opaque)
 	pglEnable(GL_BLEND);           // enable color blending
 
@@ -694,8 +687,6 @@ void SetStates(void)
 
 	//pglEnable(GL_CULL_FACE);
 	//pglCullFace(GL_FRONT);
-	//pglPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
-	//pglPolygonMode(GL_FRONT, GL_LINE);
 
 	//glFogi(GL_FOG_MODE, GL_EXP);
 	//pglHint(GL_FOG_HINT, GL_FASTEST);
@@ -711,7 +702,7 @@ void SetStates(void)
 	// bp : when no t&l :)
 	pglLoadIdentity();
 	pglScalef(1.0f, 1.0f, -1.0f);
-	pglGetDoublev(GL_MODELVIEW_MATRIX, modelMatrix); // added for new coronas' code (without depth buffer)
+	pglGetFloatv(GL_MODELVIEW_MATRIX, modelMatrix); // added for new coronas' code (without depth buffer)
 }
 
 
@@ -877,7 +868,7 @@ EXPORT void HWRAPI(GClipRect) (INT32 minx, INT32 miny, INT32 maxx, INT32 maxy, f
 
 	// added for new coronas' code (without depth buffer)
 	pglGetIntegerv(GL_VIEWPORT, viewport);
-	pglGetDoublev(GL_PROJECTION_MATRIX, projMatrix);
+	pglGetFloatv(GL_PROJECTION_MATRIX, projMatrix);
 }
 
 
@@ -911,6 +902,8 @@ EXPORT void HWRAPI(ClearBuffer) (FBOOLEAN ColorMask,
 	SetBlend(DepthMask ? PF_Occlude | CurrentPolyFlags : CurrentPolyFlags&~PF_Occlude);
 
 	pglClear(ClearMask);
+	pglEnableClientState(GL_VERTEX_ARRAY); // We always use this one
+	pglEnableClientState(GL_TEXTURE_COORD_ARRAY); // And mostly this one, too
 }
 
 
@@ -921,26 +914,35 @@ EXPORT void HWRAPI(Draw2DLine) (F2DCoord * v1,
                                    F2DCoord * v2,
                                    RGBA_t Color)
 {
-	GLRGBAFloat c;
-
 	// DBG_Printf ("DrawLine() (%f %f %f) %d\n", v1->x, -v1->y, -v1->z, v1->argb);
+	GLfloat p[12];
+	GLfloat dx, dy;
+	GLfloat angle;
 
 	// BP: we should reflect the new state in our variable
 	//SetBlend(PF_Modulated|PF_NoTexture);
 
 	pglDisable(GL_TEXTURE_2D);
 
-	c.red   = byte2float[Color.s.red];
-	c.green = byte2float[Color.s.green];
-	c.blue  = byte2float[Color.s.blue];
-	c.alpha = byte2float[Color.s.alpha];
+	// This is the preferred, 'modern' way of rendering lines -- creating a polygon.
+	if (fabsf(v2->x - v1->x) > FLT_EPSILON)
+		angle = (float)atan((v2->y-v1->y)/(v2->x-v1->x));
+	else
+		angle = (float)N_PI_DEMI;
+	dx = (float)sin(angle) / (float)screen_width;
+	dy = (float)cos(angle) / (float)screen_height;
+
+	p[0] = v1->x - dx;  p[1] = -(v1->y + dy); p[2] = 1;
+	p[3] = v2->x - dx;  p[4] = -(v2->y + dy); p[5] = 1;
+	p[6] = v2->x + dx;  p[7] = -(v2->y - dy); p[8] = 1;
+	p[9] = v1->x + dx;  p[10] = -(v1->y - dy); p[11] = 1;
 
-	pglColor4fv(&c.red);    // is in RGBA float format
-	pglBegin(GL_LINES);
-		pglVertex3f(v1->x, -v1->y, 1.0f);
-		pglVertex3f(v2->x, -v2->y, 1.0f);
-	pglEnd();
+	pglDisableClientState(GL_TEXTURE_COORD_ARRAY);
+	pglColor4ubv((GLubyte*)&Color.s);
+	pglVertexPointer(3, GL_FLOAT, 0, p);
+	pglDrawArrays(GL_TRIANGLE_FAN, 0, 4);
 
+	pglEnableClientState(GL_TEXTURE_COORD_ARRAY);
 	pglEnable(GL_TEXTURE_2D);
 }
 
@@ -985,7 +987,7 @@ EXPORT void HWRAPI(SetBlend) (FBITFIELD PolyFlags)
 					break;
 				case PF_Substractive & PF_Blending:
 					// good for shadow
-					// not realy but what else ?
+					// not really but what else ?
 					pglBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
 					pglAlphaFunc(GL_NOTEQUAL, 0.0f);
 					break;
@@ -1050,7 +1052,7 @@ EXPORT void HWRAPI(SetBlend) (FBITFIELD PolyFlags)
 			if (oglflags & GLF_NOTEXENV)
 			{
 				if (!(PolyFlags & PF_Modulated))
-					pglColor4f(1.0f, 1.0f, 1.0f, 1.0f);
+					pglColor4ubv(white);
 			}
 			else
 #endif
@@ -1125,15 +1127,6 @@ EXPORT void HWRAPI(SetTexture) (FTextureInfo *pTexInfo)
 		w = pTexInfo->width;
 		h = pTexInfo->height;
 
-#ifdef USE_PALETTED_TEXTURE
-		if (glColorTableEXT &&
-			(pTexInfo->grInfo.format == GR_TEXFMT_P_8) &&
-			!(pTexInfo->flags & TF_CHROMAKEYED))
-		{
-			// do nothing here.
-		}
-		else
-#endif
 		if ((pTexInfo->grInfo.format == GR_TEXFMT_P_8) ||
 			(pTexInfo->grInfo.format == GR_TEXFMT_AP_88))
 		{
@@ -1233,17 +1226,6 @@ EXPORT void HWRAPI(SetTexture) (FTextureInfo *pTexInfo)
 			pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min_filter);
 		}
 
-#ifdef USE_PALETTED_TEXTURE
-			//Hurdler: not really supported and not tested recently
-		if (glColorTableEXT &&
-			(pTexInfo->grInfo.format == GR_TEXFMT_P_8) &&
-			!(pTexInfo->flags & TF_CHROMAKEYED))
-		{
-			glColorTableEXT(GL_TEXTURE_2D, GL_RGB8, 256, GL_RGB, GL_UNSIGNED_BYTE, palette_tex);
-			pglTexImage2D(GL_TEXTURE_2D, 0, GL_COLOR_INDEX8_EXT, w, h, 0, GL_COLOR_INDEX, GL_UNSIGNED_BYTE, pTexInfo->grInfo.data);
-		}
-		else
-#endif
 		if (pTexInfo->grInfo.format == GR_TEXFMT_ALPHA_INTENSITY_88)
 		{
 			//pglTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, ptex);
@@ -1328,7 +1310,6 @@ EXPORT void HWRAPI(DrawPolygon) (FSurfaceInfo  *pSurf,
 {
 	FUINT i;
 	FUINT j;
-	GLRGBAFloat c = {0,0,0,0};
 
 	if ((PolyFlags & PF_Corona) && (oglflags & GLF_NOZBUFREAD))
 		PolyFlags &= ~(PF_NoDepthTest|PF_Corona);
@@ -1337,24 +1318,7 @@ EXPORT void HWRAPI(DrawPolygon) (FSurfaceInfo  *pSurf,
 
 	// If Modulated, mix the surface colour to the texture
 	if ((CurrentPolyFlags & PF_Modulated) && pSurf)
-	{
-		if (pal_col)
-		{ // hack for non-palettized mode
-			c.red   = (const_pal_col.red  +byte2float[pSurf->FlatColor.s.red])  /2.0f;
-			c.green = (const_pal_col.green+byte2float[pSurf->FlatColor.s.green])/2.0f;
-			c.blue  = (const_pal_col.blue +byte2float[pSurf->FlatColor.s.blue]) /2.0f;
-			c.alpha = byte2float[pSurf->FlatColor.s.alpha];
-		}
-		else
-		{
-			c.red   = byte2float[pSurf->FlatColor.s.red];
-			c.green = byte2float[pSurf->FlatColor.s.green];
-			c.blue  = byte2float[pSurf->FlatColor.s.blue];
-			c.alpha = byte2float[pSurf->FlatColor.s.alpha];
-		}
-
-		pglColor4fv(&c.red);    // is in RGBA float format
-	}
+		pglColor4ubv((GLubyte*)&pSurf->FlatColor.s);
 
 	// this test is added for new coronas' code (without depth buffer)
 	// I think I should do a separate function for drawing coronas, so it will be a little faster
@@ -1362,10 +1326,14 @@ EXPORT void HWRAPI(DrawPolygon) (FSurfaceInfo  *pSurf,
 	{
 		//rem: all 8 (or 8.0f) values are hard coded: it can be changed to a higher value
 		GLfloat     buf[8][8];
-		GLdouble    cx, cy, cz;
-		GLdouble    px = 0.0f, py = 0.0f, pz = -1.0f;
+		GLfloat    cx, cy, cz;
+		GLfloat    px = 0.0f, py = 0.0f, pz = -1.0f;
 		GLfloat     scalef = 0.0f;
 
+		GLubyte c[4];
+
+		float alpha;
+
 		cx = (pOutVerts[0].x + pOutVerts[2].x) / 2.0f; // we should change the coronas' ...
 		cy = (pOutVerts[0].y + pOutVerts[2].y) / 2.0f; // ... code so its only done once.
 		cz = pOutVerts[0].z;
@@ -1401,21 +1369,20 @@ EXPORT void HWRAPI(DrawPolygon) (FSurfaceInfo  *pSurf,
 		if (scalef < 0.05f)
 			return;
 
-		c.alpha *= scalef; // change the alpha value (it seems better than changing the size of the corona)
-		pglColor4fv(&c.red);
-	}
-	if (PolyFlags & PF_MD2)
-		return;
+		// GLubyte c[4];
+		c[0] = pSurf->FlatColor.s.red;
+		c[1] = pSurf->FlatColor.s.green;
+		c[2] = pSurf->FlatColor.s.blue;
 
-	pglBegin(GL_TRIANGLE_FAN);
-	for (i = 0; i < iNumPts; i++)
-	{
-		pglTexCoord2f(pOutVerts[i].sow, pOutVerts[i].tow);
-		//Hurdler: test code: -pOutVerts[i].z => pOutVerts[i].z
-		pglVertex3f(pOutVerts[i].x, pOutVerts[i].y, pOutVerts[i].z);
-		//pglVertex3f(pOutVerts[i].x, pOutVerts[i].y, -pOutVerts[i].z);
+		alpha = byte2float[pSurf->FlatColor.s.alpha];
+		alpha *= scalef; // change the alpha value (it seems better than changing the size of the corona)
+		c[3] = (unsigned char)(alpha * 255);
+		pglColor4ubv(c);
 	}
-	pglEnd();
+
+	pglVertexPointer(3, GL_FLOAT, sizeof(FOutVector), &pOutVerts[0].x);
+	pglTexCoordPointer(2, GL_FLOAT, sizeof(FOutVector), &pOutVerts[0].sow);
+	pglDrawArrays(GL_TRIANGLE_FAN, 0, iNumPts);
 
 	if (PolyFlags & PF_RemoveYWrap)
 		pglTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
@@ -1444,13 +1411,20 @@ typedef struct
 
 typedef struct
 {
-	int id;
+	unsigned int id;
 	int rows, columns;
 	int loopcount;
 	GLSkyLoopDef *loops;
 	vbo_vertex_t *data;
 } GLSkyVBO;
 
+static const boolean gl_ext_arb_vertex_buffer_object = true;
+
+#define NULL_VBO_VERTEX ((vbo_vertex_t*)NULL)
+#define sky_vbo_x (gl_ext_arb_vertex_buffer_object ? &NULL_VBO_VERTEX->x : &vbo->data[0].x)
+#define sky_vbo_u (gl_ext_arb_vertex_buffer_object ? &NULL_VBO_VERTEX->u : &vbo->data[0].u)
+#define sky_vbo_r (gl_ext_arb_vertex_buffer_object ? &NULL_VBO_VERTEX->r : &vbo->data[0].r)
+
 // The texture offset to be applied to the texture coordinates in SkyVertex().
 static int rows, columns;
 static boolean yflip;
@@ -1587,20 +1561,55 @@ static void gld_BuildSky(int row_count, int col_count)
 static void RenderDome(INT32 skytexture)
 {
 	int i, j;
+	int vbosize;
 	GLSkyVBO *vbo = &sky_vbo;
 
-	pglRotatef(270.0f, 0.0f, 1.0f, 0.0f);
-
 	rows = 4;
 	columns = 4 * gl_sky_detail;
 
+	vbosize = 2 * rows * (columns * 2 + 2) + columns * 2;
+
+	// Build the sky dome! Yes!
 	if (lasttex != skytexture)
 	{
+		// delete VBO when already exists
+		if (gl_ext_arb_vertex_buffer_object)
+		{
+			if (vbo->id)
+				pglDeleteBuffers(1, &vbo->id);
+		}
+
 		lasttex = skytexture;
 		gld_BuildSky(rows, columns);
+
+		if (gl_ext_arb_vertex_buffer_object)
+		{
+			// generate a new VBO and get the associated ID
+			pglGenBuffers(1, &vbo->id);
+
+			// bind VBO in order to use
+			pglBindBuffer(GL_ARRAY_BUFFER, vbo->id);
+
+			// upload data to VBO
+			pglBufferData(GL_ARRAY_BUFFER, vbosize * sizeof(vbo->data[0]), vbo->data, GL_STATIC_DRAW);
+		}
 	}
 
+	// bind VBO in order to use
+	if (gl_ext_arb_vertex_buffer_object)
+		pglBindBuffer(GL_ARRAY_BUFFER, vbo->id);
+
+	// activate and specify pointers to arrays
+	pglVertexPointer(3, GL_FLOAT, sizeof(vbo->data[0]), sky_vbo_x);
+	pglTexCoordPointer(2, GL_FLOAT, sizeof(vbo->data[0]), sky_vbo_u);
+	pglColorPointer(4, GL_UNSIGNED_BYTE, sizeof(vbo->data[0]), sky_vbo_r);
+
+	// activate color arrays
+	pglEnableClientState(GL_COLOR_ARRAY);
+
+	// set transforms
 	pglScalef(1.0f, (float)texh / 230.0f, 1.0f);
+	pglRotatef(270.0f, 0.0f, 1.0f, 0.0f);
 
 	for (j = 0; j < 2; j++)
 	{
@@ -1610,25 +1619,20 @@ static void RenderDome(INT32 skytexture)
 
 			if (j == 0 ? loop->use_texture : !loop->use_texture)
 				continue;
-			else
-			{
-				int k;
-				pglBegin(loop->mode);
-				for (k = loop->vertexindex; k < (loop->vertexindex + loop->vertexcount); k++)
-				{
-					vbo_vertex_t *v = &vbo->data[k];
-					if (loop->use_texture)
-						pglTexCoord2f(v->u, v->v);
-					pglColor4f(v->r, v->g, v->b, v->a);
-					pglVertex3f(v->x, v->y, v->z);
-				}
-				pglEnd();
-			}
+
+			pglDrawArrays(loop->mode, loop->vertexindex, loop->vertexcount);
 		}
 	}
 
 	pglScalef(1.0f, 1.0f, 1.0f);
-	pglColor4f(1.0f, 1.0f, 1.0f, 1.0f);
+	pglColor4ubv(white);
+
+	// bind with 0, so, switch back to normal pointer operation
+	if (gl_ext_arb_vertex_buffer_object)
+		pglBindBuffer(GL_ARRAY_BUFFER, 0);
+
+	// deactivate color array
+	pglDisableClientState(GL_COLOR_ARRAY);
 }
 
 EXPORT void HWRAPI(RenderSkyDome) (INT32 tex, INT32 texture_width, INT32 texture_height, FTransform transform)
@@ -1659,15 +1663,6 @@ EXPORT void HWRAPI(SetSpecialState) (hwdspecialstate_t IdState, INT32 Value)
 		}
 #endif
 
-		case HWD_SET_PALETTECOLOR:
-		{
-			pal_col = Value;
-			const_pal_col.blue  = byte2float[((Value>>16)&0xff)];
-			const_pal_col.green = byte2float[((Value>>8)&0xff)];
-			const_pal_col.red   = byte2float[((Value)&0xff)];
-			break;
-		}
-
 		case HWD_SET_FOG_COLOR:
 		{
 			GLfloat fogcolor[4];
@@ -1709,13 +1704,6 @@ EXPORT void HWRAPI(SetSpecialState) (hwdspecialstate_t IdState, INT32 Value)
 				pglDisable(GL_FOG);
 			break;
 
-		case HWD_SET_POLYGON_SMOOTH:
-			if (Value)
-				pglEnable(GL_POLYGON_SMOOTH);
-			else
-				pglDisable(GL_POLYGON_SMOOTH);
-			break;
-
 		case HWD_SET_TEXTUREFILTERMODE:
 			switch (Value)
 			{
@@ -1770,19 +1758,227 @@ EXPORT void HWRAPI(SetSpecialState) (hwdspecialstate_t IdState, INT32 Value)
 	}
 }
 
-static  void DrawMD2Ex(INT32 *gl_cmd_buffer, md2_frame_t *frame, INT32 duration, INT32 tics, md2_frame_t *nextframe, FTransform *pos, float scale, UINT8 flipped, UINT8 *color)
+static float *vertBuffer = NULL;
+static float *normBuffer = NULL;
+static size_t lerpBufferSize = 0;
+static short *vertTinyBuffer = NULL;
+static char *normTinyBuffer = NULL;
+static size_t lerpTinyBufferSize = 0;
+
+// Static temporary buffer for doing frame interpolation
+// 'size' is the vertex size
+static void AllocLerpBuffer(size_t size)
+{
+	if (lerpBufferSize >= size)
+		return;
+
+	if (vertBuffer != NULL)
+		free(vertBuffer);
+
+	if (normBuffer != NULL)
+		free(normBuffer);
+
+	lerpBufferSize = size;
+	vertBuffer = malloc(lerpBufferSize);
+	normBuffer = malloc(lerpBufferSize);
+}
+
+// Static temporary buffer for doing frame interpolation
+// 'size' is the vertex size
+static void AllocLerpTinyBuffer(size_t size)
+{
+	if (lerpTinyBufferSize >= size)
+		return;
+
+	if (vertTinyBuffer != NULL)
+		free(vertTinyBuffer);
+
+	if (normTinyBuffer != NULL)
+		free(normTinyBuffer);
+
+	lerpTinyBufferSize = size;
+	vertTinyBuffer = malloc(lerpTinyBufferSize);
+	normTinyBuffer = malloc(lerpTinyBufferSize / 2);
+}
+
+#ifndef GL_STATIC_DRAW
+#define GL_STATIC_DRAW 0x88E4
+#endif
+
+#ifndef GL_ARRAY_BUFFER
+#define GL_ARRAY_BUFFER 0x8892
+#endif
+
+static void CreateModelVBO(mesh_t *mesh, mdlframe_t *frame)
+{
+	int bufferSize = sizeof(vbo64_t)*mesh->numTriangles * 3;
+	vbo64_t *buffer = (vbo64_t*)malloc(bufferSize);
+	vbo64_t *bufPtr = buffer;
+
+	float *vertPtr = frame->vertices;
+	float *normPtr = frame->normals;
+	float *tanPtr = frame->tangents;
+	float *uvPtr = mesh->uvs;
+	float *lightPtr = mesh->lightuvs;
+	char *colorPtr = frame->colors;
+
+	int i;
+	for (i = 0; i < mesh->numTriangles * 3; i++)
+	{
+		bufPtr->x = *vertPtr++;
+		bufPtr->y = *vertPtr++;
+		bufPtr->z = *vertPtr++;
+
+		bufPtr->nx = *normPtr++;
+		bufPtr->ny = *normPtr++;
+		bufPtr->nz = *normPtr++;
+
+		bufPtr->s0 = *uvPtr++;
+		bufPtr->t0 = *uvPtr++;
+
+		if (tanPtr != NULL)
+		{
+			bufPtr->tan0 = *tanPtr++;
+			bufPtr->tan1 = *tanPtr++;
+			bufPtr->tan2 = *tanPtr++;
+		}
+
+		if (lightPtr != NULL)
+		{
+			bufPtr->s1 = *lightPtr++;
+			bufPtr->t1 = *lightPtr++;
+		}
+
+		if (colorPtr)
+		{
+			bufPtr->r = *colorPtr++;
+			bufPtr->g = *colorPtr++;
+			bufPtr->b = *colorPtr++;
+			bufPtr->a = *colorPtr++;
+		}
+		else
+		{
+			bufPtr->r = 255;
+			bufPtr->g = 255;
+			bufPtr->b = 255;
+			bufPtr->a = 255;
+		}
+
+		bufPtr++;
+	}
+
+	pglGenBuffers(1, &frame->vboID);
+	pglBindBuffer(GL_ARRAY_BUFFER, frame->vboID);
+	pglBufferData(GL_ARRAY_BUFFER, bufferSize, buffer, GL_STATIC_DRAW);
+	free(buffer);
+
+	// Don't leave the array buffer bound to the model,
+	// since this is called mid-frame
+	pglBindBuffer(GL_ARRAY_BUFFER, 0);
+}
+
+static void CreateModelVBOTiny(mesh_t *mesh, tinyframe_t *frame)
+{
+	int bufferSize = sizeof(vbotiny_t)*mesh->numTriangles * 3;
+	vbotiny_t *buffer = (vbotiny_t*)malloc(bufferSize);
+	vbotiny_t *bufPtr = buffer;
+
+	short *vertPtr = frame->vertices;
+	char *normPtr = frame->normals;
+	float *uvPtr = mesh->uvs;
+	char *tanPtr = frame->tangents;
+
+	int i;
+	for (i = 0; i < mesh->numVertices; i++)
+	{
+		bufPtr->x = *vertPtr++;
+		bufPtr->y = *vertPtr++;
+		bufPtr->z = *vertPtr++;
+
+		bufPtr->nx = *normPtr++;
+		bufPtr->ny = *normPtr++;
+		bufPtr->nz = *normPtr++;
+
+		bufPtr->s0 = *uvPtr++;
+		bufPtr->t0 = *uvPtr++;
+
+		if (tanPtr)
+		{
+			bufPtr->tanx = *tanPtr++;
+			bufPtr->tany = *tanPtr++;
+			bufPtr->tanz = *tanPtr++;
+		}
+
+		bufPtr++;
+	}
+
+	pglGenBuffers(1, &frame->vboID);
+	pglBindBuffer(GL_ARRAY_BUFFER, frame->vboID);
+	pglBufferData(GL_ARRAY_BUFFER, bufferSize, buffer, GL_STATIC_DRAW);
+	free(buffer);
+
+	// Don't leave the array buffer bound to the model,
+	// since this is called mid-frame
+	pglBindBuffer(GL_ARRAY_BUFFER, 0);
+}
+
+EXPORT void HWRAPI(CreateModelVBOs) (model_t *model)
+{
+	int i;
+	for (i = 0; i < model->numMeshes; i++)
+	{
+		mesh_t *mesh = &model->meshes[i];
+
+		if (mesh->frames)
+		{
+			int j;
+			for (j = 0; j < model->meshes[i].numFrames; j++)
+			{
+				mdlframe_t *frame = &mesh->frames[j];
+				if (frame->vboID)
+					pglDeleteBuffers(1, &frame->vboID);
+				frame->vboID = 0;
+				CreateModelVBO(mesh, frame);
+			}
+		}
+		else if (mesh->tinyframes)
+		{
+			int j;
+			for (j = 0; j < model->meshes[i].numFrames; j++)
+			{
+				tinyframe_t *frame = &mesh->tinyframes[j];
+				if (frame->vboID)
+					pglDeleteBuffers(1, &frame->vboID);
+				frame->vboID = 0;
+				CreateModelVBOTiny(mesh, frame);
+			}
+		}
+	}
+}
+
+#define BUFFER_OFFSET(i) ((char*)NULL + (i))
+
+static void DrawModelEx(model_t *model, INT32 frameIndex, INT32 duration, INT32 tics, INT32 nextFrameIndex, FTransform *pos, float scale, UINT8 flipped, UINT8 *color)
 {
-	INT32     val, count, pindex;
-	GLfloat s, t;
 	GLfloat ambient[4];
 	GLfloat diffuse[4];
 
 	float pol = 0.0f;
-	float scalex = scale, scaley = scale, scalez = scale;
+	float scalex, scaley, scalez;
+
+	boolean useTinyFrames;
+
+	int i;
 
 	// Because Otherwise, scaling the screen negatively vertically breaks the lighting
 	GLfloat LightPos[] = {0.0f, 1.0f, 0.0f, 0.0f};
 
+	// Affect input model scaling
+	scale *= 0.5f;
+	scalex = scale;
+	scaley = scale;
+	scalez = scale;
+
 	if (duration != 0 && duration != -1 && tics != -1) // don't interpolate if instantaneous or infinite in length
 	{
 		UINT32 newtime = (duration - tics); // + 1;
@@ -1816,7 +2012,21 @@ static  void DrawMD2Ex(INT32 *gl_cmd_buffer, md2_frame_t *frame, INT32 duration,
 	}
 
 	pglEnable(GL_CULL_FACE);
+	pglEnable(GL_NORMALIZE);
 
+#ifdef USE_FTRANSFORM_MIRROR
+	// flipped is if the object is flipped
+	// pos->flip is if the screen is flipped vertically
+	// pos->mirror is if the screen is flipped horizontally
+	// XOR all the flips together to figure out what culling to use!
+	{
+		boolean reversecull = (flipped ^ pos->flip ^ pos->mirror);
+		if (reversecull)
+			pglCullFace(GL_FRONT);
+		else
+			pglCullFace(GL_BACK);
+	}
+#else
 	// pos->flip is if the screen is flipped too
 	if (flipped != pos->flip) // If either are active, but not both, invert the model's culling
 	{
@@ -1826,6 +2036,7 @@ static  void DrawMD2Ex(INT32 *gl_cmd_buffer, md2_frame_t *frame, INT32 duration,
 	{
 		pglCullFace(GL_BACK);
 	}
+#endif
 
 	pglLightfv(GL_LIGHT0, GL_POSITION, LightPos);
 
@@ -1848,98 +2059,145 @@ static  void DrawMD2Ex(INT32 *gl_cmd_buffer, md2_frame_t *frame, INT32 duration,
 	pglTranslatef(pos->x, pos->z, pos->y);
 	if (flipped)
 		scaley = -scaley;
-	pglRotatef(pos->angley, 0.0f, -1.0f, 0.0f);
+#ifdef USE_FTRANSFORM_ANGLEZ
+	pglRotatef(pos->anglez, 0.0f, 0.0f, -1.0f); // rotate by slope from Kart
+#endif
 	pglRotatef(pos->anglex, -1.0f, 0.0f, 0.0f);
+	pglRotatef(pos->angley, 0.0f, -1.0f, 0.0f);
+
+	pglScalef(scalex, scaley, scalez);
+
+	useTinyFrames = model->meshes[0].tinyframes != NULL;
 
-	val = *gl_cmd_buffer++;
+	if (useTinyFrames)
+		pglScalef(1 / 64.0f, 1 / 64.0f, 1 / 64.0f);
 
-	while (val != 0)
+	pglEnableClientState(GL_NORMAL_ARRAY);
+
+	for (i = 0; i < model->numMeshes; i++)
 	{
-		if (val < 0)
-		{
-			pglBegin(GL_TRIANGLE_FAN);
-			count = -val;
-		}
-		else
-		{
-			pglBegin(GL_TRIANGLE_STRIP);
-			count = val;
-		}
+		mesh_t *mesh = &model->meshes[i];
 
-		while (count--)
+		if (useTinyFrames)
 		{
-			s = *(float *) gl_cmd_buffer++;
-			t = *(float *) gl_cmd_buffer++;
-			pindex = *gl_cmd_buffer++;
+			tinyframe_t *frame = &mesh->tinyframes[frameIndex % mesh->numFrames];
+			tinyframe_t *nextframe = NULL;
 
-			pglTexCoord2f(s, t);
+			if (nextFrameIndex != -1)
+				nextframe = &mesh->tinyframes[nextFrameIndex % mesh->numFrames];
 
 			if (!nextframe || fpclassify(pol) == FP_ZERO)
 			{
-				pglNormal3f(frame->vertices[pindex].normal[0],
-				            frame->vertices[pindex].normal[1],
-				            frame->vertices[pindex].normal[2]);
+				pglBindBuffer(GL_ARRAY_BUFFER, frame->vboID);
+				pglVertexPointer(3, GL_SHORT, sizeof(vbotiny_t), BUFFER_OFFSET(0));
+				pglNormalPointer(GL_BYTE, sizeof(vbotiny_t), BUFFER_OFFSET(sizeof(short)*3));
+				pglTexCoordPointer(2, GL_FLOAT, sizeof(vbotiny_t), BUFFER_OFFSET(sizeof(short) * 3 + sizeof(char) * 6));
 
-				pglVertex3f(frame->vertices[pindex].vertex[0]*scalex/2.0f,
-				            frame->vertices[pindex].vertex[1]*scaley/2.0f,
-				            frame->vertices[pindex].vertex[2]*scalez/2.0f);
+				pglDrawElements(GL_TRIANGLES, mesh->numTriangles * 3, GL_UNSIGNED_SHORT, mesh->indices);
+				pglBindBuffer(GL_ARRAY_BUFFER, 0);
 			}
 			else
 			{
-				// Interpolate
-				float px1 = frame->vertices[pindex].vertex[0]*scalex/2.0f;
-				float px2 = nextframe->vertices[pindex].vertex[0]*scalex/2.0f;
-				float py1 = frame->vertices[pindex].vertex[1]*scaley/2.0f;
-				float py2 = nextframe->vertices[pindex].vertex[1]*scaley/2.0f;
-				float pz1 = frame->vertices[pindex].vertex[2]*scalez/2.0f;
-				float pz2 = nextframe->vertices[pindex].vertex[2]*scalez/2.0f;
-				float nx1 = frame->vertices[pindex].normal[0];
-				float nx2 = nextframe->vertices[pindex].normal[0];
-				float ny1 = frame->vertices[pindex].normal[1];
-				float ny2 = nextframe->vertices[pindex].normal[1];
-				float nz1 = frame->vertices[pindex].normal[2];
-				float nz2 = nextframe->vertices[pindex].normal[2];
-
-				pglNormal3f((nx1 + pol * (nx2 - nx1)),
-				            (ny1 + pol * (ny2 - ny1)),
-				            (nz1 + pol * (nz2 - nz1)));
-				pglVertex3f((px1 + pol * (px2 - px1)),
-				            (py1 + pol * (py2 - py1)),
-				            (pz1 + pol * (pz2 - pz1)));
+				short *vertPtr;
+				char *normPtr;
+				int j;
+
+				// Dangit, I soooo want to do this in a GLSL shader...
+				AllocLerpTinyBuffer(mesh->numVertices * sizeof(short) * 3);
+				vertPtr = vertTinyBuffer;
+				normPtr = normTinyBuffer;
+				j = 0;
+
+				for (j = 0; j < mesh->numVertices * 3; j++)
+				{
+					// Interpolate
+					*vertPtr++ = (short)(frame->vertices[j] + (pol * (nextframe->vertices[j] - frame->vertices[j])));
+					*normPtr++ = (char)(frame->normals[j] + (pol * (nextframe->normals[j] - frame->normals[j])));
+				}
+
+				pglVertexPointer(3, GL_SHORT, 0, vertTinyBuffer);
+				pglNormalPointer(GL_BYTE, 0, normTinyBuffer);
+				pglTexCoordPointer(2, GL_FLOAT, 0, mesh->uvs);
+				pglDrawElements(GL_TRIANGLES, mesh->numTriangles * 3, GL_UNSIGNED_SHORT, mesh->indices);
 			}
 		}
+		else
+		{
+			mdlframe_t *frame = &mesh->frames[frameIndex % mesh->numFrames];
+			mdlframe_t *nextframe = NULL;
+
+			if (nextFrameIndex != -1)
+				nextframe = &mesh->frames[nextFrameIndex % mesh->numFrames];
+
+			if (!nextframe || fpclassify(pol) == FP_ZERO)
+			{
+				// Zoom! Take advantage of just shoving the entire arrays to the GPU.
+/*				pglVertexPointer(3, GL_FLOAT, 0, frame->vertices);
+				pglNormalPointer(GL_FLOAT, 0, frame->normals);
+				pglTexCoordPointer(2, GL_FLOAT, 0, mesh->uvs);
+				pglDrawArrays(GL_TRIANGLES, 0, mesh->numTriangles * 3);*/
+
+				pglBindBuffer(GL_ARRAY_BUFFER, frame->vboID);
+				pglVertexPointer(3, GL_FLOAT, sizeof(vbo64_t), BUFFER_OFFSET(0));
+				pglNormalPointer(GL_FLOAT, sizeof(vbo64_t), BUFFER_OFFSET(sizeof(float) * 3));
+				pglTexCoordPointer(2, GL_FLOAT, sizeof(vbo64_t), BUFFER_OFFSET(sizeof(float) * 6));
+
+				pglDrawArrays(GL_TRIANGLES, 0, mesh->numTriangles * 3);
+				// No tinyframes, no mesh indices
+				//pglDrawElements(GL_TRIANGLES, mesh->numTriangles * 3, GL_UNSIGNED_SHORT, mesh->indices);
+				pglBindBuffer(GL_ARRAY_BUFFER, 0);
+			}
+			else
+			{
+				float *vertPtr;
+				float *normPtr;
+				int j = 0;
+
+				// Dangit, I soooo want to do this in a GLSL shader...
+				AllocLerpBuffer(mesh->numVertices * sizeof(float) * 3);
+				vertPtr = vertBuffer;
+				normPtr = normBuffer;
+				//int j = 0;
 
-		pglEnd();
+				for (j = 0; j < mesh->numVertices * 3; j++)
+				{
+					// Interpolate
+					*vertPtr++ = frame->vertices[j] + (pol * (nextframe->vertices[j] - frame->vertices[j]));
+					*normPtr++ = frame->normals[j] + (pol * (nextframe->normals[j] - frame->normals[j]));
+				}
 
-		val = *gl_cmd_buffer++;
+				pglVertexPointer(3, GL_FLOAT, 0, vertBuffer);
+				pglNormalPointer(GL_FLOAT, 0, normBuffer);
+				pglTexCoordPointer(2, GL_FLOAT, 0, mesh->uvs);
+				pglDrawArrays(GL_TRIANGLES, 0, mesh->numVertices);
+			}
+		}
 	}
+
+	pglDisableClientState(GL_NORMAL_ARRAY);
+
 	pglPopMatrix(); // should be the same as glLoadIdentity
 	if (color)
 		pglDisable(GL_LIGHTING);
 	pglShadeModel(GL_FLAT);
 	pglDisable(GL_CULL_FACE);
+	pglDisable(GL_NORMALIZE);
 }
 
 // -----------------+
 // HWRAPI DrawMD2   : Draw an MD2 model with glcommands
 // -----------------+
-EXPORT void HWRAPI(DrawMD2i) (INT32 *gl_cmd_buffer, md2_frame_t *frame, INT32 duration, INT32 tics, md2_frame_t *nextframe, FTransform *pos, float scale, UINT8 flipped, UINT8 *color)
+EXPORT void HWRAPI(DrawModel) (model_t *model, INT32 frameIndex, INT32 duration, INT32 tics, INT32 nextFrameIndex, FTransform *pos, float scale, UINT8 flipped, UINT8 *color)
 {
-	DrawMD2Ex(gl_cmd_buffer, frame, duration, tics,  nextframe, pos, scale, flipped, color);
+	DrawModelEx(model, frameIndex, duration, tics,  nextFrameIndex, pos, scale, flipped, color);
 }
 
-EXPORT void HWRAPI(DrawMD2) (INT32 *gl_cmd_buffer, md2_frame_t *frame, FTransform *pos, float scale)
-{
-	DrawMD2Ex(gl_cmd_buffer, frame, 0, 0,  NULL, pos, scale, false, NULL);
-}
-
-
 // -----------------+
 // SetTransform     :
 // -----------------+
 EXPORT void HWRAPI(SetTransform) (FTransform *stransform)
 {
-	static INT32 special_splitscreen;
+	static boolean special_splitscreen;
 	pglLoadIdentity();
 	if (stransform)
 	{
@@ -1947,6 +2205,12 @@ EXPORT void HWRAPI(SetTransform) (FTransform *stransform)
 		// keep a trace of the transformation for md2
 		memcpy(&md2_transform, stransform, sizeof (md2_transform));
 
+#ifdef USE_FTRANSFORM_MIRROR
+		// mirroring from Kart
+		if (stransform->mirror)
+			pglScalef(-stransform->scalex, stransform->scaley, -stransform->scalez);
+		else
+#endif
 		if (stransform->flip)
 			pglScalef(stransform->scalex, -stransform->scaley, -stransform->scalez);
 		else
@@ -1961,10 +2225,10 @@ EXPORT void HWRAPI(SetTransform) (FTransform *stransform)
 		fovx90 = stransform->fovxangle > 0.0f && fabsf(stransform->fovxangle - 90.0f) < 0.5f;
 		special_splitscreen = (stransform->splitscreen && fovx90);
 		if (special_splitscreen)
-			GLPerspective(53.13l, 2*ASPECT_RATIO);  // 53.13 = 2*atan(0.5)
+			GLPerspective(53.13f, 2*ASPECT_RATIO);  // 53.13 = 2*atan(0.5)
 		else
 			GLPerspective(stransform->fovxangle, ASPECT_RATIO);
-		pglGetDoublev(GL_PROJECTION_MATRIX, projMatrix); // added for new coronas' code (without depth buffer)
+		pglGetFloatv(GL_PROJECTION_MATRIX, projMatrix); // added for new coronas' code (without depth buffer)
 		pglMatrixMode(GL_MODELVIEW);
 	}
 	else
@@ -1974,15 +2238,15 @@ EXPORT void HWRAPI(SetTransform) (FTransform *stransform)
 		pglMatrixMode(GL_PROJECTION);
 		pglLoadIdentity();
 		if (special_splitscreen)
-			GLPerspective(53.13l, 2*ASPECT_RATIO);  // 53.13 = 2*atan(0.5)
+			GLPerspective(53.13f, 2*ASPECT_RATIO);  // 53.13 = 2*atan(0.5)
 		else
 			//Hurdler: is "fov" correct?
 			GLPerspective(fov, ASPECT_RATIO);
-		pglGetDoublev(GL_PROJECTION_MATRIX, projMatrix); // added for new coronas' code (without depth buffer)
+		pglGetFloatv(GL_PROJECTION_MATRIX, projMatrix); // added for new coronas' code (without depth buffer)
 		pglMatrixMode(GL_MODELVIEW);
 	}
 
-	pglGetDoublev(GL_MODELVIEW_MATRIX, modelMatrix); // added for new coronas' code (without depth buffer)
+	pglGetFloatv(GL_MODELVIEW_MATRIX, modelMatrix); // added for new coronas' code (without depth buffer)
 }
 
 EXPORT INT32  HWRAPI(GetTextureUsed) (void)
@@ -2003,7 +2267,6 @@ EXPORT INT32  HWRAPI(GetRenderVersion) (void)
 	return VERSION;
 }
 
-#ifdef SHUFFLE
 EXPORT void HWRAPI(PostImgRedraw) (float points[SCREENVERTS][SCREENVERTS][2])
 {
 	INT32 x, y;
@@ -2011,6 +2274,14 @@ EXPORT void HWRAPI(PostImgRedraw) (float points[SCREENVERTS][SCREENVERTS][2])
 	float xfix, yfix;
 	INT32 texsize = 2048;
 
+	const float blackBack[16] =
+	{
+		-16.0f, -16.0f, 6.0f,
+		-16.0f, 16.0f, 6.0f,
+		16.0f, 16.0f, 6.0f,
+		16.0f, -16.0f, 6.0f
+	};
+
 	// Use a power of two texture, dammit
 	if(screen_width <= 1024)
 		texsize = 1024;
@@ -2023,47 +2294,66 @@ EXPORT void HWRAPI(PostImgRedraw) (float points[SCREENVERTS][SCREENVERTS][2])
 
 	pglDisable(GL_DEPTH_TEST);
 	pglDisable(GL_BLEND);
-	pglBegin(GL_QUADS);
 
-		// Draw a black square behind the screen texture,
-		// so nothing shows through the edges
-		pglColor4f(1.0f, 1.0f, 1.0f, 1.0f);
-		pglVertex3f(-16.0f, -16.0f, 6.0f);
-		pglVertex3f(-16.0f, 16.0f, 6.0f);
-		pglVertex3f(16.0f, 16.0f, 6.0f);
-		pglVertex3f(16.0f, -16.0f, 6.0f);
+	// const float blackBack[16]
+
+	// Draw a black square behind the screen texture,
+	// so nothing shows through the edges
+	pglColor4ubv(white);
 
-		for(x=0;x<SCREENVERTS-1;x++)
+	pglVertexPointer(3, GL_FLOAT, 0, blackBack);
+	pglDrawArrays(GL_TRIANGLE_FAN, 0, 4);
+
+	for(x=0;x<SCREENVERTS-1;x++)
+	{
+		for(y=0;y<SCREENVERTS-1;y++)
 		{
-			for(y=0;y<SCREENVERTS-1;y++)
-			{
-				// Used for texture coordinates
-				// Annoying magic numbers to scale the square texture to
-				// a non-square screen..
-				float_x = (float)(x/(xfix));
-				float_y = (float)(y/(yfix));
-				float_nextx = (float)(x+1)/(xfix);
-				float_nexty = (float)(y+1)/(yfix);
-
-				// Attach the squares together.
-				pglTexCoord2f( float_x, float_y);
-				pglVertex3f(points[x][y][0], points[x][y][1], 4.4f);
-
-				pglTexCoord2f( float_x, float_nexty);
-				pglVertex3f(points[x][y+1][0], points[x][y+1][1], 4.4f);
-
-				pglTexCoord2f( float_nextx, float_nexty);
-				pglVertex3f(points[x+1][y+1][0], points[x+1][y+1][1], 4.4f);
-
-				pglTexCoord2f( float_nextx, float_y);
-				pglVertex3f(points[x+1][y][0], points[x+1][y][1], 4.4f);
-			}
+			float stCoords[8];
+			float vertCoords[12];
+
+			// Used for texture coordinates
+			// Annoying magic numbers to scale the square texture to
+			// a non-square screen..
+			float_x = (float)(x/(xfix));
+			float_y = (float)(y/(yfix));
+			float_nextx = (float)(x+1)/(xfix);
+			float_nexty = (float)(y+1)/(yfix);
+
+			// float stCoords[8];
+			stCoords[0] = float_x;
+			stCoords[1] = float_y;
+			stCoords[2] = float_x;
+			stCoords[3] = float_nexty;
+			stCoords[4] = float_nextx;
+			stCoords[5] = float_nexty;
+			stCoords[6] = float_nextx;
+			stCoords[7] = float_y;
+
+			pglTexCoordPointer(2, GL_FLOAT, 0, stCoords);
+
+			// float vertCoords[12];
+			vertCoords[0] = points[x][y][0];
+			vertCoords[1] = points[x][y][1];
+			vertCoords[2] = 4.4f;
+			vertCoords[3] = points[x][y + 1][0];
+			vertCoords[4] = points[x][y + 1][1];
+			vertCoords[5] = 4.4f;
+			vertCoords[6] = points[x + 1][y + 1][0];
+			vertCoords[7] = points[x + 1][y + 1][1];
+			vertCoords[8] = 4.4f;
+			vertCoords[9] = points[x + 1][y][0];
+			vertCoords[10] = points[x + 1][y][1];
+			vertCoords[11] = 4.4f;
+
+			pglVertexPointer(3, GL_FLOAT, 0, vertCoords);
+
+			pglDrawArrays(GL_TRIANGLE_FAN, 0, 4);
 		}
-	pglEnd();
+	}
+
 	pglEnable(GL_DEPTH_TEST);
 	pglEnable(GL_BLEND);
 }
-#endif //SHUFFLE
 
 // Sryder:	This needs to be called whenever the screen changes resolution in order to reset the screen textures to use
 //			a new size
@@ -2148,6 +2438,16 @@ EXPORT void HWRAPI(DrawIntermissionBG)(void)
 	float xfix, yfix;
 	INT32 texsize = 2048;
 
+	const float screenVerts[12] =
+	{
+		-1.0f, -1.0f, 1.0f,
+		-1.0f, 1.0f, 1.0f,
+		1.0f, 1.0f, 1.0f,
+		1.0f, -1.0f, 1.0f
+	};
+
+	float fix[8];
+
 	if(screen_width <= 1024)
 		texsize = 1024;
 	if(screen_width <= 512)
@@ -2156,41 +2456,56 @@ EXPORT void HWRAPI(DrawIntermissionBG)(void)
 	xfix = 1/((float)(texsize)/((float)((screen_width))));
 	yfix = 1/((float)(texsize)/((float)((screen_height))));
 
-	pglClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
-
-	pglBindTexture(GL_TEXTURE_2D, screentexture);
-	pglBegin(GL_QUADS);
-
-		pglColor4f(1.0f, 1.0f, 1.0f, 1.0f);
-		// Bottom left
-		pglTexCoord2f(0.0f, 0.0f);
-		pglVertex3f(-1.0f, -1.0f, 1.0f);
+	// const float screenVerts[12]
 
-		// Top left
-		pglTexCoord2f(0.0f, yfix);
-		pglVertex3f(-1.0f, 1.0f, 1.0f);
+	// float fix[8];
+	fix[0] = 0.0f;
+	fix[1] = 0.0f;
+	fix[2] = 0.0f;
+	fix[3] = yfix;
+	fix[4] = xfix;
+	fix[5] = yfix;
+	fix[6] = xfix;
+	fix[7] = 0.0f;
 
-		// Top right
-		pglTexCoord2f(xfix, yfix);
-		pglVertex3f(1.0f, 1.0f, 1.0f);
+	pglClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
 
-		// Bottom right
-		pglTexCoord2f(xfix, 0.0f);
-		pglVertex3f(1.0f, -1.0f, 1.0f);
+	pglBindTexture(GL_TEXTURE_2D, screentexture);
+	pglColor4ubv(white);
 
-	pglEnd();
+	pglTexCoordPointer(2, GL_FLOAT, 0, fix);
+	pglVertexPointer(3, GL_FLOAT, 0, screenVerts);
+	pglDrawArrays(GL_TRIANGLE_FAN, 0, 4);
 
 	tex_downloaded = screentexture;
 }
 
 // Do screen fades!
-EXPORT void HWRAPI(DoScreenWipe)(float alpha)
+EXPORT void HWRAPI(DoScreenWipe)(void)
 {
 	INT32 texsize = 2048;
 	float xfix, yfix;
 
 	INT32 fademaskdownloaded = tex_downloaded; // the fade mask that has been set
 
+	const float screenVerts[12] =
+	{
+		-1.0f, -1.0f, 1.0f,
+		-1.0f, 1.0f, 1.0f,
+		1.0f, 1.0f, 1.0f,
+		1.0f, -1.0f, 1.0f
+	};
+
+	float fix[8];
+
+	const float defaultST[8] =
+	{
+		0.0f, 1.0f,
+		0.0f, 0.0f,
+		1.0f, 0.0f,
+		1.0f, 1.0f
+	};
+
 	// Use a power of two texture, dammit
 	if(screen_width <= 1024)
 		texsize = 1024;
@@ -2200,101 +2515,60 @@ EXPORT void HWRAPI(DoScreenWipe)(float alpha)
 	xfix = 1/((float)(texsize)/((float)((screen_width))));
 	yfix = 1/((float)(texsize)/((float)((screen_height))));
 
+	// const float screenVerts[12]
+
+	// float fix[8];
+	fix[0] = 0.0f;
+	fix[1] = 0.0f;
+	fix[2] = 0.0f;
+	fix[3] = yfix;
+	fix[4] = xfix;
+	fix[5] = yfix;
+	fix[6] = xfix;
+	fix[7] = 0.0f;
+
 	pglClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
 
 	SetBlend(PF_Modulated|PF_NoDepthTest|PF_Clip|PF_NoZClip);
+	pglEnable(GL_TEXTURE_2D);
 
 	// Draw the original screen
 	pglBindTexture(GL_TEXTURE_2D, startScreenWipe);
-	pglBegin(GL_QUADS);
-		pglColor4f(1.0f, 1.0f, 1.0f, 1.0f);
+	pglColor4ubv(white);
+	pglTexCoordPointer(2, GL_FLOAT, 0, fix);
+	pglVertexPointer(3, GL_FLOAT, 0, screenVerts);
+	pglDrawArrays(GL_TRIANGLE_FAN, 0, 4);
 
-		// Bottom left
-		pglTexCoord2f(0.0f, 0.0f);
-		pglVertex3f(-1.0f, -1.0f, 1.0f);
+	SetBlend(PF_Modulated|PF_Translucent|PF_NoDepthTest|PF_Clip|PF_NoZClip);
 
-		// Top left
-		pglTexCoord2f(0.0f, yfix);
-		pglVertex3f(-1.0f, 1.0f, 1.0f);
+	// Draw the end screen that fades in
+	pglActiveTexture(GL_TEXTURE0);
+	pglEnable(GL_TEXTURE_2D);
+	pglBindTexture(GL_TEXTURE_2D, endScreenWipe);
+	pglTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
 
-		// Top right
-		pglTexCoord2f(xfix, yfix);
-		pglVertex3f(1.0f, 1.0f, 1.0f);
+	pglActiveTexture(GL_TEXTURE1);
+	pglEnable(GL_TEXTURE_2D);
+	pglBindTexture(GL_TEXTURE_2D, fademaskdownloaded);
 
-		// Bottom right
-		pglTexCoord2f(xfix, 0.0f);
-		pglVertex3f(1.0f, -1.0f, 1.0f);
+	pglTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
 
-	pglEnd();
+	// const float defaultST[8]
 
-	SetBlend(PF_Modulated|PF_Translucent|PF_NoDepthTest|PF_Clip|PF_NoZClip);
+	pglClientActiveTexture(GL_TEXTURE0);
+	pglTexCoordPointer(2, GL_FLOAT, 0, fix);
+	pglVertexPointer(3, GL_FLOAT, 0, screenVerts);
+	pglClientActiveTexture(GL_TEXTURE1);
+	pglEnableClientState(GL_TEXTURE_COORD_ARRAY);
+	pglTexCoordPointer(2, GL_FLOAT, 0, defaultST);
+	pglDrawArrays(GL_TRIANGLE_FAN, 0, 4);
 
-	if (gl13)
-	{
-		// Draw the end screen that fades in
-		pglActiveTexture(GL_TEXTURE0);
-		pglEnable(GL_TEXTURE_2D);
-		pglBindTexture(GL_TEXTURE_2D, endScreenWipe);
-		pglTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
-
-		pglActiveTexture(GL_TEXTURE1);
-		pglEnable(GL_TEXTURE_2D);
-		pglBindTexture(GL_TEXTURE_2D, fademaskdownloaded);
-
-		pglTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
-		pglBegin(GL_QUADS);
-			pglColor4f(1.0f, 1.0f, 1.0f, 1.0f);
-
-			// Bottom left
-			pglMultiTexCoord2f(GL_TEXTURE0, 0.0f, 0.0f);
-			pglMultiTexCoord2f(GL_TEXTURE1, 0.0f, 1.0f);
-			pglVertex3f(-1.0f, -1.0f, 1.0f);
-
-			// Top left
-			pglMultiTexCoord2f(GL_TEXTURE0, 0.0f, yfix);
-			pglMultiTexCoord2f(GL_TEXTURE1, 0.0f, 0.0f);
-			pglVertex3f(-1.0f, 1.0f, 1.0f);
-
-			// Top right
-			pglMultiTexCoord2f(GL_TEXTURE0, xfix, yfix);
-			pglMultiTexCoord2f(GL_TEXTURE1, 1.0f, 0.0f);
-			pglVertex3f(1.0f, 1.0f, 1.0f);
-
-			// Bottom right
-			pglMultiTexCoord2f(GL_TEXTURE0, xfix, 0.0f);
-			pglMultiTexCoord2f(GL_TEXTURE1, 1.0f, 1.0f);
-			pglVertex3f(1.0f, -1.0f, 1.0f);
-		pglEnd();
-
-		pglDisable(GL_TEXTURE_2D); // disable the texture in the 2nd texture unit
-		pglActiveTexture(GL_TEXTURE0);
-		tex_downloaded = endScreenWipe;
-	}
-	else
-	{
-		// Draw the end screen that fades in
-		pglBindTexture(GL_TEXTURE_2D, endScreenWipe);
-		pglBegin(GL_QUADS);
-			pglColor4f(1.0f, 1.0f, 1.0f, alpha);
-
-			// Bottom left
-			pglTexCoord2f(0.0f, 0.0f);
-			pglVertex3f(-1.0f, -1.0f, 1.0f);
-
-			// Top left
-			pglTexCoord2f(0.0f, yfix);
-			pglVertex3f(-1.0f, 1.0f, 1.0f);
-
-			// Top right
-			pglTexCoord2f(xfix, yfix);
-			pglVertex3f(1.0f, 1.0f, 1.0f);
-
-			// Bottom right
-			pglTexCoord2f(xfix, 0.0f);
-			pglVertex3f(1.0f, -1.0f, 1.0f);
-		pglEnd();
-		tex_downloaded = endScreenWipe;
-	}
+	pglDisable(GL_TEXTURE_2D); // disable the texture in the 2nd texture unit
+	pglDisableClientState(GL_TEXTURE_COORD_ARRAY);
+
+	pglActiveTexture(GL_TEXTURE0);
+	pglClientActiveTexture(GL_TEXTURE0);
+	tex_downloaded = endScreenWipe;
 }
 
 
@@ -2357,7 +2631,6 @@ EXPORT void HWRAPI(MakeScreenFinalTexture) (void)
 		pglCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, texsize, texsize);
 
 	tex_downloaded = finalScreenTexture;
-
 }
 
 EXPORT void HWRAPI(DrawScreenFinalTexture)(int width, int height)
@@ -2368,6 +2641,9 @@ EXPORT void HWRAPI(DrawScreenFinalTexture)(int width, int height)
 	FRGBAFloat clearColour;
 	INT32 texsize = 2048;
 
+	float off[12];
+	float fix[8];
+
 	if(screen_width <= 1024)
 		texsize = 1024;
 	if(screen_width <= 512)
@@ -2389,33 +2665,43 @@ EXPORT void HWRAPI(DrawScreenFinalTexture)(int width, int height)
 		yoff = newaspect / origaspect;
 	}
 
+	// float off[12];
+	off[0] = -xoff;
+	off[1] = -yoff;
+	off[2] = 1.0f;
+	off[3] = -xoff;
+	off[4] = yoff;
+	off[5] = 1.0f;
+	off[6] = xoff;
+	off[7] = yoff;
+	off[8] = 1.0f;
+	off[9] = xoff;
+	off[10] = -yoff;
+	off[11] = 1.0f;
+
+	// float fix[8];
+	fix[0] = 0.0f;
+	fix[1] = 0.0f;
+	fix[2] = 0.0f;
+	fix[3] = yfix;
+	fix[4] = xfix;
+	fix[5] = yfix;
+	fix[6] = xfix;
+	fix[7] = 0.0f;
+
 	pglViewport(0, 0, width, height);
 
 	clearColour.red = clearColour.green = clearColour.blue = 0;
 	clearColour.alpha = 1;
 	ClearBuffer(true, false, &clearColour);
 	pglBindTexture(GL_TEXTURE_2D, finalScreenTexture);
-	pglBegin(GL_QUADS);
-
-		pglColor4f(1.0f, 1.0f, 1.0f, 1.0f);
-		// Bottom left
-		pglTexCoord2f(0.0f, 0.0f);
-		pglVertex3f(-xoff, -yoff, 1.0f);
-
-		// Top left
-		pglTexCoord2f(0.0f, yfix);
-		pglVertex3f(-xoff, yoff, 1.0f);
-
-		// Top right
-		pglTexCoord2f(xfix, yfix);
-		pglVertex3f(xoff, yoff, 1.0f);
 
-		// Bottom right
-		pglTexCoord2f(xfix, 0.0f);
-		pglVertex3f(xoff, -yoff, 1.0f);
+	pglColor4ubv(white);
 
-	pglEnd();
+	pglTexCoordPointer(2, GL_FLOAT, 0, fix);
+	pglVertexPointer(3, GL_FLOAT, 0, off);
 
+	pglDrawArrays(GL_TRIANGLE_FAN, 0, 4);
 	tex_downloaded = finalScreenTexture;
 }
 
diff --git a/src/hardware/r_opengl/r_opengl.h b/src/hardware/r_opengl/r_opengl.h
index e6cf164bb088b72ae778bc40fa02814e7f96fcd9..1bb97c37c31aec5dcde5524b907bea8c5576f639 100644
--- a/src/hardware/r_opengl/r_opengl.h
+++ b/src/hardware/r_opengl/r_opengl.h
@@ -71,7 +71,6 @@ extern FILE             *gllogstream;
 #endif
 
 #ifndef DRIVER_STRING
-//    #define USE_PALETTED_TEXTURE
 #define DRIVER_STRING "HWRAPI Init(): SRB2 OpenGL renderer" // Tails
 #endif
 
@@ -89,10 +88,6 @@ int SetupPixelFormat(INT32 WantColorBits, INT32 WantStencilBits, INT32 WantDepth
 void SetModelView(GLint w, GLint h);
 void SetStates(void);
 FUNCMATH float byteasfloat(UINT8 fbyte);
-#ifdef USE_PALETTED_TEXTURE
-extern PFNGLCOLORTABLEEXTPROC glColorTableEXT;
-extern GLubyte                palette_tex[256*3];
-#endif
 
 #ifndef GL_EXT_texture_filter_anisotropic
 #define GL_TEXTURE_MAX_ANISOTROPY_EXT     0x84FE
@@ -118,6 +113,10 @@ typedef void (APIENTRY * PFNglGetIntegerv) (GLenum pname, GLint *params);
 extern PFNglGetIntegerv pglGetIntegerv;
 typedef const GLubyte* (APIENTRY  * PFNglGetString) (GLenum name);
 extern PFNglGetString pglGetString;
+#if 0
+typedef void (APIENTRY * PFNglEnableClientState) (GLenum cap); // redefined in r_opengl.c
+static PFNglEnableClientState pglEnableClientState;
+#endif
 #endif
 
 // ==========================================================================
diff --git a/src/hardware/r_opengl/r_vbo.h b/src/hardware/r_opengl/r_vbo.h
new file mode 100644
index 0000000000000000000000000000000000000000..ca1a974dabbbe6cc296e0f237d2f7dacd1d2328b
--- /dev/null
+++ b/src/hardware/r_opengl/r_vbo.h
@@ -0,0 +1,52 @@
+/*
+	From the 'Wizard2' engine by Spaddlewit Inc. ( http://www.spaddlewit.com )
+	An experimental work-in-progress.
+
+	Donated to Sonic Team Junior and adapted to work with
+	Sonic Robo Blast 2. The license of this code matches whatever
+	the licensing is for Sonic Robo Blast 2.
+*/
+#ifndef _R_VBO_H_
+#define _R_VBO_H_
+
+typedef struct
+{
+	float x, y, z;		// Vertex
+	float nx, ny, nz;	// Normal
+	float s0, t0;		// Texcoord0
+} vbo32_t;
+
+typedef struct
+{
+	float x, y, z;	// Vertex
+	float s0, t0;	// Texcoord0
+	unsigned char r, g, b, a; // Color
+	float pad[2]; // Pad
+} vbo2d32_t;
+
+typedef struct
+{
+	float x, y; // Vertex
+	float s0, t0; // Texcoord0
+} vbofont_t;
+
+typedef struct
+{
+	short x, y, z; // Vertex
+	char nx, ny, nz; // Normal
+	char tanx, tany, tanz; // Tangent
+	float s0, t0; // Texcoord0
+} vbotiny_t;
+
+typedef struct
+{
+	float x, y, z;      // Vertex
+	float nx, ny, nz;   // Normal
+	float s0, t0;       // Texcoord0
+	float s1, t1;       // Texcoord1
+	float s2, t2;       // Texcoord2
+	float tan0, tan1, tan2; // Tangent
+	unsigned char r, g, b, a;	// Color
+} vbo64_t;
+
+#endif
diff --git a/src/hardware/u_list.c b/src/hardware/u_list.c
new file mode 100644
index 0000000000000000000000000000000000000000..dc49a74e7f7ab1a0502fbf5fb5f13cbbad888275
--- /dev/null
+++ b/src/hardware/u_list.c
@@ -0,0 +1,230 @@
+/*
+	From the 'Wizard2' engine by Spaddlewit Inc. ( http://www.spaddlewit.com )
+	An experimental work-in-progress.
+
+	Donated to Sonic Team Junior and adapted to work with
+	Sonic Robo Blast 2. The license of this code matches whatever
+	the licensing is for Sonic Robo Blast 2.
+*/
+
+#include "u_list.h"
+#include "../z_zone.h"
+
+// Utility for managing
+// structures in a linked
+// list.
+//
+// Struct must have "next" and "prev" pointers
+// as its first two variables.
+//
+
+//
+// ListAdd
+//
+// Adds an item to the list
+//
+void ListAdd(void *pItem, listitem_t **itemHead)
+{
+	listitem_t *item = (listitem_t*)pItem;
+
+	if (*itemHead == NULL)
+	{
+		*itemHead = item;
+		(*itemHead)->prev = (*itemHead)->next = NULL;
+	}
+	else
+	{
+		listitem_t *tail;
+		tail = *itemHead;
+
+		while (tail->next != NULL)
+			tail = tail->next;
+
+		tail->next = item;
+
+		tail->next->prev = tail;
+
+		item->next = NULL;
+	}
+}
+
+//
+// ListAddFront
+//
+// Adds an item to the front of the list
+// (This is much faster)
+//
+void ListAddFront(void *pItem, listitem_t **itemHead)
+{
+	listitem_t *item = (listitem_t*)pItem;
+
+	if (*itemHead == NULL)
+	{
+		*itemHead = item;
+		(*itemHead)->prev = (*itemHead)->next = NULL;
+	}
+	else
+	{
+		(*itemHead)->prev = item;
+		item->next = (*itemHead);
+		item->prev = NULL;
+		*itemHead = item;
+	}
+}
+
+//
+// ListAddBefore
+//
+// Adds an item before the item specified in the list
+//
+void ListAddBefore(void *pItem, void *pSpot, listitem_t **itemHead)
+{
+	listitem_t *item = (listitem_t*)pItem;
+	listitem_t *spot = (listitem_t*)pSpot;
+
+	listitem_t *prev = spot->prev;
+
+	if (!prev)
+		ListAddFront(pItem, itemHead);
+	else
+	{
+		item->next = spot;
+		spot->prev = item;
+		item->prev = prev;
+		prev->next = item;
+	}
+}
+
+//
+// ListAddAfter
+//
+// Adds an item after the item specified in the list
+//
+void ListAddAfter(void *pItem, void *pSpot, listitem_t **itemHead)
+{
+	listitem_t *item = (listitem_t*)pItem;
+	listitem_t *spot = (listitem_t*)pSpot;
+
+	listitem_t *next = spot->next;
+
+	if (!next)
+		ListAdd(pItem, itemHead);
+	else
+	{
+		item->prev = spot;
+		spot->next = item;
+		item->next = next;
+		next->prev = item;
+	}
+}
+
+//
+// ListRemove
+//
+// Take an item out of the list and free its memory.
+//
+void ListRemove(void *pItem, listitem_t **itemHead)
+{
+	listitem_t *item = (listitem_t*)pItem;
+
+	if (item == *itemHead) // Start of list
+	{
+		*itemHead = item->next;
+
+		if (*itemHead)
+			(*itemHead)->prev = NULL;
+	}
+	else if (item->next == NULL) // end of list
+	{
+		item->prev->next = NULL;
+	}
+	else // Somewhere in between
+	{
+		item->prev->next = item->next;
+		item->next->prev = item->prev;
+	}
+
+	Z_Free (item);
+}
+
+//
+// ListRemoveAll
+//
+// Removes all items from the list, freeing their memory.
+//
+void ListRemoveAll(listitem_t **itemHead)
+{
+	listitem_t *item;
+	listitem_t *next;
+	for (item = *itemHead; item; item = next)
+	{
+		next = item->next;
+		ListRemove(item, itemHead);
+	}
+}
+
+//
+// ListRemoveNoFree
+//
+// Take an item out of the list, but don't free its memory.
+//
+void ListRemoveNoFree(void *pItem, listitem_t **itemHead)
+{
+	listitem_t *item = (listitem_t*)pItem;
+
+	if (item == *itemHead) // Start of list
+	{
+		*itemHead = item->next;
+
+		if (*itemHead)
+			(*itemHead)->prev = NULL;
+	}
+	else if (item->next == NULL) // end of list
+	{
+		item->prev->next = NULL;
+	}
+	else // Somewhere in between
+	{
+		item->prev->next = item->next;
+		item->next->prev = item->prev;
+	}
+}
+
+//
+// ListGetCount
+//
+// Counts the # of items in a list
+// Should not be used in performance-minded code
+//
+unsigned int ListGetCount(void *itemHead)
+{
+	listitem_t *item = (listitem_t*)itemHead;
+
+	unsigned int count = 0;
+	for (; item; item = item->next)
+		count++;
+
+	return count;
+}
+
+//
+// ListGetByIndex
+//
+// Gets an item in the list by its index
+// Should not be used in performance-minded code
+//
+listitem_t *ListGetByIndex(void *itemHead, unsigned int index)
+{
+	listitem_t *head = (listitem_t*)itemHead;
+	unsigned int count = 0;
+	listitem_t *node;
+	for (node = head; node; node = node->next)
+	{
+		if (count == index)
+			return node;
+
+		count++;
+	}
+
+	return NULL;
+}
diff --git a/src/hardware/u_list.h b/src/hardware/u_list.h
new file mode 100644
index 0000000000000000000000000000000000000000..7e9a3cabd3bb9e5a9ded2c2d9f1d0f34b074ec3f
--- /dev/null
+++ b/src/hardware/u_list.h
@@ -0,0 +1,29 @@
+/*
+	From the 'Wizard2' engine by Spaddlewit Inc. ( http://www.spaddlewit.com )
+	An experimental work-in-progress.
+
+	Donated to Sonic Team Junior and adapted to work with
+	Sonic Robo Blast 2. The license of this code matches whatever
+	the licensing is for Sonic Robo Blast 2.
+*/
+
+#ifndef _U_LIST_H_
+#define _U_LIST_H_
+
+typedef struct listitem_s
+{
+	struct listitem_s *next;
+	struct listitem_s *prev;
+} listitem_t;
+
+void ListAdd(void *pItem, listitem_t **itemHead);
+void ListAddFront(void *pItem, listitem_t **itemHead);
+void ListAddBefore(void *pItem, void *pSpot, listitem_t **itemHead);
+void ListAddAfter(void *pItem, void *pSpot, listitem_t **itemHead);
+void ListRemove(void *pItem, listitem_t **itemHead);
+void ListRemoveAll(listitem_t **itemHead);
+void ListRemoveNoFree(void *pItem, listitem_t **itemHead);
+unsigned int ListGetCount(void *itemHead);
+listitem_t *ListGetByIndex(void *itemHead, unsigned int index);
+
+#endif
diff --git a/src/info.c b/src/info.c
index 1064eaf42ae5e9cd8dbbb87b1c5b1118806fece0..640957a7950a61154778b6657451ec3147b700b4 100644
--- a/src/info.c
+++ b/src/info.c
@@ -808,7 +808,7 @@ state_t states[NUMSTATES] =
 	{SPR_PLAY, SPR2_LIFE, 20, {NULL}, 0,  4, S_NULL},       // S_PLAY_ICON3
 
 	// Level end sign (uses player sprite)
-	{SPR_PLAY, SPR2_SIGN, 1, {NULL}, 0, 24, S_PLAY_SIGN},         // S_PLAY_SIGN
+	{SPR_PLAY, SPR2_SIGN|FF_PAPERSPRITE, -1, {NULL}, 0, 29, S_PLAY_SIGN},         // S_PLAY_SIGN
 
 	// NiGHTS Player, transforming
 	{SPR_PLAY, SPR2_TRNS|FF_ANIMATE,     7, {NULL},          0, 4, S_PLAY_NIGHTS_TRANS2}, // S_PLAY_NIGHTS_TRANS1
@@ -1911,59 +1911,18 @@ state_t states[NUMSTATES] =
 	{SPR_BBLS, 3, 8, {A_BubbleCheck}, 0, 0, S_BUBBLES1}, // S_BUBBLES4
 
 	// Level End Sign
-	{SPR_SIGN, 0, 1, {NULL}, 0, 0, S_SIGN2},         // S_SIGN1
-	{SPR_SIGN, 1, 1, {NULL}, 0, 0, S_SIGN3},         // S_SIGN2
-	{SPR_SIGN, 2, 1, {NULL}, 0, 0, S_SIGN4},         // S_SIGN3
-	{SPR_SIGN, 5, 1, {NULL}, 0, 0, S_SIGN5},         // S_SIGN4
-	{SPR_SIGN, 0, 1, {NULL}, 0, 0, S_SIGN6},         // S_SIGN5
-	{SPR_SIGN, 1, 1, {NULL}, 0, 0, S_SIGN7},         // S_SIGN6
-	{SPR_SIGN, 2, 1, {NULL}, 0, 0, S_SIGN8},         // S_SIGN7
-	{SPR_SIGN, 3, 1, {NULL}, 0, 0, S_SIGN9},         // S_SIGN8
-	{SPR_SIGN, 0, 1, {NULL}, 0, 0, S_SIGN10},        // S_SIGN9
-	{SPR_SIGN, 1, 1, {NULL}, 0, 0, S_SIGN11},        // S_SIGN10
-	{SPR_SIGN, 2, 1, {NULL}, 0, 0, S_SIGN12},        // S_SIGN11
-	{SPR_SIGN, 4, 1, {NULL}, 0, 0, S_SIGN13},        // S_SIGN12
-	{SPR_SIGN, 0, 1, {NULL}, 0, 0, S_SIGN14},        // S_SIGN13
-	{SPR_SIGN, 1, 1, {NULL}, 0, 0, S_SIGN15},        // S_SIGN14
-	{SPR_SIGN, 2, 1, {NULL}, 0, 0, S_SIGN16},        // S_SIGN15
-	{SPR_SIGN, 3, 1, {NULL}, 0, 0, S_SIGN17},        // S_SIGN16
-	{SPR_SIGN, 0, 1, {NULL}, 0, 0, S_SIGN18},        // S_SIGN17
-	{SPR_SIGN, 1, 1, {NULL}, 0, 0, S_SIGN19},        // S_SIGN18
-	{SPR_SIGN, 2, 1, {NULL}, 0, 0, S_SIGN20},        // S_SIGN19
-	{SPR_SIGN, 6, 1, {NULL}, 0, 0, S_SIGN21},        // S_SIGN20
-	{SPR_SIGN, 0, 1, {NULL}, 0, 0, S_SIGN22},        // S_SIGN21
-	{SPR_SIGN, 1, 1, {NULL}, 0, 0, S_SIGN23},        // S_SIGN22
-	{SPR_SIGN, 2, 1, {NULL}, 0, 0, S_SIGN24},        // S_SIGN23
-	{SPR_SIGN, 3, 1, {NULL}, 0, 0, S_SIGN25},        // S_SIGN24
-	{SPR_SIGN, 0, 1, {NULL}, 0, 0, S_SIGN26},        // S_SIGN25
-	{SPR_SIGN, 1, 1, {NULL}, 0, 0, S_SIGN27},        // S_SIGN26
-	{SPR_SIGN, 2, 1, {NULL}, 0, 0, S_SIGN28},        // S_SIGN27
-	{SPR_SIGN, 5, 1, {NULL}, 0, 0, S_SIGN29},        // S_SIGN28
-	{SPR_SIGN, 0, 1, {NULL}, 0, 0, S_SIGN30},        // S_SIGN29
-	{SPR_SIGN, 1, 1, {NULL}, 0, 0, S_SIGN31},        // S_SIGN30
-	{SPR_SIGN, 2, 1, {NULL}, 0, 0, S_SIGN32},        // S_SIGN31
-	{SPR_SIGN, 3, 1, {NULL}, 0, 0, S_SIGN33},        // S_SIGN32
-	{SPR_SIGN, 0, 1, {NULL}, 0, 0, S_SIGN34},        // S_SIGN33
-	{SPR_SIGN, 1, 1, {NULL}, 0, 0, S_SIGN35},        // S_SIGN34
-	{SPR_SIGN, 2, 1, {NULL}, 0, 0, S_SIGN36},        // S_SIGN35
-	{SPR_SIGN, 4, 1, {NULL}, 0, 0, S_SIGN37},        // S_SIGN36
-	{SPR_SIGN, 0, 1, {NULL}, 0, 0, S_SIGN38},        // S_SIGN37
-	{SPR_SIGN, 1, 1, {NULL}, 0, 0, S_SIGN39},        // S_SIGN38
-	{SPR_SIGN, 2, 1, {NULL}, 0, 0, S_SIGN40},        // S_SIGN39
-	{SPR_SIGN, 3, 1, {NULL}, 0, 0, S_SIGN41},        // S_SIGN40
-	{SPR_SIGN, 0, 1, {NULL}, 0, 0, S_SIGN42},        // S_SIGN41
-	{SPR_SIGN, 1, 1, {NULL}, 0, 0, S_SIGN43},        // S_SIGN42
-	{SPR_SIGN, 2, 1, {NULL}, 0, 0, S_SIGN44},        // S_SIGN43
-	{SPR_SIGN, 6, 1, {NULL}, 0, 0, S_SIGN45},        // S_SIGN44
-	{SPR_SIGN, 0, 1, {NULL}, 0, 0, S_SIGN46},        // S_SIGN45
-	{SPR_SIGN, 1, 1, {NULL}, 0, 0, S_SIGN47},        // S_SIGN46
-	{SPR_SIGN, 2, 1, {NULL}, 0, 0, S_SIGN48},        // S_SIGN47
-	{SPR_SIGN, 3, 1, {NULL}, 0, 0, S_SIGN49},        // S_SIGN48
-	{SPR_SIGN, 0, 1, {NULL}, 0, 0, S_SIGN50},        // S_SIGN49
-	{SPR_SIGN, 1, 1, {NULL}, 0, 0, S_SIGN51},        // S_SIGN50
-	{SPR_SIGN, 2, 1, {NULL}, 0, 0, S_SIGN53},        // S_SIGN51
-	{SPR_SIGN, 3, -1, {NULL}, 0, 0, S_NULL},         // S_SIGN52 Eggman
-	{SPR_SIGN, 7, -1, {A_SignPlayer}, 0, 0, S_NULL}, // S_SIGN53 Blank
+	{SPR_SIGN,                0, -1, {A_SignPlayer}, -3, 0, S_NULL},                    // S_SIGN
+	{SPR_SIGN,                0,  1,   {A_SignSpin}, 30, 0, S_SIGNSPIN2},            // S_SIGNSPIN1
+	{SPR_SIGN,                0,  0,     {A_Repeat},  4, S_SIGNSPIN1, S_SIGNSPIN3},   // S_SIGNSPIN2
+	{SPR_SIGN,                0,  0, {A_SignPlayer}, -2, 0, S_SIGNSPIN4},                // S_SIGNSPIN3
+	{SPR_SIGN,                0,  1,   {A_SignSpin}, 30, 0, S_SIGNSPIN5},            // S_SIGNSPIN4
+	{SPR_SIGN,                0,  0,     {A_Repeat},  4, S_SIGNSPIN4, S_SIGNSPIN6},   // S_SIGNSPIN5
+	{SPR_SIGN,                0,  0, {A_SignPlayer}, -3, 0, S_SIGNSPIN1},                // S_SIGNSPIN6
+	{SPR_SIGN,                0,  1, {A_SignPlayer}, -1, 0, S_SIGNSLOW},                // S_SIGNPLAYER
+	{SPR_SIGN,                0,  1,   {A_SignSpin}, 30, 0, S_SIGNSLOW},                // S_SIGNSLOW
+	{SPR_SIGN,                0, -1,         {NULL},  0, 0, S_NULL},                    // S_SIGNSTOP
+	{SPR_SIGN, FF_PAPERSPRITE|2, -1,         {NULL},  0, 0, S_NULL},                    // S_SIGNBOARD
+	{SPR_SIGN, FF_PAPERSPRITE|1, -1,         {NULL},  0, 29, S_NULL},                   // S_EGGMANSIGN
 
 	// Spike Ball
 	{SPR_SPIK, 0, 1, {NULL}, 0, 0, S_SPIKEBALL2}, // S_SPIKEBALL1
@@ -5280,7 +5239,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOGRAVITY|MF_NOCLIPTHING|MF_NOBLOCKMAP|MF_RUNSPAWNFUNC, // flags
+		MF_NOGRAVITY|MF_NOCLIPTHING|MF_NOBLOCKMAP|MF_RUNSPAWNFUNC|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
@@ -5307,7 +5266,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOGRAVITY|MF_NOCLIPTHING|MF_NOBLOCKMAP|MF_RUNSPAWNFUNC, // flags
+		MF_NOGRAVITY|MF_NOCLIPTHING|MF_NOBLOCKMAP|MF_RUNSPAWNFUNC|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
@@ -7818,29 +7777,29 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 
 	{           // MT_SIGN
 		501,            // doomednum
-		S_SIGN52,       // spawnstate
+		S_SIGN,         // spawnstate
 		1000,           // spawnhealth
 		S_PLAY_SIGN,    // seestate
 		sfx_lvpass,     // seesound
 		8,              // reactiontime
 		sfx_None,       // attacksound
-		S_NULL,         // painstate
-		0,              // painchance
+		S_SIGNPLAYER,   // painstate
+		MT_SPARK,       // painchance
 		sfx_None,       // painsound
-		S_NULL,         // meleestate
+		S_EGGMANSIGN,   // meleestate
 		S_NULL,         // missilestate
-		S_NULL,         // deathstate
+		S_SIGNSTOP,     // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
 		8,              // speed
-		8*FRACUNIT,     // radius
+		36*FRACUNIT,    // radius
 		32*FRACUNIT,    // height
 		0,              // display offset
 		16,             // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOCLIP|MF_SCENERY, // flags
-		S_NULL          // raisestate
+		MF_NOCLIP|MF_SCENERY|MF_BOUNCE|MF_RUNSPAWNFUNC, // flags
+		S_SIGNBOARD     // raisestate
 	},
 
 	{           // MT_SPIKEBALL
@@ -13286,7 +13245,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
 		sfx_None,       // deathsound
-		0,              // speed
+		3200*FRACUNIT,  // speed
 		30*FRACUNIT,    // radius
 		32*FRACUNIT,    // height
 		1,              // display offset
@@ -13374,7 +13333,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIP|MF_SPAWNCEILING,  // flags
+		MF_NOBLOCKMAP|MF_NOGRAVITY|MF_NOCLIP|MF_SPAWNCEILING|MF_SCENERY,  // flags
 		S_NULL          // raisestate
 	},
 
@@ -19024,7 +18983,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // deathstate
 		S_PUMA_DOWN3,   // xdeathstate
 		sfx_None,       // deathsound
-		0,              // speed
+		2000*FRACUNIT,  // speed
 		8*FRACUNIT,     // radius
 		16*FRACUNIT,    // height
 		0,              // display offset
@@ -19058,7 +19017,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		100,            // mass
 		0,              // damage
 		sfx_None,       // activesound
-		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY, // flags
+		MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY|MF_SCENERY, // flags
 		S_NULL          // raisestate
 	},
 
diff --git a/src/info.h b/src/info.h
index b5da1cd4989599fd8d35a66b0cf3ef299594892b..087577b821e5fc6a829ec24abe7b001c25db1ae9 100644
--- a/src/info.h
+++ b/src/info.h
@@ -63,6 +63,7 @@ void A_FishJump(); // Fish Jump
 void A_ThrownRing(); // Sparkle trail for red ring
 void A_SetSolidSteam();
 void A_UnsetSolidSteam();
+void A_SignSpin();
 void A_SignPlayer();
 void A_OverlayThink();
 void A_JetChase();
@@ -2040,59 +2041,18 @@ typedef enum state
 	S_BUBBLES4,
 
 	// Level End Sign
-	S_SIGN1,
-	S_SIGN2,
-	S_SIGN3,
-	S_SIGN4,
-	S_SIGN5,
-	S_SIGN6,
-	S_SIGN7,
-	S_SIGN8,
-	S_SIGN9,
-	S_SIGN10,
-	S_SIGN11,
-	S_SIGN12,
-	S_SIGN13,
-	S_SIGN14,
-	S_SIGN15,
-	S_SIGN16,
-	S_SIGN17,
-	S_SIGN18,
-	S_SIGN19,
-	S_SIGN20,
-	S_SIGN21,
-	S_SIGN22,
-	S_SIGN23,
-	S_SIGN24,
-	S_SIGN25,
-	S_SIGN26,
-	S_SIGN27,
-	S_SIGN28,
-	S_SIGN29,
-	S_SIGN30,
-	S_SIGN31,
-	S_SIGN32,
-	S_SIGN33,
-	S_SIGN34,
-	S_SIGN35,
-	S_SIGN36,
-	S_SIGN37,
-	S_SIGN38,
-	S_SIGN39,
-	S_SIGN40,
-	S_SIGN41,
-	S_SIGN42,
-	S_SIGN43,
-	S_SIGN44,
-	S_SIGN45,
-	S_SIGN46,
-	S_SIGN47,
-	S_SIGN48,
-	S_SIGN49,
-	S_SIGN50,
-	S_SIGN51,
-	S_SIGN52, // Eggman
-	S_SIGN53,
+	S_SIGN,
+	S_SIGNSPIN1,
+	S_SIGNSPIN2,
+	S_SIGNSPIN3,
+	S_SIGNSPIN4,
+	S_SIGNSPIN5,
+	S_SIGNSPIN6,
+	S_SIGNPLAYER,
+	S_SIGNSLOW,
+	S_SIGNSTOP,
+	S_SIGNBOARD,
+	S_EGGMANSIGN,
 
 	// Spike Ball
 	S_SPIKEBALL1,
diff --git a/src/m_fixed.c b/src/m_fixed.c
index d45bb70bf572dabacef5f9874dfe84d3b9bdef32..5e78967396a4b9802dc29e301a14112f03b3168a 100644
--- a/src/m_fixed.c
+++ b/src/m_fixed.c
@@ -56,7 +56,7 @@ fixed_t FixedDiv2(fixed_t a, fixed_t b)
 	if (b == 0)
 		I_Error("FixedDiv: divide by zero");
 
-	ret = (((INT64)a * FRACUNIT) ) / b;
+	ret = (((INT64)a * FRACUNIT)) / b;
 
 	if ((ret > INT32_MAX) || (ret < INT32_MIN))
 		I_Error("FixedDiv: divide by zero");
@@ -117,7 +117,7 @@ fixed_t FixedHypot(fixed_t x, fixed_t y)
 		yx = FixedDiv(y, x); // (x/y)
 	}
 	yx2 = FixedMul(yx, yx); // (x/y)^2
-	yx1 = FixedSqrt(1*FRACUNIT + yx2); // (1 + (x/y)^2)^1/2
+	yx1 = FixedSqrt(1 * FRACUNIT + yx2); // (1 + (x/y)^2)^1/2
 	return FixedMul(ax, yx1); // |x|*((1 + (x/y)^2)^1/2)
 }
 
@@ -191,8 +191,8 @@ vector2_t *FV2_Divide(vector2_t *a_i, fixed_t a_c)
 // Vector Complex Math
 vector2_t *FV2_Midpoint(const vector2_t *a_1, const vector2_t *a_2, vector2_t *a_o)
 {
-	a_o->x = FixedDiv(a_2->x - a_1->x, 2*FRACUNIT);
-	a_o->y = FixedDiv(a_2->y - a_1->y, 2*FRACUNIT);
+	a_o->x = FixedDiv(a_2->x - a_1->x, 2 * FRACUNIT);
+	a_o->y = FixedDiv(a_2->y - a_1->y, 2 * FRACUNIT);
 	a_o->x = a_1->x + a_o->x;
 	a_o->y = a_1->y + a_o->y;
 	return a_o;
@@ -200,16 +200,16 @@ vector2_t *FV2_Midpoint(const vector2_t *a_1, const vector2_t *a_2, vector2_t *a
 
 fixed_t FV2_Distance(const vector2_t *p1, const vector2_t *p2)
 {
-	fixed_t xs = FixedMul(p2->x-p1->x,p2->x-p1->x);
-	fixed_t ys = FixedMul(p2->y-p1->y,p2->y-p1->y);
-	return FixedSqrt(xs+ys);
+	fixed_t xs = FixedMul(p2->x - p1->x, p2->x - p1->x);
+	fixed_t ys = FixedMul(p2->y - p1->y, p2->y - p1->y);
+	return FixedSqrt(xs + ys);
 }
 
 fixed_t FV2_Magnitude(const vector2_t *a_normal)
 {
-	fixed_t xs = FixedMul(a_normal->x,a_normal->x);
-	fixed_t ys = FixedMul(a_normal->y,a_normal->y);
-	return FixedSqrt(xs+ys);
+	fixed_t xs = FixedMul(a_normal->x, a_normal->x);
+	fixed_t ys = FixedMul(a_normal->y, a_normal->y);
+	return FixedSqrt(xs + ys);
 }
 
 // Also returns the magnitude
@@ -240,7 +240,7 @@ vector2_t *FV2_Negate(vector2_t *a_1)
 
 boolean FV2_Equal(const vector2_t *a_1, const vector2_t *a_2)
 {
-	fixed_t Epsilon = FRACUNIT/FRACUNIT;
+	fixed_t Epsilon = FRACUNIT / FRACUNIT;
 
 	if ((abs(a_2->x - a_1->x) > Epsilon) ||
 		(abs(a_2->y - a_1->y) > Epsilon))
@@ -261,7 +261,7 @@ fixed_t FV2_Dot(const vector2_t *a_1, const vector2_t *a_2)
 //
 // Given two points, create a vector between them.
 //
-vector2_t *FV2_Point2Vec (const vector2_t *point1, const vector2_t *point2, vector2_t *a_o)
+vector2_t *FV2_Point2Vec(const vector2_t *point1, const vector2_t *point2, vector2_t *a_o)
 {
 	a_o->x = point1->x - point2->x;
 	a_o->y = point1->y - point2->y;
@@ -344,9 +344,9 @@ vector3_t *FV3_Divide(vector3_t *a_i, fixed_t a_c)
 // Vector Complex Math
 vector3_t *FV3_Midpoint(const vector3_t *a_1, const vector3_t *a_2, vector3_t *a_o)
 {
-	a_o->x = FixedDiv(a_2->x - a_1->x, 2*FRACUNIT);
-	a_o->y = FixedDiv(a_2->y - a_1->y, 2*FRACUNIT);
-	a_o->z = FixedDiv(a_2->z - a_1->z, 2*FRACUNIT);
+	a_o->x = FixedDiv(a_2->x - a_1->x, 2 * FRACUNIT);
+	a_o->y = FixedDiv(a_2->y - a_1->y, 2 * FRACUNIT);
+	a_o->z = FixedDiv(a_2->z - a_1->z, 2 * FRACUNIT);
 	a_o->x = a_1->x + a_o->x;
 	a_o->y = a_1->y + a_o->y;
 	a_o->z = a_1->z + a_o->z;
@@ -355,18 +355,18 @@ vector3_t *FV3_Midpoint(const vector3_t *a_1, const vector3_t *a_2, vector3_t *a
 
 fixed_t FV3_Distance(const vector3_t *p1, const vector3_t *p2)
 {
-	fixed_t xs = FixedMul(p2->x-p1->x,p2->x-p1->x);
-	fixed_t ys = FixedMul(p2->y-p1->y,p2->y-p1->y);
-	fixed_t zs = FixedMul(p2->z-p1->z,p2->z-p1->z);
-	return FixedSqrt(xs+ys+zs);
+	fixed_t xs = FixedMul(p2->x - p1->x, p2->x - p1->x);
+	fixed_t ys = FixedMul(p2->y - p1->y, p2->y - p1->y);
+	fixed_t zs = FixedMul(p2->z - p1->z, p2->z - p1->z);
+	return FixedSqrt(xs + ys + zs);
 }
 
 fixed_t FV3_Magnitude(const vector3_t *a_normal)
 {
-	fixed_t xs = FixedMul(a_normal->x,a_normal->x);
-	fixed_t ys = FixedMul(a_normal->y,a_normal->y);
-	fixed_t zs = FixedMul(a_normal->z,a_normal->z);
-	return FixedSqrt(xs+ys+zs);
+	fixed_t xs = FixedMul(a_normal->x, a_normal->x);
+	fixed_t ys = FixedMul(a_normal->y, a_normal->y);
+	fixed_t zs = FixedMul(a_normal->z, a_normal->z);
+	return FixedSqrt(xs + ys + zs);
 }
 
 // Also returns the magnitude
@@ -399,7 +399,7 @@ vector3_t *FV3_Negate(vector3_t *a_1)
 
 boolean FV3_Equal(const vector3_t *a_1, const vector3_t *a_2)
 {
-	fixed_t Epsilon = FRACUNIT/FRACUNIT;
+	fixed_t Epsilon = FRACUNIT / FRACUNIT;
 
 	if ((abs(a_2->x - a_1->x) > Epsilon) ||
 		(abs(a_2->y - a_1->y) > Epsilon) ||
@@ -458,6 +458,20 @@ vector3_t *FV3_ClosestPointOnLine(const vector3_t *Line, const vector3_t *p, vec
 	return FV3_AddEx(&Line[0], &V, out);
 }
 
+//
+// ClosestPointOnVector
+//
+// Similar to ClosestPointOnLine, but uses a vector instead of two points.
+//
+void FV3_ClosestPointOnVector(const vector3_t *dir, const vector3_t *p, vector3_t *out)
+{
+	fixed_t t = FV3_Dot(dir, p);
+
+	// Return the point on the line closest
+	FV3_MulEx(dir, t, out);
+	return;
+}
+
 //
 // ClosestPointOnTriangle
 //
@@ -465,7 +479,7 @@ vector3_t *FV3_ClosestPointOnLine(const vector3_t *Line, const vector3_t *p, vec
 // the closest point on the edge of
 // the triangle is returned.
 //
-void FV3_ClosestPointOnTriangle (const vector3_t *tri, const vector3_t *point, vector3_t *result)
+void FV3_ClosestPointOnTriangle(const vector3_t *tri, const vector3_t *point, vector3_t *result)
 {
 	UINT8 i;
 	fixed_t dist, closestdist;
@@ -506,7 +520,7 @@ void FV3_ClosestPointOnTriangle (const vector3_t *tri, const vector3_t *point, v
 //
 // Given two points, create a vector between them.
 //
-vector3_t *FV3_Point2Vec (const vector3_t *point1, const vector3_t *point2, vector3_t *a_o)
+vector3_t *FV3_Point2Vec(const vector3_t *point1, const vector3_t *point2, vector3_t *a_o)
 {
 	a_o->x = point1->x - point2->x;
 	a_o->y = point1->y - point2->y;
@@ -519,7 +533,7 @@ vector3_t *FV3_Point2Vec (const vector3_t *point1, const vector3_t *point2, vect
 //
 // Calculates the normal of a polygon.
 //
-void FV3_Normal (const vector3_t *a_triangle, vector3_t *a_normal)
+fixed_t FV3_Normal(const vector3_t *a_triangle, vector3_t *a_normal)
 {
 	vector3_t a_1;
 	vector3_t a_2;
@@ -529,7 +543,28 @@ void FV3_Normal (const vector3_t *a_triangle, vector3_t *a_normal)
 
 	FV3_Cross(&a_1, &a_2, a_normal);
 
-	FV3_NormalizeEx(a_normal, a_normal);
+	return FV3_NormalizeEx(a_normal, a_normal);
+}
+
+//
+// Strength
+//
+// Measures the 'strength' of a vector in a particular direction.
+//
+fixed_t FV3_Strength(const vector3_t *a_1, const vector3_t *dir)
+{
+	vector3_t normal;
+	fixed_t dist = FV3_NormalizeEx(a_1, &normal);
+	fixed_t dot = FV3_Dot(&normal, dir);
+
+	FV3_ClosestPointOnVector(dir, a_1, &normal);
+
+	dist = FV3_Magnitude(&normal);
+
+	if (dot < 0) // Not facing same direction, so negate result.
+		dist = -dist;
+
+	return dist;
 }
 
 //
@@ -550,11 +585,11 @@ boolean FV3_IntersectedPlane(const vector3_t *a_triangle, const vector3_t *a_lin
 
 	*originDistance = FV3_PlaneDistance(a_normal, &a_triangle[0]);
 
-	distance1 = (FixedMul(a_normal->x, a_line[0].x)  + FixedMul(a_normal->y, a_line[0].y)
-				+ FixedMul(a_normal->z, a_line[0].z)) + *originDistance;
+	distance1 = (FixedMul(a_normal->x, a_line[0].x) + FixedMul(a_normal->y, a_line[0].y)
+		+ FixedMul(a_normal->z, a_line[0].z)) + *originDistance;
 
-	distance2 = (FixedMul(a_normal->x, a_line[1].x)  + FixedMul(a_normal->y, a_line[1].y)
-				+ FixedMul(a_normal->z, a_line[1].z)) + *originDistance;
+	distance2 = (FixedMul(a_normal->x, a_line[1].x) + FixedMul(a_normal->y, a_line[1].y)
+		+ FixedMul(a_normal->z, a_line[1].z)) + *originDistance;
 
 	// Positive or zero number means no intersection
 	if (FixedMul(distance1, distance2) >= 0)
@@ -575,8 +610,8 @@ boolean FV3_IntersectedPlane(const vector3_t *a_triangle, const vector3_t *a_lin
 fixed_t FV3_PlaneIntersection(const vector3_t *pOrigin, const vector3_t *pNormal, const vector3_t *rOrigin, const vector3_t *rVector)
 {
 	fixed_t d = -(FV3_Dot(pNormal, pOrigin));
-	fixed_t number = FV3_Dot(pNormal,rOrigin) + d;
-	fixed_t denom = FV3_Dot(pNormal,rVector);
+	fixed_t number = FV3_Dot(pNormal, rOrigin) + d;
+	fixed_t denom = FV3_Dot(pNormal, rVector);
 	return -FixedDiv(number, denom);
 }
 
@@ -597,11 +632,11 @@ fixed_t FV3_IntersectRaySphere(const vector3_t *rO, const vector3_t *rV, const v
 
 	c = FV3_Magnitude(&Q);
 	v = FV3_Dot(&Q, rV);
-	d = FixedMul(sR, sR) - (FixedMul(c,c) - FixedMul(v,v));
+	d = FixedMul(sR, sR) - (FixedMul(c, c) - FixedMul(v, v));
 
 	// If there was no intersection, return -1
-	if (d < 0*FRACUNIT)
-		return (-1*FRACUNIT);
+	if (d < 0 * FRACUNIT)
+		return (-1 * FRACUNIT);
 
 	// Return the distance to the [first] intersecting point
 	return (v - FixedSqrt(d));
@@ -629,9 +664,9 @@ vector3_t *FV3_IntersectionPoint(const vector3_t *vNormal, const vector3_t *vLin
 	//    Here I just chose a arbitrary point as the point to find that distance.  You notice we negate that
 	//    distance.  We negate the distance because we want to eventually go BACKWARDS from our point to the plane.
 	//    By doing this is will basically bring us back to the plane to find our intersection point.
-	Numerator = - (FixedMul(vNormal->x, vLine[0].x) +		// Use the plane equation with the normal and the line
-				   FixedMul(vNormal->y, vLine[0].y) +
-				   FixedMul(vNormal->z, vLine[0].z) + distance);
+	Numerator = -(FixedMul(vNormal->x, vLine[0].x) +		// Use the plane equation with the normal and the line
+		FixedMul(vNormal->y, vLine[0].y) +
+		FixedMul(vNormal->z, vLine[0].z) + distance);
 
 	// 3) If we take the dot product between our line vector and the normal of the polygon,
 	//    this will give us the cosine of the angle between the 2 (since they are both normalized - length 1).
@@ -643,7 +678,7 @@ vector3_t *FV3_IntersectionPoint(const vector3_t *vNormal, const vector3_t *vLin
 	// on the plane (the normal is perpendicular to the line - (Normal.Vector = 0)).
 	// In this case, we should just return any point on the line.
 
-	if( Denominator == 0*FRACUNIT) // Check so we don't divide by zero
+	if (Denominator == 0 * FRACUNIT) // Check so we don't divide by zero
 	{
 		ReturnVec->x = vLine[0].x;
 		ReturnVec->y = vLine[0].y;
@@ -686,8 +721,8 @@ vector3_t *FV3_IntersectionPoint(const vector3_t *vNormal, const vector3_t *vLin
 //
 UINT8 FV3_PointOnLineSide(const vector3_t *point, const vector3_t *line)
 {
-	fixed_t s1 = FixedMul((point->y - line[0].y),(line[1].x - line[0].x));
-	fixed_t s2 = FixedMul((point->x - line[0].x),(line[1].y - line[0].y));
+	fixed_t s1 = FixedMul((point->y - line[0].y), (line[1].x - line[0].x));
+	fixed_t s2 = FixedMul((point->x - line[0].x), (line[1].y - line[0].y));
 	return (UINT8)(s1 - s2 < 0);
 }
 
@@ -752,7 +787,7 @@ void FM_CreateObjectMatrix(matrix_t *matrix, fixed_t x, fixed_t y, fixed_t z, fi
 	matrix->m[0] = upcross.x;
 	matrix->m[1] = upcross.y;
 	matrix->m[2] = upcross.z;
-	matrix->m[3] = 0*FRACUNIT;
+	matrix->m[3] = 0 * FRACUNIT;
 
 	matrix->m[4] = upx;
 	matrix->m[5] = upy;
@@ -764,9 +799,9 @@ void FM_CreateObjectMatrix(matrix_t *matrix, fixed_t x, fixed_t y, fixed_t z, fi
 	matrix->m[10] = anglez;
 	matrix->m[11] = 0;
 
-	matrix->m[12] = x - FixedMul(upx,radius);
-	matrix->m[13] = y - FixedMul(upy,radius);
-	matrix->m[14] = z - FixedMul(upz,radius);
+	matrix->m[12] = x - FixedMul(upx, radius);
+	matrix->m[13] = y - FixedMul(upy, radius);
+	matrix->m[14] = z - FixedMul(upz, radius);
 	matrix->m[15] = FRACUNIT;
 }
 
@@ -778,20 +813,20 @@ void FM_CreateObjectMatrix(matrix_t *matrix, fixed_t x, fixed_t y, fixed_t z, fi
 void FM_MultMatrixVec3(const matrix_t *matrix, const vector3_t *vec, vector3_t *out)
 {
 #define M(row,col)  matrix->m[col * 4 + row]
-	out->x = FixedMul(vec->x,M(0, 0))
-	       + FixedMul(vec->y,M(0, 1))
-	       + FixedMul(vec->z,M(0, 2))
-	       + M(0, 3);
-
-	out->y = FixedMul(vec->x,M(1, 0))
-	       + FixedMul(vec->y,M(1, 1))
-	       + FixedMul(vec->z,M(1, 2))
-	       + M(1, 3);
-
-	out->z = FixedMul(vec->x,M(2, 0))
-	       + FixedMul(vec->y,M(2, 1))
-	       + FixedMul(vec->z,M(2, 2))
-	       + M(2, 3);
+	out->x = FixedMul(vec->x, M(0, 0))
+		+ FixedMul(vec->y, M(0, 1))
+		+ FixedMul(vec->z, M(0, 2))
+		+ M(0, 3);
+
+	out->y = FixedMul(vec->x, M(1, 0))
+		+ FixedMul(vec->y, M(1, 1))
+		+ FixedMul(vec->z, M(1, 2))
+		+ M(1, 3);
+
+	out->z = FixedMul(vec->x, M(2, 0))
+		+ FixedMul(vec->y, M(2, 1))
+		+ FixedMul(vec->z, M(2, 2))
+		+ M(2, 3);
 #undef M
 }
 
@@ -811,7 +846,7 @@ void FM_MultMatrix(matrix_t *dest, const matrix_t *multme)
 	for (i = 0; i < 4; i++)
 	{
 		for (j = 0; j < 4; j++)
-			R(i, j) = FixedMul(D(i, 0),M(0, j)) + FixedMul(D(i, 1),M(1, j)) + FixedMul(D(i, 2),M(2, j)) + FixedMul(D(i, 3),M(3, j));
+			R(i, j) = FixedMul(D(i, 0), M(0, j)) + FixedMul(D(i, 1), M(1, j)) + FixedMul(D(i, 2), M(2, j)) + FixedMul(D(i, 3), M(3, j));
 	}
 
 	M_Memcpy(dest, &result, sizeof(matrix_t));
@@ -869,8 +904,8 @@ void FM_Scale(matrix_t *dest, fixed_t x, fixed_t y, fixed_t z)
 
 static inline void M_print(INT64 a)
 {
-	const fixed_t w = (a>>FRACBITS);
-	fixed_t f = a%FRACUNIT;
+	const fixed_t w = (a >> FRACBITS);
+	fixed_t f = a % FRACUNIT;
 	fixed_t d = FRACUNIT;
 
 	if (f == 0)
@@ -878,7 +913,7 @@ static inline void M_print(INT64 a)
 		printf("%d", (fixed_t)w);
 		return;
 	}
-	else while (f != 1 && f/2 == f>>1)
+	else while (f != 1 && f / 2 == f >> 1)
 	{
 		d /= 2;
 		f /= 2;
@@ -892,7 +927,7 @@ static inline void M_print(INT64 a)
 
 FUNCMATH FUNCINLINE static inline fixed_t FixedMulC(fixed_t a, fixed_t b)
 {
-	return (fixed_t)((((INT64)a * b) ) / FRACUNIT);
+	return (fixed_t)((((INT64)a * b)) / FRACUNIT);
 }
 
 FUNCMATH FUNCINLINE static inline fixed_t FixedDivC2(fixed_t a, fixed_t b)
@@ -902,7 +937,7 @@ FUNCMATH FUNCINLINE static inline fixed_t FixedDivC2(fixed_t a, fixed_t b)
 	if (b == 0)
 		I_Error("FixedDiv: divide by zero");
 
-	ret = (((INT64)a * FRACUNIT) ) / b;
+	ret = (((INT64)a * FRACUNIT)) / b;
 
 	if ((ret > INT32_MAX) || (ret < INT32_MIN))
 		I_Error("FixedDiv: divide by zero");
@@ -911,7 +946,7 @@ FUNCMATH FUNCINLINE static inline fixed_t FixedDivC2(fixed_t a, fixed_t b)
 
 FUNCMATH FUNCINLINE static inline fixed_t FixedDivC(fixed_t a, fixed_t b)
 {
-	if ((abs(a) >> (FRACBITS-2)) >= abs(b))
+	if ((abs(a) >> (FRACBITS - 2)) >= abs(b))
 		return (a^b) < 0 ? INT32_MIN : INT32_MAX;
 
 	return FixedDivC2(a, b);
@@ -938,43 +973,43 @@ int main(int argc, char** argv)
 
 #ifdef MULDIV_TEST
 	for (a = 1; a <= INT32_MAX; a += FRACUNIT)
-	for (b = 0; b <= INT32_MAX; b += FRACUNIT)
-	{
-		c = FixedMul(a, b);
-		d = FixedMulC(a, b);
-		if (c != d)
+		for (b = 0; b <= INT32_MAX; b += FRACUNIT)
 		{
-			printf("(");
-			M_print(a);
-			printf(") * (");
-			M_print(b);
-			printf(") = (");
-			M_print(c);
-			printf(") != (");
-			M_print(d);
-			printf(") \n");
-			n--;
-			printf("%d != %d\n", c, d);
+			c = FixedMul(a, b);
+			d = FixedMulC(a, b);
+			if (c != d)
+			{
+				printf("(");
+				M_print(a);
+				printf(") * (");
+				M_print(b);
+				printf(") = (");
+				M_print(c);
+				printf(") != (");
+				M_print(d);
+				printf(") \n");
+				n--;
+				printf("%d != %d\n", c, d);
+			}
+			c = FixedDiv(a, b);
+			d = FixedDivC(a, b);
+			if (c != d)
+			{
+				printf("(");
+				M_print(a);
+				printf(") / (");
+				M_print(b);
+				printf(") = (");
+				M_print(c);
+				printf(") != (");
+				M_print(d);
+				printf(")\n");
+				n--;
+				printf("%d != %d\n", c, d);
+			}
+			if (n <= 0)
+				exit(-1);
 		}
-		c = FixedDiv(a, b);
-		d = FixedDivC(a, b);
-		if (c != d)
-		{
-			printf("(");
-			M_print(a);
-			printf(") / (");
-			M_print(b);
-			printf(") = (");
-			M_print(c);
-			printf(") != (");
-			M_print(d);
-			printf(")\n");
-			n--;
-			printf("%d != %d\n", c, d);
-		}
-		if (n <= 0)
-			exit(-1);
-	}
 #endif
 
 #ifdef SQRT_TEST
@@ -982,7 +1017,7 @@ int main(int argc, char** argv)
 	{
 		c = FixedSqrt(a);
 		d = FixedSqrtC(a);
-		b = abs(c-d);
+		b = abs(c - d);
 		if (b > 1)
 		{
 			printf("sqrt(");
diff --git a/src/m_fixed.h b/src/m_fixed.h
index d8e722b13c8f0117f10098064973997cf0695df8..370633c1f6b0de4752f575bc3ab5929fd0a329cd 100644
--- a/src/m_fixed.h
+++ b/src/m_fixed.h
@@ -389,9 +389,11 @@ boolean FV3_Equal(const vector3_t *a_1, const vector3_t *a_2);
 fixed_t FV3_Dot(const vector3_t *a_1, const vector3_t *a_2);
 vector3_t *FV3_Cross(const vector3_t *a_1, const vector3_t *a_2, vector3_t *a_o);
 vector3_t *FV3_ClosestPointOnLine(const vector3_t *Line, const vector3_t *p, vector3_t *out);
+void FV3_ClosestPointOnVector(const vector3_t *dir, const vector3_t *p, vector3_t *out);
 void FV3_ClosestPointOnTriangle(const vector3_t *tri, const vector3_t *point, vector3_t *result);
 vector3_t *FV3_Point2Vec(const vector3_t *point1, const vector3_t *point2, vector3_t *a_o);
-void FV3_Normal(const vector3_t *a_triangle, vector3_t *a_normal);
+fixed_t FV3_Normal(const vector3_t *a_triangle, vector3_t *a_normal);
+fixed_t FV3_Strength(const vector3_t *a_1, const vector3_t *dir);
 fixed_t FV3_PlaneDistance(const vector3_t *a_normal, const vector3_t *a_point);
 boolean FV3_IntersectedPlane(const vector3_t *a_triangle, const vector3_t *a_line, vector3_t *a_normal, fixed_t *originDistance);
 fixed_t FV3_PlaneIntersection(const vector3_t *pOrigin, const vector3_t *pNormal, const vector3_t *rOrigin, const vector3_t *rVector);
diff --git a/src/m_menu.c b/src/m_menu.c
index 20aec7a01fe278688ca9bd5fa6565e0593164cbe..f3528d8e5cb33874a58203e0f832020c7696514f 100644
--- a/src/m_menu.c
+++ b/src/m_menu.c
@@ -1295,18 +1295,21 @@ static menuitem_t OP_ColorOptionsMenu[] =
 #ifdef HWRENDER
 static menuitem_t OP_OpenGLOptionsMenu[] =
 {
-	{IT_STRING|IT_CVAR,         NULL, "Field of view",   &cv_grfov,            10},
-	{IT_STRING|IT_CVAR,         NULL, "Quality",         &cv_scr_depth,        20},
-	{IT_STRING|IT_CVAR,         NULL, "Texture Filter",  &cv_grfiltermode,     30},
-	{IT_STRING|IT_CVAR,         NULL, "Anisotropic",     &cv_granisotropicmode,40},
+	{IT_STRING|IT_CVAR,         NULL, "Models",              &cv_grmodels,             10},
+	{IT_STRING|IT_CVAR,         NULL, "Model interpolation", &cv_grmodelinterpolation, 20},
+
+	{IT_STRING|IT_CVAR,         NULL, "Field of view",   &cv_grfov,            40},
+	{IT_STRING|IT_CVAR,         NULL, "Quality",         &cv_scr_depth,        50},
+	{IT_STRING|IT_CVAR,         NULL, "Texture Filter",  &cv_grfiltermode,     60},
+	{IT_STRING|IT_CVAR,         NULL, "Anisotropic",     &cv_granisotropicmode,70},
 #if defined (_WINDOWS) && (!((defined (__unix__) && !defined (MSDOS)) || defined (UNIXCOMMON) || defined (HAVE_SDL)))
-	{IT_STRING|IT_CVAR,         NULL, "Fullscreen",      &cv_fullscreen,       50},
+	{IT_STRING|IT_CVAR,         NULL, "Fullscreen",      &cv_fullscreen,       80},
 #endif
 #ifdef ALAM_LIGHTING
-	{IT_SUBMENU|IT_STRING,      NULL, "Lighting...",     &OP_OpenGLLightingDef,     70},
+	{IT_SUBMENU|IT_STRING,      NULL, "Lighting...",     &OP_OpenGLLightingDef,     100},
 #endif
-	{IT_SUBMENU|IT_STRING,      NULL, "Fog...",          &OP_OpenGLFogDef,          80},
-	{IT_SUBMENU|IT_STRING,      NULL, "Gamma...",        &OP_OpenGLColorDef,        90},
+	{IT_SUBMENU|IT_STRING,      NULL, "Fog...",          &OP_OpenGLFogDef,          110},
+	{IT_SUBMENU|IT_STRING,      NULL, "Gamma...",        &OP_OpenGLColorDef,        120},
 };
 
 #ifdef ALAM_LIGHTING
@@ -2289,6 +2292,13 @@ void M_InitMenuPresTables(void)
 		// so-called "undefined"
 		menupres[i].fadestrength = -1;
 		menupres[i].hidetitlepics = -1; // inherits global hidetitlepics
+		menupres[i].ttmode = TTMODE_NONE;
+		menupres[i].ttscale = UINT8_MAX;
+		menupres[i].ttname[0] = 0;
+		menupres[i].ttx = INT16_MAX;
+		menupres[i].tty = INT16_MAX;
+		menupres[i].ttloop = INT16_MAX;
+		menupres[i].tttics = UINT16_MAX;
 		menupres[i].enterwipe = -1;
 		menupres[i].exitwipe = -1;
 		menupres[i].bgcolor = -1;
@@ -2467,7 +2477,7 @@ static boolean MIT_SetCurFadeValue(UINT32 menutype, INT32 level, INT32 *retval,
 	return false;
 }
 
-static boolean MIT_SetCurHideTitlePics(UINT32 menutype, INT32 level, INT32 *retval, void **input, boolean fromoldest)
+static boolean MIT_SetCurTitlePics(UINT32 menutype, INT32 level, INT32 *retval, void **input, boolean fromoldest)
 {
 	(void)input;
 	(void)retval;
@@ -2481,8 +2491,41 @@ static boolean MIT_SetCurHideTitlePics(UINT32 menutype, INT32 level, INT32 *retv
 		curhidepics = menupres[menutype].hidetitlepics;
 		return true;
 	}
+	else if (menupres[menutype].ttmode == TTMODE_USER)
+	{
+		if (menupres[menutype].ttname[0])
+		{
+			curhidepics = menupres[menutype].hidetitlepics;
+			curttmode = menupres[menutype].ttmode;
+			curttscale = (menupres[menutype].ttscale != UINT8_MAX ? menupres[menutype].ttscale : ttscale);
+			strncpy(curttname, menupres[menutype].ttname, 9);
+			curttx = (menupres[menutype].ttx != INT16_MAX ? menupres[menutype].ttx : ttx);
+			curtty = (menupres[menutype].tty != INT16_MAX ? menupres[menutype].tty : tty);
+			curttloop = (menupres[menutype].ttloop != INT16_MAX ? menupres[menutype].ttloop : ttloop);
+			curtttics = (menupres[menutype].tttics != UINT16_MAX ? menupres[menutype].tttics : tttics);
+		}
+		else
+			curhidepics = menupres[menutype].hidetitlepics;
+		return true;
+	}
+	else if (menupres[menutype].ttmode != TTMODE_NONE)
+	{
+		curhidepics = menupres[menutype].hidetitlepics;
+		curttmode = menupres[menutype].ttmode;
+		curttscale = (menupres[menutype].ttscale != UINT8_MAX ? menupres[menutype].ttscale : ttscale);
+		return true;
+	}
 	else if (!level)
+	{
 		curhidepics = hidetitlepics;
+		curttmode = ttmode;
+		curttscale = ttscale;
+		strncpy(curttname, ttname, 9);
+		curttx = ttx;
+		curtty = tty;
+		curttloop = ttloop;
+		curtttics = tttics;
+	}
 	return false;
 }
 
@@ -2527,9 +2570,9 @@ void M_SetMenuCurFadeValue(UINT8 defaultvalue)
 	M_IterateMenuTree(MIT_SetCurFadeValue, &defaultvalue);
 }
 
-void M_SetMenuCurHideTitlePics(void)
+void M_SetMenuCurTitlePics(void)
 {
-	M_IterateMenuTree(MIT_SetCurHideTitlePics, NULL);
+	M_IterateMenuTree(MIT_SetCurTitlePics, NULL);
 }
 
 // ====================================
@@ -2579,12 +2622,20 @@ static void M_HandleMenuPresState(menu_t *newMenu)
 	curbgyspeed = titlescrollyspeed;
 	curbghide = (gamestate != GS_TIMEATTACK); // show in time attack, hide in other menus
 
+	curttmode = ttmode;
+	curttscale = ttscale;
+	strncpy(curttname, ttname, 9);
+	curttx = ttx;
+	curtty = tty;
+	curttloop = ttloop;
+	curtttics = tttics;
+
 	// don't do the below during the in-game menus
 	if (gamestate != GS_TITLESCREEN && gamestate != GS_TIMEATTACK)
 		return;
 
 	M_SetMenuCurFadeValue(16);
-	M_SetMenuCurHideTitlePics();
+	M_SetMenuCurTitlePics();
 
 	// Loop through both menu IDs in parallel and look for type changes
 	// The youngest child in activeMenuId is the entered menu
@@ -6512,7 +6563,7 @@ static void M_LevelSelectWarp(INT32 choice)
 
 	if (W_CheckNumForName(G_BuildMapName(cv_nextmap.value)) == LUMPERROR)
 	{
-//		CONS_Alert(CONS_WARNING, "Internal game map '%s' not found\n", G_BuildMapName(cv_nextmap.value));
+		CONS_Alert(CONS_WARNING, "Internal game map '%s' not found\n", G_BuildMapName(cv_nextmap.value));
 		return;
 	}
 
@@ -7929,81 +7980,80 @@ static void M_SetupChoosePlayer(INT32 choice)
 	char *and;
 	(void)choice;
 
-	SP_PlayerMenu[0].status &= ~IT_DYBIGSPACE; // Correcting a hack that may be made below.
-
-	for (i = 0; i < 32; i++) // Handle charsels, availability, and unlocks.
+	if (!(mapheaderinfo[startmap-1]
+			&& (mapheaderinfo[startmap-1]->forcecharacter[0] != '\0'
+			|| (mapheaderinfo[startmap-1]->typeoflevel & TOL_NIGHTS)) // remove this later when everyone gets their own nights sprites, maybe
+		))
 	{
-		if (description[i].used) // If the character's disabled through SOC, there's nothing we can do for it.
+		for (i = 0; i < 32; i++) // Handle charsels, availability, and unlocks.
 		{
-			and = strchr(description[i].skinname, '&');
-			if (and)
-			{
-				char firstskin[SKINNAMESIZE+1];
-				strncpy(firstskin, description[i].skinname, (and - description[i].skinname));
-				firstskin[(and - description[i].skinname)] = '\0';
-				description[i].skinnum[0] = R_SkinAvailable(firstskin);
-				description[i].skinnum[1] = R_SkinAvailable(and+1);
-			}
-			else
-			{
-				description[i].skinnum[0] = R_SkinAvailable(description[i].skinname);
-				description[i].skinnum[1] = -1;
-			}
-			skinnum = description[i].skinnum[0];
-			if ((skinnum != -1) && (R_SkinUsable(-1, skinnum)))
+			if (description[i].used) // If the character's disabled through SOC, there's nothing we can do for it.
 			{
-				// Handling order.
-				if (firstvalid == 255)
-					firstvalid = i;
+				and = strchr(description[i].skinname, '&');
+				if (and)
+				{
+					char firstskin[SKINNAMESIZE+1];
+					strncpy(firstskin, description[i].skinname, (and - description[i].skinname));
+					firstskin[(and - description[i].skinname)] = '\0';
+					description[i].skinnum[0] = R_SkinAvailable(firstskin);
+					description[i].skinnum[1] = R_SkinAvailable(and+1);
+				}
 				else
 				{
-					description[i].prev = lastvalid;
-					description[lastvalid].next = i;
+					description[i].skinnum[0] = R_SkinAvailable(description[i].skinname);
+					description[i].skinnum[1] = -1;
 				}
-				lastvalid = i;
+				skinnum = description[i].skinnum[0];
+				if ((skinnum != -1) && (R_SkinUsable(-1, skinnum)))
+				{
+					// Handling order.
+					if (firstvalid == 255)
+						firstvalid = i;
+					else
+					{
+						description[i].prev = lastvalid;
+						description[lastvalid].next = i;
+					}
+					lastvalid = i;
 
-				if (i == char_on)
-					allowed = true;
+					if (i == char_on)
+						allowed = true;
 
-				if (!(description[i].picname[0]))
-				{
-					if (skins[skinnum].sprites[SPR2_XTRA].numframes >= XTRA_CHARSEL+1)
+					if (!(description[i].picname[0]))
 					{
-						spritedef_t *sprdef = &skins[skinnum].sprites[SPR2_XTRA];
-						spriteframe_t *sprframe = &sprdef->spriteframes[XTRA_CHARSEL];
-						description[i].charpic = W_CachePatchNum(sprframe->lumppat[0], PU_CACHE);
+						if (skins[skinnum].sprites[SPR2_XTRA].numframes >= XTRA_CHARSEL+1)
+						{
+							spritedef_t *sprdef = &skins[skinnum].sprites[SPR2_XTRA];
+							spriteframe_t *sprframe = &sprdef->spriteframes[XTRA_CHARSEL];
+							description[i].charpic = W_CachePatchNum(sprframe->lumppat[0], PU_CACHE);
+						}
+						else
+							description[i].charpic = W_CachePatchName("MISSING", PU_CACHE);
 					}
 					else
-						description[i].charpic = W_CachePatchName("MISSING", PU_CACHE);
-				}
-				else
-					description[i].charpic = W_CachePatchName(description[i].picname, PU_CACHE);
+						description[i].charpic = W_CachePatchName(description[i].picname, PU_CACHE);
 
-				if (description[i].nametag[0])
-				{
-					const char *nametag = description[i].nametag;
-					description[i].namepic = NULL;
-					if (W_LumpExists(nametag))
-						description[i].namepic = W_CachePatchName(nametag, PU_CACHE);
+					if (description[i].nametag[0])
+					{
+						const char *nametag = description[i].nametag;
+						description[i].namepic = NULL;
+						if (W_LumpExists(nametag))
+							description[i].namepic = W_CachePatchName(nametag, PU_CACHE);
+					}
 				}
+				// else -- Technically, character select icons without corresponding skins get bundled away behind this too. Sucks to be them.
 			}
-			// else -- Technically, character select icons without corresponding skins get bundled away behind this too. Sucks to be them.
 		}
 	}
 
-	if ((firstvalid != 255)
-		&& !(mapheaderinfo[startmap-1]
-			&& (mapheaderinfo[startmap-1]->forcecharacter[0] != '\0')
-			)
-		)
+	if (firstvalid != 255)
 	{ // One last bit of order we can't do in the iteration above.
 		description[firstvalid].prev = lastvalid;
 		description[lastvalid].next = firstvalid;
 	}
-	else // We're being forced into a specific character, so might as well.
+	else // We're being forced into a specific character, so might as well just skip it.
 	{
-		SP_PlayerMenu[0].status |= IT_DYBIGSPACE; // This is a dummy flag hack to make a non-IT_CALL character in slot 0 not softlock the game.
-		M_ChoosePlayer(0);
+		M_ChoosePlayer(-1);
 		return;
 	}
 
@@ -8330,38 +8380,42 @@ static void M_DrawSetupChoosePlayerMenu(void)
 static void M_ChoosePlayer(INT32 choice)
 {
 	boolean ultmode = (ultimate_selectable && SP_PlayerDef.prevMenu == &SP_LoadDef && saveSlotSelected == NOSAVESLOT);
+	UINT8 skinnum;
 
 	// skip this if forcecharacter or no characters available
-	if (!(SP_PlayerMenu[0].status & IT_DYBIGSPACE))
+	if (choice == -1)
+	{
+		skinnum = botskin = 0;
+		botingame = false;
+	}
+	// M_SetupChoosePlayer didn't call us directly, that means we've been properly set up.
+	else
 	{
-		// M_SetupChoosePlayer didn't call us directly, that means we've been properly set up.
 		char_scroll = 0; // finish scrolling the menu
 		M_DrawSetupChoosePlayerMenu(); // draw the finally selected character one last time for the fadeout
 		// Is this a hack?
 		charseltimer = 0;
-	}
-	M_ClearMenus(true);
 
-	if (description[choice].skinnum[1] != -1) {
-		// this character has a second skin
-		botingame = true;
-		botskin = (UINT8)(description[choice].skinnum[1]+1);
-		botcolor = skins[description[choice].skinnum[1]].prefcolor;
-	}
-	else
-	{
-		botingame = false;
-		botskin = 0;
-		botcolor = 0;
+		skinnum = description[choice].skinnum[0];
+
+		if ((botingame = (description[choice].skinnum[1] != -1))) {
+			// this character has a second skin
+			botskin = (UINT8)(description[choice].skinnum[1]+1);
+			botcolor = skins[description[choice].skinnum[1]].prefcolor;
+		}
+		else
+			botskin = botcolor = 0;
 	}
 
+	M_ClearMenus(true);
+
 	if (startmap != spstage_start)
 		cursaveslot = 0;
 
 	//lastmapsaved = 0;
 	gamecomplete = false;
 
-	G_DeferedInitNew(ultmode, G_BuildMapName(startmap), (UINT8)description[choice].skinnum[0], false, fromlevelselect);
+	G_DeferedInitNew(ultmode, G_BuildMapName(startmap), skinnum, false, fromlevelselect);
 	COM_BufAddText("dummyconsvar 1\n"); // G_DeferedInitNew doesn't do this
 
 	if (levelselect.rows)
diff --git a/src/m_menu.h b/src/m_menu.h
index 3bfa48597b9d2ecafcb086d571eb6d405581a4c8..51c734a43c8a6199c37857a622c69c2a567113f7 100644
--- a/src/m_menu.h
+++ b/src/m_menu.h
@@ -18,6 +18,7 @@
 #include "d_event.h"
 #include "command.h"
 #include "r_things.h" // for SKINNAMESIZE
+#include "f_finale.h" // for ttmode_enum
 
 //
 // MENUS
@@ -128,19 +129,27 @@ typedef enum
 typedef struct
 {
 	char bgname[8]; // name for background gfx lump; lays over titlemap if this is set
-	SINT8 hidetitlepics; // hide title gfx per menu; -1 means undefined, inherits global setting
+	SINT8 fadestrength;  // darken background when displaying this menu, strength 0-31 or -1 for undefined
+	INT32 bgcolor; // fill color, overrides bg name. -1 means follow bg name rules.
 	INT32 titlescrollxspeed; // background gfx scroll per menu; inherits global setting
 	INT32 titlescrollyspeed; // y scroll
-	INT32 bgcolor; // fill color, overrides bg name. -1 means follow bg name rules.
 	boolean bghide; // for titlemaps, hide the background.
 
+	SINT8 hidetitlepics; // hide title gfx per menu; -1 means undefined, inherits global setting
+	ttmode_enum ttmode; // title wing animation mode; default TTMODE_OLD
+	UINT8 ttscale; // scale of title wing gfx (FRACUNIT / ttscale); -1 means undefined, inherits global setting
+	char ttname[9]; // lump name of title wing gfx. If name length is <= 6, engine will attempt to load numbered frames (TTNAMExx)
+	INT16 ttx; // X position of title wing
+	INT16 tty; // Y position of title wing
+	INT16 ttloop; // # frame to loop; -1 means dont loop
+	UINT16 tttics; // # of tics per frame
+
 	char musname[7]; ///< Music track to play. "" for no music.
 	UINT16 mustrack; ///< Subsong to play. Only really relevant for music modules and specific formats supported by GME. 0 to ignore.
 	boolean muslooping; ///< Loop the music
 	boolean musstop; ///< Don't play any music
 	boolean musignore; ///< Let the current music keep playing
 
-	SINT8 fadestrength;  // darken background when displaying this menu, strength 0-31 or -1 for undefined
 	boolean enterbubble; // run all entrance line execs after common ancestor and up to child. If false, only run the child's exec
 	boolean exitbubble; // run all exit line execs from child and up to before common ancestor. If false, only run the child's exec
 	INT32 entertag; // line exec to run on menu enter, if titlemap
@@ -158,7 +167,7 @@ UINT8 M_GetYoungestChildMenu(void);
 void M_ChangeMenuMusic(const char *defaultmusname, boolean defaultmuslooping);
 void M_SetMenuCurBackground(const char *defaultname);
 void M_SetMenuCurFadeValue(UINT8 defaultvalue);
-void M_SetMenuCurHideTitlePics(void);
+void M_SetMenuCurTitlePics(void);
 
 // Called by main loop,
 // saves config file and calls I_Quit when user exits.
diff --git a/src/p_enemy.c b/src/p_enemy.c
index 9d2a8e95a8bf6460170510ce59fdda69cadf3753..4c256e9edf7024ada09650f5d97a6fc52f3b84d3 100644
--- a/src/p_enemy.c
+++ b/src/p_enemy.c
@@ -129,6 +129,7 @@ void A_FishJump(mobj_t *actor);
 void A_ThrownRing(mobj_t *actor);
 void A_SetSolidSteam(mobj_t *actor);
 void A_UnsetSolidSteam(mobj_t *actor);
+void A_SignSpin(mobj_t *actor);
 void A_SignPlayer(mobj_t *actor);
 void A_OverlayThink(mobj_t *actor);
 void A_JetChase(mobj_t *actor);
@@ -3930,11 +3931,15 @@ void A_BossDeath(mobj_t *mo)
 		{
 			// Touching the egg trap button calls P_DoPlayerExit, which calls P_RestoreMusic.
 			// So just park ourselves in the mapmus variables.
-			boolean changed = strnicmp(mapheaderinfo[gamemap-1]->musname, mapmusname, 7);
-			strncpy(mapmusname, mapheaderinfo[gamemap-1]->muspostbossname, 7);
-			mapmusname[6] = 0;
-			mapmusflags = (mapheaderinfo[gamemap-1]->muspostbosstrack & MUSIC_TRACKMASK) | MUSIC_RELOADRESET;
-			mapmusposition = mapheaderinfo[gamemap-1]->muspostbosspos;
+			// But don't change the mapmus variables if they were modified from their level header values (e.g., TUNES).
+			boolean changed = strnicmp(mapheaderinfo[gamemap-1]->musname, S_MusicName(), 7);
+			if (!strnicmp(mapheaderinfo[gamemap-1]->musname, mapmusname, 7))
+			{
+				strncpy(mapmusname, mapheaderinfo[gamemap-1]->muspostbossname, 7);
+				mapmusname[6] = 0;
+				mapmusflags = (mapheaderinfo[gamemap-1]->muspostbosstrack & MUSIC_TRACKMASK) | MUSIC_RELOADRESET;
+				mapmusposition = mapheaderinfo[gamemap-1]->muspostbosspos;
+			}
 
 			// don't change if we're in another tune
 			// but in case we're in jingle, use our parked mapmus variables so the correct track restores
@@ -4759,7 +4764,7 @@ void A_DropMine(mobj_t *actor)
 // Description: Makes the stupid harmless fish in Greenflower Zone jump.
 //
 // var1 = Jump strength (in FRACBITS), if specified. Otherwise, uses the angle value.
-// var2 = unused
+// var2 = Trail object to spawn, if desired.
 //
 void A_FishJump(mobj_t *actor)
 {
@@ -4772,8 +4777,17 @@ void A_FishJump(mobj_t *actor)
 
 	if (locvar2)
 	{
-		fixed_t rad = actor->radius>>FRACBITS;
-		P_SpawnMobjFromMobj(actor, P_RandomRange(rad, -rad)<<FRACBITS, P_RandomRange(rad, -rad)<<FRACBITS, 0, (mobjtype_t)locvar2);
+		UINT8 i;
+		// Don't spawn trail unless a player is nearby.
+		for (i = 0; i < MAXPLAYERS; ++i)
+			if (playeringame[i] && players[i].mo
+				&& P_AproxDistance(actor->x - players[i].mo->x, actor->y - players[i].mo->y) < (actor->info->speed))
+				break; // Stop looking.
+		if (i < MAXPLAYERS)
+		{
+			fixed_t rad = actor->radius>>FRACBITS;
+			P_SpawnMobjFromMobj(actor, P_RandomRange(rad, -rad)<<FRACBITS, P_RandomRange(rad, -rad)<<FRACBITS, 0, (mobjtype_t)locvar2);
+		}
 	}
 
 	if ((actor->z <= actor->floorz) || (actor->z <= actor->watertop - FixedMul((64 << FRACBITS), actor->scale)))
@@ -5006,59 +5020,186 @@ void A_UnsetSolidSteam(mobj_t *actor)
 	actor->flags |= MF_NOCLIP;
 }
 
+// Function: A_SignSpin
+//
+// Description: Spins a signpost until it hits the ground and reaches its mapthing's angle.
+//
+// var1 = degrees to rotate object (must be positive, because I'm lazy)
+// var2 = unused
+//
+void A_SignSpin(mobj_t *actor)
+{
+	INT32 locvar1 = var1;
+	INT32 locvar2 = var2;
+	INT16 i;
+	angle_t rotateangle = FixedAngle(locvar1 << FRACBITS);
+
+#ifdef HAVE_BLUA
+	if (LUA_CallAction("A_SignSpin", actor))
+		return;
+#endif
+
+	if (P_IsObjectOnGround(actor) && P_MobjFlip(actor) * actor->momz <= 0)
+	{
+		if (actor->spawnpoint)
+		{
+			angle_t mapangle = FixedAngle(actor->spawnpoint->angle << FRACBITS);
+			angle_t diff = mapangle - actor->angle;
+			if (diff < ANG2)
+			{
+				actor->angle = mapangle;
+				P_SetMobjState(actor, actor->info->deathstate);
+				return;
+			}
+			if ((statenum_t)(actor->state-states) != actor->info->painstate)
+				P_SetMobjState(actor, actor->info->painstate);
+			actor->movedir = min((mapangle - actor->angle) >> 2, actor->movedir);
+		}
+		else // no mapthing? just finish in your current angle
+		{
+			P_SetMobjState(actor, locvar2);
+			return;
+		}
+	}
+	else
+	{
+		actor->movedir = rotateangle;
+	}
+	actor->angle += actor->movedir;
+	if (actor->tracer == NULL || P_MobjWasRemoved(actor->tracer)) return;
+	for (i = -1; i < 2; i += 2)
+	{
+		P_SpawnMobjFromMobj(actor,
+			P_ReturnThrustX(actor, actor->tracer->angle, i * actor->radius),
+			P_ReturnThrustY(actor, actor->tracer->angle, i * actor->radius),
+			(actor->eflags & MFE_VERTICALFLIP) ? 0 : actor->height,
+			actor->info->painchance)->destscale >>= 1;
+	}
+}
+
 // Function: A_SignPlayer
 //
 // Description: Changes the state of a level end sign to reflect the player that hit it.
+//              Also used to display Eggman or the skin roulette whilst spinning.
 //
-// var1 = unused
-// var2 = unused
+// var1 = number of skin to display (e.g. 2 = Knuckles; special cases: -1 = target's skin, -2 = skin roulette, -3 = Eggman)
+// var2 = custom sign color, if desired.
 //
 void A_SignPlayer(mobj_t *actor)
 {
+	INT32 locvar1 = var1;
+	INT32 locvar2 = var2;
+	skin_t *skin = NULL;
 	mobj_t *ov;
-	skin_t *skin;
+	UINT8 facecolor, signcolor = (UINT8)locvar2;
+	UINT32 signframe = states[actor->info->raisestate].frame;
+
 #ifdef HAVE_BLUA
 	if (LUA_CallAction("A_SignPlayer", actor))
 		return;
 #endif
-	if (!actor->target)
-		return;
 
-	if (!actor->target->player)
+	if (actor->tracer == NULL || locvar1 < -3 || locvar1 >= numskins)
 		return;
 
-	skin = &skins[actor->target->player->skin];
+	// if no face overlay, spawn one
+	if (actor->tracer->tracer == NULL || P_MobjWasRemoved(actor->tracer->tracer))
+	{
+		ov = P_SpawnMobj(actor->x, actor->y, actor->z, MT_OVERLAY);
+		P_SetTarget(&ov->target, actor->tracer);
+		P_SetTarget(&actor->tracer->tracer, ov);
+	}
+	else
+		ov = actor->tracer->tracer;
 
-	if ((actor->target->player->skincolor == skin->prefcolor) && (skin->prefoppositecolor)) // Set it as the skin's preferred oppositecolor?
+	if (locvar1 == -1) // set to target's skin
 	{
-		actor->color = skin->prefoppositecolor;
-		/*
-		If you're here from the comment above Color_Opposite,
-		the following line is the one which is dependent on the
-		array being symmetrical. It gets the opposite of the
-		opposite of your desired colour just so it can get the
-		brightness frame for the End Sign. It's not a great
-		design choice, but it's constant time array access and
-		the idea that the colours should be OPPOSITES is kind
-		of in the name. If you have a better idea, feel free
-		to let me know. ~toast 2016/07/20
-		*/
-		actor->frame += (15 - Color_Opposite[Color_Opposite[skin->prefoppositecolor - 1][0] - 1][1]);
+		if (!actor->target)
+			return;
+
+		if (!actor->target->player)
+			return;
+
+		skin = &skins[actor->target->player->skin];
+		facecolor = actor->target->player->skincolor;
+
+		if (signcolor)
+			;
+		else if ((actor->target->player->skincolor == skin->prefcolor) && (skin->prefoppositecolor)) // Set it as the skin's preferred oppositecolor?
+		{
+			signcolor = skin->prefoppositecolor;
+			/*
+			If you're here from the comment above Color_Opposite,
+			the following line is the one which is dependent on the
+			array being symmetrical. It gets the opposite of the
+			opposite of your desired colour just so it can get the
+			brightness frame for the End Sign. It's not a great
+			design choice, but it's constant time array access and
+			the idea that the colours should be OPPOSITES is kind
+			of in the name. If you have a better idea, feel free
+			to let me know. ~toast 2016/07/20
+			*/
+			signframe += (15 - Color_Opposite[Color_Opposite[skin->prefoppositecolor - 1][0] - 1][1]);
+		}
+		else if (actor->target->player->skincolor) // Set the sign to be an appropriate background color for this player's skincolor.
+		{
+			signcolor = Color_Opposite[actor->target->player->skincolor - 1][0];
+			signframe += (15 - Color_Opposite[actor->target->player->skincolor - 1][1]);
+		}
+		else
+			signcolor = SKINCOLOR_NONE;
 	}
-	else if (actor->target->player->skincolor) // Set the sign to be an appropriate background color for this player's skincolor.
+	else if (locvar1 != -3) // set to a defined skin
 	{
-		actor->color = Color_Opposite[actor->target->player->skincolor - 1][0];
-		actor->frame += (15 - Color_Opposite[actor->target->player->skincolor - 1][1]);
+		// I turned this function into a fucking mess. I'm so sorry. -Lach
+		if (locvar1 == -2) // next skin
+		{
+			if (ov->skin == NULL) // pick a random skin to start with!
+				skin = &skins[P_RandomKey(numskins)];
+			else // otherwise, advance 1 skin
+			{
+				UINT8 skinnum = (skin_t*)ov->skin-skins;
+				player_t *player = actor->target ? actor->target->player : NULL;
+				while ((skinnum = (skinnum + 1) % numskins) && (player ? !R_SkinUsable(player-players, skinnum) : skins[skinnum].availability > 0));
+				skin = &skins[skinnum];
+			}
+		}
+		else // specific skin
+		{
+			skin = &skins[locvar1];
+		}
+
+		facecolor = skin->prefcolor;
+		if (signcolor)
+			;
+		else if (skin->prefoppositecolor)
+		{
+			signcolor = skin->prefoppositecolor;
+		}
+		else
+		{
+			signcolor = Color_Opposite[facecolor - 1][0];
+		}
+		signframe += (15 - Color_Opposite[Color_Opposite[signcolor - 1][0] - 1][1]);
 	}
 
-	if (skin->sprites[SPR2_SIGN].numframes)
+	if (skin != NULL && skin->sprites[SPR2_SIGN].numframes) // player face
 	{
-		// spawn an overlay of the player's face.
-		ov = P_SpawnMobj(actor->x, actor->y, actor->z, MT_OVERLAY);
-		P_SetTarget(&ov->target, actor);
-		ov->color = actor->target->player->skincolor;
+		ov->color = facecolor;
 		ov->skin = skin;
 		P_SetMobjState(ov, actor->info->seestate); // S_PLAY_SIGN
+		actor->tracer->color = signcolor;
+		actor->tracer->frame = signframe;
+	}
+	else // Eggman face
+	{
+		ov->color = SKINCOLOR_NONE;
+		P_SetMobjState(ov, actor->info->meleestate); // S_EGGMANSIGN
+		if (signcolor)
+			actor->tracer->color = signcolor;
+		else
+			actor->tracer->color = signcolor = SKINCOLOR_CARBON;
+		actor->tracer->frame = signframe += (15 - Color_Opposite[Color_Opposite[signcolor - 1][0] - 1][1]);
 	}
 }
 
@@ -5106,7 +5247,7 @@ void A_OverlayThink(mobj_t *actor)
 		actor->z = actor->target->z + actor->target->height - mobjinfo[actor->type].height  - ((var2>>16) ? -1 : 1)*(var2&0xFFFF)*FRACUNIT;
 	else
 		actor->z = actor->target->z + ((var2>>16) ? -1 : 1)*(var2&0xFFFF)*FRACUNIT;
-	actor->angle = actor->target->angle;
+	actor->angle = actor->target->angle + actor->movedir;
 	actor->eflags = actor->target->eflags;
 
 	actor->momx = actor->target->momx;
@@ -14025,7 +14166,7 @@ void A_LavafallRocks(mobj_t *actor)
 	// Don't spawn rocks unless a player is relatively close by.
 	for (i = 0; i < MAXPLAYERS; ++i)
 		if (playeringame[i] && players[i].mo
-			&& P_AproxDistance(actor->x - players[i].mo->x, actor->y - players[i].mo->y) < (1600 << FRACBITS))
+			&& P_AproxDistance(actor->x - players[i].mo->x, actor->y - players[i].mo->y) < (actor->info->speed >> 1))
 			break; // Stop looking.
 
 	if (i < MAXPLAYERS)
@@ -14048,6 +14189,7 @@ void A_LavafallRocks(mobj_t *actor)
 void A_LavafallLava(mobj_t *actor)
 {
 	mobj_t *lavafall;
+	UINT8 i;
 
 #ifdef HAVE_BLUA
 	if (LUA_CallAction("A_LavafallLava", actor))
@@ -14057,6 +14199,15 @@ void A_LavafallLava(mobj_t *actor)
 	if ((40 - actor->fuse) % (2*(actor->scale >> FRACBITS)))
 		return;
 
+	// Don't spawn lava unless a player is nearby.
+	for (i = 0; i < MAXPLAYERS; ++i)
+		if (playeringame[i] && players[i].mo
+			&& P_AproxDistance(actor->x - players[i].mo->x, actor->y - players[i].mo->y) < (actor->info->speed))
+			break; // Stop looking.
+
+	if (i >= MAXPLAYERS)
+		return;
+
 	lavafall = P_SpawnMobjFromMobj(actor, 0, 0, -8*FRACUNIT, MT_LAVAFALL_LAVA);
 	lavafall->momz = -P_MobjFlip(actor)*25*FRACUNIT;
 }
diff --git a/src/p_inter.c b/src/p_inter.c
index e25fb395a7094743222f18d5a6bdf1965540947f..2841dfebbe763ad344d4bdeb46637974df1b2912 100644
--- a/src/p_inter.c
+++ b/src/p_inter.c
@@ -633,7 +633,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck)
 				if (!(netgame || multiplayer))
 				{
 					player->continues += 1;
-					players->gotcontinue = true;
+					player->gotcontinue = true;
 					if (P_IsLocalPlayer(player))
 						S_StartSound(NULL, sfx_s3kac);
 					else
@@ -2482,6 +2482,8 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget
 		P_UnsetThingPosition(target);
 		target->flags |= MF_NOBLOCKMAP|MF_NOCLIP|MF_NOCLIPHEIGHT|MF_NOGRAVITY;
 		P_SetThingPosition(target);
+		target->standingslope = NULL;
+		target->pmomz = 0;
 
 		if (target->player->powers[pw_super])
 		{
diff --git a/src/p_map.c b/src/p_map.c
index 33ac2026572780431a6af1afdc88725462036f66..bb56a50b16932ae818f2a8e62f13a9e940cd3688 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -540,7 +540,7 @@ static void P_DoFanAndGasJet(mobj_t *spring, mobj_t *object)
 
 static void P_DoPterabyteCarry(player_t *player, mobj_t *ptera)
 {
-	if (player->powers[pw_carry] && players->powers[pw_carry] != CR_ROLLOUT)
+	if (player->powers[pw_carry] && player->powers[pw_carry] != CR_ROLLOUT)
 		return;
 	if (ptera->extravalue1 != 1)
 		return; // Not swooping
diff --git a/src/p_mobj.c b/src/p_mobj.c
index 395d434c10faa013caf39b4b3c3c2232355cb9ff..c1ca78d325da27504992a95b79f15ea09cb360eb 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -1861,6 +1861,9 @@ void P_XYMovement(mobj_t *mo)
 	oldy = mo->y;
 
 #ifdef ESLOPE
+	if (mo->flags & MF_NOCLIPHEIGHT)
+		mo->standingslope = NULL;
+
 	// adjust various things based on slope
 	if (mo->standingslope && abs(mo->standingslope->zdelta) > FRACUNIT>>8) {
 		if (!P_IsObjectOnGround(mo)) { // We fell off at some point? Do the twisty thing!
@@ -1991,7 +1994,7 @@ void P_XYMovement(mobj_t *mo)
 					mo->momz = transfermomz;
 					mo->standingslope = NULL;
 					if (player->pflags & PF_SPINNING)
-						player->pflags = (player->pflags & ~PF_SPINNING) | (PF_JUMPED | PF_THOKKED);
+						player->pflags |= PF_THOKKED;
 				}
 			}
 #endif
@@ -2051,7 +2054,7 @@ void P_XYMovement(mobj_t *mo)
 		return;
 
 #ifdef ESLOPE
-	if (moved && oldslope) { // Check to see if we ran off
+	if (moved && oldslope && !(mo->flags & MF_NOCLIPHEIGHT)) { // Check to see if we ran off
 
 		if (oldslope != mo->standingslope) { // First, compare different slopes
 			angle_t oldangle, newangle;
@@ -2459,16 +2462,6 @@ static boolean P_ZMovement(mobj_t *mo)
 				P_RemoveMobj(mo);
 				return false;
 			}
-			if (mo->momz
-			&& !(mo->flags & MF_NOGRAVITY)
-			&& ((!(mo->eflags & MFE_VERTICALFLIP) && mo->z <= mo->floorz)
-			|| ((mo->eflags & MFE_VERTICALFLIP) && mo->z+mo->height >= mo->ceilingz)))
-			{
-				mo->flags |= MF_NOGRAVITY;
-				mo->momx = 8; // this is a hack which is used to ensure it still behaves as a missile and can damage others
-				mo->momy = mo->momz = 0;
-				mo->z = ((mo->eflags & MFE_VERTICALFLIP) ? mo->ceilingz-mo->height : mo->floorz);
-			}
 			break;
 		case MT_GOOP:
 			if (P_CheckDeathPitCollide(mo))
@@ -3325,7 +3318,7 @@ void P_MobjCheckWater(mobj_t *mobj)
 	ffloor_t *rover;
 	player_t *p = mobj->player; // Will just be null if not a player.
 	fixed_t height = (p ? P_GetPlayerHeight(p) : mobj->height); // for players, calculation height does not necessarily match actual height for gameplay reasons (spin, etc)
-	boolean wasgroundpounding = (p && (mobj->eflags & MFE_GOOWATER) && ((p->powers[pw_shield] & SH_NOSTACK) == SH_ELEMENTAL || (p->powers[pw_shield] & SH_NOSTACK) == SH_BUBBLEWRAP) && (p->pflags & PF_SHIELDABILITY));
+	boolean wasgroundpounding = (p && ((p->powers[pw_shield] & SH_NOSTACK) == SH_ELEMENTAL || (p->powers[pw_shield] & SH_NOSTACK) == SH_BUBBLEWRAP) && (p->pflags & PF_SHIELDABILITY));
 
 	// Default if no water exists.
 	mobj->watertop = mobj->waterbottom = mobj->z - 1000*FRACUNIT;
@@ -3425,7 +3418,7 @@ void P_MobjCheckWater(mobj_t *mobj)
 			p->powers[pw_underwater] = underwatertics + 1;
 		}
 
-		if (wasgroundpounding)
+		if ((wasgroundpounding = ((mobj->eflags & MFE_GOOWATER) && wasgroundpounding)))
 		{
 			p->pflags &= ~PF_SHIELDABILITY;
 			mobj->momz >>= 1;
@@ -6948,7 +6941,7 @@ void P_RunOverlays(void)
 
 		mo->eflags = (mo->eflags & ~MFE_VERTICALFLIP) | (mo->target->eflags & MFE_VERTICALFLIP);
 		mo->scale = mo->destscale = mo->target->scale;
-		mo->angle = mo->target->angle;
+		mo->angle = mo->target->angle + mo->movedir;
 
 		if (!(mo->state->frame & FF_ANIMATE))
 			zoffs = FixedMul(((signed)mo->state->var2)*FRACUNIT, mo->scale);
@@ -9498,6 +9491,13 @@ void P_MobjThinker(mobj_t *mobj)
 
 					hdist = R_PointToDist2(mobj->x, mobj->y, mobj->target->x, mobj->target->y);
 
+					if (hdist > 1500*FRACUNIT)
+					{
+						mobj->flags2 &= ~MF2_BOSSNOTRAP;
+						P_SetTarget(&mobj->target, NULL);
+						break;
+					}
+
 					if (!(mobj->flags2 & MF2_BOSSNOTRAP) && hdist <= 450*FRACUNIT)
 						mobj->flags2 |= MF2_BOSSNOTRAP;
 
@@ -9517,11 +9517,6 @@ void P_MobjThinker(mobj_t *mobj)
 						mobj->momx = 0;
 						mobj->momy = 0;
 						mobj->momz = 0;
-						if (hdist >= 1500*FRACUNIT)
-						{
-							mobj->flags2 &= ~MF2_BOSSNOTRAP;
-							P_SetTarget(&mobj->target, NULL);
-						}
 					}
 					break;
 				}
@@ -9631,6 +9626,14 @@ void P_MobjThinker(mobj_t *mobj)
 					else
 						mobj->z = mobj->floorz;
 				}
+				else if ((!(mobj->eflags & MFE_VERTICALFLIP) && mobj->z <= mobj->floorz)
+				|| ((mobj->eflags & MFE_VERTICALFLIP) && mobj->z+mobj->height >= mobj->ceilingz))
+				{
+					mobj->flags |= MF_NOGRAVITY;
+					mobj->momx = 8; // this is a hack which is used to ensure it still behaves as a missile and can damage others
+					mobj->momy = mobj->momz = 0;
+					mobj->z = ((mobj->eflags & MFE_VERTICALFLIP) ? mobj->ceilingz-mobj->height : mobj->floorz);
+				}
 				/* FALLTHRU */
 			default:
 				// check mobj against possible water content, before movement code
@@ -10506,6 +10509,11 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type)
 			mobj->extravalue2 = 0;
 			mobj->fuse = 100;
 			break;
+		case MT_SIGN:
+			P_SetTarget(&mobj->tracer, P_SpawnMobjFromMobj(mobj, 0, 0, 0, MT_OVERLAY));
+			P_SetTarget(&mobj->tracer->target, mobj);
+			P_SetMobjState(mobj->tracer, S_SIGNBOARD);
+			mobj->tracer->movedir = ANGLE_90;
 		default:
 			break;
 	}
diff --git a/src/p_setup.c b/src/p_setup.c
index cef17663635907eaa19feb77a40a33b22a636dc5..d844a29e6b0b29247a30315b9f3ad037da7d5fcd 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -75,6 +75,7 @@
 #ifdef HWRENDER
 #include "hardware/hw_main.h"
 #include "hardware/hw_light.h"
+#include "hardware/hw_model.h"
 #endif
 
 #ifdef ESLOPE
@@ -546,52 +547,118 @@ size_t P_PrecacheLevelFlats(void)
 	//SoM: 4/18/2000: New flat code to make use of levelflats.
 	for (i = 0; i < numlevelflats; i++)
 	{
-		lump = levelflats[i].lumpnum;
-		if (devparm)
-			flatmemory += W_LumpLength(lump);
-		R_GetFlat(lump);
+		if (levelflats[i].type == LEVELFLAT_FLAT)
+		{
+			lump = levelflats[i].u.flat.lumpnum;
+			if (devparm)
+				flatmemory += W_LumpLength(lump);
+			R_GetFlat(lump);
+		}
 	}
 	return flatmemory;
 }
 
-// Auxiliary function. Find a flat in the active wad files,
-// allocate an id for it, and set the levelflat (to speedup search)
-INT32 P_AddLevelFlat(const char *flatname, levelflat_t *levelflat)
+/*
+levelflat refers to an array of level flats,
+or NULL if we want to allocate it now.
+*/
+static INT32
+Ploadflat (levelflat_t *levelflat, const char *flatname)
 {
-	size_t i;
-
-	// Scan through the already found flats, break if it matches.
-	for (i = 0; i < numlevelflats; i++, levelflat++)
-		if (strnicmp(levelflat->name, flatname, 8) == 0)
-			break;
+	UINT8         buffer[8];
 
-	// If there is no match, make room for a new flat.
-	if (i == numlevelflats)
-	{
-		// Store the name.
-		strlcpy(levelflat->name, flatname, sizeof (levelflat->name));
-		strupr(levelflat->name);
+	lumpnum_t    flatnum;
+	int       texturenum;
 
-		// store the flat lump number
-		levelflat->lumpnum = R_GetFlatNumForName(flatname);
-		levelflat->texturenum = R_CheckTextureNumForName(flatname);
-		levelflat->lasttexturenum = levelflat->texturenum;
+	size_t i;
 
-		levelflat->baselumpnum = LUMPERROR;
-		levelflat->basetexturenum = -1;
+	if (levelflat)
+	{
+		// Scan through the already found flats, return if it matches.
+		for (i = 0; i < numlevelflats; i++)
+		{
+			if (strnicmp(levelflat[i].name, flatname, 8) == 0)
+				return i;
+		}
+	}
 
 #ifndef ZDEBUG
-		CONS_Debug(DBG_SETUP, "flat #%03d: %s\n", atoi(sizeu1(numlevelflats)), levelflat->name);
+	CONS_Debug(DBG_SETUP, "flat #%03d: %s\n", atoi(sizeu1(numlevelflats)), levelflat->name);
 #endif
 
-		numlevelflats++;
+	if (numlevelflats >= MAXLEVELFLATS)
+		I_Error("Too many flats in level\n");
 
-		if (numlevelflats >= MAXLEVELFLATS)
-			I_Error("Too many flats in level\n");
+	if (levelflat)
+		levelflat += numlevelflats;
+	else
+	{
+		// allocate new flat memory
+		levelflats = Z_Realloc(levelflats, (numlevelflats + 1) * sizeof(*levelflats), PU_LEVEL, NULL);
+		levelflat  = levelflats + numlevelflats;
 	}
 
-	// level flat id
-	return (INT32)i;
+	// Store the name.
+	strlcpy(levelflat->name, flatname, sizeof (levelflat->name));
+	strupr(levelflat->name);
+
+	/* If we can't find a flat, try looking for a texture! */
+	if (( flatnum = R_GetFlatNumForName(flatname) ) == LUMPERROR)
+	{
+		if (( texturenum = R_CheckTextureNumForName(flatname) ) == -1)
+		{
+			// check for REDWALL
+			if (( texturenum = R_CheckTextureNumForName("REDWALL") ) != -1)
+				goto texturefound;
+			// check for REDFLR
+			else if (( flatnum = R_GetFlatNumForName("REDFLR") ) != LUMPERROR)
+				goto flatfound;
+			// nevermind
+			levelflat->type = LEVELFLAT_NONE;
+		}
+		else
+		{
+texturefound:
+			levelflat->type = LEVELFLAT_TEXTURE;
+			levelflat->u.texture.    num = texturenum;
+			levelflat->u.texture.lastnum = texturenum;
+			/* start out unanimated */
+			levelflat->u.texture.basenum = -1;
+		}
+	}
+	else
+	{
+flatfound:
+		/* This could be a flat, patch, or PNG. */
+		if (R_CheckIfPatch(flatnum))
+			levelflat->type = LEVELFLAT_PATCH;
+		else
+		{
+#ifndef NO_PNG_LUMPS
+			/*
+			Only need eight bytes for PNG headers.
+			FIXME: Put this elsewhere.
+			*/
+			W_ReadLumpHeader(flatnum, buffer, 8, 0);
+			if (R_IsLumpPNG(buffer, W_LumpLength(flatnum)))
+				levelflat->type = LEVELFLAT_PNG;
+			else
+#endif/*NO_PNG_LUMPS*/
+				levelflat->type = LEVELFLAT_FLAT;/* phew */
+		}
+
+		levelflat->u.flat.    lumpnum = flatnum;
+		levelflat->u.flat.baselumpnum = LUMPERROR;
+	}
+
+	return ( numlevelflats++ );
+}
+
+// Auxiliary function. Find a flat in the active wad files,
+// allocate an id for it, and set the levelflat (to speedup search)
+INT32 P_AddLevelFlat(const char *flatname, levelflat_t *levelflat)
+{
+	return Ploadflat(levelflat, flatname);
 }
 
 // help function for Lua and $$$.sav reading
@@ -600,44 +667,7 @@ INT32 P_AddLevelFlat(const char *flatname, levelflat_t *levelflat)
 //
 INT32 P_AddLevelFlatRuntime(const char *flatname)
 {
-	size_t i;
-	levelflat_t *levelflat = levelflats;
-
-	//
-	//  first scan through the already found flats
-	//
-	for (i = 0; i < numlevelflats; i++, levelflat++)
-		if (strnicmp(levelflat->name,flatname,8)==0)
-			break;
-
-	// that flat was already found in the level, return the id
-	if (i == numlevelflats)
-	{
-		// allocate new flat memory
-		levelflats = Z_Realloc(levelflats, (numlevelflats + 1) * sizeof(*levelflats), PU_LEVEL, NULL);
-		levelflat = levelflats+i;
-
-		// store the name
-		strlcpy(levelflat->name, flatname, sizeof (levelflat->name));
-		strupr(levelflat->name);
-
-		// store the flat lump number
-		levelflat->lumpnum = R_GetFlatNumForName(flatname);
-		levelflat->texturenum = R_CheckTextureNumForName(flatname);
-		levelflat->lasttexturenum = levelflat->texturenum;
-
-		levelflat->baselumpnum = LUMPERROR;
-		levelflat->basetexturenum = -1;
-
-#ifndef ZDEBUG
-		CONS_Debug(DBG_SETUP, "flat #%03d: %s\n", atoi(sizeu1(numlevelflats)), levelflat->name);
-#endif
-
-		numlevelflats++;
-	}
-
-	// level flat id
-	return (INT32)i;
+	return Ploadflat(0, flatname);
 }
 
 // help function for $$$.sav checking
@@ -3472,6 +3502,10 @@ boolean P_AddWadFile(const char *wadfilename)
 	if (!mapsadded)
 		CONS_Printf(M_GetText("No maps added\n"));
 
+#ifdef HWRENDER
+	HWR_ReloadModels();
+#endif // HWRENDER
+
 	// reload status bar (warning should have valid player!)
 	if (gamestate == GS_LEVEL)
 		ST_Start();
diff --git a/src/p_setup.h b/src/p_setup.h
index 7e3a149eb882965e436dbc4ff4327d0fcefa8a00..d8787073b08c823630e4cc41d1484f3ec8bd2c3b 100644
--- a/src/p_setup.h
+++ b/src/p_setup.h
@@ -30,20 +30,51 @@ extern boolean levelloading;
 extern UINT8 levelfadecol;
 
 extern lumpnum_t lastloadedmaplumpnum; // for comparative savegame
+
+/* for levelflat type */
+enum
+{
+	LEVELFLAT_NONE,/* HOM time my friend */
+	LEVELFLAT_FLAT,
+	LEVELFLAT_PATCH,
+#ifndef NO_PNG_LUMPS
+	LEVELFLAT_PNG,
+#endif
+	LEVELFLAT_TEXTURE,
+};
+
 //
 // MAP used flats lookup table
 //
 typedef struct
 {
 	char name[9]; // resource name from wad
-	lumpnum_t lumpnum; // lump number of the flat
-	INT32 texturenum, lasttexturenum; // texture number of the flat
+
+	UINT8  type;
+	union
+	{
+		struct
+		{
+			lumpnum_t     lumpnum; // lump number of the flat
+			// for flat animation
+			lumpnum_t baselumpnum;
+		}
+		flat;
+		struct
+		{
+			INT32             num;
+			INT32         lastnum; // texture number of the flat
+			// for flat animation
+			INT32         basenum;
+		}
+		texture;
+	}
+	u;
+
 	UINT16 width, height;
 	fixed_t topoffset, leftoffset;
 
 	// for flat animation
-	lumpnum_t baselumpnum;
-	INT32 basetexturenum;
 	INT32 animseq; // start pos. in the anim sequence
 	INT32 numpics;
 	INT32 speed;
diff --git a/src/p_spec.c b/src/p_spec.c
index 7b23ecbe7ae22794eb8961399ec76b8f9e9aaf28..d6a8cb48e4979f2d14ecb29bbbe6b5549ad0e0f0 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -464,11 +464,11 @@ static inline void P_FindAnimatedFlat(INT32 animnum)
 	for (i = 0; i < numlevelflats; i++, foundflats++)
 	{
 		// is that levelflat from the flat anim sequence ?
-		if ((anims[animnum].istexture) && (foundflats->texturenum != 0 && foundflats->texturenum != -1)
-			&& ((UINT16)foundflats->texturenum >= startflatnum && (UINT16)foundflats->texturenum <= endflatnum))
+		if ((anims[animnum].istexture) && (foundflats->type == LEVELFLAT_TEXTURE)
+			&& ((UINT16)foundflats->u.texture.num >= startflatnum && (UINT16)foundflats->u.texture.num <= endflatnum))
 		{
-			foundflats->basetexturenum = startflatnum;
-			foundflats->animseq = foundflats->texturenum - startflatnum;
+			foundflats->u.texture.basenum = startflatnum;
+			foundflats->animseq = foundflats->u.texture.num - startflatnum;
 			foundflats->numpics = endflatnum - startflatnum + 1;
 			foundflats->speed = anims[animnum].speed;
 
@@ -476,10 +476,10 @@ static inline void P_FindAnimatedFlat(INT32 animnum)
 					atoi(sizeu1(i)), foundflats->name, foundflats->animseq,
 					foundflats->numpics,foundflats->speed);
 		}
-		else if (foundflats->lumpnum >= startflatnum && foundflats->lumpnum <= endflatnum)
+		else if (foundflats->u.flat.lumpnum >= startflatnum && foundflats->u.flat.lumpnum <= endflatnum)
 		{
-			foundflats->baselumpnum = startflatnum;
-			foundflats->animseq = foundflats->lumpnum - startflatnum;
+			foundflats->u.flat.baselumpnum = startflatnum;
+			foundflats->animseq = foundflats->u.flat.lumpnum - startflatnum;
 			foundflats->numpics = endflatnum - startflatnum + 1;
 			foundflats->speed = anims[animnum].speed;
 
@@ -4054,11 +4054,15 @@ void P_SetupSignExit(player_t *player)
 		if (thing->type != MT_SIGN)
 			continue;
 
+		if (!player->mo->target || player->mo->target->type != MT_SIGN)
+			P_SetTarget(&player->mo->target, thing);
+
 		if (thing->state != &states[thing->info->spawnstate])
 			continue;
 
 		P_SetTarget(&thing->target, player->mo);
-		P_SetMobjState(thing, S_SIGN1);
+		P_SetObjectMomZ(thing, 12*FRACUNIT, false);
+		P_SetMobjState(thing, S_SIGNSPIN1);
 		if (thing->info->seesound)
 			S_StartSound(thing, thing->info->seesound);
 
@@ -4079,11 +4083,15 @@ void P_SetupSignExit(player_t *player)
 		if (thing->type != MT_SIGN)
 			continue;
 
+		if (!player->mo->target || player->mo->target->type != MT_SIGN)
+			P_SetTarget(&player->mo->target, thing);
+
 		if (thing->state != &states[thing->info->spawnstate])
 			continue;
 
 		P_SetTarget(&thing->target, player->mo);
-		P_SetMobjState(thing, S_SIGN1);
+		P_SetObjectMomZ(thing, 12*FRACUNIT, false);
+		P_SetMobjState(thing, S_SIGNSPIN1);
 		if (thing->info->seesound)
 			S_StartSound(thing, thing->info->seesound);
 
@@ -5581,11 +5589,11 @@ void P_UpdateSpecials(void)
 		if (foundflats->speed) // it is an animated flat
 		{
 			// update the levelflat texture number
-			if (foundflats->basetexturenum != -1)
-				foundflats->texturenum = foundflats->basetexturenum + ((leveltime/foundflats->speed + foundflats->animseq) % foundflats->numpics);
+			if (foundflats->type == LEVELFLAT_TEXTURE)
+				foundflats->u.texture.num = foundflats->u.texture.basenum + ((leveltime/foundflats->speed + foundflats->animseq) % foundflats->numpics);
 			// update the levelflat lump number
-			else if (foundflats->baselumpnum != LUMPERROR)
-				foundflats->lumpnum = foundflats->baselumpnum + ((leveltime/foundflats->speed + foundflats->animseq) % foundflats->numpics);
+			else if ((foundflats->type == LEVELFLAT_FLAT) && (foundflats->u.flat.baselumpnum != LUMPERROR))
+				foundflats->u.flat.lumpnum = foundflats->u.flat.baselumpnum + ((leveltime/foundflats->speed + foundflats->animseq) % foundflats->numpics);
 		}
 	}
 }
diff --git a/src/p_user.c b/src/p_user.c
index eca60613e784c0ebf54cfb94f2ee37aeef6b84fd..6a67e329cc09072ec74d5c926ae3c2f6446d8e91 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -1240,6 +1240,8 @@ void P_GivePlayerLives(player_t *player, INT32 numlives)
 			numlives = (numlives + prevlives - player->lives);
 		}
 	}
+	else if (player->lives == INFLIVES)
+		return;
 
 	player->lives += numlives;
 
@@ -2223,8 +2225,8 @@ boolean P_PlayerHitFloor(player_t *player, boolean dorollstuff)
 	{
 		if (dorollstuff)
 		{
-			if ((player->charability2 == CA2_SPINDASH) && !(player->pflags & PF_THOKKED) && (player->cmd.buttons & BT_USE) && (FixedHypot(player->mo->momx, player->mo->momy) > (5*player->mo->scale)))
-				player->pflags |= PF_SPINNING;
+			if ((player->charability2 == CA2_SPINDASH) && !((player->pflags & (PF_SPINNING|PF_THOKKED)) == PF_THOKKED) && (player->cmd.buttons & BT_USE) && (FixedHypot(player->mo->momx, player->mo->momy) > (5*player->mo->scale)))
+				player->pflags = (player->pflags|PF_SPINNING) & ~PF_THOKKED;
 			else if (!(player->pflags & PF_STARTDASH))
 				player->pflags &= ~PF_SPINNING;
 		}
@@ -2261,7 +2263,7 @@ boolean P_PlayerHitFloor(player_t *player, boolean dorollstuff)
 				else if (!player->skidtime)
 					player->pflags &= ~PF_GLIDING;
 			}
-			else if (player->charability == CA_GLIDEANDCLIMB && player->pflags & PF_THOKKED && (~player->pflags) & PF_SHIELDABILITY)
+			else if (player->charability == CA_GLIDEANDCLIMB && player->pflags & PF_THOKKED && !(player->pflags & PF_SHIELDABILITY) && player->mo->state-states == S_PLAY_FALL)
 			{
 				if (player->mo->state-states != S_PLAY_GLIDE_LANDING)
 				{
@@ -5300,14 +5302,23 @@ static void P_DoJumpStuff(player_t *player, ticcmd_t *cmd)
 					// Now Knuckles-type abilities are checked.
 					if (!(player->pflags & PF_THOKKED) || player->charflags & SF_MULTIABILITY)
 					{
-						INT32 glidespeed = player->actionspd;
+						fixed_t glidespeed = FixedMul(player->actionspd, player->mo->scale);
+						fixed_t playerspeed = player->speed;
+
+						if (player->mo->eflags & MFE_UNDERWATER)
+						{
+							glidespeed >>= 1;
+							playerspeed >>= 1;
+							player->mo->momx = ((player->mo->momx - player->cmomx) >> 1) + player->cmomx;
+							player->mo->momy = ((player->mo->momy - player->cmomy) >> 1) + player->cmomy;
+						}
 
 						player->pflags |= PF_GLIDING|PF_THOKKED;
 						player->glidetime = 0;
 
 						P_SetPlayerMobjState(player->mo, S_PLAY_GLIDE);
-						if (player->speed < glidespeed)
-							P_Thrust(player->mo, player->mo->angle, glidespeed - player->speed);
+						if (playerspeed < glidespeed)
+							P_Thrust(player->mo, player->mo->angle, glidespeed - playerspeed);
 						player->pflags &= ~(PF_SPINNING|PF_STARTDASH);
 					}
 					break;
@@ -5764,7 +5775,7 @@ static void P_2dMovement(player_t *player)
 			movepushforward >>= 1; // Proper air movement
 
 		// Allow a bit of movement while spinning
-		if (player->pflags & PF_SPINNING)
+		if ((player->pflags & (PF_SPINNING|PF_THOKKED)) == PF_SPINNING)
 		{
 			if (!(player->pflags & PF_STARTDASH))
 				movepushforward = movepushforward/48;
@@ -5791,7 +5802,7 @@ static void P_3dMovement(player_t *player)
 	angle_t dangle; // replaces old quadrants bits
 	fixed_t normalspd = FixedMul(player->normalspeed, player->mo->scale);
 	boolean analogmove = false;
-	boolean spin = ((onground = P_IsObjectOnGround(player->mo)) && player->pflags & PF_SPINNING && (player->rmomx || player->rmomy) && !(player->pflags & PF_STARTDASH));
+	boolean spin = ((onground = P_IsObjectOnGround(player->mo)) && (player->pflags & (PF_SPINNING|PF_THOKKED)) == PF_SPINNING && (player->rmomx || player->rmomy) && !(player->pflags & PF_STARTDASH));
 	fixed_t oldMagnitude, newMagnitude;
 #ifdef ESLOPE
 	vector3_t totalthrust;
@@ -5967,7 +5978,12 @@ static void P_3dMovement(player_t *player)
 	if (player->climbing)
 	{
 		if (cmd->forwardmove)
-			P_SetObjectMomZ(player->mo, FixedDiv(cmd->forwardmove*FRACUNIT, 15*FRACUNIT>>1), false);
+		{
+			if (player->mo->eflags & MFE_UNDERWATER)
+				P_SetObjectMomZ(player->mo, FixedDiv(cmd->forwardmove*FRACUNIT, 10*FRACUNIT), false);
+			else
+				P_SetObjectMomZ(player->mo, FixedDiv(cmd->forwardmove*FRACUNIT, 15*FRACUNIT>>1), false);
+		}
 	}
 	else if (!analogmove
 		&& cmd->forwardmove != 0 && !(player->pflags & PF_GLIDING || player->exiting
@@ -5976,7 +5992,7 @@ static void P_3dMovement(player_t *player)
 		movepushforward = cmd->forwardmove * (thrustfactor * acceleration);
 
 		// Allow a bit of movement while spinning
-		if (player->pflags & PF_SPINNING)
+		if ((player->pflags & (PF_SPINNING|PF_THOKKED)) == PF_SPINNING)
 		{
 			if ((mforward && cmd->forwardmove > 0) || (mbackward && cmd->forwardmove < 0)
 			|| (player->pflags & PF_STARTDASH))
@@ -6001,7 +6017,12 @@ static void P_3dMovement(player_t *player)
 	}
 	// Sideways movement
 	if (player->climbing)
-		P_InstaThrust(player->mo, player->mo->angle-ANGLE_90, FixedDiv(cmd->sidemove*player->mo->scale, 15*FRACUNIT>>1));
+	{
+		if (player->mo->eflags & MFE_UNDERWATER)
+			P_InstaThrust(player->mo, player->mo->angle-ANGLE_90, FixedDiv(cmd->sidemove*player->mo->scale, 10*FRACUNIT));
+		else
+			P_InstaThrust(player->mo, player->mo->angle-ANGLE_90, FixedDiv(cmd->sidemove*player->mo->scale, 15*FRACUNIT>>1));
+	}
 	// Analog movement control
 	else if (analogmove)
 	{
@@ -6017,7 +6038,7 @@ static void P_3dMovement(player_t *player)
 			movepushforward = max(abs(cmd->sidemove), abs(cmd->forwardmove)) * (thrustfactor * acceleration);
 
 			// Allow a bit of movement while spinning
-			if (player->pflags & PF_SPINNING)
+			if ((player->pflags & (PF_SPINNING|PF_THOKKED)) == PF_SPINNING)
 			{
 				if ((mforward && cmd->forwardmove > 0) || (mbackward && cmd->forwardmove < 0)
 				|| (player->pflags & PF_STARTDASH))
@@ -6052,11 +6073,11 @@ static void P_3dMovement(player_t *player)
 		{
 			movepushside >>= 2; // proper air movement
 			// Reduce movepushslide even more if over "max" flight speed
-			if ((player->pflags & PF_SPINNING) || (player->powers[pw_tailsfly] && player->speed > topspeed))
+			if (((player->pflags & (PF_SPINNING|PF_THOKKED)) == PF_SPINNING) || (player->powers[pw_tailsfly] && player->speed > topspeed))
 				movepushside >>= 2;
 		}
 		// Allow a bit of movement while spinning
-		else if (player->pflags & PF_SPINNING)
+		else if ((player->pflags & (PF_SPINNING|PF_THOKKED)) == PF_SPINNING)
 		{
 			if (player->pflags & PF_STARTDASH)
 				movepushside = 0;
@@ -8594,6 +8615,9 @@ static void P_MovePlayer(player_t *player)
 	// Look for Quicksand!
 	if (CheckForQuicksand)
 		P_CheckQuicksand(player);
+
+	if (P_IsObjectOnGround(player->mo))
+		player->mo->pmomz = 0;
 }
 
 static void P_DoZoomTube(player_t *player)
@@ -9497,7 +9521,7 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 	fixed_t x, y, z, dist, distxy, distz, checkdist, viewpointx, viewpointy, camspeed, camdist, camheight, pviewheight, slopez = 0;
 	INT32 camrotate;
 	boolean camstill, cameranoclip, camorbit;
-	mobj_t *mo;
+	mobj_t *mo, *sign = NULL;
 	subsector_t *newsubsec;
 	fixed_t f1, f2;
 
@@ -9507,6 +9531,9 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 
 	mo = player->mo;
 
+	if (player->exiting && mo->target && mo->target->type == MT_SIGN)
+		sign = mo->target;
+
 	cameranoclip = (player->powers[pw_carry] == CR_NIGHTSMODE || player->pflags & PF_NOCLIP) || (mo->flags & (MF_NOCLIP|MF_NOCLIPHEIGHT)); // Noclipping player camera noclips too!!
 
 	if (!(player->climbing || (player->powers[pw_carry] == CR_NIGHTSMODE) || player->playerstate == PST_DEAD || tutorialmode))
@@ -9553,6 +9580,11 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 		focusangle = mo->angle;
 		focusaiming = 0;
 	}
+	else if (sign)
+	{
+		focusangle = FixedAngle(sign->spawnpoint->angle << FRACBITS) + ANGLE_180;
+		focusaiming = 0;
+	}
 	else if (player == &players[consoleplayer])
 	{
 		focusangle = localangle;
@@ -9701,6 +9733,12 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 			camheight = FixedMul(camheight, 6*FRACUNIT/5);
 		}
 
+		if (sign)
+		{
+			camheight = mo->scale << 7;
+			camspeed = FRACUNIT/12;
+		}
+
 		if (player->climbing || player->exiting || player->playerstate == PST_DEAD || (player->powers[pw_carry] == CR_ROPEHANG || player->powers[pw_carry] == CR_GENERIC || player->powers[pw_carry] == CR_MACESPIN))
 			dist <<= 1;
 	}
@@ -9747,8 +9785,16 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 		distz = slopez;
 	}
 
-	x = mo->x - FixedMul(FINECOSINE((angle>>ANGLETOFINESHIFT) & FINEMASK), distxy);
-	y = mo->y - FixedMul(FINESINE((angle>>ANGLETOFINESHIFT) & FINEMASK), distxy);
+	if (sign)
+	{
+		x = sign->x - FixedMul(FINECOSINE((angle>>ANGLETOFINESHIFT) & FINEMASK), distxy);
+		y = sign->y - FixedMul(FINESINE((angle>>ANGLETOFINESHIFT) & FINEMASK), distxy);
+	}
+	else
+	{
+		x = mo->x - FixedMul(FINECOSINE((angle>>ANGLETOFINESHIFT) & FINEMASK), distxy);
+		y = mo->y - FixedMul(FINESINE((angle>>ANGLETOFINESHIFT) & FINEMASK), distxy);
+	}
 
 #if 0
 	if (twodlevel || (mo->flags2 & MF2_TWOD))
@@ -9993,14 +10039,30 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall
 	// point viewed by the camera
 	// this point is just 64 unit forward the player
 	dist = FixedMul(64 << FRACBITS, mo->scale);
-	viewpointx = mo->x + FixedMul(FINECOSINE((angle>>ANGLETOFINESHIFT) & FINEMASK), dist);
-	viewpointy = mo->y + FixedMul(FINESINE((angle>>ANGLETOFINESHIFT) & FINEMASK), dist);
+	if (sign)
+	{
+		viewpointx = sign->x + FixedMul(FINECOSINE((angle>>ANGLETOFINESHIFT) & FINEMASK), dist);
+		viewpointy = sign->y + FixedMul(FINESINE((angle>>ANGLETOFINESHIFT) & FINEMASK), dist);
+	}
+	else
+	{
+		viewpointx = mo->x + FixedMul(FINECOSINE((angle>>ANGLETOFINESHIFT) & FINEMASK), dist);
+		viewpointy = mo->y + FixedMul(FINESINE((angle>>ANGLETOFINESHIFT) & FINEMASK), dist);
+	}
 
 	if (!camstill && !resetcalled && !paused)
 		thiscam->angle = R_PointToAngle2(thiscam->x, thiscam->y, viewpointx, viewpointy);
 
-	viewpointx = mo->x + FixedMul(FINECOSINE((angle>>ANGLETOFINESHIFT) & FINEMASK), dist);
-	viewpointy = mo->y + FixedMul(FINESINE((angle>>ANGLETOFINESHIFT) & FINEMASK), dist);
+	if (sign)
+	{
+		viewpointx = sign->x + FixedMul(FINECOSINE((angle>>ANGLETOFINESHIFT) & FINEMASK), dist);
+		viewpointy = sign->y + FixedMul(FINESINE((angle>>ANGLETOFINESHIFT) & FINEMASK), dist);
+	}
+	else
+	{
+		viewpointx = mo->x + FixedMul(FINECOSINE((angle>>ANGLETOFINESHIFT) & FINEMASK), dist);
+		viewpointy = mo->y + FixedMul(FINESINE((angle>>ANGLETOFINESHIFT) & FINEMASK), dist);
+	}
 
 /*
 	if (twodlevel || (mo->flags2 & MF2_TWOD))
@@ -11821,8 +11883,8 @@ void P_PlayerThink(player_t *player)
 	player->pflags &= ~PF_SLIDING;
 
 #define dashmode player->dashmode
-	// Dash mode - thanks be to Iceman404
-	if ((player->charflags & SF_DASHMODE) && !(player->gotflag) && !(maptol & TOL_NIGHTS) && !metalrecording) // woo, dashmode! no nights tho.
+	// Dash mode - thanks be to VelocitOni
+	if ((player->charflags & SF_DASHMODE) && !player->gotflag && !player->powers[pw_carry] && !player->exiting && !(maptol & TOL_NIGHTS) && !metalrecording) // woo, dashmode! no nights tho.
 	{
 		boolean totallyradical = player->speed >= FixedMul(player->runspeed, player->mo->scale);
 		boolean floating = (player->secondjump == 1);
@@ -11832,12 +11894,16 @@ void P_PlayerThink(player_t *player)
 			if (dashmode < DASHMODE_MAX)
 				dashmode++; // Counter. Adds 1 to dash mode per tic in top speed.
 			if (dashmode == DASHMODE_THRESHOLD) // This isn't in the ">=" equation because it'd cause the sound to play infinitely.
-				S_StartSound(player->mo, sfx_s3ka2); // If the player enters dashmode, play this sound on the the tic it starts.
+				S_StartSound(player->mo, (player->charflags & SF_MACHINE) ? sfx_kc4d : sfx_cdfm40); // If the player enters dashmode, play this sound on the the tic it starts.
 		}
 		else if ((!totallyradical || !floating) && !(player->pflags & PF_SPINNING))
 		{
 			if (dashmode > 3)
+			{
 				dashmode -= 3; // Rather than lose it all, it gently counts back down!
+				if ((dashmode+3) >= DASHMODE_THRESHOLD && dashmode < DASHMODE_THRESHOLD)
+					S_StartSound(player->mo, sfx_kc65);
+			}
 			else
 				dashmode = 0;
 		}
@@ -11868,6 +11934,7 @@ void P_PlayerThink(player_t *player)
 		{
 			player->normalspeed = skins[player->skin].normalspeed;
 			player->jumpfactor = skins[player->skin].jumpfactor;
+			S_StartSound(player->mo, sfx_kc65);
 		}
 		dashmode = 0;
 	}
@@ -12395,9 +12462,6 @@ void P_PlayerAfterThink(player_t *player)
 		player->mo->flags |= MF_NOGRAVITY;
 	}
 
-	if (P_IsObjectOnGround(player->mo))
-		player->mo->pmomz = 0;
-
 	if (player->followmobj && (player->spectator || player->mo->health <= 0 || player->followmobj->type != player->followitem))
 	{
 		P_RemoveMobj(player->followmobj);
diff --git a/src/r_data.c b/src/r_data.c
index d37f7822d84b8e49b2ca964b33cc09869b38662f..5a5783074334fba0b91b79b9ff54c2e8d1d154c8 100644
--- a/src/r_data.c
+++ b/src/r_data.c
@@ -456,10 +456,11 @@ static UINT8 *R_GenerateTexture(size_t texnum)
 	texture_t *texture;
 	texpatch_t *patch;
 	patch_t *realpatch;
+	boolean dealloc = false;
 	int x, x1, x2, i, width, height;
 	size_t blocksize;
 	column_t *patchcol;
-	UINT32 *colofs;
+	UINT8 *colofs;
 
 	UINT16 wadnum;
 	lumpnum_t lumpnum;
@@ -487,19 +488,16 @@ static UINT8 *R_GenerateTexture(size_t texnum)
 
 #ifndef NO_PNG_LUMPS
 		if (R_IsLumpPNG((UINT8 *)realpatch, lumplength))
-		{
-			realpatch = R_PNGToPatch((UINT8 *)realpatch, lumplength, NULL, false);
 			goto multipatch;
-		}
 #endif
 
 		// Check the patch for holes.
 		if (texture->width > SHORT(realpatch->width) || texture->height > SHORT(realpatch->height))
 			holey = true;
-		colofs = (UINT32 *)realpatch->columnofs;
+		colofs = (UINT8 *)realpatch->columnofs;
 		for (x = 0; x < texture->width && !holey; x++)
 		{
-			column_t *col = (column_t *)((UINT8 *)realpatch + LONG(colofs[x]));
+			column_t *col = (column_t *)((UINT8 *)realpatch + LONG(*(UINT32 *)&colofs[x<<2]));
 			INT32 topdelta, prevdelta = -1, y = 0;
 			while (col->topdelta != 0xff)
 			{
@@ -528,19 +526,19 @@ static UINT8 *R_GenerateTexture(size_t texnum)
 			texturememory += blocksize;
 
 			// use the patch's column lookup
-			colofs = (UINT32 *)(void *)(block + 8);
-			texturecolumnofs[texnum] = colofs;
+			colofs = (block + 8);
+			texturecolumnofs[texnum] = (UINT32 *)colofs;
 			blocktex = block;
 			if (patch->flip & 1) // flip the patch horizontally
 			{
-				UINT32 *realcolofs = (UINT32 *)realpatch->columnofs;
+				UINT8 *realcolofs = (UINT8 *)realpatch->columnofs;
 				for (x = 0; x < texture->width; x++)
-					colofs[x] = realcolofs[texture->width-1-x]; // swap with the offset of the other side of the texture
+					*(UINT32 *)&colofs[x<<2] = realcolofs[( texture->width-1-x )<<2]; // swap with the offset of the other side of the texture
 			}
 			// we can't as easily flip the patch vertically sadly though,
 			//  we have wait until the texture itself is drawn to do that
 			for (x = 0; x < texture->width; x++)
-				colofs[x] = LONG(LONG(colofs[x]) + 3);
+				*(UINT32 *)&colofs[x<<2] = LONG(LONG(*(UINT32 *)&colofs[x<<2]) + 3);
 			goto done;
 		}
 
@@ -560,8 +558,8 @@ static UINT8 *R_GenerateTexture(size_t texnum)
 	memset(block, TRANSPARENTPIXEL, blocksize+1); // Transparency hack
 
 	// columns lookup table
-	colofs = (UINT32 *)(void *)block;
-	texturecolumnofs[texnum] = colofs;
+	colofs = block;
+	texturecolumnofs[texnum] = (UINT32 *)colofs;
 
 	// texture data after the lookup table
 	blocktex = block + (texture->width*4);
@@ -579,9 +577,14 @@ static UINT8 *R_GenerateTexture(size_t texnum)
 		lumpnum = patch->lump;
 		lumplength = W_LumpLengthPwad(wadnum, lumpnum);
 		realpatch = W_CacheLumpNumPwad(wadnum, lumpnum, PU_CACHE);
+		dealloc = false;
+
 #ifndef NO_PNG_LUMPS
 		if (R_IsLumpPNG((UINT8 *)realpatch, lumplength))
+		{
 			realpatch = R_PNGToPatch((UINT8 *)realpatch, lumplength, NULL, false);
+			dealloc = true;
+		}
 #endif
 
 		x1 = patch->originx;
@@ -616,9 +619,12 @@ static UINT8 *R_GenerateTexture(size_t texnum)
 				patchcol = (column_t *)((UINT8 *)realpatch + LONG(realpatch->columnofs[x-x1]));
 
 			// generate column ofset lookup
-			colofs[x] = LONG((x * texture->height) + (texture->width*4));
-			ColumnDrawerPointer(patchcol, block + LONG(colofs[x]), patch, texture->height, height);
+			*(UINT32 *)&colofs[x<<2] = LONG((x * texture->height) + (texture->width*4));
+			ColumnDrawerPointer(patchcol, block + LONG(*(UINT32 *)&colofs[x<<2]), patch, texture->height, height);
 		}
+
+		if (dealloc)
+			Z_Free(realpatch);
 	}
 
 done:
@@ -1450,48 +1456,6 @@ lumpnum_t R_GetFlatNumForName(const char *name)
 		lump = LUMPERROR;
 	}
 
-	// Detect textures
-	if (lump == LUMPERROR)
-	{
-		// Scan wad files backwards so patched textures take preference.
-		for (i = numwadfiles - 1; i >= 0; i--)
-		{
-			switch (wadfiles[i]->type)
-			{
-			case RET_WAD:
-				if ((start = W_CheckNumForNamePwad("TX_START", (UINT16)i, 0)) == INT16_MAX)
-					continue;
-				if ((end = W_CheckNumForNamePwad("TX_END", (UINT16)i, start)) == INT16_MAX)
-					continue;
-				break;
-			case RET_PK3:
-				if ((start = W_CheckNumForFolderStartPK3("Textures/", i, 0)) == INT16_MAX)
-					continue;
-				if ((end = W_CheckNumForFolderEndPK3("Textures/", i, start)) == INT16_MAX)
-					continue;
-				break;
-			default:
-				continue;
-			}
-
-			// Now find lump with specified name in that range.
-			lump = W_CheckNumForNamePwad(name, (UINT16)i, start);
-			if (lump < end)
-			{
-				lump += (i<<16); // found it, in our constraints
-				break;
-			}
-			lump = LUMPERROR;
-		}
-	}
-
-	if (lump == LUMPERROR)
-	{
-		if (strcmp(name, SKYFLATNAME))
-			CONS_Debug(DBG_SETUP, "R_GetFlatNumForName: Could not find flat %.8s\n", name);
-		lump = W_CheckNumForName("REDFLR");
-	}
-
 	return lump;
 }
 
diff --git a/src/r_main.c b/src/r_main.c
index 9d9d1c39a8c6826b2597070f5fb3d986183b438c..3ed509af568565c95c67a443ab76b5625ff4c840 100644
--- a/src/r_main.c
+++ b/src/r_main.c
@@ -1208,7 +1208,6 @@ void R_RegisterEngineStuff(void)
 	CV_RegisterVar(&cv_grgammared);
 	CV_RegisterVar(&cv_grfovchange);
 	CV_RegisterVar(&cv_grfog);
-	CV_RegisterVar(&cv_voodoocompatibility);
 	CV_RegisterVar(&cv_grfogcolor);
 	CV_RegisterVar(&cv_grsoftwarefog);
 #ifdef ALAM_LIGHTING
@@ -1217,7 +1216,8 @@ void R_RegisterEngineStuff(void)
 	CV_RegisterVar(&cv_grcoronas);
 	CV_RegisterVar(&cv_grcoronasize);
 #endif
-	CV_RegisterVar(&cv_grmd2);
+	CV_RegisterVar(&cv_grmodelinterpolation);
+	CV_RegisterVar(&cv_grmodels);
 	CV_RegisterVar(&cv_grspritebillboarding);
 	CV_RegisterVar(&cv_grskydome);
 #endif
diff --git a/src/r_plane.c b/src/r_plane.c
index 9f417ee6bff85de8aa409b6d6db677d33b2bff2f..53b58c274386a242f095e777347163072af9f146 100644
--- a/src/r_plane.c
+++ b/src/r_plane.c
@@ -756,9 +756,9 @@ static UINT8 *R_GenerateFlat(UINT16 width, UINT16 height)
 static UINT8 *R_GetTextureFlat(levelflat_t *levelflat, boolean leveltexture, boolean ispng)
 {
 	UINT8 *flat;
-	textureflat_t *texflat = &texflats[levelflat->texturenum];
+	textureflat_t *texflat = &texflats[levelflat->u.texture.num];
 	patch_t *patch = NULL;
-	boolean texturechanged = (leveltexture ? (levelflat->texturenum != levelflat->lasttexturenum) : false);
+	boolean texturechanged = (leveltexture ? (levelflat->u.texture.num != levelflat->u.texture.lastnum) : false);
 
 	// Check if the texture changed.
 	if (leveltexture && (!texturechanged))
@@ -780,12 +780,12 @@ static UINT8 *R_GetTextureFlat(levelflat_t *levelflat, boolean leveltexture, boo
 		// Level texture
 		if (leveltexture)
 		{
-			texture_t *texture = textures[levelflat->texturenum];
+			texture_t *texture = textures[levelflat->u.texture.num];
 			texflat->width = ds_flatwidth = texture->width;
 			texflat->height = ds_flatheight = texture->height;
 
 			texflat->flat = R_GenerateFlat(ds_flatwidth, ds_flatheight);
-			R_TextureToFlat(levelflat->texturenum, texflat->flat);
+			R_TextureToFlat(levelflat->u.texture.num, texflat->flat);
 			flat = texflat->flat;
 
 			levelflat->flatpatch = flat;
@@ -799,7 +799,7 @@ static UINT8 *R_GetTextureFlat(levelflat_t *levelflat, boolean leveltexture, boo
 #ifndef NO_PNG_LUMPS
 			if (ispng)
 			{
-				levelflat->flatpatch = R_PNGToFlat(&levelflat->width, &levelflat->height, ds_source, W_LumpLength(levelflat->lumpnum));
+				levelflat->flatpatch = R_PNGToFlat(&levelflat->width, &levelflat->height, ds_source, W_LumpLength(levelflat->u.flat.lumpnum));
 				levelflat->topoffset = levelflat->leftoffset = 0;
 				ds_flatwidth = levelflat->width;
 				ds_flatheight = levelflat->height;
@@ -829,7 +829,7 @@ static UINT8 *R_GetTextureFlat(levelflat_t *levelflat, boolean leveltexture, boo
 	xoffs += levelflat->leftoffset;
 	yoffs += levelflat->topoffset;
 
-	levelflat->lasttexturenum = levelflat->texturenum;
+	levelflat->u.texture.lastnum = levelflat->u.texture.num;
 	return flat;
 }
 
@@ -839,10 +839,9 @@ void R_DrawSinglePlane(visplane_t *pl)
 	INT32 light = 0;
 	INT32 x;
 	INT32 stop, angle;
-	size_t size;
 	ffloor_t *rover;
 	levelflat_t *levelflat;
-	boolean rawflat = false;
+	int type;
 
 	if (!(pl->minx <= pl->maxx))
 		return;
@@ -996,43 +995,45 @@ void R_DrawSinglePlane(visplane_t *pl)
 
 	currentplane = pl;
 	levelflat = &levelflats[pl->picnum];
-	size = W_LumpLength(levelflat->lumpnum);
-	ds_source = (UINT8 *)W_CacheLumpNum(levelflat->lumpnum, PU_STATIC); // Stay here until Z_ChangeTag
 
-	// Check if the flat is actually a wall texture.
-	if (levelflat->texturenum != 0 && levelflat->texturenum != -1)
-		flat = R_GetTextureFlat(levelflat, true, false);
+	/* :james: */
+	type = levelflat->type;
+	switch (type)
+	{
+		case LEVELFLAT_NONE:
+			return;
+		case LEVELFLAT_FLAT:
+			ds_source = W_CacheLumpNum(levelflat->u.flat.lumpnum, PU_CACHE);
+			R_CheckFlatLength(W_LumpLength(levelflat->u.flat.lumpnum));
+			// Raw flats always have dimensions that are powers-of-two numbers.
+			ds_powersoftwo = true;
+			break;
+		default:
+			switch (type)
+			{
+				case LEVELFLAT_TEXTURE:
+					/* Textures get cached differently and don't need ds_source */
+					ds_source = R_GetTextureFlat(levelflat, true, false);
+					break;
+				default:
+					ds_source = W_CacheLumpNum(levelflat->u.flat.lumpnum, PU_STATIC);
+					flat      = R_GetTextureFlat(levelflat, false,
 #ifndef NO_PNG_LUMPS
-	// Maybe it's a PNG?!
-	else if (R_IsLumpPNG(ds_source, size))
-		flat = R_GetTextureFlat(levelflat, false, true);
+							( type == LEVELFLAT_PNG )
+#else
+							false
 #endif
-	// Maybe it's just a patch, then?
-	else if (R_CheckIfPatch(levelflat->lumpnum))
-		flat = R_GetTextureFlat(levelflat, false, false);
-	// It's a raw flat.
-	else
-	{
-		rawflat = true;
-		R_CheckFlatLength(size);
-		flat = ds_source;
-	}
-
-	Z_ChangeTag(ds_source, PU_CACHE);
-	ds_source = flat;
-
-	if (ds_source == NULL)
-		return;
-
-	// Raw flats always have dimensions that are powers-of-two numbers.
-	if (rawflat)
-		ds_powersoftwo = true;
-	// Otherwise, check if this texture or patch has such dimensions.
-	else if (R_CheckPowersOfTwo())
-	{
-		R_CheckFlatLength(ds_flatwidth * ds_flatheight);
-		if (spanfunc == basespanfunc)
-			spanfunc = mmxspanfunc;
+					);
+					Z_ChangeTag(ds_source, PU_CACHE);
+					ds_source = flat;
+			}
+			// Check if this texture or patch has power-of-two dimensions.
+			if (R_CheckPowersOfTwo())
+			{
+				R_CheckFlatLength(ds_flatwidth * ds_flatheight);
+				if (spanfunc == basespanfunc)
+					spanfunc = mmxspanfunc;
+			}
 	}
 
 	if (light >= LIGHTLEVELS)
diff --git a/src/r_things.c b/src/r_things.c
index 22f9c5d89cb0126321d397861b9d69d669b1af09..b2818806f2983a2cff221f61b3e9e1e95dbd1d67 100644
--- a/src/r_things.c
+++ b/src/r_things.c
@@ -400,10 +400,6 @@ void R_AddSpriteDefs(UINT16 wadnum)
 		start = W_CheckNumForNamePwad("S_START", wadnum, 0);
 		if (start == INT16_MAX)
 			start = W_CheckNumForNamePwad("SS_START", wadnum, 0); //deutex compatib.
-		if (start == INT16_MAX)
-			start = 0; //let say S_START is lump 0
-		else
-			start++;   // just after S_START
 
 		end = W_CheckNumForNamePwad("S_END",wadnum,start);
 		if (end == INT16_MAX)
@@ -417,9 +413,16 @@ void R_AddSpriteDefs(UINT16 wadnum)
 		return;
 	}
 
-	// ignore skin wads (we don't want skin sprites interfering with vanilla sprites)
-	if (start == 0 && W_CheckNumForNamePwad("S_SKIN", wadnum, 0) != UINT16_MAX)
-		return;
+	if (start == INT16_MAX)
+	{
+		// ignore skin wads (we don't want skin sprites interfering with vanilla sprites)
+		if (W_CheckNumForNamePwad("S_SKIN", wadnum, 0) != UINT16_MAX)
+			return;
+
+		start = 0; //let say S_START is lump 0
+	}
+	else
+		start++;   // just after S_START
 
 	if (end == INT16_MAX)
 	{
@@ -441,7 +444,7 @@ void R_AddSpriteDefs(UINT16 wadnum)
 		{
 #ifdef HWRENDER
 			if (rendermode == render_opengl)
-				HWR_AddSpriteMD2(i);
+				HWR_AddSpriteModel(i);
 #endif
 			// if a new sprite was added (not just replaced)
 			addsprites++;
@@ -755,10 +758,13 @@ static void R_DrawVisSprite(vissprite_t *vis)
 			dc_translation = R_GetTranslationColormap(TC_RAINBOW, vis->mobj->color, GTC_CACHE);
 		else if (!(vis->cut & SC_PRECIP)
 			&& vis->mobj->player && vis->mobj->player->dashmode >= DASHMODE_THRESHOLD
-			&& (vis->mobj->player->charflags & (SF_DASHMODE|SF_MACHINE)) == (SF_DASHMODE|SF_MACHINE)
+			&& (vis->mobj->player->charflags & SF_DASHMODE)
 			&& ((leveltime/2) & 1))
 		{
-			dc_translation = R_GetTranslationColormap(TC_DASHMODE, 0, GTC_CACHE);
+			if (vis->mobj->player->charflags & SF_MACHINE)
+				dc_translation = R_GetTranslationColormap(TC_DASHMODE, 0, GTC_CACHE);
+			else
+				dc_translation = R_GetTranslationColormap(TC_RAINBOW, vis->mobj->color, GTC_CACHE);
 		}
 		else if (!(vis->cut & SC_PRECIP) && vis->mobj->skin && vis->mobj->sprite == SPR_PLAY) // MT_GHOST LOOKS LIKE A PLAYER SO USE THE PLAYER TRANSLATION TABLES. >_>
 		{
@@ -783,10 +789,13 @@ static void R_DrawVisSprite(vissprite_t *vis)
 			dc_translation = R_GetTranslationColormap(TC_RAINBOW, vis->mobj->color, GTC_CACHE);
 		else if (!(vis->cut & SC_PRECIP)
 			&& vis->mobj->player && vis->mobj->player->dashmode >= DASHMODE_THRESHOLD
-			&& (vis->mobj->player->charflags & (SF_DASHMODE|SF_MACHINE)) == (SF_DASHMODE|SF_MACHINE)
+			&& (vis->mobj->player->charflags & SF_DASHMODE)
 			&& ((leveltime/2) & 1))
 		{
-			dc_translation = R_GetTranslationColormap(TC_DASHMODE, 0, GTC_CACHE);
+			if (vis->mobj->player->charflags & SF_MACHINE)
+				dc_translation = R_GetTranslationColormap(TC_DASHMODE, 0, GTC_CACHE);
+			else
+				dc_translation = R_GetTranslationColormap(TC_RAINBOW, vis->mobj->color, GTC_CACHE);
 		}
 		else if (!(vis->cut & SC_PRECIP) && vis->mobj->skin && vis->mobj->sprite == SPR_PLAY) // This thing is a player!
 		{
@@ -2551,7 +2560,7 @@ UINT8 P_GetSkinSprite2(skin_t *skin, UINT8 spr2, player_t *player)
 	if ((playersprite_t)(spr2 & ~FF_SPR2SUPER) >= free_spr2)
 		return 0;
 
-	while (!(skin->sprites[spr2].numframes)
+	while (!skin->sprites[spr2].numframes
 		&& spr2 != SPR2_STND
 		&& ++i < 32) // recursion limiter
 	{
@@ -3181,7 +3190,7 @@ next_token:
 
 #ifdef HWRENDER
 		if (rendermode == render_opengl)
-			HWR_AddPlayerMD2(numskins);
+			HWR_AddPlayerModel(numskins);
 #endif
 
 		numskins++;
diff --git a/src/s_sound.c b/src/s_sound.c
index 6134e338c2649f5114531364275c70ff518498df..8b87bd08328b9d00e0de2a0f0d4bbbc5b4f9d2e8 100644
--- a/src/s_sound.c
+++ b/src/s_sound.c
@@ -64,6 +64,8 @@ static void ModFilter_OnChange(void);
 
 static lumpnum_t S_GetMusicLumpNum(const char *mname);
 
+static boolean S_CheckQueue(void);
+
 // commands for music and sound servers
 #ifdef MUSSERV
 consvar_t musserver_cmd = {"musserver_cmd", "musserver", CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
@@ -1613,13 +1615,14 @@ static void S_AddMusicStackEntry(const char *mname, UINT16 mflags, boolean loopi
 	if (!music_stacks)
 	{
 		music_stacks = Z_Calloc(sizeof (*mst), PU_MUSIC, NULL);
-		strncpy(music_stacks->musname, (status == JT_MASTER ? mname : mapmusname), 7);
-		music_stacks->musflags = (status == JT_MASTER ? mflags : mapmusflags);
-		music_stacks->looping = (status == JT_MASTER ? looping : true);
-		music_stacks->position = (status == JT_MASTER ? position : S_GetMusicPosition());
+		strncpy(music_stacks->musname, (status == JT_MASTER ? mname : (S_CheckQueue() ? queue_name : mapmusname)), 7);
+		music_stacks->musflags = (status == JT_MASTER ? mflags : (S_CheckQueue() ? queue_flags : mapmusflags));
+		music_stacks->looping = (status == JT_MASTER ? looping : (S_CheckQueue() ? queue_looping : true));
+		music_stacks->position = (status == JT_MASTER ? position : (S_CheckQueue() ? queue_position : S_GetMusicPosition()));
 		music_stacks->tic = gametic;
 		music_stacks->status = JT_MASTER;
 		music_stacks->mlumpnum = S_GetMusicLumpNum(music_stacks->musname);
+		music_stacks->noposition = S_CheckQueue();
 
 		if (status == JT_MASTER)
 			return; // we just added the user's entry here
@@ -1638,6 +1641,7 @@ static void S_AddMusicStackEntry(const char *mname, UINT16 mflags, boolean loopi
 	new_mst->tic = gametic;
 	new_mst->status = status;
 	new_mst->mlumpnum = S_GetMusicLumpNum(new_mst->musname);
+	new_mst->noposition = false;
 
 	mst->next = new_mst;
 	new_mst->prev = mst;
@@ -1745,11 +1749,23 @@ boolean S_RecallMusic(UINT16 status, boolean fromfirst)
 		entry->tic = gametic;
 		entry->status = JT_MASTER;
 		entry->mlumpnum = S_GetMusicLumpNum(entry->musname);
+		entry->noposition = false; // don't set this until we do the mapmuschanged check, below. Else, this breaks some resumes.
 	}
 
 	if (entry->status == JT_MASTER)
 	{
 		mapmuschanged = strnicmp(entry->musname, mapmusname, 7);
+		if (mapmuschanged)
+		{
+			strncpy(entry->musname, mapmusname, 7);
+			entry->musflags = mapmusflags;
+			entry->looping = true;
+			entry->position = mapmusposition;
+			entry->tic = gametic;
+			entry->status = JT_MASTER;
+			entry->mlumpnum = S_GetMusicLumpNum(entry->musname);
+			entry->noposition = true;
+		}
 		S_ResetMusicStack();
 	}
 	else if (!entry->status)
@@ -1758,7 +1774,7 @@ boolean S_RecallMusic(UINT16 status, boolean fromfirst)
 		return false;
 	}
 
-	if (!mapmuschanged && strncmp(entry->musname, S_MusicName(), 7)) // don't restart music if we're already playing it
+	if (strncmp(entry->musname, S_MusicName(), 7)) // don't restart music if we're already playing it
 	{
 		if (music_stack_fadeout)
 			S_ChangeMusicEx(entry->musname, entry->musflags, entry->looping, 0, music_stack_fadeout, 0);
@@ -1766,7 +1782,7 @@ boolean S_RecallMusic(UINT16 status, boolean fromfirst)
 		{
 			S_ChangeMusicEx(entry->musname, entry->musflags, entry->looping, 0, 0, music_stack_fadein);
 
-			if (!music_stack_noposition) // HACK: Global boolean to toggle position resuming, e.g., de-superize
+			if (!entry->noposition && !music_stack_noposition) // HACK: Global boolean to toggle position resuming, e.g., de-superize
 			{
 				UINT32 poslapse = 0;
 
@@ -1908,6 +1924,11 @@ static void S_QueueMusic(const char *mmusic, UINT16 mflags, boolean looping, UIN
 	queue_fadeinms = fadeinms;
 }
 
+static boolean S_CheckQueue(void)
+{
+	return queue_name[0];
+}
+
 static void S_ClearQueue(void)
 {
 	queue_name[0] = queue_flags = queue_looping = queue_position = queue_fadeinms = 0;
diff --git a/src/s_sound.h b/src/s_sound.h
index 20b2489a5de6fdb75f0d8467b0b2058ee27d9324..c46c9fa089b40b02965c9dd142469543cf1f8fe2 100644
--- a/src/s_sound.h
+++ b/src/s_sound.h
@@ -220,6 +220,7 @@ typedef struct musicstack_s
 	tic_t tic;
 	UINT16 status;
 	lumpnum_t mlumpnum;
+	boolean noposition; // force music stack resuming from zero (like music_stack_noposition)
 
     struct musicstack_s *prev;
     struct musicstack_s *next;
diff --git a/src/sdl/Srb2SDL-vc10.vcxproj b/src/sdl/Srb2SDL-vc10.vcxproj
index 72c38b3dc090061f370d6822d7050912ed2f34ca..0bdc26a127171ba73bcb3e4b692768475fe598a4 100644
--- a/src/sdl/Srb2SDL-vc10.vcxproj
+++ b/src/sdl/Srb2SDL-vc10.vcxproj
@@ -229,6 +229,10 @@
     <ClInclude Include="..\hardware\hw_light.h" />
     <ClInclude Include="..\hardware\hw_main.h" />
     <ClInclude Include="..\hardware\hw_md2.h" />
+    <ClInclude Include="..\hardware\hw_md2load.h" />
+    <ClInclude Include="..\hardware\hw_md3load.h" />
+    <ClInclude Include="..\hardware\hw_model.h" />
+    <ClInclude Include="..\hardware\u_list.h" />
     <ClInclude Include="..\hu_stuff.h" />
     <ClInclude Include="..\info.h" />
     <ClInclude Include="..\i_addrinfo.h" />
@@ -368,8 +372,12 @@
     <ClCompile Include="..\hardware\hw_light.c" />
     <ClCompile Include="..\hardware\hw_main.c" />
     <ClCompile Include="..\hardware\hw_md2.c" />
+    <ClCompile Include="..\hardware\hw_md2load.c" />
+    <ClCompile Include="..\hardware\hw_md3load.c" />
+    <ClCompile Include="..\hardware\hw_model.c" />
     <ClCompile Include="..\hardware\hw_trick.c" />
     <ClCompile Include="..\hardware\r_opengl\r_opengl.c" />
+    <ClCompile Include="..\hardware\u_list.c" />
     <ClCompile Include="..\hu_stuff.c" />
     <ClCompile Include="..\info.c" />
     <ClCompile Include="..\i_addrinfo.c">
diff --git a/src/sdl/Srb2SDL-vc10.vcxproj.filters b/src/sdl/Srb2SDL-vc10.vcxproj.filters
index 9e442000fdc1a5ad0a2e6643cd06eb99ddd8cf11..a2110822edbbe45eb4bcb016b3bed02c33c09784 100644
--- a/src/sdl/Srb2SDL-vc10.vcxproj.filters
+++ b/src/sdl/Srb2SDL-vc10.vcxproj.filters
@@ -246,6 +246,18 @@
     <ClInclude Include="..\hardware\hw_md2.h">
       <Filter>Hw_Hardware</Filter>
     </ClInclude>
+    <ClInclude Include="..\hardware\hw_md2load.h">
+      <Filter>Hw_Hardware</Filter>
+    </ClInclude>
+    <ClInclude Include="..\hardware\hw_md3load.h">
+      <Filter>Hw_Hardware</Filter>
+    </ClInclude>
+    <ClInclude Include="..\hardware\hw_model.h">
+      <Filter>Hw_Hardware</Filter>
+    </ClInclude>
+    <ClInclude Include="..\hardware\u_list.h">
+      <Filter>Hw_Hardware</Filter>
+    </ClInclude>
     <ClInclude Include="..\byteptr.h">
       <Filter>I_Interface</Filter>
     </ClInclude>
@@ -630,9 +642,21 @@
     <ClCompile Include="..\hardware\hw_md2.c">
       <Filter>Hw_Hardware</Filter>
     </ClCompile>
+    <ClCompile Include="..\hardware\hw_md2load.c">
+      <Filter>Hw_Hardware</Filter>
+    </ClCompile>
+    <ClCompile Include="..\hardware\hw_md3load.c">
+      <Filter>Hw_Hardware</Filter>
+    </ClCompile>
+    <ClCompile Include="..\hardware\hw_model.c">
+      <Filter>Hw_Hardware</Filter>
+    </ClCompile>
     <ClCompile Include="..\hardware\hw_trick.c">
       <Filter>Hw_Hardware</Filter>
     </ClCompile>
+    <ClCompile Include="..\hardware\u_list.c">
+      <Filter>Hw_Hardware</Filter>
+    </ClCompile>
     <ClCompile Include="..\filesrch.c">
       <Filter>I_Interface</Filter>
     </ClCompile>
diff --git a/src/sdl/hwsym_sdl.c b/src/sdl/hwsym_sdl.c
index 103398405f1e98518b65157859f39a981836fca1..5f040023a8032e1086ebec70f8a28891a0b741e1 100644
--- a/src/sdl/hwsym_sdl.c
+++ b/src/sdl/hwsym_sdl.c
@@ -88,13 +88,11 @@ void *hwSym(const char *funcName,void *handle)
 	GETFUNC(ClearMipMapCache);
 	GETFUNC(SetSpecialState);
 	GETFUNC(GetTextureUsed);
-	GETFUNC(DrawMD2);
-	GETFUNC(DrawMD2i);
+	GETFUNC(DrawModel);
+	GETFUNC(CreateModelVBOs);
 	GETFUNC(SetTransform);
 	GETFUNC(GetRenderVersion);
-#ifdef SHUFFLE
 	GETFUNC(PostImgRedraw);
-#endif //SHUFFLE
 	GETFUNC(FlushScreenTextures);
 	GETFUNC(StartScreenWipe);
 	GETFUNC(EndScreenWipe);
diff --git a/src/sdl/i_video.c b/src/sdl/i_video.c
index 57591af104922eecc821dea8d3973545b6db7775..fb0f4b2ba3eaea01e6881a77f2717672860cb21f 100644
--- a/src/sdl/i_video.c
+++ b/src/sdl/i_video.c
@@ -1654,13 +1654,11 @@ void I_StartupGraphics(void)
 		HWD.pfnSetSpecialState  = hwSym("SetSpecialState",NULL);
 		HWD.pfnSetPalette       = hwSym("SetPalette",NULL);
 		HWD.pfnGetTextureUsed   = hwSym("GetTextureUsed",NULL);
-		HWD.pfnDrawMD2          = hwSym("DrawMD2",NULL);
-		HWD.pfnDrawMD2i         = hwSym("DrawMD2i",NULL);
+		HWD.pfnDrawModel        = hwSym("DrawModel",NULL);
+		HWD.pfnCreateModelVBOs  = hwSym("CreateModelVBOs",NULL);
 		HWD.pfnSetTransform     = hwSym("SetTransform",NULL);
 		HWD.pfnGetRenderVersion = hwSym("GetRenderVersion",NULL);
-#ifdef SHUFFLE
 		HWD.pfnPostImgRedraw    = hwSym("PostImgRedraw",NULL);
-#endif
 		HWD.pfnFlushScreenTextures=hwSym("FlushScreenTextures",NULL);
 		HWD.pfnStartScreenWipe  = hwSym("StartScreenWipe",NULL);
 		HWD.pfnEndScreenWipe    = hwSym("EndScreenWipe",NULL);
diff --git a/src/sounds.c b/src/sounds.c
index 8dc97b1e635f302c7b45eb32c00926b3cef68600..791a7571e432b23343fcb39b63ca5a6d9c86bd7f 100644
--- a/src/sounds.c
+++ b/src/sounds.c
@@ -693,7 +693,7 @@ sfxinfo_t S_sfx[NUMSFX] =
   {"cdfm37", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
   {"cdfm38", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Drowning"},
   {"cdfm39", false, 128,  8, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
-  {"cdfm40", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"cdfm40", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Power up"},
   {"cdfm41", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
   {"cdfm42", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
   {"cdfm43", false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
@@ -780,7 +780,7 @@ sfxinfo_t S_sfx[NUMSFX] =
   {"kc4a",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
   {"kc4b",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
   {"kc4c",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
-  {"kc4d",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc4d",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Power up"},
   {"kc4e",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
   {"kc4f",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
   {"kc50",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
@@ -804,7 +804,7 @@ sfxinfo_t S_sfx[NUMSFX] =
   {"kc62",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
   {"kc63",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
   {"kc64",   false,  96,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Terrifying rumble"},
-  {"kc65",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
+  {"kc65",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, "Power down"},
   {"kc66",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
   {"kc67",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
   {"kc68",   false,  64,  0, -1, NULL, 0,        -1,  -1, LUMPERROR, ""},
diff --git a/src/v_video.c b/src/v_video.c
index 34d64cb04bca5c1d33119ff767038965bf2cd665..7f99cfd326c8089b878e4624a90c9f0e295edf87 100644
--- a/src/v_video.c
+++ b/src/v_video.c
@@ -89,8 +89,8 @@ static void CV_Gammaxxx_ONChange(void);
 // but they won't do anything.
 static CV_PossibleValue_t grgamma_cons_t[] = {{1, "MIN"}, {255, "MAX"}, {0, NULL}};
 static CV_PossibleValue_t grsoftwarefog_cons_t[] = {{0, "Off"}, {1, "On"}, {2, "LightPlanes"}, {0, NULL}};
+static CV_PossibleValue_t grmodelinterpolation_cons_t[] = {{0, "Off"}, {1, "Sometimes"}, {2, "Always"}, {0, NULL}};
 
-consvar_t cv_voodoocompatibility = {"gr_voodoocompatibility", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_grfovchange = {"gr_fovchange", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_grfog = {"gr_fog", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_grfogcolor = {"gr_fogcolor", "AAAAAA", CV_SAVE, NULL, NULL, 0, NULL, NULL, 0, 0, NULL};
@@ -108,9 +108,9 @@ consvar_t cv_grcoronas = {"gr_coronas", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL,
 consvar_t cv_grcoronasize = {"gr_coronasize", "1", CV_SAVE| CV_FLOAT, 0, NULL, 0, NULL, NULL, 0, 0, NULL};
 #endif
 
-static CV_PossibleValue_t CV_MD2[] = {{0, "Off"}, {1, "On"}, {2, "Old"}, {0, NULL}};
-// console variables in development
-consvar_t cv_grmd2 = {"gr_md2", "Off", CV_SAVE, CV_MD2, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_grmodels = {"gr_models", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
+consvar_t cv_grmodelinterpolation = {"gr_modelinterpolation", "Sometimes", CV_SAVE, grmodelinterpolation_cons_t, NULL, 0, NULL, NULL, 0, 0, NULL};
+
 consvar_t cv_grspritebillboarding = {"gr_spritebillboarding", "Off", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 consvar_t cv_grskydome = {"gr_skydome", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL};
 #endif
diff --git a/src/w_wad.c b/src/w_wad.c
index 51c77fd8783d7439bb88b9775adb93c57546d0a3..ea9310580a08dd2e6d97302023d06d96866e4f6c 100644
--- a/src/w_wad.c
+++ b/src/w_wad.c
@@ -1565,22 +1565,22 @@ void *W_CachePatchNumPwad(UINT16 wad, UINT16 lump, INT32 tag)
 
 	grPatch = HWR_GetCachedGLPatchPwad(wad, lump);
 
-	if (grPatch->mipmap.grInfo.data)
+	if (grPatch->mipmap->grInfo.data)
 	{
 		if (tag == PU_CACHE)
 			tag = PU_HWRCACHE;
-		Z_ChangeTag(grPatch->mipmap.grInfo.data, tag);
+		Z_ChangeTag(grPatch->mipmap->grInfo.data, tag);
 	}
 	else
 	{
 		patch_t *ptr = NULL;
 
 		// Only load the patch if we haven't initialised the grPatch yet
-		if (grPatch->mipmap.width == 0)
+		if (grPatch->mipmap->width == 0)
 			ptr = W_CacheLumpNumPwad(grPatch->wadnum, grPatch->lumpnum, PU_STATIC);
 
 		// Run HWR_MakePatch in all cases, to recalculate some things
-		HWR_MakePatch(ptr, grPatch, &grPatch->mipmap, false);
+		HWR_MakePatch(ptr, grPatch, grPatch->mipmap, false);
 		Z_Free(ptr);
 	}
 
@@ -1679,12 +1679,12 @@ void W_VerifyFileMD5(UINT16 wadfilenum, const char *matchmd5)
 	{
 		char actualmd5text[2*MD5_LEN+1];
 		PrintMD5String(wadfiles[wadfilenum]->md5sum, actualmd5text);
-#ifdef _DEBUG
+/*#ifdef _DEBUG
 		CONS_Printf
 #else
 		I_Error
 #endif
-			(M_GetText("File is corrupt or has been modified: %s (found md5: %s, wanted: %s)\n"), wadfiles[wadfilenum]->filename, actualmd5text, matchmd5);
+			(M_GetText("File is corrupt or has been modified: %s (found md5: %s, wanted: %s)\n"), wadfiles[wadfilenum]->filename, actualmd5text, matchmd5);*/
 	}
 #endif
 }
diff --git a/src/win32/Srb2win-vc10.vcxproj b/src/win32/Srb2win-vc10.vcxproj
index c0fe8eda9ad5f2d73f7a0c2cd7f1a2023a02dd0e..dea71925c09796e93ae577fad417b9a2bbc3d3a2 100644
--- a/src/win32/Srb2win-vc10.vcxproj
+++ b/src/win32/Srb2win-vc10.vcxproj
@@ -231,7 +231,11 @@
     <ClCompile Include="..\hardware\hw_light.c" />
     <ClCompile Include="..\hardware\hw_main.c" />
     <ClCompile Include="..\hardware\hw_md2.c" />
+    <ClCompile Include="..\hardware\hw_md2load.c" />
+    <ClCompile Include="..\hardware\hw_md3load.c" />
+    <ClCompile Include="..\hardware\hw_model.c" />
     <ClCompile Include="..\hardware\hw_trick.c" />
+    <ClCompile Include="..\hardware\u_list.c" />
     <ClCompile Include="..\hu_stuff.c" />
     <ClCompile Include="..\info.c" />
     <ClCompile Include="..\i_addrinfo.c">
@@ -396,6 +400,10 @@
     <ClInclude Include="..\hardware\hw_light.h" />
     <ClInclude Include="..\hardware\hw_main.h" />
     <ClInclude Include="..\hardware\hw_md2.h" />
+    <ClInclude Include="..\hardware\hw_md2load.h" />
+    <ClInclude Include="..\hardware\hw_md3load.h" />
+    <ClInclude Include="..\hardware\hw_model.h" />
+    <ClInclude Include="..\hardware\u_list.h" />
     <ClInclude Include="..\hu_stuff.h" />
     <ClInclude Include="..\info.h" />
     <ClInclude Include="..\i_addrinfo.h" />
diff --git a/src/win32/Srb2win-vc10.vcxproj.filters b/src/win32/Srb2win-vc10.vcxproj.filters
index 93806e3951066fff4172cdc9f60a0315171e74f4..c7e93ddd9a257feb994a025d4ffd74b328b723f2 100644
--- a/src/win32/Srb2win-vc10.vcxproj.filters
+++ b/src/win32/Srb2win-vc10.vcxproj.filters
@@ -453,6 +453,18 @@
     <ClCompile Include="..\string.c">
       <Filter>M_Misc</Filter>
     </ClCompile>
+    <ClCompile Include="..\hardware\hw_md2load.c">
+      <Filter>Hw_Hardware</Filter>
+    </ClCompile>
+    <ClCompile Include="..\hardware\hw_md3load.c">
+      <Filter>Hw_Hardware</Filter>
+    </ClCompile>
+    <ClCompile Include="..\hardware\hw_model.c">
+      <Filter>Hw_Hardware</Filter>
+    </ClCompile>
+    <ClCompile Include="..\hardware\u_list.c">
+      <Filter>Hw_Hardware</Filter>
+    </ClCompile>
     <ClCompile Include="..\hardware\hw_clip.c">
       <Filter>Hw_Hardware</Filter>
     </ClCompile>
@@ -513,6 +525,15 @@
     <ClInclude Include="..\hardware\hw_md2.h">
       <Filter>Hw_Hardware</Filter>
     </ClInclude>
+    <ClInclude Include="..\hardware\hw_md2load.h">
+      <Filter>Hw_Hardware</Filter>
+    </ClInclude>
+    <ClInclude Include="..\hardware\hw_md3load.h">
+      <Filter>Hw_Hardware</Filter>
+    </ClInclude>
+    <ClInclude Include="..\hardware\hw_model.h">
+      <Filter>Hw_Hardware</Filter>
+    </ClInclude>
     <ClInclude Include="..\hardware\hw3dsdrv.h">
       <Filter>Hw_Hardware</Filter>
     </ClInclude>
@@ -522,6 +543,9 @@
     <ClInclude Include="..\hardware\hws_data.h">
       <Filter>Hw_Hardware</Filter>
     </ClInclude>
+    <ClInclude Include="..\hardware\u_list.h">
+      <Filter>Hw_Hardware</Filter>
+    </ClInclude>
     <ClInclude Include="..\blua\lapi.h">
       <Filter>BLUA</Filter>
     </ClInclude>
diff --git a/src/win32/win_dll.c b/src/win32/win_dll.c
index 4ea792a250d353596a20dfb67b006b9ada06ad7f..5378bb52f645bc10abc0c58dcf27d38f15d9b85a 100644
--- a/src/win32/win_dll.c
+++ b/src/win32/win_dll.c
@@ -110,8 +110,7 @@ static loadfunc_t hwdFuncTable[] = {
 	{"GClipRect@20",        &hwdriver.pfnGClipRect},
 	{"ClearMipMapCache@0",  &hwdriver.pfnClearMipMapCache},
 	{"SetSpecialState@8",   &hwdriver.pfnSetSpecialState},
-	{"DrawMD2@16",          &hwdriver.pfnDrawMD2},
-	{"DrawMD2i@36",         &hwdriver.pfnDrawMD2i},
+	{"DrawModel@16",          &hwdriver.pfnDrawModel},
 	{"SetTransform@4",      &hwdriver.pfnSetTransform},
 	{"GetTextureUsed@0",    &hwdriver.pfnGetTextureUsed},
 	{"GetRenderVersion@0",  &hwdriver.pfnGetRenderVersion},
@@ -142,8 +141,7 @@ static loadfunc_t hwdFuncTable[] = {
 	{"GClipRect",           &hwdriver.pfnGClipRect},
 	{"ClearMipMapCache",    &hwdriver.pfnClearMipMapCache},
 	{"SetSpecialState",     &hwdriver.pfnSetSpecialState},
-	{"DrawMD2",             &hwdriver.pfnDrawMD2},
-	{"DrawMD2i",            &hwdriver.pfnDrawMD2i},
+	{"DrawModel",           &hwdriver.pfnDrawModel},
 	{"SetTransform",        &hwdriver.pfnSetTransform},
 	{"GetTextureUsed",      &hwdriver.pfnGetTextureUsed},
 	{"GetRenderVersion",    &hwdriver.pfnGetRenderVersion},
diff --git a/src/y_inter.c b/src/y_inter.c
index 209ab5014946e30875d36cc28b3d55d5ccc709b5..21e4bc56e480503693286014246ea7dbc0c08679 100644
--- a/src/y_inter.c
+++ b/src/y_inter.c
@@ -2131,7 +2131,7 @@ static void Y_AwardSpecialStageBonus(void)
 
 	data.spec.score = players[consoleplayer].score;
 	memset(data.spec.bonuses, 0, sizeof(data.spec.bonuses));
-	memset(data.spec.bonuspatches, 0, sizeof(data.coop.bonuspatches));
+	memset(data.spec.bonuspatches, 0, sizeof(data.spec.bonuspatches));
 
 	for (i = 0; i < MAXPLAYERS; i++)
 	{
diff --git a/src/z_zone.c b/src/z_zone.c
index 001c69bb3c9f3adc1a689537a2fc155d042bcb43..d5146f7a3a6ee7d8346ae2180421832455aee9c2 100644
--- a/src/z_zone.c
+++ b/src/z_zone.c
@@ -247,7 +247,11 @@ void Z_Free(void *ptr)
 static void *xm(size_t size)
 {
 	const size_t padedsize = size+sizeof (size_t);
-	void *p = malloc(padedsize);
+	void *p;
+
+	if (padedsize < size)/* overflow check */
+		I_Error("You are allocating memory too large!");
+	p = malloc(padedsize);
 
 	if (p == NULL)
 	{
@@ -295,6 +299,9 @@ void *Z_MallocAlign(size_t size, INT32 tag, void *user, INT32 alignbits)
 	CONS_Debug(DBG_MEMORY, "Z_Malloc %s:%d\n", file, line);
 #endif
 
+	if (blocksize < size)/* overflow check */
+		I_Error("You are allocating memory too large!");
+
 	block = xm(sizeof *block);
 #ifdef HAVE_VALGRIND
 	padsize += (1<<sizeof(size_t))*2;