diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 7482477f1db7342de6243372eef9f938ff3898e3..424d64f5428a9019bb001d8dbba6eb607314d102 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -202,7 +202,7 @@ Debian testing GCC:
     - - |
           # cmake
           echo -e "\e[0Ksection_start:`date +%s`:cmake[collapsed=false]\r\e[0KBuilding Makefiles"
-      - cmake -B build.camke -DCPM_USE_LOCAL_PACKAGES:BOOL=ON -DSRB2_CONFIG_ENABLE_TESTS:BOOL=OFF -DSRB2_CONFIG_SYSTEM_LIBRARIES:BOOL=ON -DSRB2_USE_LIBGME:BOOL=OFF -DSRB2_CONFIG_ENABLE_WEBM_MOVIES=OFF -G "Unix Makefiles"
+      - cmake -B build.cmake -DSRB2_CONFIG_ENABLE_WEBM_MOVIES=OFF -G "Unix Makefiles"
       - |
           # cmake
           echo -e "\e[0Ksection_end:`date +%s`:cmake\r\e[0K"
@@ -218,17 +218,19 @@ Debian testing GCC:
 Windows x86:
   stage: build
 
-  when: manual
+  when: on_success
 
   artifacts:
     paths:
-      - "build.cmake/bin/"
-      - "build.cmake/src/config.h"
+      - "build/ninja-x86_mingw_static_vcpkg-debug/bin/"
+      - "build/ninja-x86_mingw_static_vcpkg-debug/src/config.h"
     expose_as: "Win32"
     name: "$CI_PROJECT_PATH_SLUG-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA-Win32"
 
   variables:
     PREFIX: i686-w64-mingw32
+    CC: /usr/bin/i686-w64-mingw32-gcc-posix
+    CXX: /usr/bin/i686-w64-mingw32-g++-posix
 
   script:
     - - |
@@ -239,10 +241,19 @@ Windows x86:
           # apt_toolchain
           echo -e "\e[0Ksection_end:`date +%s`:apt_toolchain\r\e[0K"
 
+    - - |
+          # apt_development
+          echo -e "\e[0Ksection_start:`date +%s`:apt_development[collapsed=true]\r\e[0KInstalling development packages"
+      - apt-get install ninja-build
+      - |
+          # apt_development
+          echo -e "\e[0Ksection_end:`date +%s`:apt_development\r\e[0K"
+
     - - |
           # cmake
           echo -e "\e[0Ksection_start:`date +%s`:cmake[collapsed=false]\r\e[0KBuilding Makefiles"
-      - cmake -B build.cmake -DCPM_USE_LOCAL_PACKAGES:BOOL=ON -DSRB2_CONFIG_ENABLE_TESTS:BOOL=OFF -DSRB2_CONFIG_SYSTEM_LIBRARIES:BOOL=ON -DSRB2_USE_LIBGME:BOOL=OFF -DSRB2_CONFIG_ENABLE_WEBM_MOVIES=OFF -G "Unix Makefiles"
+      - cmake --preset ninja-x86_mingw_static_vcpkg-debug -DVCPKG_TARGET_TRIPLET=x86-mingw-static -DCMAKE_C_COMPILER=/usr/bin/i686-w64-mingw32-gcc-posix -DCMAKE_CXX_COMPILER=/usr/bin/i686-w64-mingw32-g++-posix -G "Unix Makefiles" -DVCPKG_CHAINLOAD_TOOLCHAIN_FILE=$VCPKG_ROOT/scripts/toolchains/mingw.cmake
+
       - |
           # cmake
           echo -e "\e[0Ksection_end:`date +%s`:cmake\r\e[0K"
@@ -250,7 +261,7 @@ Windows x86:
     - - |
           # make
           echo -e "\e[0Ksection_start:`date +%s`:make[collapsed=false]\r\e[0KCompiling SRB2"
-      - make --directory=build.cmake --keep-going || make --directory=build.cmake --keep-going
+      - cmake --build --preset ninja-x86_mingw_static_vcpkg-debug -- -k -j8 || cmake --build --preset ninja-x86_mingw_static_vcpkg-debug -- -k
       - |
           # make
           echo -e "\e[0Ksection_end:`date +%s`:make\r\e[0K"
@@ -294,7 +305,7 @@ Debian stable:amd64:
     - - |
           # cmake
           echo -e "\e[0Ksection_start:`date +%s`:cmake[collapsed=false]\r\e[0KBuilding Makefiles"
-      - cmake -B build.cmake -DCPM_USE_LOCAL_PACKAGES:BOOL=ON -DSRB2_CONFIG_ENABLE_TESTS:BOOL=OFF -DSRB2_CONFIG_SYSTEM_LIBRARIES:BOOL=ON -DSRB2_USE_LIBGME:BOOL=OFF -DSRB2_CONFIG_ENABLE_WEBM_MOVIES=OFF -G "Unix Makefiles"
+      - cmake -B build.cmake -DSRB2_CONFIG_ENABLE_WEBM_MOVIES=OFF -G "Unix Makefiles"
       - |
           # cmake
           echo -e "\e[0Ksection_end:`date +%s`:cmake\r\e[0K"
@@ -343,7 +354,7 @@ Debian oldstable:amd64:
     - - |
           # cmake
           echo -e "\e[0Ksection_start:`date +%s`:cmake[collapsed=false]\r\e[0KBuilding Makefiles"
-      - cmake -B build.cmake -DCPM_USE_LOCAL_PACKAGES:BOOL=ON -DSRB2_CONFIG_ENABLE_TESTS:BOOL=OFF -DSRB2_CONFIG_SYSTEM_LIBRARIES:BOOL=ON -DSRB2_USE_LIBGME:BOOL=OFF -DSRB2_CONFIG_ENABLE_WEBM_MOVIES=OFF -G "Unix Makefiles"
+      - cmake -B build.cmake -DSRB2_CONFIG_ENABLE_WEBM_MOVIES=OFF -G "Unix Makefiles"
       - |
           # cmake
           echo -e "\e[0Ksection_end:`date +%s`:cmake\r\e[0K"
@@ -394,7 +405,7 @@ Debian stable:i386:
     - - |
           # cmake
           echo -e "\e[0Ksection_start:`date +%s`:cmake[collapsed=false]\r\e[0KBuilding Makefiles"
-      - cmake -B build.cmake -DCPM_USE_LOCAL_PACKAGES:BOOL=ON -DSRB2_CONFIG_ENABLE_TESTS:BOOL=OFF -DSRB2_CONFIG_SYSTEM_LIBRARIES:BOOL=ON -DSRB2_USE_LIBGME:BOOL=OFF -DSRB2_CONFIG_ENABLE_WEBM_MOVIES=OFF -G "Unix Makefiles"
+      - cmake -B build.cmake -DSRB2_CONFIG_ENABLE_WEBM_MOVIES=OFF -G "Unix Makefiles"
       - |
           # cmake
           echo -e "\e[0Ksection_end:`date +%s`:cmake\r\e[0K"
@@ -447,7 +458,7 @@ Debian stable:arm64:
     - - |
           # cmake
           echo -e "\e[0Ksection_start:`date +%s`:cmake[collapsed=false]\r\e[0KBuilding Makefiles"
-      - cmake -B build.cmake -DCPM_USE_LOCAL_PACKAGES:BOOL=ON -DSRB2_CONFIG_ENABLE_TESTS:BOOL=OFF -DSRB2_CONFIG_SYSTEM_LIBRARIES:BOOL=ON -DSRB2_USE_LIBGME:BOOL=OFF -DSRB2_CONFIG_ENABLE_WEBM_MOVIES=OFF -G "Unix Makefiles"
+      - cmake -B build.cmake -DSRB2_CONFIG_ENABLE_WEBM_MOVIES=OFF -G "Unix Makefiles"
       - |
           # cmake
           echo -e "\e[0Ksection_end:`date +%s`:cmake\r\e[0K"
@@ -496,7 +507,7 @@ Debian oldstable:arm64:
     - - |
           # cmake
           echo -e "\e[0Ksection_start:`date +%s`:cmake[collapsed=false]\r\e[0KBuilding Makefiles"
-      - cmake -B build.cmake -DCPM_USE_LOCAL_PACKAGES:BOOL=ON -DSRB2_CONFIG_ENABLE_TESTS:BOOL=OFF -DSRB2_CONFIG_SYSTEM_LIBRARIES:BOOL=ON -DSRB2_USE_LIBGME:BOOL=OFF -DSRB2_CONFIG_ENABLE_WEBM_MOVIES=OFF -G "Unix Makefiles"
+      - cmake -B build.cmake -DSRB2_CONFIG_ENABLE_WEBM_MOVIES=OFF -G "Unix Makefiles"
       - |
           # cmake
           echo -e "\e[0Ksection_end:`date +%s`:cmake\r\e[0K"
@@ -543,7 +554,7 @@ batocera:arm64:
     - - |
           # cmake
           echo -e "\e[0Ksection_start:`date +%s`:cmake[collapsed=false]\r\e[0KBuilding Makefiles"
-      - cmake -B build.cmake -DCPM_USE_LOCAL_PACKAGES:BOOL=ON -DSRB2_CONFIG_ENABLE_TESTS:BOOL=OFF -DSRB2_CONFIG_SYSTEM_LIBRARIES:BOOL=ON -DSRB2_USE_LIBGME:BOOL=OFF -DSRB2_CONFIG_ENABLE_WEBM_MOVIES=OFF -G "Unix Makefiles"
+      - cmake -B build.cmake -DSRB2_CONFIG_ENABLE_WEBM_MOVIES=OFF -G "Unix Makefiles"
       - |
           # cmake
           echo -e "\e[0Ksection_end:`date +%s`:cmake\r\e[0K"
@@ -561,10 +572,12 @@ Windows x64:
 
   when: manual
 
+  allow_failure: true
+
   artifacts:
     paths:
-      - "bin/"
-      - "src/comptime.h"
+      - "build.cmake/bin/"
+      - "build.cmake/src/config.h"
     expose_as: "Win64"
     name: "$CI_PROJECT_PATH_SLUG-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA-Win64"
 
@@ -580,10 +593,18 @@ Windows x64:
           # apt_toolchain
           echo -e "\e[0Ksection_end:`date +%s`:apt_toolchain\r\e[0K"
 
