diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 29f5ef5fff9f1bc4afb83006c65f54088762e6dc..14c36213e051f0af306b2d392af1f8da491ce289 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -12,6 +12,7 @@ variables:
 
 stages:
   - build
+  - osxcross
 
 default:
   interruptible: true
diff --git a/.gitlab/ci/jobs/alpine-3-gcc-dedicated-makefile.yml b/.gitlab/ci/jobs/alpine-3-gcc-dedicated-makefile.yml
index fe63e09c8368cca1da7203b81411f55b3165ac44..5d875d8657694b4899a31f33ba3440a2cd47e314 100644
--- a/.gitlab/ci/jobs/alpine-3-gcc-dedicated-makefile.yml
+++ b/.gitlab/ci/jobs/alpine-3-gcc-dedicated-makefile.yml
@@ -12,7 +12,7 @@ Alpine 3 GCC Dedicated Makefile:
     - - |
           # apk_toolchain
           echo -e "\e[0Ksection_start:`date +%s`:apk_toolchain[collapsed=true]\r\e[0KInstalling toolchain packages"
-      - apk add gcc g++
+      - apk add g++
       - |
           # apk_toolchain
           echo -e "\e[0Ksection_end:`date +%s`:apk_toolchain\r\e[0K"
diff --git a/.gitlab/ci/jobs/alpine-3-gcc-makefile.yml b/.gitlab/ci/jobs/alpine-3-gcc-makefile.yml
index 2cc656ca78a5641a594ee894a07f591c628ee36f..6b0b6a06f5b01dde129a3ecd75b92e9c06e3bbbb 100644
--- a/.gitlab/ci/jobs/alpine-3-gcc-makefile.yml
+++ b/.gitlab/ci/jobs/alpine-3-gcc-makefile.yml
@@ -95,7 +95,7 @@ Alpine 3 GCC Makefile:
     - - |
           # apk_toolchain
           echo -e "\e[0Ksection_start:`date +%s`:apk_toolchain[collapsed=true]\r\e[0KInstalling toolchain packages"
-      - apk add gcc g++
+      - apk add g++
       - |
           # apk_toolchain
           echo -e "\e[0Ksection_end:`date +%s`:apk_toolchain\r\e[0K"
diff --git a/.gitlab/ci/jobs/alpine-3-gcc.yml b/.gitlab/ci/jobs/alpine-3-gcc.yml
index 1881bf3c2a445b3a517e20f49fd2ecec34b81460..72994d405b3dc36e6545fd33b210fb5246971c6d 100644
--- a/.gitlab/ci/jobs/alpine-3-gcc.yml
+++ b/.gitlab/ci/jobs/alpine-3-gcc.yml
@@ -15,8 +15,8 @@ Alpine 3 GCC:
 
   artifacts:
     paths:
-      - "build.alpine3/bin/"
-      - "build.alpine3/src/config.h"
+      - "build.cmake/bin/"
+      - "build.cmake/src/config.h"
     expose_as: "Apline-3"
     name: "$CI_PROJECT_PATH_SLUG-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA-Apline-3"
 
@@ -95,7 +95,7 @@ Alpine 3 GCC:
     - - |
           # apk_toolchain
           echo -e "\e[0Ksection_start:`date +%s`:apk_toolchain[collapsed=true]\r\e[0KInstalling toolchain packages"
-      - apk add gcc g++
+      - apk add g++
       - |
           # apk_toolchain
           echo -e "\e[0Ksection_end:`date +%s`:apk_toolchain\r\e[0K"
@@ -111,7 +111,7 @@ Alpine 3 GCC:
     - - |
           # cmake
           echo -e "\e[0Ksection_start:`date +%s`:cmake[collapsed=false]\r\e[0KBuilding Makefiles"
-      - cmake -B build.alpine3 -DSRB2_USE_CCACHE=YES -DSRB2_CONFIG_ERRORMODE=ON -DSRB2_CONFIG_EXECINFO=NO -G "Unix Makefiles"
+      - cmake -B build.cmake -DSRB2_USE_CCACHE=YES -DSRB2_CONFIG_ERRORMODE=ON -DSRB2_CONFIG_EXECINFO=NO -DSRB2_CONFIG_USE_GME:BOOL=ON
       - |
           # cmake
           echo -e "\e[0Ksection_end:`date +%s`:cmake\r\e[0K"
@@ -119,7 +119,7 @@ Alpine 3 GCC:
     - - |
           # make
           echo -e "\e[0Ksection_start:`date +%s`:make[collapsed=false]\r\e[0KCompiling SRB2"
-      - make --directory=build.alpine3 --keep-going || make --directory=build.alpine3 --keep-going
+      - cmake --build build.cmake --parallel 1 --verbose
       - |
           # make
           echo -e "\e[0Ksection_end:`date +%s`:make\r\e[0K"
diff --git a/.gitlab/ci/jobs/batocera-arm64-makefile.yml b/.gitlab/ci/jobs/batocera-arm64-makefile.yml
index 9a590807de1d819d2be713011a2d8a1fbb61d1d1..e02497d40d7e190cac34bde6f7cb12c3cf755647 100644
--- a/.gitlab/ci/jobs/batocera-arm64-makefile.yml
+++ b/.gitlab/ci/jobs/batocera-arm64-makefile.yml
@@ -16,7 +16,7 @@ batocera:arm64 Makefile:
     - - |
           # apt_toolchain
           echo -e "\e[0Ksection_start:`date +%s`:apt_toolchain[collapsed=true]\r\e[0KInstalling toolchain packages"
-      - apt-get install gcc-aarch64-linux-gnu || apt-get install gcc
+      - apt-get install g++-aarch64-linux-gnu || apt-get install g++
       - |
           # apt_toolchain
           echo -e "\e[0Ksection_end:`date +%s`:apt_toolchain\r\e[0K"
diff --git a/.gitlab/ci/jobs/batocera-arm64.yml b/.gitlab/ci/jobs/batocera-arm64.yml
index c83a5badb0e096a48a83cc6fc32388a05ed245dd..3dcd73a0e2f422e84b5601a584d987a4bd0f2ed9 100644
--- a/.gitlab/ci/jobs/batocera-arm64.yml
+++ b/.gitlab/ci/jobs/batocera-arm64.yml
@@ -16,7 +16,7 @@ batocera:arm64:
     - - |
           # apt_toolchain
           echo -e "\e[0Ksection_start:`date +%s`:apt_toolchain[collapsed=true]\r\e[0KInstalling toolchain packages"
-      - apt-get install gcc-aarch64-linux-gnu || apt-get install gcc
+      - apt-get install g++-aarch64-linux-gnu || apt-get install g++
       - |
           # apt_toolchain
           echo -e "\e[0Ksection_end:`date +%s`:apt_toolchain\r\e[0K"
@@ -32,7 +32,7 @@ batocera:arm64:
     - - |
           # cmake
           echo -e "\e[0Ksection_start:`date +%s`:cmake[collapsed=false]\r\e[0KBuilding Makefiles"
-      - cmake -B build.cmake -DSRB2_USE_CCACHE=YES -DSRB2_CONFIG_ERRORMODE=ON -DSRB2_CONFIG_FORCE_NO_MS_BITFIELDS=ON -DSRB2_CONFIG_USE_GME=OFF -G "Unix Makefiles"
+      - cmake -B build.cmake -DSRB2_USE_CCACHE=YES -DSRB2_CONFIG_ERRORMODE=ON -DSRB2_CONFIG_FORCE_NO_MS_BITFIELDS=ON -DSRB2_CONFIG_USE_GME:BOOL=OFF
       - |
           # cmake
           echo -e "\e[0Ksection_end:`date +%s`:cmake\r\e[0K"
@@ -40,7 +40,7 @@ batocera:arm64:
     - - |
           # 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 build.cmake --parallel 1 --verbose
       - |
           # make
           echo -e "\e[0Ksection_end:`date +%s`:make\r\e[0K"
diff --git a/.gitlab/ci/jobs/debian-oldstable-amd64-makefile.yml b/.gitlab/ci/jobs/debian-oldstable-amd64-makefile.yml
index ba2e745bd423326fa326e18ee2985f659a235401..bd4a927419ad65ce51745214b44500f84751209a 100644
--- a/.gitlab/ci/jobs/debian-oldstable-amd64-makefile.yml
+++ b/.gitlab/ci/jobs/debian-oldstable-amd64-makefile.yml
@@ -18,7 +18,7 @@ Debian oldstable:amd64 Makefile:
     - - |
           # apt_toolchain
           echo -e "\e[0Ksection_start:`date +%s`:apt_toolchain[collapsed=true]\r\e[0KInstalling toolchain packages"
-      - apt-get install gcc-x86-64-linux-gnu || apt-get install gcc
+      - apt-get install g++-x86-64-linux-gnu || apt-get install g++
       - |
           # apt_toolchain
           echo -e "\e[0Ksection_end:`date +%s`:apt_toolchain\r\e[0K"
diff --git a/.gitlab/ci/jobs/debian-oldstable-amd64.yml b/.gitlab/ci/jobs/debian-oldstable-amd64.yml
index 32c7f3e40f6f9ac8145a6c679c86856ff879212a..231e8485d71d5a50e94c1ecc08fb140549595e5e 100644
--- a/.gitlab/ci/jobs/debian-oldstable-amd64.yml
+++ b/.gitlab/ci/jobs/debian-oldstable-amd64.yml
@@ -18,7 +18,7 @@ Debian oldstable:amd64:
     - - |
           # apt_toolchain
           echo -e "\e[0Ksection_start:`date +%s`:apt_toolchain[collapsed=true]\r\e[0KInstalling toolchain packages"
-      - apt-get install gcc-x86-64-linux-gnu || apt-get install gcc
+      - apt-get install ++-x86-64-linux-gnu || apt-get install g++
       - |
           # apt_toolchain
           echo -e "\e[0Ksection_end:`date +%s`:apt_toolchain\r\e[0K"
@@ -34,7 +34,7 @@ Debian oldstable:amd64:
     - - |
           # cmake
           echo -e "\e[0Ksection_start:`date +%s`:cmake[collapsed=false]\r\e[0KBuilding Makefiles"
-      - cmake -B build.cmake -DSRB2_USE_CCACHE=YES -DSRB2_CONFIG_ERRORMODE=ON -DSRB2_CONFIG_USE_GME=OFF -G "Unix Makefiles"
+      - cmake -B build.cmake -DSRB2_USE_CCACHE=YES -DSRB2_CONFIG_ERRORMODE=ON -DSRB2_CONFIG_USE_GME:BOOL=ON
       - |
           # cmake
           echo -e "\e[0Ksection_end:`date +%s`:cmake\r\e[0K"
@@ -42,7 +42,7 @@ Debian oldstable:amd64:
     - - |
           # 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 build.cmake --parallel 1 --verbose
       - |
           # make
           echo -e "\e[0Ksection_end:`date +%s`:make\r\e[0K"
diff --git a/.gitlab/ci/jobs/debian-oldstable-arm64-makefile.yml b/.gitlab/ci/jobs/debian-oldstable-arm64-makefile.yml
index 79e282bc62bbaf450e67da14041cb89bfbd910ca..426934bf97d9cae2875ec1bd4ed8dff7ad58af68 100644
--- a/.gitlab/ci/jobs/debian-oldstable-arm64-makefile.yml
+++ b/.gitlab/ci/jobs/debian-oldstable-arm64-makefile.yml
@@ -18,7 +18,7 @@ Debian oldstable:arm64 Makefile:
     - - |
           # apt_toolchain
           echo -e "\e[0Ksection_start:`date +%s`:apt_toolchain[collapsed=true]\r\e[0KInstalling toolchain packages"
-      - apt-get install gcc-aarch64-linux-gnu || apt-get install gcc
+      - apt-get install g++-aarch64-linux-gnu || apt-get install g++
       - |
           # apt_toolchain
           echo -e "\e[0Ksection_end:`date +%s`:apt_toolchain\r\e[0K"
diff --git a/.gitlab/ci/jobs/debian-oldstable-arm64.yml b/.gitlab/ci/jobs/debian-oldstable-arm64.yml
index 4bf324e3be14425830c4ff6d218d5ec33c35e559..76d401309d07df3ee312b85488c09a9dd0fd28fb 100644
--- a/.gitlab/ci/jobs/debian-oldstable-arm64.yml
+++ b/.gitlab/ci/jobs/debian-oldstable-arm64.yml
@@ -18,7 +18,7 @@ Debian oldstable:arm64:
     - - |
           # apt_toolchain
           echo -e "\e[0Ksection_start:`date +%s`:apt_toolchain[collapsed=true]\r\e[0KInstalling toolchain packages"
-      - apt-get install gcc-aarch64-linux-gnu || apt-get install gcc
+      - apt-get install g++-aarch64-linux-gnu || apt-get install g++
       - |
           # apt_toolchain
           echo -e "\e[0Ksection_end:`date +%s`:apt_toolchain\r\e[0K"
@@ -34,7 +34,7 @@ Debian oldstable:arm64:
     - - |
           # cmake
           echo -e "\e[0Ksection_start:`date +%s`:cmake[collapsed=false]\r\e[0KBuilding Makefiles"
-      - cmake -B build.cmake -DSRB2_USE_CCACHE=YES -DSRB2_CONFIG_ERRORMODE=ON -DSRB2_CONFIG_FORCE_NO_MS_BITFIELDS=ON -DSRB2_CONFIG_USE_GME=OFF -G "Unix Makefiles"
+      - cmake -B build.cmake -DSRB2_USE_CCACHE=YES -DSRB2_CONFIG_ERRORMODE=ON -DSRB2_CONFIG_FORCE_NO_MS_BITFIELDS=ON -DSRB2_CONFIG_USE_GME:BOOL=ON
       - |
           # cmake
           echo -e "\e[0Ksection_end:`date +%s`:cmake\r\e[0K"
@@ -42,7 +42,7 @@ Debian oldstable:arm64:
     - - |
           # 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 build.cmake --parallel 1 --verbose
       - |
           # make
           echo -e "\e[0Ksection_end:`date +%s`:make\r\e[0K"
diff --git a/.gitlab/ci/jobs/debian-stable-amd64-makefile.yml b/.gitlab/ci/jobs/debian-stable-amd64-makefile.yml
index 6dfe176cd5fda3b28f545f8d5e7907e2538f07de..a6aebfac34bf1ad1e99380b167f16f56a5686f12 100644
--- a/.gitlab/ci/jobs/debian-stable-amd64-makefile.yml
+++ b/.gitlab/ci/jobs/debian-stable-amd64-makefile.yml
@@ -12,6 +12,7 @@ Debian stable:amd64 Makefile:
 
   variables:
     CC: x86_64-linux-gnu-gcc
+    CXX: x86_64-linux-gnu-g++
     LDFLAGS: -Wl,-fuse-ld=gold
     OBJCOPY: x86_64-linux-gnu-objcopy
     OBJDUMP: x86_64-linux-gnu-objdump
@@ -23,7 +24,7 @@ Debian stable:amd64 Makefile:
     - - |
           # apt_toolchain
           echo -e "\e[0Ksection_start:`date +%s`:apt_toolchain[collapsed=true]\r\e[0KInstalling toolchain packages"
-      - apt-get install gcc-x86-64-linux-gnu || apt-get install gcc
+      - apt-get install g++-x86-64-linux-gnu || apt-get install g++
       - |
           # apt_toolchain
           echo -e "\e[0Ksection_end:`date +%s`:apt_toolchain\r\e[0K"
diff --git a/.gitlab/ci/jobs/debian-stable-amd64.yml b/.gitlab/ci/jobs/debian-stable-amd64.yml
index 533a3151d5d81e1d9df62e31471269c6bf2b89da..ed1f5ce23e0ff88a96ed73ddc82f5371a7b13656 100644
--- a/.gitlab/ci/jobs/debian-stable-amd64.yml
+++ b/.gitlab/ci/jobs/debian-stable-amd64.yml
@@ -12,6 +12,7 @@ Debian stable:amd64:
 
   variables:
     CC: x86_64-linux-gnu-gcc
+    CXX: x86_64-linux-gnu-g++
     LDFLAGS: -Wl,-fuse-ld=gold
     OBJCOPY: x86_64-linux-gnu-objcopy
     OBJDUMP: x86_64-linux-gnu-objdump
@@ -23,7 +24,7 @@ Debian stable:amd64:
     - - |
           # apt_toolchain
           echo -e "\e[0Ksection_start:`date +%s`:apt_toolchain[collapsed=true]\r\e[0KInstalling toolchain packages"
-      - apt-get install gcc-x86-64-linux-gnu || apt-get install gcc
+      - apt-get install g++-x86-64-linux-gnu || apt-get install g++
       - |
           # apt_toolchain
           echo -e "\e[0Ksection_end:`date +%s`:apt_toolchain\r\e[0K"
@@ -39,7 +40,7 @@ Debian stable:amd64:
     - - |
           # cmake
           echo -e "\e[0Ksection_start:`date +%s`:cmake[collapsed=false]\r\e[0KBuilding Makefiles"
-      - cmake -B build.cmake -DSRB2_USE_CCACHE=YES -DSRB2_CONFIG_ERRORMODE=ON -G "Unix Makefiles"
+      - cmake -B build.cmake -DSRB2_USE_CCACHE=YES -DSRB2_CONFIG_ERRORMODE=ON -DSRB2_CONFIG_USE_GME:BOOL=ON
       - |
           # cmake
           echo -e "\e[0Ksection_end:`date +%s`:cmake\r\e[0K"
@@ -47,7 +48,7 @@ Debian stable:amd64:
     - - |
           # 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 build.cmake --parallel 1 --verbose
       - |
           # make
           echo -e "\e[0Ksection_end:`date +%s`:make\r\e[0K"
diff --git a/.gitlab/ci/jobs/debian-stable-arm64-makefile.yml b/.gitlab/ci/jobs/debian-stable-arm64-makefile.yml
index d932e9f4236d1983950ca61a9e41715af4aed5f5..53625138abe3c56dc23612b9abca129f461f8b68 100644
--- a/.gitlab/ci/jobs/debian-stable-arm64-makefile.yml
+++ b/.gitlab/ci/jobs/debian-stable-arm64-makefile.yml
@@ -14,6 +14,7 @@ Debian stable:arm64 Makefile:
 
   variables:
     CC: aarch64-linux-gnu-gcc
