diff --git a/src/menu/menu.cpp b/src/menu/menu.cpp
index 0a11b1b137..65a5d5a99b 100644
--- a/src/menu/menu.cpp
+++ b/src/menu/menu.cpp
@@ -1124,7 +1124,7 @@ DEFINE_FIELD(FOptionMenuSettings, mLinespacing)
 
 struct IJoystickConfig;
 // These functions are used by dynamic menu creation.
-DMenuItemBase * CreateOptionMenuItemStaticText(const char *name, bool v)
+DMenuItemBase * CreateOptionMenuItemStaticText(const char *name, int v)
 {
 	auto c = PClass::FindClass("OptionMenuItemStaticText");
 	auto p = c->CreateNew();
diff --git a/src/menu/menu.h b/src/menu/menu.h
index 7e985df6dc..d5211421ff 100644
--- a/src/menu/menu.h
+++ b/src/menu/menu.h
@@ -109,6 +109,7 @@ extern FSavegameManager savegameManager;
 class DMenu;
 extern DMenu *CurrentMenu;
 extern int MenuTime;
+class DMenuItemBase;
 
 //=============================================================================
 //
@@ -124,18 +125,18 @@ public:
 	FName mMenuName;
 	FString mNetgameMessage;
 	PClass *mClass = nullptr;
+	bool mProtected = false;
+	TArray<DMenuItemBase *> mItems;
 
 	virtual size_t PropagateMark() { return 0;  }
 };
 
-class DMenuItemBase;
 
 class DListMenuDescriptor : public DMenuDescriptor
 {
 	DECLARE_CLASS(DListMenuDescriptor, DMenuDescriptor)
 
 public:
-	TArray<DMenuItemBase *> mItems;
 	int mSelectedItem;
 	double mSelectOfsX;
 	double mSelectOfsY;
@@ -186,7 +187,6 @@ class DOptionMenuDescriptor : public DMenuDescriptor
 	DECLARE_CLASS(DOptionMenuDescriptor, DMenuDescriptor)
 
 public:
-	TArray<DMenuItemBase *> mItems;
 	FString mTitle;
 	int mSelectedItem;
 	int mDrawTop;
@@ -349,7 +349,7 @@ void M_MarkMenus();
 
 
 struct IJoystickConfig;
-DMenuItemBase * CreateOptionMenuItemStaticText(const char *name, bool v);
+DMenuItemBase * CreateOptionMenuItemStaticText(const char *name, int v = -1);
 DMenuItemBase * CreateOptionMenuItemSubmenu(const char *label, FName cmd, int center);
 DMenuItemBase * CreateOptionMenuItemControl(const char *label, FName cmd, FKeyBindings *bindings);
 DMenuItemBase * CreateOptionMenuItemJoyConfigMenu(const char *label, IJoystickConfig *joy);
diff --git a/src/menu/menudef.cpp b/src/menu/menudef.cpp
index 37ca2fc0aa..e3184d3168 100644
--- a/src/menu/menudef.cpp
+++ b/src/menu/menudef.cpp
@@ -54,6 +54,7 @@
 #include "vm.h"
 #include "types.h"
 #include "gameconfigfile.h"
+#include "m_argv.h"
 
 
 
@@ -515,11 +516,88 @@ static bool CheckCompatible(DMenuDescriptor *newd, DMenuDescriptor *oldd)
 	return newd->mClass->IsDescendantOf(oldd->mClass);
 }
 