+    - - |
+          # apt_development
+          echo -e "\e[0Ksection_start:`date +%s`:apt_development[collapsed=true]\r\e[0KInstalling development packages"
+      - apt-get install ninja-build
+      - |
+          # apt_development
+          echo -e "\e[0Ksection_end:`date +%s`:apt_development\r\e[0K"
+
     - - |
           # cmake
           echo -e "\e[0Ksection_start:`date +%s`:cmake[collapsed=false]\r\e[0KBuilding Makefiles"
-      - cmake -B build.clang -DCPM_USE_LOCAL_PACKAGES:BOOL=ON -DSRB2_CONFIG_ENABLE_TESTS:BOOL=OFF -DSRB2_CONFIG_SYSTEM_LIBRARIES:BOOL=ON -DSRB2_USE_LIBGME:BOOL=OFF -DSRB2_CONFIG_ENABLE_WEBM_MOVIES=OFF -G "Unix Makefiles"
+      - cmake -B build.cmake -DSRB2_CONFIG_ENABLE_WEBM_MOVIES=OFF -DSRB2_CONFIG_ENABLE_DISCORDRPC=OFF -DCMAKE_TOOLCHAIN_FILE=$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-mingw-static -DVCPKG_CHAINLOAD_TOOLCHAIN_FILE=$VCPKG_ROOT/scripts/toolchains/mingw.cmake
       - |
           # cmake
           echo -e "\e[0Ksection_end:`date +%s`:cmake\r\e[0K"
@@ -637,7 +658,7 @@ Debian stable Clang:
     - - |
           # cmake
           echo -e "\e[0Ksection_start:`date +%s`:cmake[collapsed=false]\r\e[0KBuilding Makefiles"
-      - cmake -B build.clang -DCPM_USE_LOCAL_PACKAGES:BOOL=ON -DSRB2_CONFIG_ENABLE_TESTS:BOOL=OFF -DSRB2_CONFIG_SYSTEM_LIBRARIES:BOOL=ON -DSRB2_USE_LIBGME:BOOL=OFF -DSRB2_CONFIG_ENABLE_WEBM_MOVIES=OFF -G "Unix Makefiles"
+      - cmake -B build.clang -DSRB2_CONFIG_ENABLE_WEBM_MOVIES=OFF -G "Unix Makefiles"
       - |
           # cmake
           echo -e "\e[0Ksection_end:`date +%s`:cmake\r\e[0K"
@@ -780,7 +801,7 @@ Alpine 3 GCC:
     - - |
           # cmake
           echo -e "\e[0Ksection_start:`date +%s`:cmake[collapsed=false]\r\e[0KBuilding Makefiles"
-      - cmake -B build.alpine3 -DCPM_USE_LOCAL_PACKAGES:BOOL=ON -DSRB2_CONFIG_ENABLE_TESTS:BOOL=OFF -DSRB2_CONFIG_SYSTEM_LIBRARIES:BOOL=ON -DSRB2_USE_LIBGME:BOOL=OFF -DSRB2_CONFIG_ENABLE_WEBM_MOVIES=OFF -G "Unix Makefiles"
+      - cmake -B build.alpine3 -DSRB2_CONFIG_ENABLE_WEBM_MOVIES=OFF -G "Unix Makefiles"
       - |
           # cmake
           echo -e "\e[0Ksection_end:`date +%s`:cmake\r\e[0K"
@@ -841,7 +862,7 @@ Alpine 3 GCC Dedicated:
     - - |
           # cmake
           echo -e "\e[0Ksection_start:`date +%s`:cmake[collapsed=false]\r\e[0KBuilding Makefiles"
-      - cmake -B build.alpine3ded -DCPM_USE_LOCAL_PACKAGES:BOOL=ON -DSRB2_CONFIG_ENABLE_TESTS:BOOL=OFF -DSRB2_CONFIG_SYSTEM_LIBRARIES:BOOL=ON -DSRB2_USE_LIBGME:BOOL=OFF -DSRB2_CONFIG_ENABLE_WEBM_MOVIES=OFF -G "Unix Makefiles"
+      - cmake -B build.alpine3ded -DSRB2_CONFIG_ENABLE_WEBM_MOVIES=OFF -G "Unix Makefiles"
       - |
           # cmake
           echo -e "\e[0Ksection_end:`date +%s`:cmake\r\e[0K"
@@ -880,7 +901,7 @@ osxcross x86_64:
     - - |
           # cmake
           echo -e "\e[0Ksection_start:`date +%s`:cmake[collapsed=false]\r\e[0KBuilding Makefiles"
-      - cmake -B build.osxcross --toolchain /osxcross/toolchain.cmake -DCPM_USE_LOCAL_PACKAGES:BOOL=ON -DOPENMPT_INCLUDE_DIR:PATH="/osxcross/macports/pkgs/opt/local/include" -DSDL2_INCLUDE_DIR:PATH="/osxcross/macports/pkgs/opt/local/lib" -DSRB2_CONFIG_ENABLE_TESTS:BOOL=OFF -DSRB2_CONFIG_SYSTEM_LIBRARIES:BOOL=ON -DSRB2_CONFIG_USE_GME:BOOL=OFF -DSRB2_CONFIG_ENABLE_WEBM_MOVIES=OFF -G "Unix Makefiles"
+      - cmake -B build.osxcross --toolchain /osxcross/toolchain.cmake -DCPM_USE_LOCAL_PACKAGES:BOOL=ON -DOPENMPT_INCLUDE_DIR:PATH="/osxcross/macports/pkgs/opt/local/include" -DSDL2_INCLUDE_DIR:PATH="/osxcross/macports/pkgs/opt/local/lib" -DSRB2_CONFIG_ENABLE_WEBM_MOVIES=OFF -G "Unix Makefiles"
       - |
           # make
           echo -e "\e[0Ksection_end:`date +%s`:make\r\e[0K"
@@ -923,7 +944,7 @@ osxcross arm64:
     - - |
           # cmake
           echo -e "\e[0Ksection_start:`date +%s`:cmake[collapsed=false]\r\e[0KBuilding Makefiles"
-      - cmake -B build.osxcross --toolchain /osxcross/toolchain.cmake -DCPM_USE_LOCAL_PACKAGES:BOOL=ON -DOPENMPT_INCLUDE_DIR:PATH="/osxcross/macports/pkgs/opt/local/include" -DSDL2_INCLUDE_DIR:PATH="/osxcross/macports/pkgs/opt/local/lib" -DSRB2_CONFIG_ENABLE_TESTS:BOOL=OFF -DSRB2_CONFIG_SYSTEM_LIBRARIES:BOOL=ON -DSRB2_CONFIG_USE_GME:BOOL=OFF -DSRB2_CONFIG_ENABLE_WEBM_MOVIES=OFF -G "Unix Makefiles"
+      - cmake -B build.osxcross --toolchain /osxcross/toolchain.cmake -DCPM_USE_LOCAL_PACKAGES:BOOL=ON -DOPENMPT_INCLUDE_DIR:PATH="/osxcross/macports/pkgs/opt/local/include" -DSDL2_INCLUDE_DIR:PATH="/osxcross/macports/pkgs/opt/local/lib" -DSRB2_CONFIG_ENABLE_WEBM_MOVIES=OFF -G "Unix Makefiles"
       - |
           # make
           echo -e "\e[0Ksection_end:`date +%s`:make\r\e[0K"
diff --git a/src/acs/call-funcs.cpp b/src/acs/call-funcs.cpp
index 196c31d0174b38789dff0b93ba8899da2dfcfcfe..3283bb86510f8d1302f34f7557e7460281eaecb4 100644
--- a/src/acs/call-funcs.cpp
+++ b/src/acs/call-funcs.cpp
@@ -2026,7 +2026,7 @@ bool CallFunc_SetLineRenderStyle(ACSVM::Thread *thread, const ACSVM::Word *argV,
 	}
 
 	alpha = argV[2];
-	alpha = std::clamp(alpha, 0, FRACUNIT);
+	alpha = std::clamp<fixed_t>(alpha, 0, FRACUNIT);
 
 	TAG_ITER_LINES(tag, lineId)
 	{
diff --git a/src/cvars.cpp b/src/cvars.cpp
index ccdca729966cd6336250925b32ab909bfc9cc221..020ebd45251f80fd065f88efb701cdf3cd661664 100644
--- a/src/cvars.cpp
+++ b/src/cvars.cpp
@@ -1201,7 +1201,7 @@ consvar_t cv_kickstartaccel[MAXSPLITSCREENPLAYERS] = {
 	Player("kickstartaccel4", "Off").on_off().onchange(weaponPrefChange4)
 };
 
-consvar_t cv_mindelay = Player("mindelay", "2").min_max(0, 30).onchange(weaponPrefChange);
+consvar_t cv_mindelay = Player("mindelay", "2").min_max(0, 15).onchange(weaponPrefChange);
 
 extern CV_PossibleValue_t Color_cons_t[];
 void Color1_OnChange(void);
diff --git a/src/d_netcmd.c b/src/d_netcmd.c
index 423c7b4349ae6f44c64e4f51cb50dc743c069f36..5853e7936beecb3f530ba9aaafd5a0d63914a265 100644
--- a/src/d_netcmd.c
+++ b/src/d_netcmd.c
@@ -2993,6 +2993,12 @@ static void Command_RestartLevel(void)
 		return;
 	}
 
+	if (K_CanChangeRules(false) == false && CV_CheatsEnabled() == false)
+	{
+		CONS_Printf(M_GetText("Cheats must be enabled.\n"));
+		return;
+	}
+
 	if (cv_kartencore.value != 0)
 	{
 		newencore = (cv_kartencore.value == 1) || encoremode;
diff --git a/src/d_ticcmd.h b/src/d_ticcmd.h
index 7f083d72be3c12cba9bd7fb52f241a8006d7c21c..046488a9895c9a6ead3e33fc3e328ad761647c9e 100644
--- a/src/d_ticcmd.h
+++ b/src/d_ticcmd.h
@@ -22,7 +22,7 @@
 extern "C" {
 #endif
 
-#define MAXPREDICTTICS 12
+#define MAXPREDICTTICS 30
 
 // Button/action code definitions.
 typedef enum
diff --git a/src/g_build_ticcmd.cpp b/src/g_build_ticcmd.cpp
index 72655ada47dc93c1fc50eb8db8d8af48ffdb3d21..b74f48d2dd88460e2db5777f746f9a87d86f74be 100644
--- a/src/g_build_ticcmd.cpp
+++ b/src/g_build_ticcmd.cpp
@@ -64,7 +64,7 @@ INT32 G_BasicDeadZoneCalculation(INT32 magnitude, fixed_t deadZone)
 	}
 
 	// Calculate how much the magnitude exceeds the deadzone
-	adjustedMagnitude = std::min(adjustedMagnitude, JOYAXISRANGE) - jdeadzone;
+	adjustedMagnitude = std::min<INT32>(adjustedMagnitude, JOYAXISRANGE) - jdeadzone;
 	return (adjustedMagnitude * JOYAXISRANGE) / (JOYAXISRANGE - jdeadzone);
 }
 