+    CXX: aarch64-linux-gnu-g++
     LDFLAGS: -Wl,-fuse-ld=gold
     OBJCOPY: aarch64-linux-gnu-objcopy
     OBJDUMP: aarch64-linux-gnu-objdump
@@ -24,7 +25,7 @@ Debian stable:arm64 Makefile:
     - - |
           # apt_toolchain
           echo -e "\e[0Ksection_start:`date +%s`:apt_toolchain[collapsed=true]\r\e[0KInstalling toolchain packages"
-      - apt-get install gcc-aarch64-linux-gnu || apt-get install gcc
+      - apt-get install gg++-aarch64-linux-gnu || apt-get install g++
       - |
           # apt_toolchain
           echo -e "\e[0Ksection_end:`date +%s`:apt_toolchain\r\e[0K"
diff --git a/.gitlab/ci/jobs/debian-stable-arm64.yml b/.gitlab/ci/jobs/debian-stable-arm64.yml
index db82ee38a0ac4c7ad483479bb81ab78cdba0a98a..b0f02dd391b181a3c3f42f3fc7a71233913d4b5d 100644
--- a/.gitlab/ci/jobs/debian-stable-arm64.yml
+++ b/.gitlab/ci/jobs/debian-stable-arm64.yml
@@ -14,6 +14,7 @@ Debian stable:arm64:
 
   variables:
     CC: aarch64-linux-gnu-gcc
+    CXX: aarch64-linux-gnu-g++
     LDFLAGS: -Wl,-fuse-ld=gold
     OBJCOPY: aarch64-linux-gnu-objcopy
     OBJDUMP: aarch64-linux-gnu-objdump
@@ -24,7 +25,7 @@ Debian stable:arm64:
     - - |
           # apt_toolchain
           echo -e "\e[0Ksection_start:`date +%s`:apt_toolchain[collapsed=true]\r\e[0KInstalling toolchain packages"
-      - apt-get install gcc-aarch64-linux-gnu || apt-get install gcc
+      - apt-get install g++-aarch64-linux-gnu || apt-get install g++
       - |
           # apt_toolchain
           echo -e "\e[0Ksection_end:`date +%s`:apt_toolchain\r\e[0K"
@@ -40,7 +41,7 @@ Debian stable:arm64:
     - - |
           # cmake
           echo -e "\e[0Ksection_start:`date +%s`:cmake[collapsed=false]\r\e[0KBuilding Makefiles"
-      - cmake -B build.cmake -DSRB2_USE_CCACHE=YES -DSRB2_CONFIG_ERRORMODE=ON -DSRB2_CONFIG_FORCE_NO_MS_BITFIELDS=ON -G "Unix Makefiles"
+      - cmake -B build.cmake -DSRB2_USE_CCACHE=YES -DSRB2_CONFIG_ERRORMODE=ON -DSRB2_CONFIG_FORCE_NO_MS_BITFIELDS=ON -DSRB2_CONFIG_USE_GME:BOOL=ON
       - |
           # cmake
           echo -e "\e[0Ksection_end:`date +%s`:cmake\r\e[0K"
@@ -48,7 +49,7 @@ Debian stable:arm64:
     - - |
           # 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 build.cmake --parallel 1 --verbose
       - |
           # make
           echo -e "\e[0Ksection_end:`date +%s`:make\r\e[0K"
diff --git a/.gitlab/ci/jobs/debian-stable-clang-amd64.yml b/.gitlab/ci/jobs/debian-stable-clang-amd64.yml
index 4686c1849196d3be6cbee936c1ad910c0f93627c..c40998e17af3157856f92fbb0121c409655900bc 100644
--- a/.gitlab/ci/jobs/debian-stable-clang-amd64.yml
+++ b/.gitlab/ci/jobs/debian-stable-clang-amd64.yml
@@ -9,8 +9,8 @@ Debian stable Clang:
 
   artifacts:
     paths:
-      - "build.clang/bin/"
-      - "build.clang/src/config.h"
+      - "build.cmake/bin/"
+      - "build.cmake/src/config.h"
     expose_as: "clang"
     name: "$CI_PROJECT_PATH_SLUG-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA-clang"
 
@@ -41,7 +41,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 -G "Unix 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_USE_GME:BOOL=ON
       - |
           # cmake
           echo -e "\e[0Ksection_end:`date +%s`:cmake\r\e[0K"
@@ -49,7 +49,7 @@ Debian stable Clang:
     - - |
           # make
           echo -e "\e[0Ksection_start:`date +%s`:make[collapsed=false]\r\e[0KCompiling SRB2"
-      - make --directory=build.clang --keep-going || make --directory=build.clang --keep-going
+      - cmake  --build build.cmake --parallel 1 --verbose
       - |
           # make
           echo -e "\e[0Ksection_end:`date +%s`:make\r\e[0K"
diff --git a/.gitlab/ci/jobs/debian-stable-i386-makefile.yml b/.gitlab/ci/jobs/debian-stable-i386-makefile.yml
index bbabee4a6cde37050ee3e6bbbc89e918914e275f..dd572ec383c489d3414a312348806f47c65f3cbc 100644
--- a/.gitlab/ci/jobs/debian-stable-i386-makefile.yml
+++ b/.gitlab/ci/jobs/debian-stable-i386-makefile.yml
@@ -14,6 +14,7 @@ Debian stable:i386 Makefile:
 
   variables:
     CC: i686-linux-gnu-gcc
+    CXX: i686-linux-gnu-g++
     OBJCOPY: i686-linux-gnu-objcopy
     OBJDUMP: i686-linux-gnu-objdump
     LD: i686-linux-gnu-ld
@@ -23,7 +24,7 @@ Debian stable:i386 Makefile:
     - - |
           # apt_toolchain
           echo -e "\e[0Ksection_start:`date +%s`:apt_toolchain[collapsed=true]\r\e[0KInstalling toolchain packages"
-      - apt-get install gcc-i686-linux-gnu || apt-get install gcc
+      - apt-get install g++-i686-linux-gnu || apt-get install g++
       - |
           # apt_toolchain
           echo -e "\e[0Ksection_end:`date +%s`:apt_toolchain\r\e[0K"
diff --git a/.gitlab/ci/jobs/debian-stable-i386.yml b/.gitlab/ci/jobs/debian-stable-i386.yml
index 8d9ea49644a700490c471414248e671eef8e3e00..970b92a69c6f00ff508a0126bd1b82962f8e5c48 100644
--- a/.gitlab/ci/jobs/debian-stable-i386.yml
+++ b/.gitlab/ci/jobs/debian-stable-i386.yml
@@ -14,6 +14,7 @@ Debian stable:i386:
 
   variables:
     CC: i686-linux-gnu-gcc
+    CXX: i686-linux-gnu-g++
     OBJCOPY: i686-linux-gnu-objcopy
     OBJDUMP: i686-linux-gnu-objdump
     LD: i686-linux-gnu-ld
@@ -23,7 +24,7 @@ Debian stable:i386:
     - - |
           # apt_toolchain
           echo -e "\e[0Ksection_start:`date +%s`:apt_toolchain[collapsed=true]\r\e[0KInstalling toolchain packages"
-      - apt-get install gcc-i686-linux-gnu || apt-get install gcc
+      - apt-get install g++-i686-linux-gnu || apt-get install g++
       - |
           # apt_toolchain
           echo -e "\e[0Ksection_end:`date +%s`:apt_toolchain\r\e[0K"
@@ -39,7 +40,7 @@ Debian stable:i386:
     - - |
           # cmake
           echo -e "\e[0Ksection_start:`date +%s`:cmake[collapsed=false]\r\e[0KBuilding Makefiles"
-      - cmake -B build.cmake -DSRB2_USE_CCACHE=YES -DSRB2_CONFIG_ERRORMODE=ON -G "Unix Makefiles"
+      - cmake -B build.cmake -DSRB2_USE_CCACHE=YES -DSRB2_CONFIG_ERRORMODE=ON -DSRB2_CONFIG_USE_GME:BOOL=ON
       - |
           # cmake
           echo -e "\e[0Ksection_end:`date +%s`:cmake\r\e[0K"
@@ -47,7 +48,7 @@ Debian stable:i386:
     - - |
           # 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 build.cmake --parallel 1 --verbose
       - |
           # make
           echo -e "\e[0Ksection_end:`date +%s`:make\r\e[0K"
diff --git a/.gitlab/ci/jobs/debian-testing-gcc-amd64-makefile.yml b/.gitlab/ci/jobs/debian-testing-gcc-amd64-makefile.yml
index 5edaddf3373e3cdcad9d0a6eda0167df2d813c2e..70d71b537703e6ef07dcd572dec0d318d86e9d93 100644
--- a/.gitlab/ci/jobs/debian-testing-gcc-amd64-makefile.yml
+++ b/.gitlab/ci/jobs/debian-testing-gcc-amd64-makefile.yml
@@ -18,13 +18,14 @@ Debian testing GCC Makefile:
 
   variables:
     CC: gcc
+    CXX: g++
     LDFLAGS: -Wl,-fuse-ld=gold
 
   script:
     - - |
           # apt_toolchain
           echo -e "\e[0Ksection_start:`date +%s`:apt_toolchain[collapsed=true]\r\e[0KInstalling toolchain packages"
-      - apt-get install gcc
+      - apt-get install g++
       - |
           # apt_toolchain
           echo -e "\e[0Ksection_end:`date +%s`:apt_toolchain\r\e[0K"
diff --git a/.gitlab/ci/jobs/debian-testing-gcc-amd64.yml b/.gitlab/ci/jobs/debian-testing-gcc-amd64.yml
index 458c6d1efdc203da3d248a41ac5a29e583a53882..72bfdc3e3a7e24884ce031d01fcab86f0baa48c8 100644
--- a/.gitlab/ci/jobs/debian-testing-gcc-amd64.yml
+++ b/.gitlab/ci/jobs/debian-testing-gcc-amd64.yml
@@ -18,13 +18,14 @@ Debian testing GCC:
 
   variables:
     CC: gcc
+    CXX: g++
     LDFLAGS: -Wl,-fuse-ld=gold
 
   script:
     - - |
           # apt_toolchain
           echo -e "\e[0Ksection_start:`date +%s`:apt_toolchain[collapsed=true]\r\e[0KInstalling toolchain packages"
-      - apt-get install gcc
+      - apt-get install g++
       - |
           # apt_toolchain
           echo -e "\e[0Ksection_end:`date +%s`:apt_toolchain\r\e[0K"
@@ -40,7 +41,7 @@ Debian testing GCC:
     - - |
           # cmake
           echo -e "\e[0Ksection_start:`date +%s`:cmake[collapsed=false]\r\e[0KBuilding Makefiles"
-      - cmake -B build.cmake -DSRB2_USE_CCACHE=YES -DSRB2_CONFIG_ERRORMODE=ON -G "Unix Makefiles"
+      - cmake -B build.cmake -DSRB2_USE_CCACHE=YES -DSRB2_CONFIG_ERRORMODE=ON -DSRB2_CONFIG_USE_GME:BOOL=ON
       - |
           # cmake
           echo -e "\e[0Ksection_end:`date +%s`:cmake\r\e[0K"
@@ -48,7 +49,7 @@ Debian testing GCC:
     - - |
           # 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 build.cmake --parallel 1 --verbose
       - |
           # make
           echo -e "\e[0Ksection_end:`date +%s`:make\r\e[0K"
diff --git a/.gitlab/ci/jobs/macos-arm64.yml b/.gitlab/ci/jobs/macos-arm64.yml
index a9e31773e6b2d58ffa4fce856085a83ad0f8bece..1c89000c2f540bc1b1251f66aba833a6557ad9f9 100644
--- a/.gitlab/ci/jobs/macos-arm64.yml
+++ b/.gitlab/ci/jobs/macos-arm64.yml
@@ -3,16 +3,13 @@ osxcross arm64:
 
   stage: build
 
-  when: manual
-
-  allow_failure: true
-
   artifacts:
     paths:
-      - "build.osxcross/bin/"
-      - "build.osxcross/src/config.h"
+      - "build.arm64/bin/"
+      - "build.arm64/dist/arm64.h"
+      - "build.arm64/src/config.h"
     expose_as: "Mac arm64"
-    name: "$CI_PROJECT_PATH_SLUG-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA-clang"
+    name: "$CI_PROJECT_PATH_SLUG-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA-arm64-apple-darwin21.4"
 
   variables:
     OSXCROSS_HOST: arm64-apple-darwin21.4
@@ -22,7 +19,8 @@ osxcross arm64:
     - - |
           # apt_development
           echo -e "\e[0Ksection_start:`date +%s`:macports_development[collapsed=true]\r\e[0KInstalling development packages"
-      - osxcross-macports install --arm64 curl libopenmpt libsdl2_mixer
+      - osxcross-macports install --arm64 libxmp wavpack libopenmpt opusfile || osxcross-macports install --arm64 libxmp wavpack libopenmpt opusfile
+      - osxcross-macports install --static --arm64 curl libsdl2_mixer libpng || osxcross-macports install --static --arm64 curl libsdl2_mixer libpng
       - |
           # apt_development
           echo -e "\e[0Ksection_end:`date +%s`:macports_development\r\e[0K"
@@ -30,7 +28,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_FORCE_NO_MS_BITFIELDS:BOOL=ON -DSRB2_CONFIG_USE_GME:BOOL=OFF -G "Unix Makefiles"
+      - cmake -B build.arm64 --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_FORCE_NO_MS_BITFIELDS:BOOL=ON -DSRB2_CONFIG_USE_GME:BOOL=OFF -DSRB2_SDL2_EXE_NAME=srb2_$CI_PIPELINE_ID
       - |
           # make
           echo -e "\e[0Ksection_end:`date +%s`:make\r\e[0K"
@@ -38,7 +36,16 @@ osxcross arm64:
     - - |
           # make
           echo -e "\e[0Ksection_start:`date +%s`:make[collapsed=false]\r\e[0KCompiling SRB2"
-      - make --directory=build.osxcross --keep-going || make --directory=build.osxcross --keep-going
+      - cmake --build build.arm64 --parallel 1 --verbose
       - |
           # make
           echo -e "\e[0Ksection_end:`date +%s`:make\r\e[0K"
+
+    - - |
+          # copy config.h
+          echo -e "\e[0Ksection_start:`date +%s`:copy[collapsed=false]\r\e[0KCopying config.h"
+      - mkdir --parents --verbose build.arm64/dist
+      - cp --reflink=auto --sparse=always --verbose build.arm64/src/config.h build.arm64/dist/arm64.h
+      - |
+          # make
+          echo -e "\e[0Ksection_end:`date +%s`:copy\r\e[0K"
diff --git a/.gitlab/ci/jobs/macos-x86_64.yml b/.gitlab/ci/jobs/macos-x86_64.yml
index 525a919c8a6976308d752bc82e68143233d59798..d40ae65f6c7729bf82d005c9024627da0fd36274 100644
--- a/.gitlab/ci/jobs/macos-x86_64.yml
+++ b/.gitlab/ci/jobs/macos-x86_64.yml
@@ -29,10 +29,11 @@ osxcross x86_64:
 
   artifacts:
     paths:
-      - "build.osxcross/bin/"
-      - "build.osxcross/src/config.h"
+      - "build.x86_64/bin/"
+      - "build.x86_64/dist/x86_64.h"
+      - "build.x86_64/src/config.h"
     expose_as: "Mac x86_64"
-    name: "$CI_PROJECT_PATH_SLUG-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA-clang"
+    name: "$CI_PROJECT_PATH_SLUG-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA-x86_64-apple-darwin21.4"
 
   variables:
     OSXCROSS_HOST: x86_64-apple-darwin21.4
@@ -63,7 +64,8 @@ osxcross x86_64:
     - - |
           # apt_development
           echo -e "\e[0Ksection_start:`date +%s`:macports_development[collapsed=true]\r\e[0KInstalling development packages"
-      - osxcross-macports install curl libopenmpt libsdl2_mixer
+      - osxcross-macports install libxmp wavpack libopenmpt opusfile || osxcross-macports install libxmp wavpack libopenmpt opusfile
+      - osxcross-macports install --static curl libsdl2_mixer libpng || osxcross-macports install --static curl libsdl2_mixer libpng
       - |
           # apt_development
           echo -e "\e[0Ksection_end:`date +%s`:macports_development\r\e[0K"
@@ -71,7 +73,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 -G "Unix Makefiles"
+      - cmake -B build.x86_64 --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_SDL2_EXE_NAME=srb2_$CI_PIPELINE_ID
       - |
           # make
           echo -e "\e[0Ksection_end:`date +%s`:make\r\e[0K"
@@ -79,11 +81,21 @@ osxcross x86_64:
     - - |
           # make
           echo -e "\e[0Ksection_start:`date +%s`:make[collapsed=false]\r\e[0KCompiling SRB2"
-      - make --directory=build.osxcross --keep-going || make --directory=build.osxcross --keep-going
+      - cmake  --build build.x86_64 --parallel 1 --verbose
       - |
           # make
           echo -e "\e[0Ksection_end:`date +%s`:make\r\e[0K"
 
+    - - |
+          # copy config.h
+          echo -e "\e[0Ksection_start:`date +%s`:copy[collapsed=false]\r\e[0KCopying config.h"
+      - mkdir --parents --verbose build.x86_64/dist
+      - cp --reflink=auto --sparse=always --verbose build.x86_64/src/config.h build.x86_64/dist/x86_64.h
+      - |
+          # make
+          echo -e "\e[0Ksection_end:`date +%s`:copy\r\e[0K"
+
+
   after_script:
     - - |
            # apt_clean