+static int GetGroup(DMenuItemBase *desc)
+{
+	if (desc->IsKindOf(NAME_OptionMenuItemCommand)) return 2;
+	if (desc->IsKindOf(NAME_OptionMenuItemSubmenu)) return 1;
+	if (desc->IsKindOf(NAME_OptionMenuItemControlBase)) return 3;
+	if (desc->IsKindOf(NAME_OptionMenuItemOptionBase)) return 4;
+	if (desc->IsKindOf(NAME_OptionMenuSliderBase)) return 4;
+	if (desc->IsKindOf(NAME_OptionMenuFieldBase)) return 4;
+	if (desc->IsKindOf(NAME_OptionMenuItemColorPicker)) return 4;
+	if (desc->IsKindOf(NAME_OptionMenuItemStaticText)) return 5;
+	if (desc->IsKindOf(NAME_OptionMenuItemStaticTextSwitchable)) return 5;
+	return 0;
+}
+
+static bool FindMatchingItem(DMenuItemBase *desc)
+{
+	int grp = GetGroup(desc);
+	if (grp == 0) return false;	// no idea what this is.
+	if (grp == 5) return true;	// static texts always match
+
+	FName name = desc->mAction;
+	if (grp == 1)
+	{
+		// Check for presence of menu
+		auto menu = MenuDescriptors.CheckKey(name);
+		if (menu == nullptr) return true;
+	}
+	else if (grp == 4)
+	{
+		// Check for presence of CVAR and blacklist
+		auto cv = GetCVar(nullptr, name.GetChars());
+		if (cv == nullptr) return true;
+	}
+
+	MenuDescriptorList::Iterator it(MenuDescriptors);
+	MenuDescriptorList::Pair *pair;
+	while (it.NextPair(pair))
+	{
+		for (auto it : pair->Value->mItems)
+		{
+			if (it->mAction == name && GetGroup(it) == grp) return true;
+		}
+	}
+	return false;
+}
+
 static bool ReplaceMenu(FScanner &sc, DMenuDescriptor *desc)
 {
 	DMenuDescriptor **pOld = MenuDescriptors.CheckKey(desc->mMenuName);
 	if (pOld != nullptr && *pOld != nullptr) 
 	{
+		if ((*pOld)->mProtected)
+		{
+			// If this tries to replace an option menu with an option menu, let's append all new entries to the old menu.
+			// Otherwise bail out because for list menus it's not that simple.
+			if (desc->IsKindOf(RUNTIME_CLASS(DListMenuDescriptor)) || (*pOld)->IsKindOf(RUNTIME_CLASS(DListMenuDescriptor)))
+			{
+				sc.ScriptMessage("Cannot replace protected menu %s.", desc->mMenuName.GetChars());
+				return true;
+			}
+			for (int i = desc->mItems.Size()-1; i >= 0; i--)
+			{
+				if (FindMatchingItem(desc->mItems[i]))
+				{
+					desc->mItems.Delete(i);
+				}
+			}
+			if (desc->mItems.Size() > 0)
+			{
+				auto sep = CreateOptionMenuItemStaticText("---------------", CR_YELLOW);
+				(*pOld)->mItems.Push(sep);
+				for (auto it : desc->mItems)
+				{
+					(*pOld)->mItems.Push(it);
+				}
+				desc->mItems.Clear();
+
+				sc.ScriptMessage("Merged %d items into %s", desc->mItems.Size(), desc->mMenuName.GetChars());
+			}
+			return true;
+		}
+
 		if (!CheckCompatible(desc, *pOld))
 		{
 			sc.ScriptMessage("Tried to replace menu '%s' with a menu of different type", desc->mMenuName.GetChars());
@@ -874,6 +952,7 @@ static void ParseOptionMenu(FScanner &sc)
 	desc->mScrollTop = DefaultOptionMenuSettings->mScrollTop;
 	desc->mIndent =  DefaultOptionMenuSettings->mIndent;
 	desc->mDontDim =  DefaultOptionMenuSettings->mDontDim;
+	desc->mProtected = sc.CheckString("protected");
 
 	ParseOptionMenuBody(sc, desc);
 	ReplaceMenu(sc, desc);
@@ -980,6 +1059,7 @@ void M_ParseMenuDefs()
 				sc.ScriptError("Unknown keyword '%s'", sc.String);
 			}
 		}
+		if (Args->CheckParm("-nocustommenu")) break;
 	}
 	DefaultListMenuClass = DefaultListMenuSettings->mClass;
 	DefaultListMenuSettings = nullptr;