@@ -133,10 +133,10 @@ class TiccmdBuilder
 		joystickvector.yaxis = (normalisedYAxis * normalisedMagnitude) / JOYAXISRANGE;
 
 		// Cap the values so they don't go above the correct maximum
-		joystickvector.xaxis = std::min(joystickvector.xaxis, JOYAXISRANGE);
-		joystickvector.xaxis = std::max(joystickvector.xaxis, -JOYAXISRANGE);
-		joystickvector.yaxis = std::min(joystickvector.yaxis, JOYAXISRANGE);
-		joystickvector.yaxis = std::max(joystickvector.yaxis, -JOYAXISRANGE);
+		joystickvector.xaxis = std::min<INT32>(joystickvector.xaxis, JOYAXISRANGE);
+		joystickvector.xaxis = std::max<INT32>(joystickvector.xaxis, -JOYAXISRANGE);
+		joystickvector.yaxis = std::min<INT32>(joystickvector.yaxis, JOYAXISRANGE);
+		joystickvector.yaxis = std::max<INT32>(joystickvector.yaxis, -JOYAXISRANGE);
 	}
 
 	void hook()
@@ -379,7 +379,7 @@ class TiccmdBuilder
 		kart_analog_input();
 
 		// Digital users can input diagonal-back for shallow turns.
-		// 
+		//
 		// There's probably some principled way of doing this in the gamepad handler itself,
 		// by only applying this filtering to inputs sourced from an axis. This is a little
 		// ugly with the current abstractions, though, and there's a fortunate trick here:
diff --git a/src/g_demo.cpp b/src/g_demo.cpp
index 4e0bfd4374d4247096325f01cc221e90ac28727d..13eba5aa308a4b5e8569597ec04fa6ca839ed09d 100644
--- a/src/g_demo.cpp
+++ b/src/g_demo.cpp
@@ -19,6 +19,7 @@
 #include <nlohmann/json.hpp>
 
 #include "doomdef.h"
+#include "doomtype.h"
 #include "console.h"
 #include "d_main.h"
 #include "d_player.h"
@@ -164,8 +165,13 @@ demoghost *ghosts = NULL;
 // - 0x000A (Ring Racers v2.0)
 //   - A bug was preventing control after ending a drift.
 //     Older behavior is kept around for staff ghost compat.
+//   - Also, polyobject bounce-back was fixed!
+// - 0x000B (Ring Racers v2.1 + In dev revisions)
+//   - SPB cup TA replays were recorded at this time
+//   - Slope physics changed with a scaling fix
+// - 0x000C (Ring Racers v2.2)
 
-#define DEMOVERSION 0x000B
+#define DEMOVERSION 0x000C
 
 boolean G_CompatLevel(UINT16 level)
 {
@@ -4135,7 +4141,7 @@ void G_SaveDemo(void)
 				strindex++;
 				dash = false;
 			}
-			else if (!dash)
+			else if (strindex && !dash)
 			{
 				demo_slug[strindex] = '-';
 				strindex++;
@@ -4143,12 +4149,31 @@ void G_SaveDemo(void)
 			}
 		}
 
-		demo_slug[strindex] = 0;
-		if (dash) demo_slug[strindex-1] = 0;
+		if (dash && strindex)
+		{
+			strindex--;
+		}
+		demo_slug[strindex] = '\0';
 
-		writepoint = strstr(strrchr(demoname, *PATHSEP), "-") + 1;
-		demo_slug[128 - (writepoint - demoname) - 4] = 0;
-		sprintf(writepoint, "%s.lmp", demo_slug);
+		if (demo_slug[0] != '\0')
+		{
+			// Slug is valid, write the chosen filename.
+			writepoint = strstr(strrchr(demoname, *PATHSEP), "-") + 1;
+			demo_slug[128 - (writepoint - demoname) - 4] = 0;
+			sprintf(writepoint, "%s.lmp", demo_slug);
+		}
+		else if (demo.titlename[0] == '\0')
+		{
+			// Slug is completely blank? Will crash if we attempt to save
+			// No bailout because empty seems like a good "no thanks" choice
+			G_ResetDemoRecording();
+			return;
+		}
+		// If a title that is invalid is provided, the user clearly wanted
+		// to save. But we can't do so at that name, so we only apply the
+		// title INSIDE the file, not in the naked filesystem.
+		// (A hypothetical example is bamboozling bot behaviour causing
+		// a player to write "?????????".) ~toast 010524
 	}
 
 	length = *(UINT32 *)demoinfo_p;
@@ -4174,8 +4199,11 @@ void G_SaveDemo(void)
 			if (gamedata->eversavedreplay == false)
 			{
 				gamedata->eversavedreplay = true;
-				M_UpdateUnlockablesAndExtraEmblems(true, true);
-				G_SaveGameData();
+				// The following will IMMEDIATELY happen on either next level load
+				// or returning to menu, so don't make the sound just to get cut off
+				//M_UpdateUnlockablesAndExtraEmblems(true, true);
+				//G_SaveGameData();
+				gamedata->deferredsave = true;
 			}
 		}
 		else
diff --git a/src/hardware/hw_md2.c b/src/hardware/hw_md2.c
index 08a4e28cc40bcb3b545532ab7ba4d7f9d57b087f..c6c25cb10ac2e7f80ee76e7ca045fb6c894edaf3 100644
--- a/src/hardware/hw_md2.c
+++ b/src/hardware/hw_md2.c
@@ -592,7 +592,7 @@ modelfound:
 	fclose(f);
 }
 