diff --git a/.gitlab/ci/jobs/osxccross-universal.yml b/.gitlab/ci/jobs/osxccross-universal.yml
new file mode 100644
index 0000000000000000000000000000000000000000..841151f60895c9ea966068a541fa74251dc3964e
--- /dev/null
+++ b/.gitlab/ci/jobs/osxccross-universal.yml
@@ -0,0 +1,67 @@
+osxcross universal:
+  image: git.do.srb2.org:5050/stjr/srb2ci/srb2ci:stable
+
+  dependencies:
+    - osxcross arm64
+    - osxcross x86_64
+  needs:
+    - job: osxcross arm64
+    - job: osxcross x86_64
+
+  stage: osxcross
+
+  artifacts:
+    paths:
+      - "dist/bin"
+      - "dist/src"
+    expose_as: "Mac Universal"
+    name: "$CI_PROJECT_PATH_SLUG-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA-lipo-apple-darwin21.4"
+
+  script:
+    - - |
+         # mkdir
+         echo -e "\e[0Ksection_start:`date +%s`:mkdir[collapsed=true]\r\e[0KMaking dist folder"
+         mkdir --parents --verbose dist/src dist/bin
+      - |
+          # mkdir
+          echo -e "\e[0Ksection_end:`date +%s`:mkdir\r\e[0K"
+
+    - - |
+         # copy-config
+         echo -e "\e[0Ksection_start:`date +%s`:x86_64-config[collapsed=true]\r\e[0KCopying x86_64 config"
+      - cp --reflink=auto --sparse=always --verbose --target-directory=dist/src/ build.*/dist/*.h
+      - |
+          # x86_64-config
+          echo -e "\e[0Ksection_end:`date +%s`:x86_64-config\r\e[0K"
+
+    - - |
+         # copy-build
+         echo -e "\e[0Ksection_start:`date +%s`:copy-build[collapsed=true]\r\e[0KCopying ALL build"
+      - cp --reflink=auto --sparse=always --recursive --verbose --target-directory=dist/ build.*/bin/
+      - |
+          # copy-build
+          echo -e "\e[0Ksection_end:`date +%s`:copy-build\r\e[0K"
+
+    - - |
+         # link-build
+         echo -e "\e[0Ksection_start:`date +%s`:link-build[collapsed=true]\r\e[0KLinking universal build"
+      - lipo -create -output dist/bin/srb2_$CI_PIPELINE_ID.app/Contents/MacOS/srb2_$CI_PIPELINE_ID build.*/bin/srb2_$CI_PIPELINE_ID.app/Contents/MacOS/srb2_$CI_PIPELINE_ID
+      - |
+          # universal-build
+          echo -e "\e[0Ksection_end:`date +%s`:link-build\r\e[0K"
+
+    - - |
+         # arm64-verify
+         echo -e "\e[0Ksection_start:`date +%s`:arm64-verify[collapsed=true]\r\e[0KVerifying arm64"
+      - lipo dist/bin/srb2_$CI_PIPELINE_ID.app/Contents/MacOS/srb2_$CI_PIPELINE_ID -verify_arch arm64
+      - |
+          # arm64-verify
+          echo -e "\e[0Ksection_end:`date +%s`:arm64-verify\r\e[0K"
+
+    - - |
+         # x86_64-verify
+         echo -e "\e[0Ksection_start:`date +%s`:x86_64-verify[collapsed=true]\r\e[0KVerifying x86_64"
+      - lipo dist/bin/srb2_$CI_PIPELINE_ID.app/Contents/MacOS/srb2_$CI_PIPELINE_ID -verify_arch x86_64
+      - |
+          # x86_64-verify
+          echo -e "\e[0Ksection_end:`date +%s`:x86_64-verify\r\e[0K"
diff --git a/.gitlab/ci/jobs/windows-x64-makefile.yml b/.gitlab/ci/jobs/windows-x64-makefile.yml
index f28fa219cc09550684abeddb5300927f00882d1a..8da30d2b9822fd42c90f00df2d061284d1c753ef 100644
--- a/.gitlab/ci/jobs/windows-x64-makefile.yml
+++ b/.gitlab/ci/jobs/windows-x64-makefile.yml
@@ -21,7 +21,7 @@ Windows x64 Makefile:
     - - |
           # apt_toolchain
           echo -e "\e[0Ksection_start:`date +%s`:apt_toolchain[collapsed=true]\r\e[0KInstalling toolchain packages"
-      - apt-get install gcc-mingw-w64-x86-64-win32
+      - apt-get install g++-mingw-w64-x86-64-win32
       - |
           # apt_toolchain
           echo -e "\e[0Ksection_end:`date +%s`:apt_toolchain\r\e[0K"
diff --git a/.gitlab/ci/jobs/windows-x64.yml b/.gitlab/ci/jobs/windows-x64.yml
index 180fa773c34823e9eebd8254500233d1be8215a7..73791e82a202d91f1b0bc4c28e764bb0a846941a 100644
--- a/.gitlab/ci/jobs/windows-x64.yml
+++ b/.gitlab/ci/jobs/windows-x64.yml
@@ -7,6 +7,30 @@ Windows x64:
 
   allow_failure: true
 
+  cache:
+    - key: ccache-$CI_JOB_NAME_SLUG-$CI_COMMIT_REF_SLUG
+      fallback_keys:
+        - ccache-$CI_JOB_NAME_SLUG-$CI_DEFAULT_BRANCH
+        - ccache-$CI_JOB_NAME_SLUG-master
+      paths:
+        - build/ccache
+        - build/ccache_statslog
+
+    - key: apt-$CI_JOB_IMAGE
+      paths:
+        - build/apt-cache
+      unprotect: true
+
+    - key: vcpkg-root
+      paths:
+        - build/vcpkg-root
+      unprotect: true
+
+    - key: vcpkg-binary-cache-x64-mingw-static
+      paths:
+        - build/vcpkg-binary-cache
+      unprotect: true
+
   artifacts:
     paths:
       - "build.cmake/bin/"
@@ -16,12 +40,35 @@ Windows x64:
 
   variables:
     PREFIX: x86_64-w64-mingw32
+    CC: /usr/lib/ccache/x86_64-w64-mingw32-gcc
+    CXX: /usr/lib/ccache/x86_64-w64-mingw32-g++
 
   script:
+    - |
+        # vcpkg
+        echo -e "\e[0Ksection_start:`date +%s`:vcpkg-root[collapsed=true]\r\e[0KUpdating vcpkg"
+
+        if [ -d "build/vcpkg-root" ]; then
+          pushd build/vcpkg-root
+          git fetch https://github.com/Microsoft/vcpkg master
+          git reset --hard FETCH_HEAD
+          popd
+        else
+          mkdir -p build
+          git clone https://github.com/Microsoft/vcpkg build/vcpkg-root
+        fi
+
+        export VCPKG_ROOT=$(pwd)/build/vcpkg-root
+        export VCPKG_BINARY_SOURCES="clear;files,/opt/vcpkg.bsources,read;files,$(pwd)/build/vcpkg-binary-cache,readwrite"
+
+        mkdir -p "build/vcpkg-binary-cache"
+
+        echo -e "\e[0Ksection_end:`date +%s`:vcpkg-root\r\e[0K"
+
     - - |
           # apt_toolchain
           echo -e "\e[0Ksection_start:`date +%s`:apt_toolchain[collapsed=true]\r\e[0KInstalling toolchain packages"
-      - apt-get install gcc-mingw-w64-x86-64-win32
+      - apt-get install g++-mingw-w64-x86-64-win32
       - |
           # apt_toolchain
           echo -e "\e[0Ksection_end:`date +%s`:apt_toolchain\r\e[0K"
@@ -37,7 +84,7 @@ Windows x64:
     - - |
           # cmake
           echo -e "\e[0Ksection_start:`date +%s`:cmake[collapsed=false]\r\e[0KBuilding Makefiles"
-      - cmake -B build.cmake -DSRB2_USE_CCACHE=YES -DSRB2_CONFIG_ERRORMODE=ON -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 -B build.cmake -DSRB2_USE_CCACHE=NO -DSRB2_CONFIG_ERRORMODE=ON -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 -G "Unix Makefiles"
       - |
           # cmake
           echo -e "\e[0Ksection_end:`date +%s`:cmake\r\e[0K"
@@ -45,7 +92,37 @@ Windows x64:
     - - |
           # 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 build.cmake --parallel 1 --verbose
       - |
           # make
           echo -e "\e[0Ksection_end:`date +%s`:make\r\e[0K"
+
+  after_script:
+    - - |
+           # apt_clean
+           echo -e "\e[0Ksection_start:`date +%s`:apt_clean[collapsed=true]\r\e[0KCleaning of unneeded APT packages"
+      - apt-get autoclean
+      - |
+          # apt_clean
+          echo -e "\e[0Ksection_end:`date +%s`:apt_clean\r\e[0K"
+
+    - - |
+          # vcpkg_clean
+          echo -e "\e[0Ksection_start:`date +%s`:vcpkg_clean[collapsed=true]\r\e[0KCleaning vcpkg-root"
+
+          if [ -d "build/vcpkg-root" ]; then
+            pushd "build/vcpkg-root"
+            git clean -f
+            popd
+          fi
+
+          echo -e "\e[0Ksection_end:`date +%s`:vcpkg_clean\r\e[0K"
+
+    - - |
+          # ccache_stats
+          echo -e "\e[0Ksection_start:`date +%s`:ccache_stats[collapsed=true]\r\e[0Kccache statistics:"
+      - ccache --show-stats
+      - ccache --show-log-stats || true
+      - |
+          # ccahe_stats
+          echo -e "\e[0Ksection_end:`date +%s`:ccache_stats\r\e[0K"
diff --git a/.gitlab/ci/jobs/windows-x86-makefile.yml b/.gitlab/ci/jobs/windows-x86-makefile.yml
index 9601cd6e68013b1971b6d3276802bad932a5a95c..213342cda81e119827937a79df8b1f74242d7ef2 100644
--- a/.gitlab/ci/jobs/windows-x86-makefile.yml
+++ b/.gitlab/ci/jobs/windows-x86-makefile.yml
@@ -21,7 +21,7 @@ Windows x86 Makefile:
     - - |
           # apt_toolchain
           echo -e "\e[0Ksection_start:`date +%s`:apt_toolchain[collapsed=true]\r\e[0KInstalling toolchain packages"
-      - apt-get install gcc-mingw-w64-i686-win32
+      - apt-get install g++-mingw-w64-i686-win32
       - |
           # apt_toolchain
           echo -e "\e[0Ksection_end:`date +%s`:apt_toolchain\r\e[0K"
diff --git a/.gitlab/ci/jobs/windows-x86.yml b/.gitlab/ci/jobs/windows-x86.yml
index 2d885508570913bdb71bbb2bab1bd34f03c7e67a..9a33364b2830df79055838037563a2711fd51d04 100644
--- a/.gitlab/ci/jobs/windows-x86.yml
+++ b/.gitlab/ci/jobs/windows-x86.yml
@@ -3,10 +3,6 @@ Windows x86:
 
   stage: build
 
-  when: manual
-
-  allow_failure: true
-
   cache:
     - key: ccache-$CI_JOB_NAME_SLUG-$CI_COMMIT_REF_SLUG
       fallback_keys:
@@ -33,15 +29,15 @@ Windows x86:
 
   artifacts:
     paths:
-      - "build/ninja-x86_mingw_static_vcpkg-debug/bin/"
-      - "build/ninja-x86_mingw_static_vcpkg-debug/src/config.h"
+      - "build.cmake/bin/"
+      - "build.cmake/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
+    CC: /usr/lib/ccache/i686-w64-mingw32-gcc
+    CXX: /usr/lib/ccache/i686-w64-mingw32-g++
 
   script:
     - |
@@ -59,7 +55,7 @@ Windows x86:
         fi
 
         export VCPKG_ROOT=$(pwd)/build/vcpkg-root
-        export VCPKG_BINARY_SOURCES="clear;files,$(pwd)/build/vcpkg-binary-cache,readwrite"
+        export VCPKG_BINARY_SOURCES="clear;files,/opt/vcpkg.bsources,read;files,$(pwd)/build/vcpkg-binary-cache,readwrite"
 
         mkdir -p "build/vcpkg-binary-cache"
 
@@ -68,7 +64,7 @@ Windows x86:
     - - |
           # apt_toolchain
           echo -e "\e[0Ksection_start:`date +%s`:apt_toolchain[collapsed=true]\r\e[0KInstalling toolchain packages"
-      - apt-get install gcc-mingw-w64-i686-win32
+      - apt-get install g++-mingw-w64-i686-win32
       - |
           # apt_toolchain
           echo -e "\e[0Ksection_end:`date +%s`:apt_toolchain\r\e[0K"
@@ -84,9 +80,7 @@ Windows x86:
     - - |
           # cmake
           echo -e "\e[0Ksection_start:`date +%s`:cmake[collapsed=false]\r\e[0KBuilding Makefiles"
-          # cmake
-          echo -e "\e[0Ksection_start:`date +%s`:cmake[collapsed=false]\r\e[0KBuilding Makefiles"
-      - cmake --preset ninja-x86_mingw_static_vcpkg-debug -G "Unix Makefiles" -DSRB2_USE_CCACHE=YES -DSRB2_CONFIG_ERRORMODE=ON -DCMAKE_TOOLCHAIN_FILE=$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake
+      - cmake -B build.cmake -DSRB2_USE_CCACHE=NO -DSRB2_CONFIG_ERRORMODE=ON -DCMAKE_TOOLCHAIN_FILE=$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x86-mingw-static -DVCPKG_CHAINLOAD_TOOLCHAIN_FILE=$VCPKG_ROOT/scripts/toolchains/mingw.cmake -G "Unix Makefiles"
       - |
           # cmake
           echo -e "\e[0Ksection_end:`date +%s`:cmake\r\e[0K"
@@ -94,7 +88,7 @@ Windows x86:
     - - |
           # make
           echo -e "\e[0Ksection_start:`date +%s`:make[collapsed=false]\r\e[0KCompiling SRB2"
-      - cmake --build --preset ninja-x86_mingw_static_vcpkg-debug --parallel 1 -- --keep-going || cmake --build --preset ninja-x86_mingw_static_vcpkg-debug --parallel 1 -- --keep-going
+      - cmake --build build.cmake --parallel 1 --verbose
       - |
           # make
           echo -e "\e[0Ksection_end:`date +%s`:make\r\e[0K"
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 2cfb56f6e9645b126320f1242da095737ac866d5..fbc34173390a1be01be3058772629779558f5c4a 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -464,8 +464,13 @@ else()
 endif()
 
 if(TARGET miniupnpc::miniupnpc)
-	target_compile_definitions(SRB2SDL2 PRIVATE -DHAVE_MINIUPNPC)
-	target_link_libraries(SRB2SDL2 PRIVATE miniupnpc::miniupnpc)
+	if("${VCPKG_TARGET_TRIPLET}" MATCHES "-mingw-static$")
+		target_compile_definitions(SRB2SDL2 PRIVATE -DHAVE_MINIUPNPC -DMINIUPNP_STATICLIB)
+		target_link_libraries(SRB2SDL2 PRIVATE miniupnpc::miniupnpc -liphlpapi)
+	else()
+		target_compile_definitions(SRB2SDL2 PRIVATE -DHAVE_MINIUPNPC)
+		target_link_libraries(SRB2SDL2 PRIVATE miniupnpc::miniupnpc)
+	endif()
 	message(STATUS "miniupnpc Found")
 else()
 	message(STATUS "No miniupnpc Found")