@@ -1391,9 +1471,9 @@ static void InitKeySections()
 			for (unsigned i = 0; i < KeySections.Size(); i++)
 			{
 				FKeySection *sect = &KeySections[i];
-				DMenuItemBase *item = CreateOptionMenuItemStaticText(" ", false);
+				DMenuItemBase *item = CreateOptionMenuItemStaticText(" ");
 				menu->mItems.Push(item);
-				item = CreateOptionMenuItemStaticText(sect->mTitle, true);
+				item = CreateOptionMenuItemStaticText(sect->mTitle, 1);
 				menu->mItems.Push(item);
 				for (unsigned j = 0; j < sect->mActions.Size(); j++)
 				{
diff --git a/src/namedef.h b/src/namedef.h
index cc3e650883..3d8fd684f3 100644
--- a/src/namedef.h
+++ b/src/namedef.h
@@ -927,3 +927,14 @@ xx(MessageBoxMenu)
 xx(Both)
 xx(Physical)
 xx(Visual)
+
+xx(OptionMenuItemSubmenu)
+xx(OptionMenuItemCommand)
+xx(OptionMenuItemControlBase)
+xx(OptionMenuItemOptionBase)
+xx(OptionMenuSliderBase)
+xx(OptionMenuFieldBase)
+xx(OptionMenuItemColorPicker)
+xx(OptionMenuItemStaticText)
+xx(OptionMenuItemStaticTextSwitchable)
+xx(mAction)
diff --git a/src/polyrenderer/scene/poly_plane.cpp b/src/polyrenderer/scene/poly_plane.cpp
index 88ae14e10e..ad471b4f06 100644
--- a/src/polyrenderer/scene/poly_plane.cpp
+++ b/src/polyrenderer/scene/poly_plane.cpp
@@ -33,7 +33,7 @@
 
 EXTERN_CVAR(Int, r_3dfloors)
 
-void RenderPolyPlane::RenderPlanes(const TriMatrix &worldToClip, const PolyClipPlane &clipPlane, PolyCull &cull, subsector_t *sub, uint32_t subsectorDepth, uint32_t stencilValue, double skyCeilingHeight, double skyFloorHeight, std::vector<std::unique_ptr<PolyDrawSectorPortal>> &sectorPortals)
+void RenderPolyPlane::RenderPlanes(const TriMatrix &worldToClip, const PolyClipPlane &clipPlane, PolyCull &cull, subsector_t *sub, uint32_t ceilingSubsectorDepth, uint32_t floorSubsectorDepth, uint32_t stencilValue, double skyCeilingHeight, double skyFloorHeight, std::vector<std::unique_ptr<PolyDrawSectorPortal>> &sectorPortals)
 {
 	RenderPolyPlane plane;
 
@@ -61,7 +61,7 @@ void RenderPolyPlane::RenderPlanes(const TriMatrix &worldToClip, const PolyClipP
 			double fakeHeight = fakeFloor->top.plane->ZatPoint(frontsector->centerspot);
 			if (fakeHeight < viewpoint.Pos.Z && fakeHeight > frontsector->floorplane.ZatPoint(frontsector->centerspot))
 			{
-				plane.Render3DFloor(worldToClip, clipPlane, sub, subsectorDepth, stencilValue, false, fakeFloor);
+				plane.Render3DFloor(worldToClip, clipPlane, sub, floorSubsectorDepth, stencilValue, false, fakeFloor);
 			}
 		}
 
@@ -82,13 +82,13 @@ void RenderPolyPlane::RenderPlanes(const TriMatrix &worldToClip, const PolyClipP
 			double fakeHeight = fakeFloor->bottom.plane->ZatPoint(frontsector->centerspot);
 			if (fakeHeight > viewpoint.Pos.Z && fakeHeight < frontsector->ceilingplane.ZatPoint(frontsector->centerspot))
 			{
-				plane.Render3DFloor(worldToClip, clipPlane, sub, subsectorDepth, stencilValue, true, fakeFloor);
+				plane.Render3DFloor(worldToClip, clipPlane, sub, ceilingSubsectorDepth, stencilValue, true, fakeFloor);
 			}
 		}
 	}
 
-	plane.Render(worldToClip, clipPlane, cull, sub, subsectorDepth, stencilValue, true, skyCeilingHeight, sectorPortals);
-	plane.Render(worldToClip, clipPlane, cull, sub, subsectorDepth, stencilValue, false, skyFloorHeight, sectorPortals);
+	plane.Render(worldToClip, clipPlane, cull, sub, ceilingSubsectorDepth, stencilValue, true, skyCeilingHeight, sectorPortals);
+	plane.Render(worldToClip, clipPlane, cull, sub, floorSubsectorDepth, stencilValue, false, skyFloorHeight, sectorPortals);
 }
 
 void RenderPolyPlane::Render3DFloor(const TriMatrix &worldToClip, const PolyClipPlane &clipPlane, subsector_t *sub, uint32_t subsectorDepth, uint32_t stencilValue, bool ceiling, F3DFloor *fakeFloor)
diff --git a/src/polyrenderer/scene/poly_plane.h b/src/polyrenderer/scene/poly_plane.h
index ce21925945..bb022aea91 100644
--- a/src/polyrenderer/scene/poly_plane.h
+++ b/src/polyrenderer/scene/poly_plane.h
@@ -30,7 +30,7 @@ class PolyCull;
 class RenderPolyPlane
 {
 public:
-	static void RenderPlanes(const TriMatrix &worldToClip, const PolyClipPlane &clipPlane, PolyCull &cull, subsector_t *sub, uint32_t subsectorDepth, uint32_t stencilValue, double skyCeilingHeight, double skyFloorHeight, std::vector<std::unique_ptr<PolyDrawSectorPortal>> &sectorPortals);
+	static void RenderPlanes(const TriMatrix &worldToClip, const PolyClipPlane &clipPlane, PolyCull &cull, subsector_t *sub, uint32_t ceilingSubsectorDepth, uint32_t floorSubsectorDepth, uint32_t stencilValue, double skyCeilingHeight, double skyFloorHeight, std::vector<std::unique_ptr<PolyDrawSectorPortal>> &sectorPortals);
 
 private:
 	struct UVTransform
diff --git a/src/polyrenderer/scene/poly_scene.cpp b/src/polyrenderer/scene/poly_scene.cpp
index ad0d88d34d..2e5b048401 100644
--- a/src/polyrenderer/scene/poly_scene.cpp
+++ b/src/polyrenderer/scene/poly_scene.cpp
@@ -30,7 +30,6 @@
 #include "polyrenderer/poly_renderer.h"
 #include "polyrenderer/scene/poly_light.h"
 
-CVAR(Bool, r_debug_cull, 0, 0)
 EXTERN_CVAR(Int, r_portal_recursions)
 
 /////////////////////////////////////////////////////////////////////////////
@@ -92,19 +91,52 @@ void RenderPolyScene::ClearBuffers()
 
 void RenderPolyScene::RenderSectors()
 {
-	if (r_debug_cull)
+	int count = (int)Cull.PvsSectors.size();
+	auto subsectors = Cull.PvsSectors.data();
+
+	int nextCeilingZChange = 0;
+	int nextFloorZChange = 0;
+	uint32_t ceilingSubsectorDepth = 0;
+	uint32_t floorSubsectorDepth = 0;
+
+	for (int i = 0; i < count; i++)
 	{
-		for (auto it = Cull.PvsSectors.rbegin(); it != Cull.PvsSectors.rend(); ++it)
-			RenderSubsector(*it);
-	}
-	else
-	{
-		for (auto it = Cull.PvsSectors.begin(); it != Cull.PvsSectors.end(); ++it)
-			RenderSubsector(*it);
+		// The software renderer only updates the clipping if the sector height changes.
+		// Find the subsector depths for when that happens.
+		if (i == nextCeilingZChange)
+		{
+			double z = subsectors[i]->sector->ceilingplane.Zat0();
+			nextCeilingZChange++;
+			while (nextCeilingZChange < count)
+			{
+				double nextZ = subsectors[nextCeilingZChange]->sector->ceilingplane.Zat0();
+				if (nextZ > z)
+					break;
+				z = nextZ;
+				nextCeilingZChange++;
+			}
+			ceilingSubsectorDepth = NextSubsectorDepth + nextCeilingZChange - i - 1;
+		}
+		if (i == nextFloorZChange)
+		{
+			double z = subsectors[i]->sector->floorplane.Zat0();
+			nextFloorZChange++;
+			while (nextFloorZChange < count)
+			{
+				double nextZ = subsectors[nextFloorZChange]->sector->floorplane.Zat0();
+				if (nextZ < z)
+					break;
+				z = nextZ;
+				nextFloorZChange++;
+			}
+			floorSubsectorDepth = NextSubsectorDepth + nextFloorZChange - i - 1;
+		}
+
+		RenderSubsector(subsectors[i], ceilingSubsectorDepth, floorSubsectorDepth);
 	}
 }
 
-void RenderPolyScene::RenderSubsector(subsector_t *sub)
+void RenderPolyScene::RenderSubsector(subsector_t *sub, uint32_t ceilingSubsectorDepth, uint32_t floorSubsectorDepth)
 {
 	sector_t *frontsector = sub->sector;
 	frontsector->MoreFlags |= SECF_DRAWN;
@@ -147,7 +179,7 @@ void RenderPolyScene::RenderSubsector(subsector_t *sub)
 
 	if (sub->sector->CenterFloor() != sub->sector->CenterCeiling())
 	{
-		RenderPolyPlane::RenderPlanes(WorldToClip, PortalPlane, Cull, sub, subsectorDepth, StencilValue, Cull.MaxCeilingHeight, Cull.MinFloorHeight, SectorPortals);
+		RenderPolyPlane::RenderPlanes(WorldToClip, PortalPlane, Cull, sub, ceilingSubsectorDepth, floorSubsectorDepth, StencilValue, Cull.MaxCeilingHeight, Cull.MinFloorHeight, SectorPortals);
 	}
 
 	if (mainBSP)
diff --git a/src/polyrenderer/scene/poly_scene.h b/src/polyrenderer/scene/poly_scene.h
index 8dafc949ba..e4a1dff4f7 100644
--- a/src/polyrenderer/scene/poly_scene.h
+++ b/src/polyrenderer/scene/poly_scene.h
@@ -86,7 +86,7 @@ private:
 	void ClearBuffers();
 	void RenderPortals(int portalDepth);
 	void RenderSectors();
-	void RenderSubsector(subsector_t *sub);
+	void RenderSubsector(subsector_t *sub, uint32_t ceilingSubsectorDepth, uint32_t floorSubsectorDepth);
 	void RenderLine(subsector_t *sub, seg_t *line, sector_t *frontsector, uint32_t subsectorDepth);
 	void RenderSprite(AActor *thing, double sortDistance, const DVector2 &left, const DVector2 &right);
 	void RenderSprite(AActor *thing, double sortDistance, DVector2 left, DVector2 right, double t1, double t2, void *node);
diff --git a/src/polyrenderer/scene/poly_wall.cpp b/src/polyrenderer/scene/poly_wall.cpp
index 87ee9ec120..db95ec94a3 100644
--- a/src/polyrenderer/scene/poly_wall.cpp
+++ b/src/polyrenderer/scene/poly_wall.cpp
@@ -117,10 +117,10 @@ bool RenderPolyWall::RenderLine(const TriMatrix &worldToClip, const PolyClipPlan
 
 		double topceilz1 = frontceilz1;
 		double topceilz2 = frontceilz2;
-		double topfloorz1 = MIN(backceilz1, frontceilz1);
-		double topfloorz2 = MIN(backceilz2, frontceilz2);
-		double bottomceilz1 = MAX(frontfloorz1, backfloorz1);
-		double bottomceilz2 = MAX(frontfloorz2, backfloorz2);
+		double topfloorz1 = MAX(MIN(backceilz1, frontceilz1), frontfloorz1);
+		double topfloorz2 = MAX(MIN(backceilz2, frontceilz2), frontfloorz2);
+		double bottomceilz1 = MIN(MAX(frontfloorz1, backfloorz1), frontceilz1);
+		double bottomceilz2 = MIN(MAX(frontfloorz2, backfloorz2), frontceilz2);
 		double bottomfloorz1 = frontfloorz1;
 		double bottomfloorz2 = frontfloorz2;
 		double middleceilz1 = topfloorz1;
@@ -135,7 +135,7 @@ bool RenderPolyWall::RenderLine(const TriMatrix &worldToClip, const PolyClipPlan
 		{
 			wall.SetCoords(line->v1->fPos(), line->v2->fPos(), topceilz1, topfloorz1, topceilz2, topfloorz2);
 			wall.TopTexZ = topTexZ;
-			wall.BottomTexZ = MIN(topfloorz1, topfloorz2);
+			wall.BottomTexZ = MIN(MIN(backceilz1, frontceilz1), MIN(backceilz2, frontceilz2));
 			wall.Texpart = side_t::top;
 			wall.Render(worldToClip, clipPlane, cull);
 		}
@@ -143,7 +143,7 @@ bool RenderPolyWall::RenderLine(const TriMatrix &worldToClip, const PolyClipPlan
 		if ((bottomfloorz1 < bottomceilz1 || bottomfloorz2 < bottomceilz2) && line->sidedef && !bothSkyFloor)
 		{
 			wall.SetCoords(line->v1->fPos(), line->v2->fPos(), bottomceilz1, bottomfloorz1, bottomceilz2, bottomfloorz2);
-			wall.TopTexZ = MAX(bottomceilz1, bottomceilz2);
+			wall.TopTexZ = MAX(MAX(frontfloorz1, backfloorz1), MAX(frontfloorz2, backfloorz2));
 			wall.BottomTexZ = bottomTexZ;
 			wall.UnpeggedCeil1 = topceilz1;
 			wall.UnpeggedCeil2 = topceilz2;
diff --git a/wadsrc/static/compatibility.txt b/wadsrc/static/compatibility.txt
index 8863326e12..b6e57bc1ab 100644
--- a/wadsrc/static/compatibility.txt
+++ b/wadsrc/static/compatibility.txt
@@ -629,6 +629,11 @@ ABC4EB5A1535ECCD0061AD14F3547908 // Plutonia Experiment, map26
 	setsectorspecial 156 0
 }
 
+FF635FB9A2F076566299910F8C78F707 // nerve.wad, level04
+{
+	setsectorspecial 868 0
+}
+
 B68EB7CFB4CC481796E2919B9C16DFBD // Moc11.wad e1m6
 {
 	setvertex 1650 -3072 2671
@@ -731,4 +736,4 @@ CA3773ED313E8899311F3DD0CA195A68 // e3m6
 	setsectortexture 145 floor F_CONCRP
 	setsectorlight 138 192
 	setwalltexture 3431 back top BRKGRY01
-}
\ No newline at end of file
+}
diff --git a/wadsrc/static/iwadinfo.txt b/wadsrc/static/iwadinfo.txt
index 6dc584a586..37f9473172 100644
--- a/wadsrc/static/iwadinfo.txt
+++ b/wadsrc/static/iwadinfo.txt
@@ -329,6 +329,17 @@ IWad
 	BannerColors = "54 54 54", "a8 a8 a8"
 }
 
+IWad
+{
+	Name = "Rise Of The Wool Ball"
+	Autoname = "woolball.rotwb"
+	Game = "Doom"
+	Config = "WoolBall"
+	Mapinfo = "mapinfo/doom2.txt"
+	MustContain = "E3M6", "B3DED", "WORLDMAP", "NUCLSKYM", "PLANETLE", "MEOWZEKI", "ZEKIINTR", "NOWAYBAC"
+	BannerColors = "32 54 43", "c6 dc d1"
+}
+
 IWad
 {
 	Name = "DOOM Shareware"
@@ -377,6 +388,16 @@ IWad
 	Load = "nerve.wad"
 }
 
+IWad
+{
+	Name = "WolfenDoom: Blade of Agony"
+	Game = "Doom"
+	BannerColors = "E30000", "E3E3E3"
+	Mapinfo = "mapinfo/doom2.txt"
+	MustContain = "BOALIB", "C1M1", "Episode3", "MSPUB0"
+	Config = "BoA"
+}
+
 // Doom 2 must be last to be checked becaude MAP01 is its only requirement
 IWad
 {
@@ -432,4 +453,6 @@ Names
 	"hacx2.wad"
 	"square1.pk3"
 	"delaweare.wad"
+	"rotwb.wad"
+	"boa_c2.pk3"
 }
diff --git a/wadsrc/static/menudef.txt b/wadsrc/static/menudef.txt
index 1641982d95..09a4a9fc47 100644
--- a/wadsrc/static/menudef.txt
+++ b/wadsrc/static/menudef.txt
@@ -335,7 +335,7 @@ DefaultOptionMenu
 	}
 }
 
-OptionMenu "OptionsMenu"
+OptionMenu "OptionsMenu" protected
 {
 	Title "$OPTMNU_TITLE"
 	Submenu "$OPTMNU_CONTROLS",			"CustomizeControls"
@@ -422,7 +422,7 @@ ListMenu "PlayerMenu"
 //
 //-------------------------------------------------------------------------------------------
 
-OptionMenu "CustomizeControls"
+OptionMenu "CustomizeControls" protected
 {
 	Title "$CNTRLMNU_TITLE"
 	ScrollTop 2
@@ -536,7 +536,7 @@ OptionString "Cursors"
 	"-", "$OPTSTR_SYSTEMCURSOR"
 }
 
-OptionMenu "MouseOptions"
+OptionMenu "MouseOptions" protected
 {
 	Title "$MOUSEMNU_TITLE"
 	Option "$MOUSEMNU_ENABLEMOUSE",		"use_mouse", "YesNo"
@@ -566,7 +566,7 @@ OptionMenu "MouseOptions"
 //
 //-------------------------------------------------------------------------------------------
 
-OptionMenu "JoystickOptionsDefaults"
+OptionMenu "JoystickOptionsDefaults" protected
 {
 	Title "$JOYMNU_OPTIONS"
 	Option "$JOYMNU_ENABLE", "use_joystick", "YesNo"
@@ -585,7 +585,7 @@ OptionMenu "JoystickOptionsDefaults"
 	// The rest will be filled in by joystick code if devices get connected or disconnected
 }
 
-OptionMenu "JoystickOptions"
+OptionMenu "JoystickOptions" protected
 {
 	Title "$JOYMNU_OPTIONS"
 }
@@ -606,7 +606,7 @@ OptionValue "Inversion"
 	1, "$OPTVAL_INVERTED"
 }
 
-OptionMenu "JoystickConfigMenu"
+OptionMenu "JoystickConfigMenu" protected
 {
 	Title "$JOY_CONFIG"
 	Class "JoystickConfigMenu"
@@ -706,7 +706,7 @@ OptionValue GPUSwitch
 }
 
 
-OptionMenu "TrueColorOptions"
+OptionMenu "TrueColorOptions" protected
 {
 	Title "$TCMNU_TITLE"
 	StaticText " "
@@ -716,7 +716,7 @@ OptionMenu "TrueColorOptions"
 	Option "$TCMNU_MIPMAP",					"r_mipmap", "OnOff"
 }
 
-OptionMenu "SWROptions"
+OptionMenu "SWROptions" protected
 {
 	Title "$DSPLYMNU_SWOPT"
 	Submenu "$DSPLYMNU_TCOPT",			"TrueColorOptions"
@@ -727,7 +727,7 @@ OptionMenu "SWROptions"
 	Option "$DSPLYMNU_GZDFULLBRIGHT",			"r_fullbrightignoresectorcolor", "OnOff"
 }
 
-OptionMenu "VideoOptions"
+OptionMenu "VideoOptions" protected
 {
 	Title "$DSPLYMNU_TITLE"
 	
@@ -840,7 +840,7 @@ OptionValue ZDoomStrife
 	1.0, "$OPTVAL_STRIFE"
 }
 
-OptionMenu "HUDOptions"
+OptionMenu "HUDOptions" protected
 {
 	Title "$HUDMNU_TITLE"
 	Submenu "$HUDMNU_SCALEOPT",				"ScalingOptions"
@@ -864,7 +864,7 @@ OptionMenu "HUDOptions"
 	Option "$HUDMNU_HAZARDFLASHES",			"pf_hazard", "ZDoomStrife"
 }
 
-OptionMenu "ScalingOptions"
+OptionMenu "ScalingOptions" protected
 {
 	Title "$SCALEMNU_TITLE"
 	ScaleSlider "$HUDMNU_UISCALE",				"uiscale", 0.0, 8.0, 1.0, "$SCALEMNU_ADAPT"
@@ -925,7 +925,7 @@ OptionValue "AltHUDAmmoOrder"
 	1, "$OPTVAL_AMMOTEXTIMAGE"
 }
 
-OptionMenu "AltHUDOptions"
+OptionMenu "AltHUDOptions" protected
 {
 	Title "$ALTHUDMNU_TITLE"
 	//Indent 220
@@ -981,7 +981,7 @@ OptionValue dehopt
 	2,	"$OPTVAL_ONLYLASTONE"
 }
 
-OptionMenu "MiscOptions"
+OptionMenu "MiscOptions" protected
 {
 	Title "$MISCMNU_TITLE"
 	//Indent 220
@@ -1072,7 +1072,7 @@ OptionValue MapTriggers
 	2, "$OPTVAL_ON"
 }
 
-OptionMenu AutomapOptions
+OptionMenu AutomapOptions protected
 {
 	Title "$AUTOMAPMNU_TITLE"
 	Option "$AUTOMAPMNU_COLORSET",				"am_colorset", "MapColorTypes"
@@ -1105,7 +1105,7 @@ OptionMenu AutomapOptions
 //
 //-------------------------------------------------------------------------------------------
 
-OptionMenu MapControlsMenu
+OptionMenu MapControlsMenu protected
 {
 	Title "$MAPCNTRLMNU_TITLE"
 	ScrollTop 2
@@ -1132,7 +1132,7 @@ OptionMenu MapControlsMenu
 //
 //-------------------------------------------------------------------------------------------
 
-OptionMenu MapColorMenu
+OptionMenu MapColorMenu protected
 {
 	Title "$MAPCOLORMNU_TITLE"
 	SafeCommand "$MAPCOLORMNU_DEFAULTMAPCOLORS", 		"am_restorecolors"
@@ -1194,7 +1194,7 @@ OptionMenu MapColorMenu
 //
 //-------------------------------------------------------------------------------------------
 
-OptionMenu ColorPickerMenu
+OptionMenu ColorPickerMenu protected
 {
 	Title "$MNU_COLORPICKER"
 	// This menu will be created by the calling code
@@ -1223,7 +1223,7 @@ OptionValue DevMessageLevels
 	4, "$OPTVAL_EVERYTHING"
 }
 
-OptionMenu MessageOptions
+OptionMenu MessageOptions protected
 {
 	Title 	"$MSGMNU_TITLE"
 	Option "$MSGMNU_SHOWMESSAGES",				"show_messages", "OnOff"
@@ -1252,7 +1252,7 @@ OptionMenu MessageOptions
 //
 //-------------------------------------------------------------------------------------------
 
-OptionMenu ScoreboardOptions
+OptionMenu ScoreboardOptions protected
 {
 	Title "$SCRBRDMNU_TITLE"
 	StaticText "$SCRBRDMNU_COOPERATIVE", 1
@@ -1307,7 +1307,7 @@ OptionValue  JumpCrouchFreeLook
 }
 
 
-OptionMenu GameplayOptions
+OptionMenu GameplayOptions protected
 {
 	Title 	"$GMPLYMNU_TITLE"
 	//Indent 222
@@ -1392,7 +1392,7 @@ OptionValue CompatModes
 	4, "$OPTVAL_ZDOOM2063"
 }
 
-OptionMenu "CompatibilityOptions"
+OptionMenu "CompatibilityOptions" protected
 {
 	Title "$CMPTMNU_TITLE"
 	Option "$CMPTMNU_MODE",							"compatmode", "CompatModes", "", 1
@@ -1583,7 +1583,7 @@ OptionString SoundBackendsOpenALOnly
 	"null",		"$OPTSTR_NOSOUND"
 }
 
-OptionMenu OpenALSoundItems
+OptionMenu OpenALSoundItems protected
 {
 	Title "$OPENALMNU_TITLE"
 	Option "$OPENALMNU_PLAYBACKDEVICE",		"snd_aldevice",		"ALDevices"
@@ -1597,7 +1597,7 @@ OptionValue MidiDevices
 	// filled in by the sound code
 }
 
-OptionMenu SoundOptions
+OptionMenu SoundOptions protected
 {
 	Title "$SNDMNU_TITLE"
 	Slider "$SNDMNU_SFXVOLUME",			"snd_sfxvolume", 0, 1, 0.05, 2
@@ -1646,7 +1646,7 @@ OptionValue OplCores
 	3, "$OPTVAL_NUKEDOPL3"
 }
 
-OptionMenu AdvSoundOptions
+OptionMenu AdvSoundOptions protected
 {
 	Title "$ADVSNDMNU_TITLE"
 	Option "$ADVSNDMNU_SAMPLERATE",			"snd_samplerate", "SampleRates"
@@ -1688,22 +1688,22 @@ OptionMenu AdvSoundOptions
 	Option "$ADVSNDMNU_REVERB", 			"wildmidi_reverb", "OnOff"
 }
 
-OptionMenu GusConfigMenu
+OptionMenu GusConfigMenu protected
 {
 	Title "$ADVSNDMNU_SELCONFIG"
 }
 
-OptionMenu WildMidiConfigMenu
+OptionMenu WildMidiConfigMenu protected
 {
 	Title "$ADVSNDMNU_SELCONFIG"
 }
 
-OptionMenu TimidityExeMenu
+OptionMenu TimidityExeMenu protected
 {
 	Title "$ADVSNDMNU_SELCONFIG"
 }
 
-OptionMenu FluidPatchsetMenu
+OptionMenu FluidPatchsetMenu protected
 {
 	Title "$ADVSNDMNU_SELCONFIG"
 }
@@ -1742,7 +1742,7 @@ OptionValue ModVolumeRamps
 }
 
 
-OptionMenu ModReplayerOptions
+OptionMenu ModReplayerOptions protected
 {
 	Title "$MODMNU_TITLE"
 	Slider "$MODMNU_MASTERVOLUME",			"mod_dumb_mastervolume", 1, 16, 0.5, 1
@@ -1792,7 +1792,7 @@ OptionValue "GLCOCOA"
 	1, "$OPTVAL_GL"
 }
 
-OptionMenu RendererMenu
+OptionMenu RendererMenu protected
 {
 	Title "$RNDMNU_TITLE"
 	Option "$RNDMNU_RENDERER",	"vid_renderer", "PolyDoom"
@@ -1848,7 +1848,7 @@ OptionValue RatiosTFT
 	 -1, "$OPTVAL_ALL"
 }
 
-OptionMenu VideoModeMenu
+OptionMenu VideoModeMenu protected
 {
 	Title "$VIDMNU_TITLE"
 	
@@ -1883,7 +1883,7 @@ OptionMenu VideoModeMenu
  *
  *=======================================*/
 
-OptionMenu NetworkOptions
+OptionMenu NetworkOptions protected
 {
 	Title "$NETMNU_TITLE"
 	StaticText "$NETMNU_LOCALOPTIONS", 1
@@ -2109,7 +2109,7 @@ OptionValue VRMode
 	10, "$OPTVAL_OPENVR"
 }
 
-OptionMenu "GLTextureGLOptions"
+OptionMenu "GLTextureGLOptions" protected
 {
 	Title "$GLTEXMNU_TITLE"
 	Option "$GLTEXMNU_TEXENABLED",		gl_texture,						"YesNo"
@@ -2126,7 +2126,7 @@ OptionMenu "GLTextureGLOptions"
 	Option "$GLTEXMNU_SORTDRAWLIST", 	gl_sort_textures,				"YesNo"
 }
 
-OptionMenu "GLLightOptions"
+OptionMenu "GLLightOptions" protected
 {
 	Title "$GLLIGHTMNU_TITLE"
 	Option "$TCMNU_DYNLIGHTS",				"r_dynlights", "OnOff"
@@ -2138,7 +2138,7 @@ OptionMenu "GLLightOptions"
 	Option "$GLLIGHTMNU_LIGHTSHADOWMAP",	gl_light_shadowmap,				"YesNo"
 }
 
-OptionMenu "OpenGLOptions"
+OptionMenu "OpenGLOptions" protected
 {
 	Title "$GLMNU_TITLE"
 	Submenu "$GLMNU_TEXOPT", 			"GLTextureGLOptions"
diff --git a/wadsrc/static/zscript/shared/movingcamera.txt b/wadsrc/static/zscript/shared/movingcamera.txt
index c6af13b42f..6ed206d4b1 100644
--- a/wadsrc/static/zscript/shared/movingcamera.txt
+++ b/wadsrc/static/zscript/shared/movingcamera.txt
@@ -45,6 +45,8 @@ class InterpolationPoint : Actor
 {
 	
 	InterpolationPoint	Next;
+
+	bool bVisited;
 	
 	default
 	{
@@ -66,9 +68,9 @@ class InterpolationPoint : Actor
 		
 		while (me != null)
 		{
-			if (me.bAmbush)	return;
+			if (me.bVisited) return;
 
-			me.bAmbush = true;
+			me.bVisited = true;
 
 			let iterator = ActorIterator.Create(me.args[3] + 256 * me.args[4], "InterpolationPoint");
 			me.Next = InterpolationPoint(iterator.Next ());