-void HWR_AddPlayerModel(int skin) // For skins that were added after startup
+void HWR_AddPlayerModel(INT32 skin) // For skins that were added after startup
 {
 	FILE *f;
 	char name[24], filename[32];
diff --git a/src/k_credits.cpp b/src/k_credits.cpp
index d867e2b6e235e4e2c1daac7eaa7cc8ec992ca52b..de2565b8666a632c0d06176f89aae4290b61a5f9 100644
--- a/src/k_credits.cpp
+++ b/src/k_credits.cpp
@@ -555,7 +555,6 @@ static boolean F_CreditsPlayDemo(void)
 
 	UINT8 ghost_id = M_RandomKey( mapheaderinfo[map_id]->ghostCount );
 	brief = mapheaderinfo[map_id]->ghostBrief[ghost_id];
-	std::string demo_name = static_cast<const char *>(W_CheckNameForNumPwad(brief->wad, brief->lump));
 
 	demo.attract = DEMO_ATTRACT_CREDITS;
 	demo.ignorefiles = true;
@@ -578,7 +577,7 @@ void F_TickCreditsDemoExit(void)
 
 	if (!menuactive && M_MenuConfirmPressed(0))
 	{
-		g_credits.demo_exit = std::max(g_credits.demo_exit, kDemoExitTicCount - 64);
+		g_credits.demo_exit = std::max<tic_t>(g_credits.demo_exit, kDemoExitTicCount - 64);
 	}
 
 	if (INT32 val = F_CreditsDemoExitFade(); val >= 0)
@@ -668,7 +667,7 @@ static boolean F_TickCreditsSlide(void)
 
 	if (g_credits.transition < FRACUNIT)
 	{
-		g_credits.transition = std::min(g_credits.transition + (FRACUNIT / TICRATE), FRACUNIT);
+		g_credits.transition = std::min<INT32>(g_credits.transition + (FRACUNIT / TICRATE), FRACUNIT);
 
 		if (g_credits.split_slide_id < g_credits.split_slide_strings.size())
 		{
diff --git a/src/k_dialogue.cpp b/src/k_dialogue.cpp
index 61bd6982a29cdf6ded6ffd8b467326ea86a3ec18..74abfa72ea2b48813099094d6afa4d56b8f726d9 100644
--- a/src/k_dialogue.cpp
+++ b/src/k_dialogue.cpp
@@ -307,7 +307,7 @@ void Dialogue::Tick(void)
 		}
 	}
 
-	slide = std::clamp(slide, 0, FRACUNIT);
+	slide = std::clamp<size_t>(slide, 0, FRACUNIT);
 
 	if (slide != FRACUNIT)
 	{
@@ -354,7 +354,7 @@ void Dialogue::Draw(void)
 
 	INT32 speakernameedge = -6;
 
-	srb2::Draw drawer = 
+	srb2::Draw drawer =
 		srb2::Draw(
 			BASEVIDWIDTH, BASEVIDHEIGHT - FixedToFloat(SlideAmount(height) - height)
 		).flags(V_SNAPTOBOTTOM);
diff --git a/src/k_director.cpp b/src/k_director.cpp
index b3881256f61749ede6cad0c24e0d48783845ec44..00035f0239669a3b11f17e0c3b15eee1aeffe096 100644
--- a/src/k_director.cpp
+++ b/src/k_director.cpp
@@ -3,7 +3,7 @@
 // Copyright (C) 2024 by AJ "Tyron" Martinez.
 // Copyright (C) 2024 by James Robert Roman.
 // Copyright (C) 2024 by Kart Krew.
-// 
+//
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
 // See the 'LICENSE' file for more details.
@@ -219,7 +219,7 @@ private:
 
 			if (playerstat[position].gap >= BREAKAWAYDIST)
 			{
-				playerstat[position].boredom = std::min(BOREDOMTIME * 2, playerstat[position].boredom + 1);
+				playerstat[position].boredom = std::min<INT32>(BOREDOMTIME * 2, playerstat[position].boredom + 1);
 			}
 			else if (playerstat[position].boredom > 0)
 			{
diff --git a/src/k_endcam.cpp b/src/k_endcam.cpp
index 6bb26672cd75aeee407c5e5c53066371f2429c09..378e00cc0d6e44ead9c393dd2c271d354ef88c53 100644
--- a/src/k_endcam.cpp
+++ b/src/k_endcam.cpp
@@ -34,7 +34,7 @@ namespace
 
 fixed_t interval(tic_t t, tic_t d)
 {
-	return (std::min(t, d) * FRACUNIT) / std::max(d, 1u);
+	return (std::min(t, d) * FRACUNIT) / std::max<tic_t>(d, 1u);
 }
 
 fixed_t interval(tic_t t, tic_t s, tic_t d)
diff --git a/src/k_hud.cpp b/src/k_hud.cpp
index 9530c344be5d8d498d1858e12b993304a3533dac..3ac3e189beb0791bd081b1cf0327533e25359752 100644
--- a/src/k_hud.cpp
+++ b/src/k_hud.cpp
@@ -1698,7 +1698,7 @@ static void K_drawKartItem(void)
 
 	// Quick Eggman numbers
 	if (stplyr->eggmanexplode > 1)
-		V_DrawScaledPatch(fx+17, fy+13-offset, V_HUDTRANS|V_SLIDEIN|fflags, kp_eggnum[std::min(5, G_TicsToSeconds(stplyr->eggmanexplode))]);
+		V_DrawScaledPatch(fx+17, fy+13-offset, V_HUDTRANS|V_SLIDEIN|fflags, kp_eggnum[std::min<INT32>(5, G_TicsToSeconds(stplyr->eggmanexplode))]);
 
 	if (stplyr->itemtype == KITEM_FLAMESHIELD && stplyr->flamelength > 0)
 	{
@@ -2695,7 +2695,7 @@ static void K_drawBossHealthBar(void)
 		;
 	else if (bossinfo.visualbarimpact)
 	{
-		INT32 mag = std::min((bossinfo.visualbarimpact/4) + 1, 8u);
+		INT32 mag = std::min<UINT32>((bossinfo.visualbarimpact/4) + 1, 8u);
 		if (bossinfo.visualbarimpact & 1)
 			starty -= mag;
 		else
@@ -2989,7 +2989,7 @@ static void K_drawRingCounter(boolean gametypeinfoshown)
 
 	if (stplyr->hudrings <= 0 && stplyr->ringvisualwarning > 1)
 	{
-		colorring = true;	
+		colorring = true;
 		if ((leveltime/2 & 1))
 		{
 			ringmap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_CRIMSON, GTC_CACHE);
@@ -3864,7 +3864,7 @@ static void K_DrawNameTagSphereMeter(INT32 x, INT32 y, INT32 width, INT32 sphere
 	// see also K_drawBlueSphereMeter
 	const UINT8 segColors[] = {73, 64, 52, 54, 55, 35, 34, 33, 202, 180, 181, 182, 164, 165, 166, 153, 152};
 
-	spheres = std::clamp(spheres, 0, 40);
+	spheres = std::clamp<INT32>(spheres, 0, 40);
 	int colorIndex = (spheres * sizeof segColors) / (40 + 1);
 
 	int px = r_splitscreen > 1 ? 1 : 2;
@@ -5297,7 +5297,7 @@ static void K_drawInput(void)
 	char mode = ((stplyr->pflags & PF_ANALOGSTICK) ? '4' : '2') + (r_splitscreen > 1);
 	bool local = !demo.playback && P_IsMachineLocalPlayer(stplyr);
 	fixed_t slide = K_GetDialogueSlide(FRACUNIT);
-	INT32 tallySlide = []
+	INT32 tallySlide = []() -> INT32
 	{
 		if (r_splitscreen <= 1)
 		{
@@ -5311,7 +5311,7 @@ static void K_drawInput(void)
 		if (stplyr->tally.state == TALLY_ST_GOTTHRU_SLIDEIN ||
 			stplyr->tally.state == TALLY_ST_GAMEOVER_SLIDEIN)
 		{
-			return Easing_OutQuad(std::min<fixed_t>(stplyr->tally.transition * 2, FRACUNIT), 0, kSlideDown);
+			return static_cast<INT32>(Easing_OutQuad(std::min<fixed_t>(stplyr->tally.transition * 2, FRACUNIT), 0, kSlideDown));
 		}
 		return kSlideDown;
 	}();
@@ -5350,7 +5350,7 @@ static void K_drawChallengerScreen(void)
 		19,20,19,20,19,20,19,20,19,20, // frame 20-21, 1 tic, 5 alternating: all text vibrates from impact
 		21,22,23,24 // frame 22-25, 1 tic: CHALLENGER turns gold
 	};
-	const UINT8 offset = std::min(52-1u, (3*TICRATE)-mapreset);
+	const UINT8 offset = std::min<UINT32>(52-1u, (3*TICRATE)-mapreset);
 
 	V_DrawFadeScreen(0xFF00, 16); // Fade out
 	V_DrawScaledPatch(0, 0, 0, kp_challenger[anim[offset]]);
diff --git a/src/k_hud_track.cpp b/src/k_hud_track.cpp
index 31fbe2d5d213f6294ad711a0c689088bece13231..4dd89cfccc5cb0ab19f6d5001a84e2b6c9d70bfd 100644
--- a/src/k_hud_track.cpp
+++ b/src/k_hud_track.cpp
@@ -752,10 +752,10 @@ void K_CullTargetList(std::vector<TargetTracking>& targetList)
 				y2 = tr.result.y + kTrackerRadius;
 			}
 
-			x1 = std::max(x1 / kBlockWidth / FRACUNIT, 0);
-			x2 = std::min(x2 / kBlockWidth / FRACUNIT, kXBlocks - 1);
-			y1 = std::max(y1 / kBlockHeight / FRACUNIT, 0);
-			y2 = std::min(y2 / kBlockHeight / FRACUNIT, kYBlocks - 1);
+			x1 = std::max<INT32>(x1 / kBlockWidth / FRACUNIT, 0);
+			x2 = std::min<INT32>(x2 / kBlockWidth / FRACUNIT, kXBlocks - 1);
+			y1 = std::max<INT32>(y1 / kBlockHeight / FRACUNIT, 0);
+			y2 = std::min<INT32>(y2 / kBlockHeight / FRACUNIT, kYBlocks - 1);
 
 			bool allMine = true;
 
diff --git a/src/k_kart.c b/src/k_kart.c
index 1dddc78fa51cd87b311cf161735802d7499b7d24..571816340bc6b3795e369e0aff9bcd06d7fd83d3 100644
--- a/src/k_kart.c
+++ b/src/k_kart.c
@@ -13531,7 +13531,8 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
 				player->tumbleHeight = 30;	// Base tumble bounce height
 				player->trickpanel = TRICKSTATE_NONE;
 				P_SetPlayerMobjState(player->mo, S_KART_SPINOUT);
-				K_AddMessageForPlayer(player, "Press <dpad> + <a> to trick!", true, false);
+				if (gametype != GT_TUTORIAL)
+					K_AddMessageForPlayer(player, "Press <dpad> + <a> to trick!", true, false);
 				if (player->itemflags & (IF_ITEMOUT|IF_EGGMANOUT))
 				{
 					//K_PopPlayerShield(player); // shield is just being yeeted, don't pop
diff --git a/src/k_menudraw.c b/src/k_menudraw.c
index 5cbd79f73a45aeb3b5b34877e3fa0f25b6a22494..a6f4880b7e11221b1b531173c3881488a07f052a 100644
--- a/src/k_menudraw.c
+++ b/src/k_menudraw.c
@@ -8093,13 +8093,24 @@ static void M_DrawStatsMaps(void)
 		return;
 	}
 
-	INT32 mapsunfinished = 0;
+	INT32 mapsunfinished = 0, medalspos;
 
-	V_DrawThinString(30, 60, 0, va("x %d/%d", statisticsmenu.gotmedals, statisticsmenu.nummedals));
+	char *medalcountstr = va("x %d/%d", statisticsmenu.gotmedals, statisticsmenu.nummedals);
+
+	V_DrawThinString(30, 60, 0, medalcountstr);
 	V_DrawMappedPatch(20, 60, 0, W_CachePatchName("GOTITA", PU_CACHE),
 				                       R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_GOLD, GTC_MENUCACHE));
 
-	INT32 medalspos = BASEVIDWIDTH - 20;
+	if (gamedata->numspraycans)
+	{
+		medalspos = 30 + V_ThinStringWidth(medalcountstr, 0);
+		medalcountstr = va("x %d/%d", gamedata->gotspraycans, gamedata->numspraycans);
+		V_DrawThinString(20 + medalspos, 60, 0, medalcountstr);
+		V_DrawMappedPatch(10 + medalspos, 60, 0, W_CachePatchName("GOTCAN", PU_CACHE),
+										   R_GetTranslationColormap(TC_DEFAULT, gamedata->spraycans[0].col, GTC_MENUCACHE));
+	}
+
+	medalspos = BASEVIDWIDTH - 20;
 
 	boolean timeattack[3];
 	timeattack[0] = M_SecretUnlocked(SECRET_TIMEATTACK, true);
diff --git a/src/k_menufunc.c b/src/k_menufunc.c
index 4a42243d1108ec439368749a7da92088df34c7d4..624625dab50a5f93899c773aa90fcaa99d41b479 100644
--- a/src/k_menufunc.c
+++ b/src/k_menufunc.c
@@ -210,6 +210,27 @@ static void M_ChangeCvar(INT32 choice)
 	}
 #endif
 
+	if (cvar == &cv_dummyprofileautoroulette &&
+		// Turning Auto Roulette on
+		cv_dummyprofileautoroulette.value == 0 &&
+		// Not setting to default (ie changing the value)
+		choice != -1)
+	{
+		M_StartMessage(
+			"Turning on Auto Roulette",
+			"\"Ring Racers\" is not designed with random items in mind. With Auto Roulette, you cannot select the item results you want or select an item early."
+			"\n"
+			"You will be at a distinct \x85" "disadvantage. \x80\n" 
+			"\n"
+			"ARE YOU SURE?",
+			M_ChangeCvarResponse,
+			MM_YESNO,
+			NULL,
+			NULL
+		);
+		return;
+	}
+
 	M_ChangeCvarDirect(choice, cvar);
 }
 
diff --git a/src/k_profiles.cpp b/src/k_profiles.cpp
index 89354bb1c874440e1e88341947c35b17c69394a4..1076cfc7814c8443eec00c0f8768bd5cede1bbea 100644
--- a/src/k_profiles.cpp
+++ b/src/k_profiles.cpp
@@ -518,6 +518,15 @@ void PR_LoadProfiles(void)
 			converted = true;
 		}
 
+		if (jsprof.version < 3)
+		{
+			// Version 2 -> 3:
+			// - Auto Roulette is turned off again so people have to see the warning message
+			newprof->autoroulette = false;
+
+			converted = true;
+		}
+
 		if (converted)
 		{
 			CONS_Printf("Profile '%s' was converted from version %d to version %d\n",
diff --git a/src/k_profiles.h b/src/k_profiles.h
index 0870c5d5ee410b298e8b68385def59cc2323dd4d..70d3abd6899016a52a88acff02e2c2089f1ac45c 100644
--- a/src/k_profiles.h
+++ b/src/k_profiles.h
@@ -112,8 +112,9 @@ extern "C" {
 // Version history:
 // 1 - first
 // 2 - litesteer is off by default, old profiles litesteer
+// 3 - auto roulette is switched off again
 //     option is reset to default
-#define PROFILEVER 2
+#define PROFILEVER 3
 #define MAXPROFILES 16
 #define PROFILESFILE "ringprofiles.prf"
 #define PROFILE_GUEST 0
diff --git a/src/k_roulette.c b/src/k_roulette.c
index 0ff171c6ea7211c58fd486a0729722aa59fd2979..02c662e2ac4e9f399e78f4e2a88a14420a13e1be 100644
--- a/src/k_roulette.c
+++ b/src/k_roulette.c
@@ -1356,7 +1356,7 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet
 	}
 	else if (K_TimeAttackRules() == true)
 	{
-		kartitems_t *presetlist = K_KartItemReelRingSneaker;
+		kartitems_t *presetlist = NULL;
 
 		// If the objective is not to go fast, it's to cause serious damage.
 		if (battleprisons == true)
@@ -1367,10 +1367,55 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet
 		{
 			presetlist = K_KartItemReelSPBAttack;
 		}
+		else if (gametype == GT_TUTORIAL)
+		{
+			presetlist = K_KartItemReelRingSneaker;
+		}
 
-		for (i = 0; presetlist[i] != KITEM_NONE; i++)
+		if (presetlist != NULL)
 		{
-			K_PushToRouletteItemList(roulette, presetlist[i]);
+			for (i = 0; presetlist[i] != KITEM_NONE; i++)
+			{
+				K_PushToRouletteItemList(roulette, presetlist[i]);
+			}
+		}
+		else
+		{
+			// New FREE PLAY behavior;
+			// every item in the game!
+
+			// Create the same item reel given the same inputs.
+			P_SetRandSeed(PR_ITEM_ROULETTE, ITEM_REEL_SEED);
+
+			for (i = 1; i < NUMKARTRESULTS; i++)
+			{
+				if (K_ItemEnabled(i) == true)
+				{
+					spawnChance[i] = ( totalSpawnChance += 1 );
+				}
+			}
+
+			while (totalSpawnChance > 0)
+			{
+				rngRoll = P_RandomKey(PR_ITEM_ROULETTE, totalSpawnChance);
+				for (i = 1; i < NUMKARTRESULTS && spawnChance[i] <= rngRoll; i++)
+				{
+					continue;
+				}
+
+				K_PushToRouletteItemList(roulette, i);
+
+				for (; i < NUMKARTRESULTS; i++)
+				{
+					// Be sure to fix the remaining items' odds too.
+					if (spawnChance[i] > 0)
+					{
+						spawnChance[i]--;
+					}
+				}
+
+				totalSpawnChance--;
+			}
 		}
 
 		return;
diff --git a/src/k_tally.cpp b/src/k_tally.cpp
index 2f32ca16abc11bb38f537891d712cd94b063fd38..ee9b0373d0f58900a8d5ee2d1c5ebe6c36da8fe5 100644
--- a/src/k_tally.cpp
+++ b/src/k_tally.cpp
@@ -303,7 +303,7 @@ void level_tally_t::Init(player_t *player)
 		: (tutorialchallenge == TUTORIALSKIP_INPROGRESS && K_IsPlayerLosing(player))
 	);
 
-	time = std::min(static_cast<INT32>(player->realtime), (100 * 60 * TICRATE) - 1);
+	time = std::min<INT32>(static_cast<INT32>(player->realtime), (100 * 60 * TICRATE) - 1);
 	ringPool = player->totalring;
 	livesAdded = 0;
 
@@ -372,7 +372,7 @@ void level_tally_t::Init(player_t *player)
 				{
 					if (playeringame[i] == true && players[i].spectator == false)
 					{
-						pointLimit = std::min(pointLimit, static_cast<int>(-players[i].roundscore));
+						pointLimit = std::min<INT32>(pointLimit, static_cast<int>(-players[i].roundscore));
 					}
 				}
 			}
@@ -920,7 +920,7 @@ void level_tally_t::Tick(void)
 			done = true;
 			break;
 		}
-		
+
 		default:
 		{
 			// error occured, silently fix
@@ -1244,7 +1244,7 @@ void level_tally_t::Draw(void)
 									work_tics % 10
 								));
 
-							if (modeattacking && !demo.playback && (state == TALLY_ST_DONE || state == TALLY_ST_TEXT_PAUSE) 
+							if (modeattacking && !demo.playback && (state == TALLY_ST_DONE || state == TALLY_ST_TEXT_PAUSE)
 								&& !K_IsPlayerLosing(&players[consoleplayer]) && players[consoleplayer].realtime < oldbest)
 							{
 								drawer_text
@@ -1422,7 +1422,7 @@ void K_TickPlayerTally(player_t *player)
 		G_PlayerInputDown(G_LocalSplitscreenPartyPosition(player - players), gc_a, 0);
 	boolean allowFastForward = player->tally.state > TALLY_ST_GOTTHRU_SLIDEIN
 		&& player->tally.state <= TALLY_ST_DONE
-		&& player->tally.releasedFastForward 
+		&& player->tally.releasedFastForward
 		// - Not allowed online so we don't have to do any
 		//   networking.
 		// - Not allowed in replays because splitscreen party
@@ -1439,8 +1439,8 @@ void K_TickPlayerTally(player_t *player)
 			player->tally.Tick();
 		while (player->tally.state != TALLY_ST_DONE && player->tally.state != TALLY_ST_GAMEOVER_DONE);
 
-		player->tally.delay = std::min(player->tally.delay, TICRATE);
-		
+		player->tally.delay = std::min<INT32>(player->tally.delay, TICRATE);
+
 		if (Y_ShouldDoIntermission())
 			musiccountdown = 2; // gets decremented to 1 in G_Ticker to immediately trigger intermission music [blows raspberry]
 	}
@@ -1457,7 +1457,7 @@ void K_TickPlayerTally(player_t *player)
 	{
 		player->tally.releasedFastForward = false;
 	}
-		
+
 }
 
 void K_DrawPlayerTally(void)
diff --git a/src/k_waypoint.cpp b/src/k_waypoint.cpp
index 95eed059ce00b15b8829e135b06ead6eec7dcac0..88d54ae0c6d131c21d8a9ecb6280d5721f829f40 100644
--- a/src/k_waypoint.cpp
+++ b/src/k_waypoint.cpp
@@ -2546,8 +2546,8 @@ static INT32 K_CalculateTrackComplexity(void)
 
 			if (delta < 0)
 			{
-				dist_factor = FixedDiv(FRACUNIT, std::max(1, dist_factor));
-				radius_factor = FixedDiv(FRACUNIT, std::max(1, radius_factor));
+				dist_factor = FixedDiv(FRACUNIT, std::max<fixed_t>(1, dist_factor));
+				radius_factor = FixedDiv(FRACUNIT, std::max<fixed_t>(1, radius_factor));
 			}
 			else
 			{
diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c
index d9501acc3a092d0f4ae69c8e608545b26e50112c..824825fe7b29ea7c62278652624d04b0b106e27c 100644
--- a/src/lua_hudlib.c
+++ b/src/lua_hudlib.c
@@ -44,7 +44,7 @@ static const char *const hud_disable_options[] = {
 	"stagetitle",
 	"textspectator",
 	"crosshair",
-
+	"score",
 	"time",
 	"gametypeinfo",	// Bumpers / Karma / Laps depending on gametype
 	"minimap",
diff --git a/src/m_misc.cpp b/src/m_misc.cpp
index bbb4a3167da984970b86f5cdf4f9b1be96bf1730..d6788a7296db1b169f8cceca6f8aca46569c4c78 100644
--- a/src/m_misc.cpp
+++ b/src/m_misc.cpp
@@ -779,8 +779,8 @@ static void M_CreateScreenShotPalette(void)
 	for (i = 0, j = 0; i < 768; i += 3, j++)
 	{
 		RGBA_t locpal = ((cv_screenshot_colorprofile.value)
-		? pLocalPalette[(std::max(st_palette,0)*256)+j]
-		: pMasterPalette[(std::max(st_palette,0)*256)+j]);
+		? pLocalPalette[(std::max<INT32>(st_palette,0)*256)+j]
+		: pMasterPalette[(std::max<INT32>(st_palette,0)*256)+j]);
 		screenshot_palette[i] = locpal.s.red;
 		screenshot_palette[i+1] = locpal.s.green;
 		screenshot_palette[i+2] = locpal.s.blue;
diff --git a/src/menus/play-char-select.c b/src/menus/play-char-select.c
index 06c6b0090f2b29825a551438bfad2f1f8b7485bb..ed8606ff02cd868a6272d683a5f329e7601a794f 100644
--- a/src/menus/play-char-select.c
+++ b/src/menus/play-char-select.c
@@ -777,6 +777,8 @@ static void M_HandleBeginningFollowers(setup_player_t *p)
 
 static void M_HandleBeginningColorsOrFollowers(setup_player_t *p)
 {
+	if (p->skin != -1)
+		S_StartSound(NULL, skins[p->skin].soundsid[S_sfx[sfx_kattk1].skinsound]);
 	if (M_HandleBeginningColors(p))
 		S_StartSound(NULL, sfx_s3k63);
 	else
@@ -1173,6 +1175,8 @@ static void M_HandleFollowerRotate(setup_player_t *p, UINT8 num)
 			p->mdepth = CSSTEP_FOLLOWERCOLORS;
 			M_NewPlayerColors(p);
 			S_StartSound(NULL, sfx_s3k63);
+			if (p->followern != -1)
+				S_StartSound(NULL, followers[p->followern].hornsound);
 		}
 		else
 		{
diff --git a/src/menus/transient/pause-addonoptions.cpp b/src/menus/transient/pause-addonoptions.cpp
index 01cff6a2ed2032671db528be39efea111a53dde4..42e2382349fe1cc2668a840ecf5c570394987f8c 100644
--- a/src/menus/transient/pause-addonoptions.cpp
+++ b/src/menus/transient/pause-addonoptions.cpp
@@ -201,7 +201,7 @@ void menu_open()
 		g_menu.insert(
 			g_menu.begin(),
 			menuitem_t {IT_DISABLED, "No addon options!", nullptr, nullptr, {}, 0, 0}
-		);		
+		);
 	}
 
 	group_menu();
@@ -267,7 +267,7 @@ void draw_menu()
 		K_drawButton((draw.x() + 8) * FRACUNIT, (draw.y() + 8) * FRACUNIT, 0, kp_button_y[0], M_MenuButtonHeld(0, MBT_Y));
 	draw = draw.y(32 + kMargin);
 
-	currentMenu->y = std::min(static_cast<int>(draw.y()), (BASEVIDHEIGHT/2) - g_menu_offsets[itemOn]);
+	currentMenu->y = std::min(static_cast<INT32>(draw.y()), (BASEVIDHEIGHT/2) - g_menu_offsets[itemOn]);
 
 	V_SetClipRect(0, draw.y() * FRACUNIT, BASEVIDWIDTH * FRACUNIT, (BASEVIDHEIGHT - draw.y() - kMargin) * FRACUNIT, 0);
 	M_DrawGenericMenu();
diff --git a/src/menus/transient/pause-cheats.cpp b/src/menus/transient/pause-cheats.cpp
index 00cca24e0ab79ba6e75f40006396f16a4843dd59..bbd9a4d8b4eb7f9fe560e311d3de9ca403bfd661 100644
--- a/src/menus/transient/pause-cheats.cpp
+++ b/src/menus/transient/pause-cheats.cpp
@@ -211,7 +211,7 @@ void draw_menu()
 	K_drawButton((draw.x() + 8) * FRACUNIT, (draw.y() + 8) * FRACUNIT, 0, kp_button_y[0], M_MenuButtonHeld(0, MBT_Y));
 	draw = draw.y(32 + kMargin);
 
-	currentMenu->y = std::min(static_cast<int>(draw.y()), (BASEVIDHEIGHT/2) - g_menu_offsets[itemOn]);
+	currentMenu->y = std::min(static_cast<INT32>(draw.y()), (BASEVIDHEIGHT/2) - g_menu_offsets[itemOn]);
 
 	V_SetClipRect(0, draw.y() * FRACUNIT, BASEVIDWIDTH * FRACUNIT, (BASEVIDHEIGHT - draw.y() - kMargin) * FRACUNIT, 0);
 	M_DrawGenericMenu();
diff --git a/src/objects/checkpoint.cpp b/src/objects/checkpoint.cpp
index 51204639839d918514b1d71bfa5c6602a2f83c74..7269e5b79af990636b6426e4dbe379c33c9ce626 100644
--- a/src/objects/checkpoint.cpp
+++ b/src/objects/checkpoint.cpp
@@ -181,7 +181,7 @@ struct Checkpoint : mobj_t
 
 			if (!clip_var())
 			{
-				speed(speed() - FixedDiv(speed() / 50, std::max(speed_multiplier(), 1)));
+				speed(speed() - FixedDiv(speed() / 50, std::max<fixed_t>(speed_multiplier(), 1)));
 			}
 		}
 		else if (!activated())
@@ -324,7 +324,7 @@ private:
 		if (xy_momentum)
 		{
 			P_Thrust(p, dir, xy_momentum);
-			p->momz = P_RandomKey(PR_DECORATION, std::max(z_momentum, 1));
+			p->momz = P_RandomKey(PR_DECORATION, std::max<fixed_t>(z_momentum, 1));
 			p->destscale = 0;
 			p->scalespeed = p->scale / 35;
 			p->color = SKINCOLOR_ULTRAMARINE;
diff --git a/src/objects/destroyed-kart.cpp b/src/objects/destroyed-kart.cpp
index e027c1cf047d71f2c3b93b33196e67dcfa0183a0..b029d8304cebd5d80487e547385bbbff643e5e27 100644
--- a/src/objects/destroyed-kart.cpp
+++ b/src/objects/destroyed-kart.cpp
@@ -387,7 +387,7 @@ private:
 		{
 			fixed_t f = burning() * FRACUNIT / burn_duration();
 
-			if ((leveltime % std::max(1, Easing_OutCubic(f, 8, 1))) == 0)
+			if ((leveltime % std::max<fixed_t>(1, Easing_OutCubic(f, 8, 1))) == 0)
 			{
 				vfx(f);
 			}
diff --git a/src/objects/gachabom-rebound.cpp b/src/objects/gachabom-rebound.cpp
index f8fa3fb83f085d802c4118b4ed5859ccbe87c4fe..f278aa58ef0389ab90dabb8f3b38aa70b79bdecf 100644
--- a/src/objects/gachabom-rebound.cpp
+++ b/src/objects/gachabom-rebound.cpp
@@ -88,7 +88,7 @@ bool award_target(mobj_t* mobj)
 			player->itemamount++;
 			if (player->roundconditions.gachabom_miser == 1)
 				player->roundconditions.gachabom_miser = 0;
-			
+
 			//S_StartSoundAtVolume(target, sfx_grbnd3, 255/3);
 			S_StartSound(target, sfx_mbs54);
 
@@ -127,7 +127,7 @@ void chase_rebound_target(mobj_t* mobj)
 		mobj->momz = zDelta / 4;
 
 		const tic_t t = distance_to_target(mobj) / travelDistance;
-		const fixed_t newSpeed = std::abs(mobj->scale - mobj->destscale) / std::max(t, 1u);
+		const fixed_t newSpeed = std::abs(mobj->scale - mobj->destscale) / std::max<tic_t>(t, 1u);
 
 		if (newSpeed > mobj->scalespeed)
 		{
diff --git a/src/objects/powerup-spinner.cpp b/src/objects/powerup-spinner.cpp
index f77254d1138b2116aaec986a3ba74f8238e56646..100c1bf279a777e7c84cd0422fcb4773b86669c7 100644
--- a/src/objects/powerup-spinner.cpp
+++ b/src/objects/powerup-spinner.cpp
@@ -45,7 +45,7 @@ struct Spinner : Mobj
 
 	void think()
 	{
-		fixed_t f = FRACUNIT - std::clamp(fuse, 0, duration()) * FRACUNIT / std::max(duration(), 1);
+		fixed_t f = FRACUNIT - std::clamp<INT32>(fuse, 0, duration()) * FRACUNIT / std::max<INT32>(duration(), 1);
 
 		if (fuse == duration() - 20)
 		{
diff --git a/src/objects/pulley.cpp b/src/objects/pulley.cpp
index 3963a6f7117f80c46350ac3df9087d4f4028813e..16fcb4b72527f60a74c893070dc9cb012f5bc9fa 100644
--- a/src/objects/pulley.cpp
+++ b/src/objects/pulley.cpp
@@ -246,7 +246,7 @@ private:
 			return;
 
 		rope()->z = hook()->top();
-		rope()->spriteyscale(Fixed {std::max(0, z - hook()->top())} / std::max<Fixed>(1, 32 * rope()->scale()));
+		rope()->spriteyscale(Fixed {std::max<fixed_t>(0, z - hook()->top())} / std::max<Fixed>(1, 32 * rope()->scale()));
 	}
 };
 
diff --git a/src/p_map.c b/src/p_map.c
index 84126fb8d3400e892cb873bd701d8f6911923190..bb7868dd44a22ca29359c0fa65dc8c7e678c3744 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -2186,6 +2186,8 @@ boolean P_CheckPosition(mobj_t *thing, fixed_t x, fixed_t y, TryMoveResult_t *re
 		}
 	}
 
+	P_ClearTestLines();
+
 	// The bounding box is extended by MAXRADIUS
 	// because mobj_ts are grouped into mapblocks
 	// based on their origin point, and can overlap
@@ -2323,8 +2325,6 @@ boolean P_CheckPosition(mobj_t *thing, fixed_t x, fixed_t y, TryMoveResult_t *re
 
 	validcount++;
 
-	P_ClearTestLines();
-
 	// check lines
 	for (bx = xl; bx <= xh; bx++)
 	{
diff --git a/src/p_polyobj.c b/src/p_polyobj.c
index 80f03d0f7339827689d6f483f8411f00b62b33e3..82eb95432ee1035b53f0a651c36ac5b48ca29132 100644
--- a/src/p_polyobj.c
+++ b/src/p_polyobj.c
@@ -201,6 +201,10 @@ boolean P_BBoxInsidePolyobj(polyobj_t *po, fixed_t *bbox)
 	{
 		if (P_BoxOnLineSide(bbox, po->lines[i]) == 0)
 			return false;
+		if (g_tm.sweep && !G_CompatLevel(0x000A))
+		{
+			P_TestLine(po->lines[i]);
+		}
 	}
 
 	return true;
diff --git a/src/p_setup.cpp b/src/p_setup.cpp
index 058f4a7924a8c203e6e86a162e2e32600eb310f5..8131364c5d6dcc2919aa069508c31e69c30f565b 100644
--- a/src/p_setup.cpp
+++ b/src/p_setup.cpp
@@ -3444,8 +3444,8 @@ static void P_ProcessLinedefsAfterSidedefs(void)
 			if (ld->flags & ML_DONTPEGBOTTOM) // alternate alpha (by texture offsets)
 			{
 				extracolormap_t *exc = R_CopyColormap(sides[ld->sidenum[0]].colormap_data, false);
-				INT16 alpha = std::max(std::min(sides[ld->sidenum[0]].textureoffset >> FRACBITS, 25), -25);
-				INT16 fadealpha = std::max(std::min(sides[ld->sidenum[0]].rowoffset >> FRACBITS, 25), -25);
+				INT16 alpha = std::max<fixed_t>(std::min<fixed_t>(sides[ld->sidenum[0]].textureoffset >> FRACBITS, 25), -25);
+				INT16 fadealpha = std::max<fixed_t>(std::min<fixed_t>(sides[ld->sidenum[0]].rowoffset >> FRACBITS, 25), -25);
 
 				// If alpha is negative, set "subtract alpha" flag and store absolute value
 				if (alpha < 0)
@@ -5840,12 +5840,12 @@ static void P_ConvertBinaryLinedefTypes(void)
 			lines[i].args[0] = tag;
 			if (lines[i].flags & ML_DONTPEGBOTTOM)
 			{
-				lines[i].args[1] = std::max(sides[lines[i].sidenum[0]].textureoffset >> FRACBITS, 0);
+				lines[i].args[1] = std::max<fixed_t>(sides[lines[i].sidenum[0]].textureoffset >> FRACBITS, 0);
 				// failsafe: if user specifies Back Y Offset and NOT Front Y Offset, use the Back Offset
 				// to be consistent with other light and fade specials
 				lines[i].args[2] = ((lines[i].sidenum[1] != 0xFFFF && !(sides[lines[i].sidenum[0]].rowoffset >> FRACBITS)) ?
-					std::max(std::min(sides[lines[i].sidenum[1]].rowoffset >> FRACBITS, 255), 0)
-					: std::max(std::min(sides[lines[i].sidenum[0]].rowoffset >> FRACBITS, 255), 0));
+					std::max<fixed_t>(std::min<fixed_t>(sides[lines[i].sidenum[1]].rowoffset >> FRACBITS, 255), 0)
+					: std::max<fixed_t>(std::min<fixed_t>(sides[lines[i].sidenum[0]].rowoffset >> FRACBITS, 255), 0));
 			}
 			else
 			{
@@ -7901,7 +7901,7 @@ static void P_LoadRecordGhosts(void)
 			savebuffer_t buf = {0};
 
 			staffbrief_t* ghostbrief = mapheaderinfo[gamemap-1]->ghostBrief[i - 1];
-			const char* lumpname = W_CheckNameForNumPwad(ghostbrief->wad, ghostbrief->lump);
+			const char* lumpname = W_CheckLongNameForNumPwad(ghostbrief->wad, ghostbrief->lump);
 			size_t lumplength = W_LumpLengthPwad(ghostbrief->wad, ghostbrief->lump);
 			if (lumplength == 0)
 			{
diff --git a/src/p_user.c b/src/p_user.c
index fc0c0bd23534dd5132d3a18775b7a8a48527354d..c23f9e1f8486ebb0a904c6bf12f256d3825785c5 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -1917,7 +1917,11 @@ static void P_3dMovement(player_t *player)
 	vector3_t totalthrust;
 
 	totalthrust.x = totalthrust.y = 0; // I forget if this is needed
-	totalthrust.z = FRACUNIT*P_MobjFlip(player->mo)/3; // A bit of extra push-back on slopes
+
+	if (G_CompatLevel(0x000B)) // Ring Racers 2.1 behavior
+		totalthrust.z = FRACUNIT*P_MobjFlip(player->mo)/3; // A bit of extra push-back on slopes
+	else
+		totalthrust.z = FixedMul(mapobjectscale, K_GrowShrinkSpeedMul(player))*P_MobjFlip(player->mo)/3; // A bit of extra push-back on slopes, but scaled for mapobject and player size
 
 	if (K_SlopeResistance(player) == true)
 	{
diff --git a/src/r_debug.cpp b/src/r_debug.cpp
index b7c7cdbb9141e7ee804895ee3b09472bc4726611..7dc8b2c2ba803fadb7ba3a3259ad5a33e984ee19 100644
--- a/src/r_debug.cpp
+++ b/src/r_debug.cpp
@@ -46,7 +46,7 @@ INT32 R_AdjustLightLevel(INT32 light)
 	if (!debugrender_highlight && cv_debugrender_contrast.value == 0)
 	{
 		const fixed_t darken = FixedMul(FixedMul(g_darkness.value[R_GetViewNumber()], mapheaderinfo[gamemap-1]->darkness), kRange);
-		return std::clamp((light * FRACUNIT) - darken, 0, kRange) / FRACUNIT;
+		return std::clamp<size_t>((light * FRACUNIT) - darken, 0, kRange) / FRACUNIT;
 	}
 
 	const fixed_t adjust = FixedMul(cv_debugrender_contrast.value, kRange);
@@ -60,7 +60,7 @@ INT32 R_AdjustLightLevel(INT32 light)
 	}
 	else
 	{
-		light = std::clamp((light * FRACUNIT) - adjust, 0, kRange);
+		light = std::clamp<size_t>((light * FRACUNIT) - adjust, 0, kRange);
 	}
 
 	return light / FRACUNIT;
diff --git a/src/r_plane.cpp b/src/r_plane.cpp
index efaa041e80e9fcfff58cc80612747ae4554640d0..56a2fcfaac1d118a6d0a478d0f1b4b88b2f683c4 100644
--- a/src/r_plane.cpp
+++ b/src/r_plane.cpp
@@ -237,7 +237,7 @@ static void R_MapTiltedPlane(drawspandata_t *ds, void(*spanfunc)(drawspandata_t*
 	{
 		ds->bgofs = R_CalculateRippleOffset(ds, y);
 
-		R_SetTiltedSpan(ds, std::clamp(y, 0, viewheight));
+		R_SetTiltedSpan(ds, std::clamp<INT32>(y, 0, viewheight));
 
 		R_CalculatePlaneRipple(ds, ds->currentplane->viewangle + ds->currentplane->plangle);
 		R_SetSlopePlaneVectors(ds, ds->currentplane, y, (ds->xoffs + ds->planeripple.xfrac), (ds->yoffs + ds->planeripple.yfrac));
diff --git a/src/r_segs.cpp b/src/r_segs.cpp
index 2455a5ab1e0b6d6188cdee1299b66f1d16075d7f..65a68c7b318eb1786c732e72a1623e6e5d08aab6 100644
--- a/src/r_segs.cpp
+++ b/src/r_segs.cpp
@@ -215,7 +215,7 @@ static void R_RenderMaskedSegLoop(drawcolumndata_t* dc, drawseg_t *drawseg, INT3
 	ldef = curline->linedef;
 	tripwire = P_IsLineTripWire(ldef);
 
-	range = std::max(drawseg->x2-drawseg->x1, 1);
+	range = std::max<INT32>(drawseg->x2-drawseg->x1, 1);
 
 	// Setup lighting based on the presence/lack-of 3D floors.
 	dc->numlights = 0;
@@ -874,7 +874,7 @@ void R_RenderThickSideRange(drawseg_t *ds, INT32 x1, INT32 x2, ffloor_t *pfloor)
 		R_SetColumnFunc(COLDRAWFUNC_FOG, brightmapped);
 	}
 
-	range = std::max(ds->x2-ds->x1, 1);
+	range = std::max<INT32>(ds->x2-ds->x1, 1);
 	//SoM: Moved these up here so they are available for my lightlist calculations
 	rw_scalestep = ds->scalestep;
 	spryscale = ds->scale1 + (x1 - ds->x1)*rw_scalestep;
diff --git a/src/r_things.cpp b/src/r_things.cpp
index e2590dcec6f19581073e0aa0b221c1df85046f57..60bb2be45217dcee3d0e3949b15c0d585a185edf 100644
--- a/src/r_things.cpp
+++ b/src/r_things.cpp
@@ -1082,7 +1082,7 @@ static void R_DrawVisSprite(vissprite_t *vis)
 		// Vertically sheared sprite
 		for (dc.x = vis->x1; dc.x <= vis->x2; dc.x++, frac += vis->xiscale, dc.texturemid -= vis->shear.tan)
 		{
-			texturecolumn = std::clamp(frac >> FRACBITS, 0, patch->width - 1);
+			texturecolumn = std::clamp<fixed_t>(frac >> FRACBITS, 0, patch->width - 1);
 
 			column = (column_t *)((UINT8 *)patch->columns + (patch->columnofs[texturecolumn]));
 			if (bmpatch)
@@ -1119,7 +1119,7 @@ static void R_DrawVisSprite(vissprite_t *vis)
 		// Non-paper drawing loop
 		for (dc.x = vis->x1; dc.x <= vis->x2; dc.x++, frac += vis->xiscale, sprtopscreen += vis->shear.tan)
 		{
-			texturecolumn = std::clamp(frac >> FRACBITS, 0, patch->width - 1);
+			texturecolumn = std::clamp<fixed_t>(frac >> FRACBITS, 0, patch->width - 1);
 
 			column = (column_t *)((UINT8 *)patch->columns + (patch->columnofs[texturecolumn]));
 
@@ -2373,7 +2373,7 @@ static void R_ProjectSprite(mobj_t *thing)
 			}
 
 			// Less change in contrast in dark sectors
-			extralight = FixedMul(extralight, std::min(std::max(0, lightnum), LIGHTLEVELS - 1) * FRACUNIT / (LIGHTLEVELS - 1));
+			extralight = FixedMul(extralight, std::min<INT32>(std::max<INT32>(0, lightnum), LIGHTLEVELS - 1) * FRACUNIT / (LIGHTLEVELS - 1));
 
 			if (papersprite)
 			{
@@ -2385,7 +2385,7 @@ static void R_ProjectSprite(mobj_t *thing)
 				fixed_t n = FixedDiv(FixedMul(xscale, LIGHTRESOLUTIONFIX), ((MAXLIGHTSCALE-1) << LIGHTSCALESHIFT));
 
 				// Less change in contrast at further distances, to counteract DOOM diminished light
-				extralight = FixedMul(extralight, std::min(n, FRACUNIT));
+				extralight = FixedMul(extralight, std::min<fixed_t>(n, FRACUNIT));
 
 				// Contrast is stronger for normal sprites, stronger than wall lighting is at the same distance
 				lightnum += FixedFloor((extralight / 4) + (FRACUNIT / 2)) / FRACUNIT;
@@ -2551,7 +2551,7 @@ static void R_ProjectSprite(mobj_t *thing)
 		lindex = FixedMul(xscale, LIGHTRESOLUTIONFIX)>>(LIGHTSCALESHIFT);
 
 		// Mitigate against negative xscale and arithmetic overflow
-		lindex = std::clamp(lindex, 0, MAXLIGHTSCALE - 1);
+		lindex = std::clamp<INT32>(lindex, 0, MAXLIGHTSCALE - 1);
 
 		if (vis->cut & SC_SEMIBRIGHT)
 			lindex = (MAXLIGHTSCALE/2) + (lindex >> 1);
diff --git a/src/rhi/gl2/gl2_rhi.cpp b/src/rhi/gl2/gl2_rhi.cpp
index 1740c0fa0d1dd03dbe099fb8845b117e02dad04e..340d24dbbfc1dca073254850dde1a11f7433464e 100644
--- a/src/rhi/gl2/gl2_rhi.cpp
+++ b/src/rhi/gl2/gl2_rhi.cpp
@@ -1918,7 +1918,7 @@ void Gl2Rhi::finish()
 	// I sure hope creating FBOs isn't costly on the driver!
 	for (auto& fbset : framebuffers_)
 	{
-		gl_->DeleteFramebuffers(1, &fbset.second);
+		gl_->DeleteFramebuffers(1, (GLuint*)&fbset.second);
 		GL_ASSERT;
 	}
 	framebuffers_.clear();
diff --git a/src/sdl/i_video.cpp b/src/sdl/i_video.cpp
index 3786b92d364fc54e3179df1bdf7364a0c532a03d..065ee395886e01dba8af69bdddb7aebeb775c61c 100644
--- a/src/sdl/i_video.cpp
+++ b/src/sdl/i_video.cpp
@@ -171,7 +171,7 @@ static SDL_bool Impl_CreateWindow(SDL_bool fullscreen);
 //static void Impl_SetWindowName(const char *title);
 static void Impl_SetWindowIcon(void);
 
-static void SDLSetMode(INT32 width, INT32 height, SDL_bool fullscreen, SDL_bool reposition)
+static void SDLSetMode(int width, int height, SDL_bool fullscreen, SDL_bool reposition)
 {
 	static SDL_bool wasfullscreen = SDL_FALSE;
 
diff --git a/src/st_stuff.c b/src/st_stuff.c
index 21e0669b1c784e9fe11f914bd4101a93a48f05e4..69e0b4d34b08a827f45b0dfc0d08f827fe3023ce 100644
--- a/src/st_stuff.c
+++ b/src/st_stuff.c
@@ -1490,7 +1490,7 @@ void ST_DrawSaveReplayHint(INT32 flags)
 	V_DrawRightAlignedThinString(
 		BASEVIDWIDTH - 2, 2,
 		flags|V_YELLOWMAP,
-		demo.willsave ? "Replay will be saved.  \xAB Change title" : "\xAB or \xAD Save replay"
+		(demo.willsave && demo.titlename[0]) ? "Replay will be saved.  \xAB Change title" : "\xAB or \xAD Save replay"
 	);
 }
 
diff --git a/src/v_video.cpp b/src/v_video.cpp
index 40f2d694ec508cd5b10cd343be6732045b0ff689..8439172f6763dc0029f38fdbd1f6dbcf68f82db7 100644
--- a/src/v_video.cpp
+++ b/src/v_video.cpp
@@ -1088,8 +1088,8 @@ void V_DrawFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c)
 		if (y < clip->top)
 			y = clip->top;
 
-		w = std::max(0, x2 - x);
-		h = std::max(0, y2 - y);
+		w = std::max<INT32>(0, x2 - x);
+		h = std::max<INT32>(0, y2 - y);
 	}
 
 	g_2d.begin_quad()
@@ -2117,7 +2117,7 @@ void V_DrawTitleCardStringFixed(fixed_t x, fixed_t y, fixed_t scale, const char
 
 				// otherwise; scalex must start at 0
 				// let's have each letter do 4 spins (360*4 + 90 = 1530 "degrees")
-				fakeang = std::min(360 + 90, let_time*41) * ANG1;
+				fakeang = std::min<INT32>(360 + 90, let_time*41) * ANG1;
 				scalex = FINESINE(fakeang>>ANGLETOFINESHIFT);
 			}
 			else if (!bossmode && let_time > threshold)
@@ -2125,7 +2125,7 @@ void V_DrawTitleCardStringFixed(fixed_t x, fixed_t y, fixed_t scale, const char
 				// Make letters disappear...
 				let_time -= threshold;
 
-				fakeang = std::max(0, (360+90) - let_time*41)*ANG1;
+				fakeang = std::max<INT32>(0, (360+90) - let_time*41)*ANG1;
 				scalex = FINESINE(fakeang>>ANGLETOFINESHIFT);
 			}
 
@@ -2205,7 +2205,7 @@ static inline fixed_t BunchedCharacterDim(
 	(void)chw;
 	(void)hchw;
 	(void)dupx;
-	(*cwp) = FixedMul(std::max(1, (*cwp) - 1) << FRACBITS, scale);
+	(*cwp) = FixedMul(std::max<INT32>(1, (*cwp) - 1) << FRACBITS, scale);
 	return 0;
 }
 
@@ -2219,7 +2219,7 @@ static inline fixed_t MenuCharacterDim(
 	(void)chw;
 	(void)hchw;
 	(void)dupx;
-	(*cwp) = FixedMul(std::max(1, (*cwp) - 2) << FRACBITS, scale);
+	(*cwp) = FixedMul(std::max<INT32>(1, (*cwp) - 2) << FRACBITS, scale);
 	return 0;
 }
 
@@ -2233,7 +2233,7 @@ static inline fixed_t GamemodeCharacterDim(
 	(void)chw;
 	(void)hchw;
 	(void)dupx;
-	(*cwp) = FixedMul(std::max(1, (*cwp) - 2) << FRACBITS, scale);
+	(*cwp) = FixedMul(std::max<INT32>(1, (*cwp) - 2) << FRACBITS, scale);
 	return 0;
 }
 
@@ -2247,7 +2247,7 @@ static inline fixed_t FileCharacterDim(
 	(void)chw;
 	(void)hchw;
 	(void)dupx;
-	(*cwp) = FixedMul(std::max(1, (*cwp) - 3) << FRACBITS, scale);
+	(*cwp) = FixedMul(std::max<INT32>(1, (*cwp) - 3) << FRACBITS, scale);
 	return 0;
 }
 
@@ -2261,7 +2261,7 @@ static inline fixed_t LSTitleCharacterDim(
 	(void)chw;
 	(void)hchw;
 	(void)dupx;
-	(*cwp) = FixedMul(std::max(1, (*cwp) - 4) << FRACBITS, scale);
+	(*cwp) = FixedMul(std::max<INT32>(1, (*cwp) - 4) << FRACBITS, scale);
 	return 0;
 }
 
diff --git a/src/w_wad.cpp b/src/w_wad.cpp
index 604ed203e44f2c6188d2247f68109c76498bee97..8d31c97a15be85b033494284633896f5ca5e0056 100644
--- a/src/w_wad.cpp
+++ b/src/w_wad.cpp
@@ -1021,6 +1021,19 @@ const char *W_CheckNameForNum(lumpnum_t lumpnum)
 	return W_CheckNameForNumPwad(WADFILENUM(lumpnum),LUMPNUM(lumpnum));
 }
 
+const char *W_CheckLongNameForNumPwad(UINT16 wad, UINT16 lump)
+{
+	if (lump >= wadfiles[wad]->numlumps || !TestValidLump(wad, 0))
+		return NULL;
+
+	return wadfiles[wad]->lumpinfo[lump].longname;
+}
+
+const char *W_CheckLongNameForNum(lumpnum_t lumpnum)
+{
+	return W_CheckLongNameForNumPwad(WADFILENUM(lumpnum),LUMPNUM(lumpnum));
+}
+
 //
 // wadid is a wad number
 // (Used for sprites loading)
diff --git a/src/w_wad.h b/src/w_wad.h
index d224475d90d4e969b38f0999cca87c292ccaa585..a928ffc44751cd0b95abbd7dbedf730110da89fe 100644
--- a/src/w_wad.h
+++ b/src/w_wad.h
@@ -154,6 +154,8 @@ INT32 W_InitMultipleFiles(char **filenames, boolean addons);
 
 const char *W_CheckNameForNumPwad(UINT16 wad, UINT16 lump);
 const char *W_CheckNameForNum(lumpnum_t lumpnum);
+const char *W_CheckLongNameForNumPwad(UINT16 wad, UINT16 lump);
+const char *W_CheckLongNameForNum(lumpnum_t lumpnum);
 
 UINT16 W_FindNextEmptyInPwad(UINT16 wad, UINT16 startlump); // checks only in one pwad
 
diff --git a/src/y_inter.cpp b/src/y_inter.cpp
index 897f009b4f8ebed5453a79b857cb34e27ed70f3d..b6a9735a0305668906121e10eaa8a8753f54b3d6 100644
--- a/src/y_inter.cpp
+++ b/src/y_inter.cpp
@@ -952,7 +952,7 @@ void Y_RoundQueueDrawer(y_data_t *standings, INT32 offset, boolean doanimations,
 		SINT8 deferxoffs = 0;
 
 		const INT32 desiredx2 = (290 + bufferspace);
-		spacetospecial = std::max(desiredx2 - widthofroundqueue - (24 - bufferspace), 16);
+		spacetospecial = std::max<INT32>(desiredx2 - widthofroundqueue - (24 - bufferspace), 16);
 
 		if (roundqueue.position == roundqueue.size)
 		{
@@ -2234,7 +2234,7 @@ void Y_StartIntermission(void)
 	else
 	{
 		// Minimum two seconds for match results, then two second slideover approx halfway through
-		sorttic = std::max((timer/2) - 2*TICRATE, 2*TICRATE);
+		sorttic = std::max<INT32>((timer/2) - 2*TICRATE, 2*TICRATE);
 	}
 
 	// TODO: code's a mess, I'm just making it extra clear
diff --git a/thirdparty/libwebm/mkvmuxer/mkvmuxer.h b/thirdparty/libwebm/mkvmuxer/mkvmuxer.h
index 2c4bb9e93e932312835eefacddc99ef596b45bde..6400f6de0086d87171f4bdf7ff5d3e2261b80d16 100644
--- a/thirdparty/libwebm/mkvmuxer/mkvmuxer.h
+++ b/thirdparty/libwebm/mkvmuxer/mkvmuxer.h
@@ -1770,7 +1770,7 @@ class Segment {
   //        accounted for.
   // index - index in the list of Cues which is currently being adjusted.
   // cue_size - sum of size of all the CuePoint elements.
-  void MoveCuesBeforeClustersHelper(uint64_t diff, int index,
+  void MoveCuesBeforeClustersHelper(uint64_t diff, int32_t index,
                                     uint64_t* cue_size);
 
   // Seeds the random number generator used to make UIDs.
diff --git a/thirdparty/libwebm/mkvmuxer/mkvmuxertypes.h b/thirdparty/libwebm/mkvmuxer/mkvmuxertypes.h
index e5db121605f691d69f3b26128a2f011bc891aa7c..bc91ff1392e9201ba4188ca0759e28b30ef498a6 100644
--- a/thirdparty/libwebm/mkvmuxer/mkvmuxertypes.h
+++ b/thirdparty/libwebm/mkvmuxer/mkvmuxertypes.h
@@ -8,14 +8,15 @@
 
 #ifndef MKVMUXER_MKVMUXERTYPES_H_
 #define MKVMUXER_MKVMUXERTYPES_H_
+#include <stdint.h>
 
 namespace mkvmuxer {
 typedef unsigned char uint8;
 typedef short int16;
-typedef int int32;
-typedef unsigned int uint32;
-typedef long long int64;
-typedef unsigned long long uint64;
+typedef int32_t int32;
+typedef uint32_t uint32;
+typedef int64_t int64;
+typedef uint64_t uint64;
 }  // namespace mkvmuxer
 
 // Copied from Chromium basictypes.h