diff --git a/src/blua/CMakeLists.txt b/src/blua/CMakeLists.txt
index 892bf534addc810434bdd00960ebe7a92ef7bde4..21278f2bbb1dc75b1e004d6b234d757776f07904 100644
--- a/src/blua/CMakeLists.txt
+++ b/src/blua/CMakeLists.txt
@@ -1,6 +1,7 @@
 target_sources(SRB2SDL2 PRIVATE
 	lapi.c
 	lbaselib.c
+	ldblib.c
 	ldo.c
 	lfunc.c
 	linit.c
diff --git a/src/blua/Sourcefile b/src/blua/Sourcefile
index dae94310981fce03d6aefd30b6e7e5781a4a4eae..2b6b9636604844687b3837cf18b30781512f184b 100644
--- a/src/blua/Sourcefile
+++ b/src/blua/Sourcefile
@@ -1,6 +1,7 @@
 lapi.c
 lbaselib.c
 ldo.c
+ldblib.c
 lfunc.c
 linit.c
 liolib.c
diff --git a/src/blua/ldblib.c b/src/blua/ldblib.c
new file mode 100644
index 0000000000000000000000000000000000000000..2eacef929bedd4e3e034158542a09dc13431f10d
--- /dev/null
+++ b/src/blua/ldblib.c
@@ -0,0 +1,310 @@
+/*
+** $Id: ldblib.c,v 1.104.1.4 2009/08/04 18:50:18 roberto Exp $
+** Interface from Lua to its debug API
+** See Copyright Notice in lua.h
+*/
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define ldblib_c
+#define LUA_LIB
+
+#include "lua.h"
+
+#include "lauxlib.h"
+#include "lualib.h"
+
+
+
+static void settabss (lua_State *L, const char *i, const char *v) {
+  lua_pushstring(L, v);
+  lua_setfield(L, -2, i);
+}
+
+
+static void settabsi (lua_State *L, const char *i, int v) {
+  lua_pushinteger(L, v);
+  lua_setfield(L, -2, i);
+}
+
+
+static lua_State *getthread (lua_State *L, int *arg) {
+  if (lua_isthread(L, 1)) {
+    *arg = 1;
+    return lua_tothread(L, 1);
+  }
+  else {
+    *arg = 0;
+    return L;
+  }
+}
+
+
+static void treatstackoption (lua_State *L, lua_State *L1, const char *fname) {
+  if (L == L1) {
+    lua_pushvalue(L, -2);
+    lua_remove(L, -3);
+  }
+  else
+    lua_xmove(L1, L, 1);
+  lua_setfield(L, -2, fname);
+}
+
+
+static int db_getinfo (lua_State *L) {
+  lua_Debug ar;
+  int arg;
+  lua_State *L1 = getthread(L, &arg);
+  const char *options = luaL_optstring(L, arg+2, "flnSu");
+  if (lua_isnumber(L, arg+1)) {
+    if (!lua_getstack(L1, (int)lua_tointeger(L, arg+1), &ar)) {
+      lua_pushnil(L);  /* level out of range */
+      return 1;
+    }
+  }
+  else if (lua_isfunction(L, arg+1)) {
+    lua_pushfstring(L, ">%s", options);
+    options = lua_tostring(L, -1);
+    lua_pushvalue(L, arg+1);
+    lua_xmove(L, L1, 1);
+  }
+  else
+    return luaL_argerror(L, arg+1, "function or level expected");
+  if (!lua_getinfo(L1, options, &ar))
+    return luaL_argerror(L, arg+2, "invalid option");
+  lua_createtable(L, 0, 2);
+  if (strchr(options, 'S')) {
+    settabss(L, "source", ar.source);
+    settabss(L, "short_src", ar.short_src);
+    settabsi(L, "linedefined", ar.linedefined);
+    settabsi(L, "lastlinedefined", ar.lastlinedefined);
+    settabss(L, "what", ar.what);
+  }
+  if (strchr(options, 'l'))
+    settabsi(L, "currentline", ar.currentline);
+  if (strchr(options, 'u'))
+    settabsi(L, "nups", ar.nups);
+  if (strchr(options, 'n')) {
+    settabss(L, "name", ar.name);
+    settabss(L, "namewhat", ar.namewhat);
+  }
+  if (strchr(options, 'L'))
+    treatstackoption(L, L1, "activelines");
+  if (strchr(options, 'f'))
+    treatstackoption(L, L1, "func");
+  return 1;  /* return table */
+}
+
+
+static int db_getlocal (lua_State *L) {
+  int arg;
+  lua_State *L1 = getthread(L, &arg);
+  lua_Debug ar;
+  const char *name;
+  if (!lua_getstack(L1, luaL_checkint(L, arg+1), &ar))  /* out of range? */
+    return luaL_argerror(L, arg+1, "level out of range");
+  name = lua_getlocal(L1, &ar, luaL_checkint(L, arg+2));
+  if (name) {
+    lua_xmove(L1, L, 1);
+    lua_pushstring(L, name);
+    lua_pushvalue(L, -2);
+    return 2;
+  }
+  else {
+    lua_pushnil(L);
+    return 1;
+  }
+}
+
+
+static int auxupvalue (lua_State *L, int get) {
+  const char *name;
+  int n = luaL_checkint(L, 2);
+  luaL_checktype(L, 1, LUA_TFUNCTION);
+  if (lua_iscfunction(L, 1)) return 0;  /* cannot touch C upvalues from Lua */
+  name = get ? lua_getupvalue(L, 1, n) : lua_setupvalue(L, 1, n);
+  if (name == NULL) return 0;
+  lua_pushstring(L, name);
+  lua_insert(L, -(get+1));
+  return get + 1;
+}
+
+
+static int db_getupvalue (lua_State *L) {
+  return auxupvalue(L, 1);
+}
+
+
+
+static char KEY_HOOK = 'h';
+
+
+static void hookf (lua_State *L, lua_Debug *ar) {
+  static const char *const hooknames[] =
+    {"call", "return", "line", "count", "tail return"};
+  lua_pushlightuserdata(L, (void *)&KEY_HOOK);
+  lua_rawget(L, LUA_REGISTRYINDEX);
+  lua_pushlightuserdata(L, L);
+  lua_rawget(L, -2);
+  if (lua_isfunction(L, -1)) {
+    lua_pushstring(L, hooknames[(int)ar->event]);
+    if (ar->currentline >= 0)
+      lua_pushinteger(L, ar->currentline);
+    else lua_pushnil(L);
+    lua_assert(lua_getinfo(L, "lS", ar));
+    lua_call(L, 2, 0);
+  }
+}
+
+
+static int makemask (const char *smask, int count) {
+  int mask = 0;
+  if (strchr(smask, 'c')) mask |= LUA_MASKCALL;
+  if (strchr(smask, 'r')) mask |= LUA_MASKRET;
+  if (strchr(smask, 'l')) mask |= LUA_MASKLINE;
+  if (count > 0) mask |= LUA_MASKCOUNT;
+  return mask;
+}
+
+
+static char *unmakemask (int mask, char *smask) {
+  int i = 0;
+  if (mask & LUA_MASKCALL) smask[i++] = 'c';
+  if (mask & LUA_MASKRET) smask[i++] = 'r';
+  if (mask & LUA_MASKLINE) smask[i++] = 'l';
+  smask[i] = '\0';
+  return smask;
+}
+
+
+static void gethooktable (lua_State *L) {
+  lua_pushlightuserdata(L, (void *)&KEY_HOOK);
+  lua_rawget(L, LUA_REGISTRYINDEX);
+  if (!lua_istable(L, -1)) {
+    lua_pop(L, 1);
+    lua_createtable(L, 0, 1);
+    lua_pushlightuserdata(L, (void *)&KEY_HOOK);
+    lua_pushvalue(L, -2);
+    lua_rawset(L, LUA_REGISTRYINDEX);
+  }
+}
+
+
+static int db_sethook (lua_State *L) {
+  int arg, mask, count;
+  lua_Hook func;
+  lua_State *L1 = getthread(L, &arg);
+  if (lua_isnoneornil(L, arg+1)) {
+    lua_settop(L, arg+1);
+    func = NULL; mask = 0; count = 0;  /* turn off hooks */
+  }
+  else {
+    const char *smask = luaL_checkstring(L, arg+2);
+    luaL_checktype(L, arg+1, LUA_TFUNCTION);
+    count = luaL_optint(L, arg+3, 0);
+    func = hookf; mask = makemask(smask, count);
+  }
+  gethooktable(L);
+  lua_pushlightuserdata(L, L1);
+  lua_pushvalue(L, arg+1);
+  lua_rawset(L, -3);  /* set new hook */
+  lua_pop(L, 1);  /* remove hook table */
+  lua_sethook(L1, func, mask, count);  /* set hooks */
+  return 0;
+}
+
+
+static int db_gethook (lua_State *L) {
+  int arg;
+  lua_State *L1 = getthread(L, &arg);
+  char buff[5];
+  int mask = lua_gethookmask(L1);
+  lua_Hook hook = lua_gethook(L1);
+  if (hook != NULL && hook != hookf)  /* external hook? */
+    lua_pushliteral(L, "external hook");
+  else {
+    gethooktable(L);
+    lua_pushlightuserdata(L, L1);
+    lua_rawget(L, -2);   /* get hook */
+    lua_remove(L, -2);  /* remove hook table */
+  }
+  lua_pushstring(L, unmakemask(mask, buff));
+  lua_pushinteger(L, lua_gethookcount(L1));
+  return 3;
+}
+
+
+#define LEVELS1	12	/* size of the first part of the stack */
+#define LEVELS2	10	/* size of the second part of the stack */
+
+static int db_errorfb (lua_State *L) {
+  int level;
+  int firstpart = 1;  /* still before eventual `...' */
+  int arg;
+  lua_State *L1 = getthread(L, &arg);
+  lua_Debug ar;
+  if (lua_isnumber(L, arg+2)) {
+    level = (int)lua_tointeger(L, arg+2);
+    lua_pop(L, 1);
+  }
+  else
+    level = (L == L1) ? 1 : 0;  /* level 0 may be this own function */
+  if (lua_gettop(L) == arg)
+    lua_pushliteral(L, "");
+  else if (!lua_isstring(L, arg+1)) return 1;  /* message is not a string */
+  else lua_pushliteral(L, "\n");
+  lua_pushliteral(L, "stack traceback:");
+  while (lua_getstack(L1, level++, &ar)) {
+    if (level > LEVELS1 && firstpart) {
+      /* no more than `LEVELS2' more levels? */
+      if (!lua_getstack(L1, level+LEVELS2, &ar))
+        level--;  /* keep going */
+      else {
+        lua_pushliteral(L, "\n\t...");  /* too many levels */
+        while (lua_getstack(L1, level+LEVELS2, &ar))  /* find last levels */
+          level++;
+      }
+      firstpart = 0;
+      continue;
+    }
+    lua_pushliteral(L, "\n\t");
+    lua_getinfo(L1, "Snl", &ar);
+    lua_pushfstring(L, "%s:", ar.short_src);
+    if (ar.currentline > 0)
+      lua_pushfstring(L, "%d:", ar.currentline);
+    if (*ar.namewhat != '\0')  /* is there a name? */
+        lua_pushfstring(L, " in function " LUA_QS, ar.name);
+    else {
+      if (*ar.what == 'm')  /* main? */
+        lua_pushfstring(L, " in main chunk");
+      else if (*ar.what == 'C' || *ar.what == 't')
+        lua_pushliteral(L, " ?");  /* C function or tail call */
+      else
+        lua_pushfstring(L, " in function <%s:%d>",
+                           ar.short_src, ar.linedefined);
+    }
+    lua_concat(L, lua_gettop(L) - arg);
+  }
+  lua_concat(L, lua_gettop(L) - arg);
+  return 1;
+}
+
+
+static const luaL_Reg dblib[] = {
+  {"gethook", db_gethook},
+  {"getinfo", db_getinfo},
+  {"getlocal", db_getlocal},
+  {"getupvalue", db_getupvalue},
+  {"sethook", db_sethook},
+  {"traceback", db_errorfb},
+  {NULL, NULL}
+};
+
+
+LUALIB_API int luaopen_debug (lua_State *L) {
+  luaL_register(L, LUA_DBLIBNAME, dblib);
+  return 1;
+}
diff --git a/src/blua/linit.c b/src/blua/linit.c
index dcf05d9f2c925e49457514c57f99afe7bf66c1d8..392d45dce47adf40ce753e400dcf1df2e2f46d91 100644
--- a/src/blua/linit.c
+++ b/src/blua/linit.c
@@ -20,6 +20,7 @@ static const luaL_Reg lualibs[] = {
   {LUA_IOLIBNAME, luaopen_io},
   {LUA_OSLIBNAME, luaopen_os},
   {LUA_STRLIBNAME, luaopen_string},
+  {LUA_DBLIBNAME, luaopen_debug},
   {NULL, NULL}
 };
 
diff --git a/src/blua/lualib.h b/src/blua/lualib.h
index 7127e4d77e56c1d2aee9c7079b59c5a3adc959a2..70facb70cca96db5924b35ad2f74bc6148509759 100644
--- a/src/blua/lualib.h
+++ b/src/blua/lualib.h
@@ -30,6 +30,9 @@ LUALIB_API int (luaopen_os) (lua_State *L);
 #define LUA_STRLIBNAME	"string"
 LUALIB_API int (luaopen_string) (lua_State *L);
 
+#define LUA_DBLIBNAME	"debug"
+LUALIB_API int (luaopen_debug) (lua_State *L);
+
 
 /* open all previous libraries */
 LUALIB_API void (luaL_openlibs) (lua_State *L);
diff --git a/src/command.c b/src/command.c
index ab6cfc08af7c4cde9f756d868b02e42ffd29229d..bc24d5e0524b00aa9123007190b3dd68689cf1cd 100644
--- a/src/command.c
+++ b/src/command.c
@@ -2482,6 +2482,22 @@ static boolean CV_FilterVarByVersion(consvar_t *v, const char *valstr)
 		if (!CV_FilterJoyAxisVars(v, valstr))
 			return false;
 	}
+
+	if (GETMAJOREXECVERSION(cv_execversion.value) < 57) // 57 = 2.2.16
+	{
+		if (
+			(!stricmp(v->name, "movebob") && atoi(valstr) == FRACUNIT) ||
+			(!stricmp(v->name, "playersforexit") && atoi(valstr) == 4) || // 4 = all
+			(!stricmp(v->name, "advancemap") && atoi(valstr) == 1) || // 1 = next
+			(!stricmp(v->name, "cam_speed") && !stricmp(valstr, "0.3")) ||
+			(!stricmp(v->name, "cam2_speed") && !stricmp(valstr, "0.3")) ||
+			(!stricmp(v->name, "timerres") && atoi(valstr) == 0) || // 0 = classic
+			(!stricmp(v->name, "gr_modelinterpolation")) || // Force reset
+			(!stricmp(v->name, "fov") && atoi(valstr) == 90)
+		)
+			return false;
+	}
+
 	return true;
 }
 
diff --git a/src/d_player.h b/src/d_player.h
index cdb547d3b863147798fe28cd5100ed54be0f23c0..1dc3fb5293d7fd56baae6518f2ea29adea5baecc 100644
--- a/src/d_player.h
+++ b/src/d_player.h
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2024 by Sonic Team Junior.
+// Copyright (C) 1999-2025 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -47,13 +47,14 @@ typedef enum
 	SF_DASHMODE         = 1<<11, // Sonic Advance 2 style top speed increase?
 	SF_FASTWAIT         = 1<<12, // Faster wait animation?
 	SF_FASTEDGE         = 1<<13, // Faster edge teeter?
-	SF_MULTIABILITY     = 1<<14, // Revenge of Final Demo.
-	SF_NONIGHTSROTATION = 1<<15, // Disable sprite rotation for NiGHTS
-	SF_NONIGHTSSUPER    = 1<<16, // Disable super colors for NiGHTS (if you have SF_SUPER)
-	SF_NOSUPERSPRITES   = 1<<17, // Don't use super sprites while super
-	SF_NOSUPERJUMPBOOST = 1<<18, // Disable the jump boost given while super (i.e. Knuckles)
-	SF_CANBUSTWALLS     = 1<<19, // Can naturally bust walls on contact? (i.e. Knuckles)
-	SF_NOSHIELDABILITY  = 1<<20, // Disable shield abilities
+	SF_JETFUME          = 1<<14, // Follow item uses Metal Sonic's jet fume behavior
+	SF_MULTIABILITY     = 1<<15, // Revenge of Final Demo.
+	SF_NONIGHTSROTATION = 1<<16, // Disable sprite rotation for NiGHTS
+	SF_NONIGHTSSUPER    = 1<<17, // Disable super sprites and colors for NiGHTS
+	SF_NOSUPERSPRITES   = 1<<18, // Don't use super sprites while super
+	SF_NOSUPERJUMPBOOST = 1<<19, // Disable the jump boost given while super (i.e. Knuckles)
+	SF_CANBUSTWALLS     = 1<<20, // Can naturally bust walls on contact? (i.e. Knuckles)
+	SF_NOSHIELDABILITY  = 1<<21, // Disable shield abilities
 
 	// free up to and including 1<<31
 } skinflags_t;
diff --git a/src/deh_tables.c b/src/deh_tables.c
index d7146325352fe35a873d4bcfe2229e394e0346c0..a96adf8ae6fb432bf04fd472d525eec1b6dcbd2a 100644
--- a/src/deh_tables.c
+++ b/src/deh_tables.c
@@ -1,7 +1,7 @@
 // SONIC ROBO BLAST 2
 //-----------------------------------------------------------------------------
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2024 by Sonic Team Junior.
+// Copyright (C) 1999-2025 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -5260,6 +5260,7 @@ struct int_const_s const INT_CONST[] = {
 	{"SF_DASHMODE",SF_DASHMODE},
 	{"SF_FASTWAIT",SF_FASTWAIT},
 	{"SF_FASTEDGE",SF_FASTEDGE},
+	{"SF_JETFUME",SF_JETFUME},
 	{"SF_MULTIABILITY",SF_MULTIABILITY},
 	{"SF_NONIGHTSROTATION",SF_NONIGHTSROTATION},
 	{"SF_NONIGHTSSUPER",SF_NONIGHTSSUPER},
diff --git a/src/hardware/hw_draw.c b/src/hardware/hw_draw.c
index 369f59ac2bfe59ccd567b3e77477394594e8d3cc..ab253d5c80f8dc1b1a958fabbf607c224746c4a0 100644
--- a/src/hardware/hw_draw.c
+++ b/src/hardware/hw_draw.c
@@ -256,8 +256,8 @@ void HWR_DrawStretchyFixedPatch(patch_t *gpatch, fixed_t x, fixed_t y, fixed_t p
 	}
 
 	// positions of the cx, cy, are between 0 and vid.width/vid.height now, we need them to be between -1 and 1
-	cx = -1 + (cx / (vid.width/2));
-	cy = 1 - (cy / (vid.height/2));
+	cx = -1.0f + (cx / (vid.width / 2.0f));
+	cy = 1.0f - (cy / (vid.height / 2.0f));
 
 	// fwidth and fheight are similar
 	fwidth /= vid.width / 2;
diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index a5befe112c60bc704e58b3c3467944b2dd1ed971..d84322dfda647d5ae34bbd09d8cd715ff906fbc5 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -2671,45 +2671,31 @@ fixed_t *hwbbox;
 
 static void HWR_RenderBSPNode(INT32 bspnum)
 {
-	node_t *bsp = &nodes[bspnum];
-
-	// Decide which side the view point is on
+	node_t *bsp;
 	INT32 side;
 
 	ps_numbspcalls.value.i++;
 
-	// Found a subsector?
-	if (bspnum & NF_SUBSECTOR)
+	while (!(bspnum & NF_SUBSECTOR))  // Found a subsector?
 	{
-		if (bspnum == -1)
-		{
-			//*(gl_drawsubsector_p++) = 0;
-			HWR_Subsector(0);
-		}
-		else
-		{
-			//*(gl_drawsubsector_p++) = bspnum&(~NF_SUBSECTOR);
-			HWR_Subsector(bspnum&(~NF_SUBSECTOR));
-		}
-		return;
-	}
+		bsp = &nodes[bspnum];
 
-	// Decide which side the view point is on.
-	side = R_PointOnSide(viewx, viewy, bsp);
+		// Decide which side the view point is on.
+		side = R_PointOnSide(viewx, viewy, bsp);
+		// BP: big hack for a test in lighning ref : 1249753487AB
+		hwbbox = bsp->bbox[side];
+		// Recursively divide front space.
+		HWR_RenderBSPNode(bsp->children[side]);
 
-	// BP: big hack for a test in lighning ref : 1249753487AB
-	hwbbox = bsp->bbox[side];
+		// Possibly divide back space.
 
-	// Recursively divide front space.
-	HWR_RenderBSPNode(bsp->children[side]);
+		if (!HWR_CheckBBox(bsp->bbox[side^1]))
+			return;
 
-	// Possibly divide back space.
-	if (HWR_CheckBBox(bsp->bbox[side^1]))
-	{
-		// BP: big hack for a test in lighning ref : 1249753487AB
-		hwbbox = bsp->bbox[side^1];
-		HWR_RenderBSPNode(bsp->children[side^1]);
+		bspnum = bsp->children[side^1];
 	}
+
+	HWR_Subsector(bspnum == -1 ? 0 : bspnum & ~NF_SUBSECTOR);
 }
 
 // ==========================================================================
@@ -5675,7 +5661,6 @@ void HWR_LoadLevel(void)
 // ==========================================================================
 
 static CV_PossibleValue_t glshaders_cons_t[] = {{0, "Off"}, {1, "On"}, {2, "Ignore custom shaders"}, {0, NULL}};
-static CV_PossibleValue_t glmodelinterpolation_cons_t[] = {{0, "Off"}, {1, "Sometimes"}, {2, "Always"}, {0, NULL}};
 static CV_PossibleValue_t glfakecontrast_cons_t[] = {{0, "Off"}, {1, "On"}, {2, "Smooth"}, {0, NULL}};
 static CV_PossibleValue_t glshearing_cons_t[] = {{0, "Off"}, {1, "On"}, {2, "Third-person"}, {0, NULL}};
 
@@ -5704,7 +5689,7 @@ consvar_t cv_glcoronasize = CVAR_INIT ("gr_coronasize", "1", CV_SAVE|CV_FLOAT, 0
 #endif
 
 consvar_t cv_glmodels = CVAR_INIT ("gr_models", "Off", CV_SAVE, CV_OnOff, NULL);
-consvar_t cv_glmodelinterpolation = CVAR_INIT ("gr_modelinterpolation", "Sometimes", CV_SAVE, glmodelinterpolation_cons_t, NULL);
+consvar_t cv_glmodelinterpolation = CVAR_INIT ("gr_modelinterpolation", "On", CV_SAVE, CV_OnOff, NULL);
 consvar_t cv_glmodellighting = CVAR_INIT ("gr_modellighting", "Off", CV_SAVE|CV_CALL, CV_OnOff, CV_glmodellighting_OnChange);
 
 consvar_t cv_glshearing = CVAR_INIT ("gr_shearing", "Off", CV_SAVE, glshearing_cons_t, NULL);
diff --git a/src/hardware/hw_md2.c b/src/hardware/hw_md2.c
index 931867142200338b7635d7b04307df587eafa129..ce2c2c34638c2b4a412a11128ce17d200088381c 100644
--- a/src/hardware/hw_md2.c
+++ b/src/hardware/hw_md2.c
@@ -1062,15 +1062,11 @@ static boolean HWR_AllowModel(mobj_t *mobj)
 
 static boolean HWR_CanInterpolateModel(mobj_t *mobj, model_t *model)
 {
-	if (cv_glmodelinterpolation.value == 2) // Always interpolate
-		return true;
 	return model->interpolate[(mobj->frame & FF_FRAMEMASK)];
 }
 
 static boolean HWR_CanInterpolateSprite2(modelspr2frames_t *spr2frame)
 {
-	if (cv_glmodelinterpolation.value == 2) // Always interpolate
-		return true;
 	return spr2frame->interpolate;
 }
 
diff --git a/src/info.c b/src/info.c
index 4d3468e10fac23edeeea539cdac550ad9ab6057a..3998eb88d2b976fc4d718506a4e6ea687c04e49e 100644
--- a/src/info.c
+++ b/src/info.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2024 by Sonic Team Junior.
+// Copyright (C) 1999-2025 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -4187,7 +4187,7 @@ mobjinfo_t mobjinfo[NUMMOBJTYPES] =
 		S_NULL,         // painstate
 		0,              // painchance
 		sfx_None,       // painsound
-		S_NULL,         // meleestate
+		S_JETFUMEFLASH, // meleestate
 		S_NULL,         // missilestate
 		S_NULL,         // deathstate
 		S_NULL,         // xdeathstate
diff --git a/src/lua_baselib.c b/src/lua_baselib.c
index ecd1ee55e648019fb883917ca361cc45ba8847b2..1ffa3968be16bf8ae67b04461f12b4a6d8d1f77b 100644
--- a/src/lua_baselib.c
+++ b/src/lua_baselib.c
@@ -3166,7 +3166,10 @@ static int lib_rCheckTextureNumForName(lua_State *L)
 {
 	const char *name = luaL_checkstring(L, 1);
 	//HUDSAFE
-	lua_pushinteger(L, R_CheckTextureNumForName(name));
+	INT32 num = R_CheckTextureNumForName(name, TEXTURETYPE_TEXTURE);
+	if (num == -1)
+		num = R_CheckTextureNumForName(name, TEXTURETYPE_FLAT);
+	lua_pushinteger(L, num);
 	return 1;
 }
 
diff --git a/src/netcode/d_clisrv.c b/src/netcode/d_clisrv.c
index 6702e2591254349a9bd3dde6d08b7fa86ef7fb29..7a5fb06a6c37f4ff95cbef8abf25077a6c16d377 100644
--- a/src/netcode/d_clisrv.c
+++ b/src/netcode/d_clisrv.c
@@ -673,6 +673,7 @@ void D_QuitNetGame(void)
 		HSendPacket(servernode, true, 0, 0);
 	}
 
+	seenplayer = NULL;
 	D_CloseConnection();
 	ClearAdminPlayers();
 
diff --git a/src/netcode/d_net.c b/src/netcode/d_net.c
index 4860d8688b4a8b998b59523c6cf52502c69d10ca..a3a47b9504d226ffa2bf01136c3e756624579300 100644
--- a/src/netcode/d_net.c
+++ b/src/netcode/d_net.c
@@ -163,12 +163,6 @@ typedef struct
 	// ack return to send (like sliding window protocol)
 	UINT8 firstacktosend;
 
-	// when no consecutive packets are received we keep in mind what packets
-	// we already received in a queue
-	UINT8 acktosend_head;
-	UINT8 acktosend_tail;
-	UINT8 acktosend[MAXACKTOSEND];
-
 	// automatically send keep alive packet when not enough trafic
 	tic_t lasttimeacktosend_sent;
 	// detect connection lost
@@ -200,10 +194,9 @@ FUNCMATH static INT32 cmpack(UINT8 a, UINT8 b)
 /** Sets freeack to a free acknum and copies the netbuffer in the ackpak table
   *
   * \param freeack  The address to store the free acknum at
-  * \param lowtimer ???
   * \return True if a free acknum was found
   */
-static boolean GetFreeAcknum(UINT8 *freeack, boolean lowtimer)
+static boolean GetFreeAcknum(UINT8 *freeack)
 {
 	node_t *node = &nodes[doomcom->remotenode];
 	INT32 numfreeslot = 0;
@@ -232,17 +225,8 @@ static boolean GetFreeAcknum(UINT8 *freeack, boolean lowtimer)
 				node->nextacknum++;
 			ackpak[i].destinationnode = (UINT8)(node - nodes);
 			ackpak[i].length = doomcom->datalength;
-			if (lowtimer)
-			{
-				// Lowtime means can't be sent now so try it as soon as possible
-				ackpak[i].senttime = 0;
-				ackpak[i].resentnum = 1;
-			}
-			else
-			{
-				ackpak[i].senttime = I_GetTime();
-				ackpak[i].resentnum = 0;
-			}
+			ackpak[i].senttime = I_GetTime();
+			ackpak[i].resentnum = 0;
 			M_Memcpy(ackpak[i].pak.raw, netbuffer, ackpak[i].length);
 
 			*freeack = ackpak[i].acknum;
@@ -259,38 +243,6 @@ static boolean GetFreeAcknum(UINT8 *freeack, boolean lowtimer)
 	return false;
 }
 
-/** Counts how many acks are free
-  *
-  * \param urgent True if the type of the packet meant to
-  *               use an ack is lower than PT_CANFAIL
-  *               If for some reason you don't want use it
-  *               for any packet type in particular,
-  *               just set to false
-  * \return The number of free acks
-  *
-  */
-INT32 Net_GetFreeAcks(boolean urgent)
-{
-	INT32 numfreeslot = 0;
-	INT32 n = 0; // Number of free acks found
-
-	for (INT32 i = 0; i < MAXACKPACKETS; i++)
-		if (!ackpak[i].acknum)
-		{
-			// For low priority packets, make sure to let freeslots so urgent packets can be sent
-			if (!urgent)
-			{
-				numfreeslot++;
-				if (numfreeslot <= URGENTFREESLOTNUM)
-					continue;
-			}
-
-			n++;
-		}
-
-	return n;
-}
-
 // Get a ack to send in the queue of this node
 static UINT8 GetAcktosend(INT32 node)
 {
@@ -308,9 +260,9 @@ static void RemoveAck(INT32 i)
 }
 
 // We have got a packet, proceed the ack request and ack return
-static int Processackpak(void)
+static boolean Processackpak(void)
 {
-	int goodpacket = 0;
+	boolean goodpacket = true;
 	node_t *node = &nodes[doomcom->remotenode];
 
 	// Received an ack return, so remove the ack in the list
@@ -319,7 +271,7 @@ static int Processackpak(void)
 		node->remotefirstack = netbuffer->ackreturn;
 		// Search the ackbuffer and free it
 		for (INT32 i = 0; i < MAXACKPACKETS; i++)
-			if (ackpak[i].acknum && ackpak[i].destinationnode == node - nodes
+			if (ackpak[i].acknum && ackpak[i].destinationnode == doomcom->remotenode
 				&& cmpack(ackpak[i].acknum, netbuffer->ackreturn) <= 0)
 			{
 				RemoveAck(i);
@@ -335,119 +287,32 @@ static int Processackpak(void)
 		{
 			DEBFILE(va("Discard(1) ack %d (duplicated)\n", ack));
 			duppacket++;
-			goodpacket = 1; // Discard packet (duplicate)
+			goodpacket = false; // Discard packet (duplicate)
 		}
 		else
 		{
-			// Check if it is not already in the queue
-			for (INT32 i = node->acktosend_tail; i != node->acktosend_head; i = (i+1) % MAXACKTOSEND)
-				if (node->acktosend[i] == ack)
-				{
-					DEBFILE(va("Discard(2) ack %d (duplicated)\n", ack));
-					duppacket++;
-					goodpacket = 1; // Discard packet (duplicate)
-					break;
-				}
-			if (goodpacket == 0)
+			// Is a good packet so increment the acknowledge number,
+			// Then search for a "hole" in the queue
+			UINT8 nextfirstack = (UINT8)(node->firstacktosend + 1);
+			if (!nextfirstack)
+				nextfirstack = 1;
+
+			if (ack == nextfirstack)
+			{
+				node->firstacktosend = nextfirstack;
+			}
+			else // Out of order packet
 			{
-				// Is a good packet so increment the acknowledge number,
-				// Then search for a "hole" in the queue
-				UINT8 nextfirstack = (UINT8)(node->firstacktosend + 1);
-				if (!nextfirstack)
-					nextfirstack = 1;
-
-				if (ack == nextfirstack)
-				{
-					UINT8 hm1; // head - 1
-					boolean change = true;
-
-					node->firstacktosend = nextfirstack++;
-					if (!nextfirstack)
-						nextfirstack = 1;
-					hm1 = (UINT8)((node->acktosend_head-1+MAXACKTOSEND) % MAXACKTOSEND);
-					while (change)
-					{
-						change = false;
-						for (INT32 i = node->acktosend_tail; i != node->acktosend_head;
-							i = (i+1) % MAXACKTOSEND)
-						{
-							if (cmpack(node->acktosend[i], nextfirstack) <= 0)
-							{
-								if (node->acktosend[i] == nextfirstack)
-								{
-									node->firstacktosend = nextfirstack++;
-									if (!nextfirstack)
-										nextfirstack = 1;
-									change = true;
-								}
-								if (i == node->acktosend_tail)
-								{
-									node->acktosend[node->acktosend_tail] = 0;
-									node->acktosend_tail = (UINT8)((i+1) % MAXACKTOSEND);
-								}
-								else if (i == hm1)
-								{
-									node->acktosend[hm1] = 0;
-									node->acktosend_head = hm1;
-									hm1 = (UINT8)((hm1-1+MAXACKTOSEND) % MAXACKTOSEND);
-								}
-							}
-						}
-					}
-				}
-				else // Out of order packet
-				{
-					// Don't increment firsacktosend, put it in asktosend queue
-					// Will be incremented when the nextfirstack comes (code above)
-					UINT8 newhead = (UINT8)((node->acktosend_head+1) % MAXACKTOSEND);
-					DEBFILE(va("out of order packet (%d expected)\n", nextfirstack));
-					if (newhead != node->acktosend_tail)
-					{
-						node->acktosend[node->acktosend_head] = ack;
-						node->acktosend_head = newhead;
-					}
-					else // Buffer full discard packet, sender will resend it
-					{ // We can admit the packet but we will not detect the duplication after :(
-						DEBFILE("no more freeackret\n");
-						goodpacket = 2;
-					}
-				}
+				// Don't increment firsacktosend, put it in asktosend queue
+				// Will be incremented when the nextfirstack comes (code above)
+				DEBFILE(va("out of order packet (%d expected)\n", nextfirstack));
+				goodpacket = false;
 			}
 		}
 	}
-	// return values: 0 = ok, 1 = duplicate, 2 = out of order
 	return goodpacket;
 }
 
-// send special packet with only ack on it
-void Net_SendAcks(INT32 node)
-{
-	netbuffer->packettype = PT_NOTHING;
-	M_Memcpy(netbuffer->u.textcmd, nodes[node].acktosend, MAXACKTOSEND);
-	HSendPacket(node, false, 0, MAXACKTOSEND);
-}
-
-static void GotAcks(void)
-{
-	for (INT32 j = 0; j < MAXACKTOSEND; j++)
-		if (netbuffer->u.textcmd[j])
-			for (INT32 i = 0; i < MAXACKPACKETS; i++)
-				if (ackpak[i].acknum && ackpak[i].destinationnode == doomcom->remotenode)
-				{
-					if (ackpak[i].acknum == netbuffer->u.textcmd[j])
-						RemoveAck(i);
-					// nextacknum is first equal to acknum, then when receiving bigger ack
-					// there is big chance the packet is lost
-					// When resent, nextacknum = nodes[node].nextacknum
-					// will redo the same but with different value
-					else if (cmpack(ackpak[i].nextacknum, netbuffer->u.textcmd[j]) <= 0
-							&& ackpak[i].senttime > 0)
-						{
-							ackpak[i].senttime--; // hurry up
-						}
-				}
-}
-
 void Net_ConnectionTimeout(INT32 node)
 {
 	// Don't timeout several times
@@ -501,11 +366,6 @@ void Net_AckTicker(void)
 		// This is something like node open flag
 		if (nodes[i].firstacktosend)
 		{
-			// We haven't sent a packet for a long time
-			// Acknowledge packet if needed
-			if (nodes[i].lasttimeacktosend_sent + ACKTOSENDTIMEOUT < I_GetTime())
-				Net_SendAcks(i);
-
 			if (!(nodes[i].flags & NF_CLOSE)
 				&& nodes[i].lasttimepacketreceived + connectiontimeout < I_GetTime())
 			{
@@ -519,37 +379,12 @@ void Net_AckTicker(void)
 // (the higher layer doesn't have room, or something else ....)
 void Net_UnAcknowledgePacket(INT32 node)
 {
-	INT32 hm1 = (nodes[node].acktosend_head-1+MAXACKTOSEND) % MAXACKTOSEND;
 	DEBFILE(va("UnAcknowledge node %d\n", node));
 	if (!node)
 		return;
-	if (nodes[node].acktosend[hm1] == netbuffer->ack)
-	{
-		nodes[node].acktosend[hm1] = 0;
-		nodes[node].acktosend_head = (UINT8)hm1;
-	}
-	else if (nodes[node].firstacktosend == netbuffer->ack)
-	{
-		nodes[node].firstacktosend--;
-		if (!nodes[node].firstacktosend)
-			nodes[node].firstacktosend = UINT8_MAX;
-	}
-	else
-	{
-		while (nodes[node].firstacktosend != netbuffer->ack)
-		{
-			nodes[node].acktosend_tail = (UINT8)
-				((nodes[node].acktosend_tail-1+MAXACKTOSEND) % MAXACKTOSEND);
-			nodes[node].acktosend[nodes[node].acktosend_tail] = nodes[node].firstacktosend;
-
-			nodes[node].firstacktosend--;
-			if (!nodes[node].firstacktosend)
-				nodes[node].firstacktosend = UINT8_MAX;
-		}
-		nodes[node].firstacktosend++;
-		if (!nodes[node].firstacktosend)
-			nodes[node].firstacktosend = 1;
-	}
+	nodes[node].firstacktosend--;
+	if (!nodes[node].firstacktosend)
+		nodes[node].firstacktosend = UINT8_MAX;
 }
 
 /** Checks if all acks have been received
@@ -592,7 +427,6 @@ void Net_WaitAllAckReceived(UINT32 timeout)
 
 static void InitNode(node_t *node)
 {
-	node->acktosend_head = node->acktosend_tail = 0;
 	node->firstacktosend = 0;
 	node->nextacknum = 1;
 	node->remotefirstack = 0;
@@ -651,11 +485,11 @@ void Net_CloseConnection(INT32 node)
 
 	nodes[node].flags |= NF_CLOSE;
 
-	// try to Send ack back (two army problem)
-	if (GetAcktosend(node))
+	if (nodes[node].firstacktosend)
 	{
-		Net_SendAcks(node);
-		Net_SendAcks(node);
+		// send a PT_NOTHING back to acknowledge the packet
+		netbuffer->packettype = PT_NOTHING;
+		HSendPacket(node, false, 0, 0);
 	}
 
 	// check if we are waiting for an ack from this node
@@ -991,7 +825,7 @@ boolean HSendPacket(INT32 node, boolean reliable, UINT8 acknum, size_t packetlen
 		netbuffer->ackreturn = 0;
 	if (reliable)
 	{
-		if (!GetFreeAcknum(&netbuffer->ack, false))
+		if (!GetFreeAcknum(&netbuffer->ack))
 			return false;
 	}
 	else
@@ -1057,7 +891,6 @@ boolean HGetPacket(void)
 	while(true)
 	{
 		//nodejustjoined = I_NetGet();
-		int goodpacket;
 		I_NetGet();
 
 		if (doomcom->remotenode == -1) // No packet received
@@ -1103,22 +936,12 @@ boolean HGetPacket(void)
 		}*/
 
 		// Proceed the ack and ackreturn field
-		goodpacket = Processackpak();
-		if (goodpacket != 0)
-		{
-			// resend the ACK in case the previous ACK didn't reach the client.
-			// prevents the client's netbuffer from locking up.
-			if (goodpacket == 1)
-				Net_SendAcks(doomcom->remotenode);
+		if (!Processackpak())
 			continue; // discarded (duplicated)
-		}
 
 		// A packet with just ackreturn
 		if (netbuffer->packettype == PT_NOTHING)
-		{
-			GotAcks();
 			continue;
-		}
 		break;
 	}
 
diff --git a/src/netcode/d_net.h b/src/netcode/d_net.h
index 2f83939f8c5324883eb11e90dbb4e2897a54254c..6b3ee5e6751bd6d19457ea704274b5de9247c483 100644
--- a/src/netcode/d_net.h
+++ b/src/netcode/d_net.h
@@ -60,7 +60,6 @@ extern netnode_t netnodes[MAXNETNODES];
 
 extern boolean serverrunning;
 
-INT32 Net_GetFreeAcks(boolean urgent);
 void Net_AckTicker(void);
 
 // If reliable return true if packet sent, 0 else
diff --git a/src/netcode/d_netcmd.c b/src/netcode/d_netcmd.c
index 94170fa0df401064f5c1a2f9569cfceb0b0c490e..35e28db364fd84c39e7857113f20ec039060b0bb 100644
--- a/src/netcode/d_netcmd.c
+++ b/src/netcode/d_netcmd.c
@@ -320,7 +320,7 @@ consvar_t cv_overtime = CVAR_INIT ("overtime", "Yes", CV_SAVE|CV_NETVAR|CV_ALLOW
 consvar_t cv_rollingdemos = CVAR_INIT ("rollingdemos", "On", CV_SAVE, CV_OnOff, NULL);
 
 static CV_PossibleValue_t timetic_cons_t[] = {{0, "Classic"}, {1, "Centiseconds"}, {2, "Mania"}, {3, "Tics"}, {0, NULL}};
-consvar_t cv_timetic = CVAR_INIT ("timerres", "Classic", CV_SAVE, timetic_cons_t, NULL);
+consvar_t cv_timetic = CVAR_INIT ("timerres", "Mania", CV_SAVE, timetic_cons_t, NULL);
 
 static CV_PossibleValue_t powerupdisplay_cons_t[] = {{0, "Never"}, {1, "First-person only"}, {2, "Always"}, {0, NULL}};
 consvar_t cv_powerupdisplay = CVAR_INIT ("powerupdisplay", "First-person only", CV_SAVE, powerupdisplay_cons_t, NULL);
@@ -372,10 +372,10 @@ static CV_PossibleValue_t cooplives_cons_t[] = {{0, "Infinite"}, {1, "Per-player
 consvar_t cv_cooplives = CVAR_INIT ("cooplives", "Avoid Game Over", CV_SAVE|CV_NETVAR|CV_CALL|CV_CHEAT|CV_ALLOWLUA, cooplives_cons_t, CoopLives_OnChange);
 
 static CV_PossibleValue_t advancemap_cons_t[] = {{0, "Off"}, {1, "Next"}, {2, "Random"}, {0, NULL}};
-consvar_t cv_advancemap = CVAR_INIT ("advancemap", "Next", CV_SAVE|CV_NETVAR|CV_ALLOWLUA, advancemap_cons_t, NULL);
+consvar_t cv_advancemap = CVAR_INIT ("advancemap", "Random", CV_SAVE|CV_NETVAR|CV_ALLOWLUA, advancemap_cons_t, NULL);
 
 static CV_PossibleValue_t playersforexit_cons_t[] = {{0, "One"}, {1, "1/4"}, {2, "Half"}, {3, "3/4"}, {4, "All"}, {0, NULL}};
-consvar_t cv_playersforexit = CVAR_INIT ("playersforexit", "All", CV_SAVE|CV_NETVAR|CV_ALLOWLUA, playersforexit_cons_t, NULL);
+consvar_t cv_playersforexit = CVAR_INIT ("playersforexit", "3/4", CV_SAVE|CV_NETVAR|CV_ALLOWLUA, playersforexit_cons_t, NULL);
 
 consvar_t cv_exitmove = CVAR_INIT ("exitmove", "On", CV_SAVE|CV_NETVAR|CV_CALL|CV_ALLOWLUA, CV_OnOff, ExitMove_OnChange);
 
@@ -4253,9 +4253,9 @@ void D_GameTypeChanged(INT32 lastgametype)
 			case GT_TEAMMATCH:
 				if (!cv_timelimit.changed && !cv_pointlimit.changed) // user hasn't changed limits
 				{
-					// default settings for match: timelimit 10 mins, no pointlimit
+					// default settings for match: timelimit 7 mins, no pointlimit
 					CV_SetValue(&cv_pointlimit, 0);
-					CV_SetValue(&cv_timelimit, 10);
+					CV_SetValue(&cv_timelimit, 7);
 				}
 				if (!cv_itemrespawntime.changed)
 					CV_Set(&cv_itemrespawntime, cv_itemrespawntime.defaultvalue); // respawn normally
@@ -4264,9 +4264,9 @@ void D_GameTypeChanged(INT32 lastgametype)
 			case GT_HIDEANDSEEK:
 				if (!cv_timelimit.changed && !cv_pointlimit.changed) // user hasn't changed limits
 				{
-					// default settings for tag: 5 mins, no pointlimit
+					// default settings for tag: 7 mins, no pointlimit
 					// Note that tag mode also uses an alternate timing mechanism in tandem with timelimit.
-					CV_SetValue(&cv_timelimit, 5);
+					CV_SetValue(&cv_timelimit, 7);
 					CV_SetValue(&cv_pointlimit, 0);
 				}
 				if (!cv_itemrespawntime.changed)
@@ -4275,8 +4275,8 @@ void D_GameTypeChanged(INT32 lastgametype)
 			case GT_CTF:
 				if (!cv_timelimit.changed && !cv_pointlimit.changed) // user hasn't changed limits
 				{
-					// default settings for CTF: no timelimit, pointlimit 5
-					CV_SetValue(&cv_timelimit, 0);
+					// default settings for CTF: 15 mins, pointlimit 5
+					CV_SetValue(&cv_timelimit, 15);
 					CV_SetValue(&cv_pointlimit, 5);
 				}
 				if (!cv_itemrespawntime.changed)
diff --git a/src/netcode/d_netfil.c b/src/netcode/d_netfil.c
index bfb67838ff68e3831914e77dd029ffa19942eaac..25290990789988be750ae5cdd41986c628f59ebf 100644
--- a/src/netcode/d_netfil.c
+++ b/src/netcode/d_netfil.c
@@ -208,8 +208,8 @@ UINT8 *PutFileNeeded(UINT16 firstfile)
 				filestatus += (WILLSEND_NO << 4); // Won't send
 			else if (wadfiles[i]->filesize <= (UINT32)cv_maxsend.value * 1024)
 				filestatus += (WILLSEND_YES << 4); // Will send if requested
-			// else
-				// filestatus += (0 << 4); -- Won't send, too big
+			else
+				filestatus += (WILLSEND_TOOLARGE << 4); // Won't send, too big
 		}
 
 		WRITEUINT8(p, filestatus);
diff --git a/src/p_enemy.c b/src/p_enemy.c
index 29fcf50c2d0fdd432f448fc5a7e088ef11899a79..7091af5429e834c098ba7d81ace6961cc238e37f 100644
--- a/src/p_enemy.c
+++ b/src/p_enemy.c
@@ -5582,7 +5582,7 @@ void A_MinusDigging(mobj_t *actor)
 
 		minus = actor;
 
-		P_DoBlockThingsIterate(xl, yl, xh, yh, PIT_MinusCarry);
+		P_DoBlockThingsIterate(xl, yl, xh, yh, PIT_MinusCarry, minus);
 	}
 	else
 	{
@@ -13726,7 +13726,7 @@ void A_DustDevilThink(mobj_t *actor)
 
 	dustdevil = actor;
 
-	P_DoBlockThingsIterate(xl, yl, xh, yh, PIT_DustDevilLaunch);
+	P_DoBlockThingsIterate(xl, yl, xh, yh, PIT_DustDevilLaunch, dustdevil);
 
 	//Whirlwind sound effect.
 	if (leveltime % 70 == 0)
@@ -13842,7 +13842,7 @@ void A_TNTExplode(mobj_t *actor)
 
 	barrel = actor;
 
-	P_DoBlockThingsIterate(xl, yl, xh, yh, PIT_TNTExplode);
+	P_DoBlockThingsIterate(xl, yl, xh, yh, PIT_TNTExplode, barrel);
 
 	// cause a quake -- P_StartQuake does not exist yet
 	epicenter.x = actor->x;
diff --git a/src/p_map.c b/src/p_map.c
index fce17f8c4bb2357eb9d789a46261119e67f9e946..337344b2fec2c966d0774513c02d9397b2974382 100644
--- a/src/p_map.c
+++ b/src/p_map.c
@@ -2262,7 +2262,7 @@ boolean P_CheckPosition(mobj_t *thing, fixed_t x, fixed_t y)
 		for (bx = xl; bx <= xh; bx++)
 			for (by = yl; by <= yh; by++)
 			{
-				if (!P_BlockThingsIterator(bx, by, PIT_CheckThing))
+				if (!P_BlockThingsIterator(bx, by, PIT_CheckThing, tmthing))
 					blockval = false;
 				else
 					tmhitthing = tmfloorthing;
@@ -2876,7 +2876,7 @@ boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff)
 		standx = x;
 		standy = y;
 
-		P_DoBlockThingsIterate(xl, yl, xh, yh, PIT_PushableMoved);
+		P_DoBlockThingsIterate(xl, yl, xh, yh, PIT_PushableMoved, stand);
 	}
 
 	// Link the thing into its new position
@@ -4223,7 +4223,7 @@ void P_RadiusAttack(mobj_t *spot, mobj_t *source, fixed_t damagedist, UINT8 dama
 	bombdamagetype = damagetype;
 	bombsightcheck = sightcheck;
 
-	P_DoBlockThingsIterate(xl, yl, xh, yh, PIT_RadiusAttack);
+	P_DoBlockThingsIterate(xl, yl, xh, yh, PIT_RadiusAttack, bombspot);
 }
 
 //
diff --git a/src/p_maputl.c b/src/p_maputl.c
index 5398fd7a4c584022e8f33bb49312a176207e74b8..3e30c5d6e3f45fdb08018fbe0ef7c3b8d414f982 100644
--- a/src/p_maputl.c
+++ b/src/p_maputl.c
@@ -1050,9 +1050,13 @@ boolean P_BlockLinesIterator(INT32 x, INT32 y, boolean (*func)(line_t *))
 //
 // P_BlockThingsIterator
 //
-boolean P_BlockThingsIterator(INT32 x, INT32 y, boolean (*func)(mobj_t *))
+boolean P_BlockThingsIterator(INT32 x, INT32 y, boolean (*func)(mobj_t *), mobj_t *thing)
 {
 	blocknode_t *block, *next = NULL;
+	
+	boolean checkthing = false;
+	if (thing)
+			checkthing = true;
 
 	if (x < 0 || y < 0 || x >= bmapwidth || y >= bmapheight)
 		return true;
@@ -1065,20 +1069,20 @@ boolean P_BlockThingsIterator(INT32 x, INT32 y, boolean (*func)(mobj_t *))
 		if (!func(block->mobj))
 			return false;
 
-		if (P_MobjWasRemoved(tmthing)) // func just popped our tmthing, cannot continue.
+		if (checkthing && P_MobjWasRemoved(thing)) // func just popped our tmthing, cannot continue.
 			return true;
 	}
 
 	return true;
 }
 
-boolean P_DoBlockThingsIterate(int x1, int y1, int x2, int y2, boolean (*func)(mobj_t *))
+boolean P_DoBlockThingsIterate(int x1, int y1, int x2, int y2, boolean (*func)(mobj_t *), mobj_t *thing)
 {
 	boolean status = true;
 
 	for (INT32 bx = x1; bx <= x2; bx++)
 		for (INT32 by = y1; by <= y2; by++)
-			if (!P_BlockThingsIterator(bx, by, func))
+			if (!P_BlockThingsIterator(bx, by, func, thing))
 				status = false;
 
 	return status;
@@ -1451,7 +1455,7 @@ boolean P_PathTraverse(fixed_t px1, fixed_t py1, fixed_t px2, fixed_t py2,
 	INT32 flags, traverser_t trav)
 {
 	fixed_t xt1, yt1, xt2, yt2;
-	fixed_t xstep, ystep, partial, xintercept, yintercept;
+	fixed_t xstep, ystep, partialx, partialy, xintercept, yintercept;
 	INT32 mapx, mapy, mapxstep, mapystep, count;
 
 	earlyout = flags & PT_EARLYOUT;
@@ -1469,57 +1473,83 @@ boolean P_PathTraverse(fixed_t px1, fixed_t py1, fixed_t px2, fixed_t py2,
 	trace.y = py1;
 	trace.dx = px2 - px1;
 	trace.dy = py2 - py1;
-
-	px1 -= bmaporgx;
-	py1 -= bmaporgy;
-	xt1 = (unsigned)px1>>MAPBLOCKSHIFT;
-	yt1 = (unsigned)py1>>MAPBLOCKSHIFT;
-
-	px2 -= bmaporgx;
-	py2 -= bmaporgy;
-	xt2 = (unsigned)px2>>MAPBLOCKSHIFT;
-	yt2 = (unsigned)py2>>MAPBLOCKSHIFT;
+	
+	xt1 = px1>>MAPBLOCKSHIFT;
+	yt1 = py2>>MAPBLOCKSHIFT;
+	px1 = (unsigned)(px1 - bmaporgx);
+	py1 = (unsigned)(py1 - bmaporgy);
+	
+	xt2 = px2>>MAPBLOCKSHIFT;
+	yt2 = py2>>MAPBLOCKSHIFT;
+	px2 = (unsigned)(px2 - bmaporgx);
+	py2 = (unsigned)(py2 - bmaporgy);
 
 	if (xt2 > xt1)
 	{
 		mapxstep = 1;
-		partial = FRACUNIT - ((px1>>MAPBTOFRAC) & FRACMASK);
+		partialx = FRACUNIT - (((unsigned)px1>>MAPBTOFRAC) & FRACMASK);
 		ystep = FixedDiv(py2 - py1, abs(px2 - px1));
 	}
 	else if (xt2 < xt1)
 	{
 		mapxstep = -1;
-		partial = (px1>>MAPBTOFRAC) & FRACMASK;
+		partialx = ((unsigned)px1>>MAPBTOFRAC) & FRACMASK;
 		ystep = FixedDiv(py2 - py1, abs(px2 - px1));
 	}
 	else
 	{
 		mapxstep = 0;
-		partial = FRACUNIT;
+		partialx = FRACUNIT;
 		ystep = 256*FRACUNIT;
 	}
 
-	yintercept = (py1>>MAPBTOFRAC) + FixedMul(partial, ystep);
+	yintercept = ((unsigned)py1>>MAPBTOFRAC) + FixedMul(partialx, ystep);
 
 	if (yt2 > yt1)
 	{
 		mapystep = 1;
-		partial = FRACUNIT - ((py1>>MAPBTOFRAC) & FRACMASK);
+		partialy = FRACUNIT - (((unsigned)py1>>MAPBTOFRAC) & FRACMASK);
 		xstep = FixedDiv(px2 - px1, abs(py2 - py1));
 	}
 	else if (yt2 < yt1)
 	{
 		mapystep = -1;
-		partial = (py1>>MAPBTOFRAC) & FRACMASK;
+		partialy = ((unsigned)py1>>MAPBTOFRAC) & FRACMASK;
 		xstep = FixedDiv(px2 - px1, abs(py2 - py1));
 	}
 	else
 	{
 		mapystep = 0;
-		partial = FRACUNIT;
+		partialy = FRACUNIT;
 		xstep = 256*FRACUNIT;
 	}
-	xintercept = (px1>>MAPBTOFRAC) + FixedMul(partial, xstep);
+	xintercept = ((unsigned)px1>>MAPBTOFRAC) + FixedMul(partialy, xstep);
+	
+	// [RH] Fix for traces that pass only through blockmap corners. In that case,
+	// xintercept and yintercept can both be set ahead of mapx and mapy, so the
+	// for loop would never advance anywhere.
+	
+	if (abs(xstep) == 1 && abs(ystep) == 1)
+	{
+		if (ystep < 0)
+		{
+			partialx = FRACUNIT - partialx;
+		}
+		if (xstep < 0)
+		{
+			partialy = FRACUNIT - partialy;
+		}
+		if (partialx == partialy)
+		{
+			xintercept = xt1;
+			yintercept = yt1;
+		}
+	}
+	
+	xt1 = (unsigned)px1>>MAPBLOCKSHIFT;
+	yt1 = (unsigned)py1>>MAPBLOCKSHIFT;
+	xt2 = (unsigned)px2>>MAPBLOCKSHIFT;
+	yt2 = (unsigned)py2>>MAPBLOCKSHIFT;
 
 	// Step through map blocks.
 	// Count is present to prevent a round off error
@@ -1534,21 +1564,64 @@ boolean P_PathTraverse(fixed_t px1, fixed_t py1, fixed_t px2, fixed_t py2,
 				return false; // early out
 
 		if (flags & PT_ADDTHINGS)
-			if (!P_BlockThingsIterator(mapx, mapy, PIT_AddThingIntercepts))
+			if (!P_BlockThingsIterator(mapx, mapy, PIT_AddThingIntercepts, NULL))
 				return false; // early out
 
-		if (mapx == xt2 && mapy == yt2)
+		// both coordinates reached the end, so end the traversing.
+		if ((mapxstep | mapystep) == 0)
 			break;
 
-		if ((yintercept >> FRACBITS) == mapy)
+		// [RH] Handle corner cases properly instead of pretending they don't exist.
+		switch ((((yintercept >> FRACBITS) == mapy) << 1) | ((xintercept >> FRACBITS) == mapx))
 		{
-			yintercept += ystep;
-			mapx += mapxstep;
-		}
-		else if ((xintercept >> FRACBITS) == mapx)
-		{
-			xintercept += xstep;
-			mapy += mapystep;
+			case 0: // neither xintercept nor yintercept match!
+				count = 64; // Stop traversing, because somebody screwed up.
+				break;
+			
+			case 1: // xintercept matches
+				xintercept += xstep;
+				mapy += mapystep;
+				if (mapy == yt2)
+					mapystep = 0;
+				break;
+			
+			case 2: // yintercept matches
+				yintercept += ystep;
+				mapx += mapxstep;
+				if (mapx == xt2)
+					mapxstep = 0;
+				break;
+			
+			case 3: // xintercept and yintercept both match
+				// The trace is exiting a block through its corner. Not only does the block
+				// being entered need to be checked (which will happen when this loop
+				// continues), but the other two blocks adjacent to the corner also need to
+				// be checked.
+				if (flags & PT_ADDLINES)
+				{
+					if (!P_BlockLinesIterator(mapx + mapxstep, mapy, PIT_AddLineIntercepts))
+						return false; // early out
+					if (!P_BlockLinesIterator(mapx, mapy + mapystep, PIT_AddLineIntercepts))
+						return false; // early out
+				}
+				
+				if (flags & PT_ADDTHINGS)
+				{
+					if (!P_BlockThingsIterator(mapx + mapxstep, mapy, PIT_AddThingIntercepts, NULL))
+						return false; // early out
+					if (!P_BlockThingsIterator(mapx, mapy + mapystep, PIT_AddThingIntercepts, NULL))
+						return false; // early out
+				}
+				
+				xintercept += xstep;
+				yintercept += ystep;
+				mapx += mapxstep;
+				mapy += mapystep;
+				if (mapx == xt2)
+					mapxstep = 0;
+				if (mapy == yt2)
+					mapystep = 0;
+				break;
 		}
 	}
 	// Go through the sorted list
diff --git a/src/p_maputl.h b/src/p_maputl.h
index 67f7fd086a3c4ec11109ead24048ad118256c2d1..11cc90c59099c6a0c28cfa7db171c126993512f2 100644
--- a/src/p_maputl.h
+++ b/src/p_maputl.h
@@ -61,7 +61,7 @@ extern ffloor_t *openfloorrover, *openceilingrover;
 void P_LineOpening(line_t *plinedef, mobj_t *mobj);
 
 boolean P_BlockLinesIterator(INT32 x, INT32 y, boolean(*func)(line_t *));
-boolean P_BlockThingsIterator(INT32 x, INT32 y, boolean(*func)(mobj_t *));
+boolean P_BlockThingsIterator(INT32 x, INT32 y, boolean(*func)(mobj_t *), mobj_t *thing);
 
 void P_ClearBlockNodes(void);
 
@@ -94,7 +94,7 @@ typedef struct bthingit_s
 bthingit_t *P_NewBlockThingsIterator(int x1, int y1, int x2, int y2);
 mobj_t *P_BlockThingsIteratorNext(bthingit_t *it, boolean centeronly);
 void P_FreeBlockThingsIterator(bthingit_t *it);
-boolean P_DoBlockThingsIterate(int x1, int y1, int x2, int y2, boolean (*func)(mobj_t *));
+boolean P_DoBlockThingsIterate(int x1, int y1, int x2, int y2, boolean (*func)(mobj_t *), mobj_t *thing);
 
 #define PT_ADDLINES     1
 #define PT_ADDTHINGS    2
diff --git a/src/p_mobj.c b/src/p_mobj.c
index ec107ff711dbe8792b3e0f35cc8642893491fa23..259bf19d492ac3a33f428b9767dac100b745c4b3 100644
--- a/src/p_mobj.c
+++ b/src/p_mobj.c
@@ -39,7 +39,7 @@
 #include "netcode/net_command.h"
 
 static CV_PossibleValue_t CV_BobSpeed[] = {{0, "MIN"}, {4*FRACUNIT, "MAX"}, {0, NULL}};
-consvar_t cv_movebob = CVAR_INIT ("movebob", "1.0", CV_FLOAT|CV_SAVE, CV_BobSpeed, NULL);
+consvar_t cv_movebob = CVAR_INIT ("movebob", "0.25", CV_FLOAT|CV_SAVE, CV_BobSpeed, NULL);
 
 actioncache_t actioncachehead;
 
@@ -324,7 +324,7 @@ static boolean P_SetPlayerMobjState(mobj_t *mobj, statenum_t state)
 		mobj->tics = st->tics;
 
 		// Adjust the player's animation speed
-		if (mobj->state-states == S_PLAY_WAIT && (player->charflags & SF_FASTWAIT))
+		if (state == S_PLAY_WAIT && (player->charflags & SF_FASTWAIT))
 			mobj->tics = 5;
 		else if (player->panim == PA_EDGE && (player->charflags & SF_FASTEDGE))
 			mobj->tics = 2;
@@ -9334,7 +9334,7 @@ static void P_PointPushThink(mobj_t *mobj)
 	yl = (unsigned)(mobj->y - radius - bmaporgy)>>MAPBLOCKSHIFT;
 	yh = (unsigned)(mobj->y + radius - bmaporgy)>>MAPBLOCKSHIFT;
 
-	P_DoBlockThingsIterate(xl, yl, xh, yh, PIT_PushThing);
+	P_DoBlockThingsIterate(xl, yl, xh, yh, PIT_PushThing, pushmobj);
 }
 
 static boolean P_MobjRegularThink(mobj_t *mobj)
diff --git a/src/p_setup.c b/src/p_setup.c
index c2b8f2db2e367cc23fcdb704f23fe572360eea81..247587e11e63d813bf57410e29d276ac5b4125e3 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -609,15 +609,15 @@ Ploadflat (levelflat_t *levelflat, const char *flatname, boolean resize)
 	levelflat->type = LEVELFLAT_TEXTURE;
 
 	// Look for a flat
-	int texturenum = R_CheckFlatNumForName(levelflat->name);
+	int texturenum = R_CheckTextureNumForName(levelflat->name, TEXTURETYPE_FLAT);
 	if (texturenum < 0)
 	{
 		// If we can't find a flat, try looking for a texture!
-		texturenum = R_CheckTextureNumForName(levelflat->name);
+		texturenum = R_CheckTextureNumForName(levelflat->name, TEXTURETYPE_TEXTURE);
 		if (texturenum < 0)
 		{
 			// Use "not found" texture
-			texturenum = R_CheckTextureNumForName("REDWALL");
+			texturenum = R_CheckTextureNumForName("REDWALL", TEXTURETYPE_TEXTURE);
 
 			// Give up?
 			if (texturenum < 0)
diff --git a/src/p_slopes.c b/src/p_slopes.c
index 7390329369b4b5dfbb9b084e4396234f99b1fb57..12d5afd02fb96d7f19965e6f445de1e5b122a6d9 100644
--- a/src/p_slopes.c
+++ b/src/p_slopes.c
@@ -1046,15 +1046,37 @@ fixed_t P_GetWallTransferMomZ(mobj_t *mo, pslope_t *slope)
 {
 	vector3_t slopemom, axis;
 	angle_t ang;
+	angle_t advanceAng = ANG15;
+	const boolean upwards = (slope->zangle < ANGLE_180);
 
 	if (slope->flags & SL_NOPHYSICS)
 		return 0;
 
 	// If there's physics, time for launching.
 	// Doesn't kill the vertical momentum as much as P_SlopeLaunch does.
-	ang = slope->zangle + ANG15*((slope->zangle > 0) ? 1 : -1);
-	if (ang > ANGLE_90 && ang < ANGLE_180)
-		ang = ((slope->zangle > 0) ? ANGLE_90 : InvAngle(ANGLE_90)); // hard cap of directly upwards
+	ang = slope->zangle;
+
+	// for the time being, let's pretend the slope inclines upwards only
+	if (!upwards)
+	{
+		ang += ANGLE_180;
+	}
+
+	// angles past 90 degrees need to shrink to get closer to 90 degrees
+	if (ang > ANGLE_90)
+	{
+		advanceAng = InvAngle(advanceAng);
+	}
+
+	// now we set the actual final angle
+	if ((ang > ANGLE_90) != (ang + advanceAng > ANGLE_90)) // does advancing the angle push it past directly upwards?
+	{
+		ang = (upwards ? ANGLE_90 : InvAngle(ANGLE_90)); // hard cap of directly upwards
+	}
+	else
+	{
+		ang = slope->zangle + advanceAng;
+	}
 
 	slopemom.x = mo->momx;
 	slopemom.y = mo->momy;
diff --git a/src/p_spec.c b/src/p_spec.c
index 93809cbb4b68118e4fc4032186c5e772435daba2..649f63518eed2017cbfd06817eb322d27280d52e 100644
--- a/src/p_spec.c
+++ b/src/p_spec.c
@@ -135,22 +135,24 @@ void P_ParseAnimationDefintion(SINT8 istexture);
 
 static boolean P_FindTextureForAnimation(anim_t *anim, animdef_t *animdef)
 {
-	if (R_CheckTextureNumForName(animdef->startname) == -1)
+	INT32 start = R_CheckTextureNumForName(animdef->startname, TEXTURETYPE_TEXTURE);
+	if (start == -1)
 		return false;
 
-	anim->picnum = R_TextureNumForName(animdef->endname);
-	anim->basepic = R_TextureNumForName(animdef->startname);
+	anim->basepic = start;
+	anim->picnum = R_CheckTextureNumForName(animdef->endname, TEXTURETYPE_TEXTURE);
 
 	return true;
 }
 
 static boolean P_FindFlatForAnimation(anim_t *anim, animdef_t *animdef)
 {
-	if (R_CheckFlatNumForName(animdef->startname) == -1)
+	INT32 start = R_CheckTextureNumForName(animdef->startname, TEXTURETYPE_FLAT);
+	if (start == -1)
 		return false;
 
-	anim->picnum = R_CheckFlatNumForName(animdef->endname);
-	anim->basepic = R_CheckFlatNumForName(animdef->startname);
+	anim->basepic = start;
+	anim->picnum = R_CheckTextureNumForName(animdef->endname, TEXTURETYPE_FLAT);
 
 	return true;
 }
diff --git a/src/p_user.c b/src/p_user.c
index 7cc9c02ae722cb539126e0a14b160f7faef00754..ba22808eade2a389dc6c7bf56383fcd6149c033d 100644
--- a/src/p_user.c
+++ b/src/p_user.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2024 by Sonic Team Junior.
+// Copyright (C) 1999-2025 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -3310,6 +3310,7 @@ static void P_DoPlayerHeadSigns(player_t *player)
 					sign->frame = 2|FF_FULLBRIGHT;
 			}
 		}
+	}
 
 	if (!P_MobjWasRemoved(sign) && splitscreen) // Hide the sign from yourself in splitscreen - In single-screen, it wouldn't get spawned if it shouldn't be visible
 	{
@@ -3347,7 +3348,6 @@ static void P_DoPlayerHeadSigns(player_t *player)
 		}
 #endif
 	}
-	}
 }
 
 //
@@ -4125,7 +4125,7 @@ static void P_DoTeeter(player_t *player)
 			teeteryl = teeteryh = player->mo->y;
 			couldteeter = false;
 			solidteeter = teeter;
-			if (!P_DoBlockThingsIterate(xl, yl, xh, yh, PIT_CheckSolidsTeeter))
+			if (!P_DoBlockThingsIterate(xl, yl, xh, yh, PIT_CheckSolidsTeeter, tmthing))
 				goto teeterdone; // we've found something that stops us teetering at all
 teeterdone:
 			teeter = solidteeter;
@@ -9833,7 +9833,7 @@ static CV_PossibleValue_t campos_cons_t[] = { {INT32_MIN, "MIN"}, {INT32_MAX, "M
 consvar_t cv_cam_dist = CVAR_INIT ("cam_curdist", "160", CV_FLOAT|CV_ALLOWLUA, campos_cons_t, NULL);
 consvar_t cv_cam_height = CVAR_INIT ("cam_curheight", "25", CV_FLOAT|CV_ALLOWLUA, campos_cons_t, NULL);
 consvar_t cv_cam_still = CVAR_INIT ("cam_still", "Off", CV_ALLOWLUA, CV_OnOff, NULL);
-consvar_t cv_cam_speed = CVAR_INIT ("cam_speed", "0.3", CV_FLOAT|CV_SAVE|CV_ALLOWLUA, CV_CamSpeed, NULL);
+consvar_t cv_cam_speed = CVAR_INIT ("cam_speed", "0.4", CV_FLOAT|CV_SAVE|CV_ALLOWLUA, CV_CamSpeed, NULL);
 consvar_t cv_cam_rotate = CVAR_INIT ("cam_rotate", "0", CV_CALL|CV_NOINIT|CV_ALLOWLUA, CV_CamRotate, CV_CamRotate_OnChange);
 consvar_t cv_cam_rotspeed = CVAR_INIT ("cam_rotspeed", "10", CV_SAVE|CV_ALLOWLUA, rotation_cons_t, NULL);
 consvar_t cv_cam_turnmultiplier = CVAR_INIT ("cam_turnmultiplier", "0.75", CV_FLOAT|CV_SAVE|CV_ALLOWLUA, multiplier_cons_t, NULL);
@@ -9842,7 +9842,7 @@ consvar_t cv_cam_adjust = CVAR_INIT ("cam_adjust", "On", CV_SAVE|CV_ALLOWLUA, CV
 consvar_t cv_cam2_dist = CVAR_INIT ("cam2_curdist", "160", CV_FLOAT|CV_ALLOWLUA, campos_cons_t, NULL);
 consvar_t cv_cam2_height = CVAR_INIT ("cam2_curheight", "25", CV_FLOAT|CV_ALLOWLUA, campos_cons_t, NULL);
 consvar_t cv_cam2_still = CVAR_INIT ("cam2_still", "Off", CV_ALLOWLUA, CV_OnOff, NULL);
-consvar_t cv_cam2_speed = CVAR_INIT ("cam2_speed", "0.3", CV_FLOAT|CV_SAVE|CV_ALLOWLUA, CV_CamSpeed, NULL);
+consvar_t cv_cam2_speed = CVAR_INIT ("cam2_speed", "0.4", CV_FLOAT|CV_SAVE|CV_ALLOWLUA, CV_CamSpeed, NULL);
 consvar_t cv_cam2_rotate = CVAR_INIT ("cam2_rotate", "0", CV_CALL|CV_NOINIT|CV_ALLOWLUA, CV_CamRotate, CV_CamRotate2_OnChange);
 consvar_t cv_cam2_rotspeed = CVAR_INIT ("cam2_rotspeed", "10", CV_SAVE|CV_ALLOWLUA, rotation_cons_t, NULL);
 consvar_t cv_cam2_turnmultiplier = CVAR_INIT ("cam2_turnmultiplier", "0.75", CV_FLOAT|CV_SAVE|CV_ALLOWLUA, multiplier_cons_t, NULL);
@@ -11515,6 +11515,18 @@ void P_DoTailsOverlay(player_t *player, mobj_t *tails)
 }
 
 // Metal Sonic's jet fume
+//
+// The follow object's state is set to its spawn state when deactivated.
+// When the player is on a moving animation, the follow object goes to its see state.
+// When dash mode is entered, the follow object switches to its melee state.
+//
+// If MF2_FRET is set, the jet fume will flash during dash mode.
+// MF2_AMBUSH can be used to enable Metal Sonic's skidding animation.
+// MF2_JUSTATTACKED will enable the color cycling.
+// If the follow item is MT_METALJETFUME, the above two effects are automatically applied.
+//
+// MF2_STRONGBOX is internally used to track if the jet fume is in its deactivated state or not.
+// MF2_BOSSNOTRAP is internally used to instantly reset the jet fume's scale to its intended scale.
 void P_DoMetalJetFume(player_t *player, mobj_t *fume)
 {
 	static const UINT8 FUME_SKINCOLORS[] =
@@ -11541,19 +11553,29 @@ void P_DoMetalJetFume(player_t *player, mobj_t *fume)
 	fixed_t heightoffset = ((mo->eflags & MFE_VERTICALFLIP) ? mo->height - (P_GetPlayerHeight(player) >> 1) : (P_GetPlayerHeight(player) >> 1));
 	panim_t panim = player->panim;
 	tic_t dashmode = min(player->dashmode, DASHMODE_MAX);
+	boolean ismetaljetfume = fume->type == MT_METALJETFUME;
+	boolean notmoving = panim != PA_WALK && panim != PA_RUN && panim != PA_DASH;
 	boolean underwater = mo->eflags & MFE_UNDERWATER;
 	statenum_t stat = fume->state-states;
 	boolean resetinterp = false;
 
-	if (panim != PA_WALK && panim != PA_RUN && panim != PA_DASH) // turn invisible when not in a coherent movement state
+	if (notmoving) // deactivate when not in a coherent movement state
 	{
-		if (stat != fume->info->spawnstate)
+		if ((fume->flags2 & MF2_STRONGBOX) == 0)
+		{
 			P_SetMobjState(fume, fume->info->spawnstate);
-		return;
+			fume->flags2 |= MF2_STRONGBOX;
+		}
+		if (P_MobjWasRemoved(fume) || ismetaljetfume)
+			return;
 	}
 
-	if (player->skidtime) // Rotate during metal sonic's new skid animation
-		angle += ANGLE_90;
+	// Rotate on skid animation if follow item is MT_METALJETFUME, or if MF2_AMBUSH is set
+	if (player->mo->sprite2 == SPR2_SKID)
+	{
+		if ((ismetaljetfume && (player->charflags & SF_JETFUME)) || (fume->flags2 & MF2_AMBUSH))
+			angle += ANGLE_90;
+	}
 
 	if (underwater) // No fume underwater; spawn bubbles instead!
 	{
@@ -11590,54 +11612,82 @@ void P_DoMetalJetFume(player_t *player, mobj_t *fume)
 
 		if (panim == PA_WALK)
 		{
-			if (stat != fume->info->spawnstate)
+			if ((fume->flags2 & MF2_STRONGBOX) == 0)
 			{
-				fume->threshold = 0;
 				P_SetMobjState(fume, fume->info->spawnstate);
+				fume->threshold = 0;
+				fume->flags2 &= ~MF2_STRONGBOX;
 			}
-			return;
+			if (P_MobjWasRemoved(fume) || ismetaljetfume)
+				return;
 		}
 	}
 
-	if (stat == fume->info->spawnstate) // If currently inivisble, activate!
+	// If currently deactivated, activate!
+	if (!notmoving && !underwater && (fume->flags2 & MF2_STRONGBOX))
 	{
 		P_SetMobjState(fume, (stat = fume->info->seestate));
+		if (P_MobjWasRemoved(fume))
+			return;
 		P_SetScale(fume, mo->scale, false);
+		fume->flags2 &= ~MF2_STRONGBOX;
 		resetinterp = true;
 	}
 
-	if (dashmode > DASHMODE_THRESHOLD && stat != fume->info->seestate) // If in dashmode, grow really big and flash
+	// If in dash mode, grow really big
+	if (dashmode > DASHMODE_THRESHOLD && stat != fume->info->meleestate)
 	{
 		fume->destscale = mo->scale;
-		fume->flags2 ^= MF2_DONTDRAW;
 		fume->flags2 |= mo->flags2 & MF2_DONTDRAW;
+
+		// Flash if follow item is MT_METALJETFUME, or if MF2_FRET is set
+		if (ismetaljetfume || (fume->flags2 & MF2_FRET))
+			fume->flags2 ^= MF2_DONTDRAW;
 	}
 	else // Otherwise, pick a size and color depending on speed and proximity to dashmode
 	{
-		if (dashmode == DASHMODE_THRESHOLD && dashmode > (tic_t)fume->movecount) // If just about to enter dashmode, play the startup animation again
+		// If just about to enter dash mode, play the startup animation again
+		if (dashmode == DASHMODE_THRESHOLD && dashmode > (tic_t)fume->movecount)
 		{
-			P_SetMobjState(fume, (stat = fume->info->seestate));
+			P_SetMobjState(fume, fume->info->meleestate);
+			if (P_MobjWasRemoved(fume))
+				return;
 			P_SetScale(fume, 2*mo->scale, true);
 		}
+
 		fume->flags2 = (fume->flags2 & ~MF2_DONTDRAW) | (mo->flags2 & MF2_DONTDRAW);
 		fume->destscale = (mo->scale + FixedDiv(player->speed, player->normalspeed)) / (underwater ? 6 : 3);
-		fume->color = FUME_SKINCOLORS[(dashmode * sizeof(FUME_SKINCOLORS)) / (DASHMODE_MAX + 1)];
+
+		// Do color cycling if follow item is MT_METALJETFUME, or if MF2_JUSTATTACKED is set
+		if (ismetaljetfume || (fume->flags2 & MF2_JUSTATTACKED))
+			fume->color = FUME_SKINCOLORS[(dashmode * sizeof(FUME_SKINCOLORS)) / (DASHMODE_MAX + 1)];
 
 		if (underwater)
 		{
-			fume->frame = (fume->frame & FF_FRAMEMASK) | FF_ANIMATE | (P_RandomRange(0, 9) * FF_TRANS10);
+			fume->frame = (fume->frame & ~FF_TRANSMASK) | (P_RandomRange(0, 9) << FF_TRANSSHIFT);
 			fume->threshold = 1;
 		}
 		else if (fume->threshold)
 		{
-			fume->frame = (fume->frame & FF_FRAMEMASK) | fume->state->frame;
+			fume->frame = (fume->frame & FF_FRAMEMASK) | (fume->state->frame & ~FF_FRAMEMASK);
 			fume->threshold = 0;
 		}
 	}
 
-	fume->movecount = dashmode; // keeps track of previous dashmode value so we know whether Metal is entering or leaving it
-	fume->flags2 = (fume->flags2 & ~MF2_OBJECTFLIP) | (mo->flags2 & MF2_OBJECTFLIP); // Make sure to flip in reverse gravity!
-	fume->eflags = (fume->eflags & ~MFE_VERTICALFLIP) | (mo->eflags & MFE_VERTICALFLIP); // Make sure to flip in reverse gravity!
+	// keeps track of previous dash mode value so we know whether Metal is entering or leaving it
+	fume->movecount = dashmode;
+
+	// Make sure to flip in reverse gravity!
+	fume->flags2 = (fume->flags2 & ~MF2_OBJECTFLIP) | (mo->flags2 & MF2_OBJECTFLIP);
+	fume->eflags = (fume->eflags & ~MFE_VERTICALFLIP) | (mo->eflags & MFE_VERTICALFLIP);
+
+	// Set the appropriate scale at spawn
+	// This is... strange, but I had to choose a flag that a follow object would not ordinarily use.
+	if ((fume->flags2 & MF2_BOSSNOTRAP) == 0)
+	{
+		P_SetScale(fume, fume->destscale, true);
+		fume->flags2 |= MF2_BOSSNOTRAP;
+	}
 
 	// Finally, set its position
 	dist = -mo->radius - FixedMul(fume->info->radius, fume->destscale - mo->scale/3);
@@ -11647,9 +11697,10 @@ void P_DoMetalJetFume(player_t *player, mobj_t *fume)
 	fume->y = mo->y + P_ReturnThrustY(fume, angle, dist);
 	fume->z = mo->z + heightoffset - (fume->height >> 1);
 	P_SetThingPosition(fume);
-	if (resetinterp) R_ResetMobjInterpolationState(fume);
+	if (resetinterp)
+		R_ResetMobjInterpolationState(fume);
 
-	// If dashmode is high enough, spawn a trail
+	// If dash mode is high enough, spawn a trail
 	if (player->normalspeed >= skins[player->skin]->normalspeed*2)
 	{
 		mobj_t *ghost = P_SpawnGhostMobj(fume);
@@ -11663,6 +11714,8 @@ void P_DoFollowMobj(player_t *player, mobj_t *followmobj)
 {
 	if (LUA_HookFollowMobj(player, followmobj) || P_MobjWasRemoved(followmobj))
 		{;}
+	else if (player->charflags & SF_JETFUME)
+		P_DoMetalJetFume(player, followmobj);
 	else
 	{
 		switch (followmobj->type)
@@ -13164,7 +13217,8 @@ void P_PlayerAfterThink(player_t *player)
 						player->followmobj->colorized = true;
 						break;
 					default:
-						player->followmobj->flags2 |= MF2_LINKDRAW;
+						if ((player->charflags & SF_JETFUME) == 0)
+							player->followmobj->flags2 |= MF2_LINKDRAW;
 						break;
 				}
 			}
diff --git a/src/r_main.c b/src/r_main.c
index 46bac9dc76a1886cb3ddc60167772312721b6a53..3de5cb1514cfa01aed8567364d749499a58a9f11 100644
--- a/src/r_main.c
+++ b/src/r_main.c
@@ -161,7 +161,7 @@ consvar_t cv_translucency = CVAR_INIT ("translucency", "On", CV_SAVE, CV_OnOff,
 consvar_t cv_drawdist = CVAR_INIT ("drawdist", "Infinite", CV_SAVE, drawdist_cons_t, NULL);
 consvar_t cv_drawdist_nights = CVAR_INIT ("drawdist_nights", "2048", CV_SAVE, drawdist_cons_t, NULL);
 consvar_t cv_drawdist_precip = CVAR_INIT ("drawdist_precip", "1024", CV_SAVE, drawdist_precip_cons_t, NULL);
-consvar_t cv_fov = CVAR_INIT ("fov", "90", CV_SAVE|CV_FLOAT|CV_CALL, fov_cons_t, Fov_OnChange);
+consvar_t cv_fov = CVAR_INIT ("fov", "100", CV_SAVE|CV_FLOAT|CV_CALL, fov_cons_t, Fov_OnChange);
 consvar_t cv_fovchange = CVAR_INIT ("fovchange", "Off", CV_SAVE, CV_OnOff, NULL);
 consvar_t cv_maxportals = CVAR_INIT ("maxportals", "2", CV_SAVE, maxportals_cons_t, NULL);
 
diff --git a/src/r_plane.c b/src/r_plane.c
index a0de048ea8c6fe7eac8265d1cdfd3559d9bba1d6..053cfe921fb16fc94c4bc14755dcf4305f3911dd 100644
--- a/src/r_plane.c
+++ b/src/r_plane.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2024 by Sonic Team Junior.
+// Copyright (C) 1999-2025 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -182,8 +182,8 @@ static void R_MapPlane(INT32 y, INT32 x1, INT32 x2)
 
 		R_CalculatePlaneRipple(currentplane->viewangle + currentplane->plangle);
 
-		ds_xfrac += planeripple.xfrac;
-		ds_yfrac += planeripple.yfrac;
+		ds_xfrac += FixedMul(planeripple.xfrac, currentplane->xscale);
+		ds_yfrac += FixedMul(planeripple.yfrac, currentplane->yscale);
 		ds_bgofs >>= FRACBITS;
 
 		if ((y + ds_bgofs) >= viewheight)
diff --git a/src/r_skins.c b/src/r_skins.c
index f364273e8405189845a385fd918b0a95e79b6220..7b5e547148a6ba975775dfc933602614be33921d 100644
--- a/src/r_skins.c
+++ b/src/r_skins.c
@@ -2,7 +2,7 @@
 //-----------------------------------------------------------------------------
 // Copyright (C) 1993-1996 by id Software, Inc.
 // Copyright (C) 1998-2000 by DooM Legacy Team.
-// Copyright (C) 1999-2024 by Sonic Team Junior.
+// Copyright (C) 1999-2025 by Sonic Team Junior.
 //
 // This program is free software distributed under the
 // terms of the GNU General Public License, version 2.
@@ -80,10 +80,19 @@ UINT16 P_ApplySuperFlagToSprite2(UINT16 spr2, mobj_t *mobj)
 {
 	if (mobj->player)
 	{
-		if (mobj->player->charflags & SF_NOSUPERSPRITES || (mobj->player->powers[pw_carry] == CR_NIGHTSMODE && (mobj->player->charflags & SF_NONIGHTSSUPER)))
+		boolean is_nights = mobj->player->powers[pw_carry] == CR_NIGHTSMODE;
+
+		if (mobj->player->charflags & SF_NOSUPERSPRITES || (is_nights && (mobj->player->charflags & SF_NONIGHTSSUPER)))
 			spr2 &= ~SPR2F_SUPER;
-		else if (mobj->player->powers[pw_super] || (mobj->player->powers[pw_carry] == CR_NIGHTSMODE && (mobj->player->charflags & SF_SUPER)))
+		else if (mobj->player->powers[pw_super] || (is_nights && (mobj->player->charflags & SF_SUPER)))
 			spr2 |= SPR2F_SUPER;
+
+		// Special case for transforming when you are NiGHTS.
+		// Do NOT apply the super sprites in this situation, even if they exist.
+		if (is_nights && mobj->state >= &states[S_PLAY_NIGHTS_TRANS1] && mobj->state <= &states[S_PLAY_NIGHTS_TRANS6])
+		{
+			spr2 &= ~SPR2F_SUPER;
+		}
 	}
 
 	if (spr2 & SPR2F_SUPER)
@@ -730,6 +739,8 @@ static boolean R_ProcessPatchableFields(skin_t *skin, char *stoken, char *value)
 	GETFLAG(MACHINE)
 	GETFLAG(DASHMODE)
 	GETFLAG(FASTEDGE)
+	GETFLAG(FASTWAIT)
+	GETFLAG(JETFUME)
 	GETFLAG(MULTIABILITY)
 	GETFLAG(NONIGHTSROTATION)
 	GETFLAG(NONIGHTSSUPER)
diff --git a/src/r_textures.c b/src/r_textures.c
index 4c52f75ebe169607d8f367bf07c03bc7f2eb70af..4dc6bbae40ea7f722ed040eab779ed1ce62ae467 100644
--- a/src/r_textures.c
+++ b/src/r_textures.c
@@ -925,7 +925,7 @@ Rloadtextures (INT32 i, INT32 w)
 
 		// printf("\"%s\" (wad: %u, lump: %u) is a single patch, dimensions %d x %d\n",W_CheckNameForNumPwad(wadnum,lumpnum),wadnum,lumpnum,width,height);
 
-		R_AddSinglePatchTexture(i, wadnum, lumpnum, width, height, TEXTURETYPE_SINGLEPATCH);
+		R_AddSinglePatchTexture(i, wadnum, lumpnum, width, height, TEXTURETYPE_TEXTURE);
 
 		i++;
 	}
@@ -1494,7 +1494,7 @@ static texture_t *R_ParseTexture(boolean actuallyLoadTexture)
 			resultTexture->hash = quickncasehash(newTextureName, 8);
 			resultTexture->width = newTextureWidth;
 			resultTexture->height = newTextureHeight;
-			resultTexture->type = TEXTURETYPE_COMPOSITE;
+			resultTexture->type = TEXTURETYPE_TEXTURE;
 		}
 		Z_Free(texturesToken);
 		texturesToken = M_GetToken(NULL);
@@ -1680,7 +1680,7 @@ static void AddTextureToCache(const char *name, UINT32 hash, INT32 id, UINT8 typ
 //
 // Check whether texture is available. Filter out NoTexture indicator.
 //
-INT32 R_CheckTextureNumForName(const char *name)
+INT32 R_CheckTextureNumForName(const char *name, UINT8 type)
 {
 	INT32 i;
 	UINT32 hash;
@@ -1692,14 +1692,14 @@ INT32 R_CheckTextureNumForName(const char *name)
 	hash = quickncasehash(name, 8);
 
 	for (i = 0; i < tidcachelen; i++)
-		if (tidcache[i].hash == hash && !strncasecmp(tidcache[i].name, name, 8))
+		if (tidcache[i].type == type && tidcache[i].hash == hash && !strncasecmp(tidcache[i].name, name, 8))
 			return tidcache[i].id;
 
 	// Need to parse the list backwards, so textures loaded more recently are used in lieu of ones loaded earlier
 	for (i = numtextures - 1; i >= 0; i--)
-		if (textures[i]->hash == hash && !strncasecmp(textures[i]->name, name, 8))
+		if (textures[i]->type == type && textures[i]->hash == hash && !strncasecmp(textures[i]->name, name, 8))
 		{
-			AddTextureToCache(name, hash, i, textures[i]->type);
+			AddTextureToCache(name, hash, i, type);
 			return i;
 		}
 
@@ -1738,47 +1738,29 @@ const char *R_TextureNameForNum(INT32 num)
 //
 // R_TextureNumForName
 //
-// Calls R_CheckTextureNumForName, aborts with error message.
+// Calls R_CheckTextureNumForName. Returns REDWALL if not found.
 //
 INT32 R_TextureNumForName(const char *name)
 {
-	const INT32 i = R_CheckTextureNumForName(name);
+	INT32 i = R_CheckTextureNumForName(name, TEXTURETYPE_TEXTURE);
 
+	// Didn't find it, so look for a flat
+	if (i == -1)
+	{
+		i = R_CheckTextureNumForName(name, TEXTURETYPE_FLAT);
+	}
+
+	// Still didn't find it, so return REDWALL
 	if (i == -1)
 	{
 		static INT32 redwall = -2;
 		CONS_Debug(DBG_SETUP, "WARNING: R_TextureNumForName: %.8s not found\n", name);
 		if (redwall == -2)
-			redwall = R_CheckTextureNumForName("REDWALL");
+			redwall = R_CheckTextureNumForName("REDWALL", TEXTURETYPE_TEXTURE);
 		if (redwall != -1)
 			return redwall;
 		return 1;
 	}
-	return i;
-}
-
-// Like R_CheckTextureNumForName, but only looks in the flat namespace specifically.
-INT32 R_CheckFlatNumForName(const char *name)
-{
-	INT32 i;
-	UINT32 hash;
-
-	// "NoTexture" marker.
-	if (name[0] == '-')
-		return 0;
-
-	hash = quickncasehash(name, 8);
 
-	for (i = 0; i < tidcachelen; i++)
-		if (tidcache[i].type == TEXTURETYPE_FLAT && tidcache[i].hash == hash && !strncasecmp(tidcache[i].name, name, 8))
-			return tidcache[i].id;
-
-	for (i = numtextures - 1; i >= 0; i--)
-		if (textures[i]->hash == hash && !strncasecmp(textures[i]->name, name, 8) && textures[i]->type == TEXTURETYPE_FLAT)
-		{
-			AddTextureToCache(name, hash, i, TEXTURETYPE_FLAT);
-			return i;
-		}
-
-	return -1;
+	return i;
 }
diff --git a/src/r_textures.h b/src/r_textures.h
index 35db40d42b662553d89ed627247ee06d640e4bcd..e6985556b65176058416c9c6ebb1aa5551d6e086 100644
--- a/src/r_textures.h
+++ b/src/r_textures.h
@@ -40,8 +40,7 @@ typedef struct
 enum
 {
 	TEXTURETYPE_UNKNOWN,
-	TEXTURETYPE_SINGLEPATCH,
-	TEXTURETYPE_COMPOSITE,
+	TEXTURETYPE_TEXTURE,
 	TEXTURETYPE_FLAT
 };
 
@@ -100,8 +99,7 @@ void R_SetFlatVars(size_t length);
 
 // Returns the texture number for the texture name.
 INT32 R_TextureNumForName(const char *name);
-INT32 R_CheckTextureNumForName(const char *name);
-INT32 R_CheckFlatNumForName(const char *name);
+INT32 R_CheckTextureNumForName(const char *name, UINT8 type);
 
 // Returns the texture name for the texture number (in case you ever needed it)
 const char *R_CheckTextureNameForNum(INT32 num);
diff --git a/src/sdl/Srb2SDL-vc10.vcxproj b/src/sdl/Srb2SDL-vc10.vcxproj
index 86ffa7082ac3210321a06c3b477ba3b6239947b9..8026cd3acbdd4adb6652269dbf308c8ec6fa4e12 100644
--- a/src/sdl/Srb2SDL-vc10.vcxproj
+++ b/src/sdl/Srb2SDL-vc10.vcxproj
@@ -447,6 +447,7 @@
     <ClCompile Include="..\blua\lauxlib.c" />
     <ClCompile Include="..\blua\lbaselib.c" />
     <ClCompile Include="..\blua\lcode.c" />
+    <ClCompile Include="..\blua\ldblib.c" />
     <ClCompile Include="..\blua\ldebug.c" />
     <ClCompile Include="..\blua\ldo.c" />
     <ClCompile Include="..\blua\ldump.c" />
@@ -641,4 +642,4 @@
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
   <ImportGroup Label="ExtensionTargets">
   </ImportGroup>
-</Project>
\ No newline at end of file
+</Project>
diff --git a/src/sdl/Srb2SDL-vc10.vcxproj.filters b/src/sdl/Srb2SDL-vc10.vcxproj.filters
index d2f9d018f2b3ef44e59def85634aa46ef24acd95..bbfca04d4c1132bc5241e02e612aa6fc2e48c7e5 100644
--- a/src/sdl/Srb2SDL-vc10.vcxproj.filters
+++ b/src/sdl/Srb2SDL-vc10.vcxproj.filters
@@ -604,6 +604,9 @@
     <ClCompile Include="..\blua\ldebug.c">
       <Filter>BLUA</Filter>
     </ClCompile>
+    <ClCompile Include="..\blua\ldblib.c">
+      <Filter>BLUA</Filter>
+    </ClCompile>
     <ClCompile Include="..\blua\ldo.c">
       <Filter>BLUA</Filter>
     </ClCompile>
@@ -1125,4 +1128,4 @@
       <Filter>SDLApp</Filter>
     </Image>
   </ItemGroup>
-</Project>
\ No newline at end of file
+</Project>