diff --git a/doc/specs/udmf_srb2.txt b/doc/specs/udmf_srb2.txt
index b25ed1af38ee55b6c2ecf568c824520d800c58e7..c99ef4ced7454d5a90900d9bb037cf0a7b42288e 100644
--- a/doc/specs/udmf_srb2.txt
+++ b/doc/specs/udmf_srb2.txt
@@ -1,5 +1,5 @@
 ===============================================================================
-Universal Doom Map Format Sonic Robo Blast 2 extensions v1.0 19.02.2024
+Universal Doom Map Format Sonic Robo Blast 2 extensions v1.1 03.05.2024
 
     Copyright (c) 2024 Sonic Team Junior
        uses Universal Doom Map Format Specification v1.1 as a template,
@@ -80,6 +80,7 @@ Sonic Robo Blast 2 defines the following standardized fields:
       noskew        = <bool>;     // Middle texture is not skewed.
       midpeg        = <bool>;     // Middle texture is pegged.
       midsolid      = <bool>;     // Middle texture is solid.
+      clipmidtex    = <bool>;     // Line's mid textures are clipped to floor and ceiling.
       wrapmidtex    = <bool>;     // Line's mid textures are wrapped.
       nonet         = <bool>;     // Special only takes effect in singleplayer games.
       netonly       = <bool>;     // Special only takes effect in multiplayer games.
@@ -143,6 +144,8 @@ Sonic Robo Blast 2 defines the following standardized fields:
       offsetx_bottom = <float>;        // X offset for lower texture. Default = 0.0.
       offsety_bottom = <float>;        // Y offset for lower texture. Default = 0.0.
 
+      clipmidtex = <bool>;             // Side's mid textures are clipped to floor and ceiling.
+
       comment = <string>; // A comment. Implementors should attach no special
                           // semantic meaning to this field.
    }
@@ -305,6 +308,9 @@ Sonic Robo Blast 2 defines the following standardized fields:
 Changelog
 =======================================
 
+1.1: 03.05.2024
+Added clipmidtex property to lines and sides.
+
 1.0: 19.02.2024
 Initial version.
 
diff --git a/src/deh_tables.c b/src/deh_tables.c
index c7c7c604068cd4761ea3944a92cd4efecd9a0ddb..0109c98793afa43f893bcdeec23388a667e5125e 100644
--- a/src/deh_tables.c
+++ b/src/deh_tables.c
@@ -4540,6 +4540,7 @@ const char *const ML_LIST[] = {
 	"EFFECT6",
 	"BOUNCY",
 	"TFERLINE",
+	"CLIPMIDTEX",
 	NULL
 };
 
diff --git a/src/doomdata.h b/src/doomdata.h
index 276e03297b6f0d453ad0d0c65fc8bd9196d9680c..45b9ec1b36fc0fc0beb0869af1a29fe587acda31 100644
--- a/src/doomdata.h
+++ b/src/doomdata.h
@@ -103,48 +103,53 @@ typedef struct
 // LineDef attributes.
 //
 
-// Solid, is an obstacle.
-#define ML_IMPASSIBLE           1
+enum
+{
+	// Solid, is an obstacle.
+	ML_IMPASSIBLE    = 1<<0,
 
-// Blocks monsters only.
-#define ML_BLOCKMONSTERS        2
+	// Blocks monsters only.
+	ML_BLOCKMONSTERS = 1<<1,
 
-// Backside will not be present at all if not two sided.
-#define ML_TWOSIDED             4
+	// Backside will not be present at all if not two sided.
+	ML_TWOSIDED      = 1<<2,
 
-// If a texture is pegged, the texture will have
-// the end exposed to air held constant at the
-// top or bottom of the texture (stairs or pulled
-// down things) and will move with a height change
-// of one of the neighbor sectors.
-// Unpegged textures allways have the first row of
-// the texture at the top pixel of the line for both
-// top and bottom textures (use next to windows).
+	// If a texture is pegged, the texture will have
+	// the end exposed to air held constant at the
+	// top or bottom of the texture (stairs or pulled
+	// down things) and will move with a height change
+	// of one of the neighbor sectors.
+	// Unpegged textures allways have the first row of
+	// the texture at the top pixel of the line for both
+	// top and bottom textures (use next to windows).
 
-// upper texture unpegged
-#define ML_DONTPEGTOP           8
+	// upper texture unpegged
+	ML_DONTPEGTOP    = 1<<3,
 
-// lower texture unpegged
-#define ML_DONTPEGBOTTOM       16
+	// lower texture unpegged
+	ML_DONTPEGBOTTOM = 1<<4,
 
-#define ML_SKEWTD              32
+	ML_SKEWTD        = 1<<5,
 
-// Don't let Knuckles climb on this line
-#define ML_NOCLIMB             64
+	// Don't let Knuckles climb on this line
+	ML_NOCLIMB       = 1<<6,
 
-#define ML_NOSKEW             128
-#define ML_MIDPEG             256
-#define ML_MIDSOLID           512
-#define ML_WRAPMIDTEX        1024
+	ML_NOSKEW        = 1<<7,
+	ML_MIDPEG        = 1<<8,
+	ML_MIDSOLID      = 1<<9,
+	ML_WRAPMIDTEX    = 1<<10,
 
-#define ML_NETONLY           2048 // Apply effect only in netgames
-#define ML_NONET             4096 // Apply  effect only in single player games
-#define ML_EFFECT6           8192
+	ML_NETONLY       = 1<<11, // Apply effect only in netgames
+	ML_NONET         = 1<<12, // Apply effect only in single player games
+	ML_EFFECT6       = 1<<13,
 
-// Bounce off walls!
-#define ML_BOUNCY           16384
+	// Bounce off walls!
+	ML_BOUNCY        = 1<<14,
 
-#define ML_TFERLINE         32768
+	ML_TFERLINE      = 1<<15,
+
+	ML_CLIPMIDTEX    = 1<<16
+};
 
 // Sector definition, from editing.
 typedef struct
diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c
index f533082f76e6db8af21e90c6dcf4f98949f7f532..5a08d83474f303fce326eed908b275a2230b04e6 100644
--- a/src/hardware/hw_main.c
+++ b/src/hardware/hw_main.c
@@ -1023,7 +1023,7 @@ static void HWR_RenderMidtexture(INT32 gl_midtexture, float cliplow, float cliph
 	}
 
 	// The cut-off values of a linedef can always be constant, since every line has an absoulute front and or back sector
-	if (gl_curline->polyseg)
+	if (gl_curline->polyseg && ((gl_linedef->flags & ML_CLIPMIDTEX) || (gl_sidedef->flags & SIDEFLAG_CLIP_MIDTEX)) == 0)
 	{
 		lowcut = polybottom;
 		highcut = polytop;
diff --git a/src/lua_maplib.c b/src/lua_maplib.c
index 6b489f22b1939941c0ebf83a5a3ae0b7e215de5a..aa29e1b7589379ee4621779706e3c500eee1e3d9 100644
--- a/src/lua_maplib.c
+++ b/src/lua_maplib.c
@@ -213,6 +213,7 @@ enum side_e {
 	side_sector,
 	side_special,
 	side_repeatcnt,
+	side_clipmidtex,
 	side_text
 };
 
@@ -241,6 +242,7 @@ static const char *const side_opt[] = {
 	"sector",
 	"special",
 	"repeatcnt",
+	"clipmidtex",
 	"text",
 	NULL};
 
@@ -1311,6 +1313,9 @@ static int side_get(lua_State *L)
 	case side_repeatcnt:
 		lua_pushinteger(L, side->repeatcnt);
 		return 1;
+	case side_clipmidtex:
+		lua_pushinteger(L, side->flags & SIDEFLAG_CLIP_MIDTEX);
+		return 1;
 	// TODO: 2.3: Delete
 	case side_text:
 		{
@@ -1413,6 +1418,12 @@ static int side_set(lua_State *L)
 	case side_repeatcnt:
 		side->repeatcnt = luaL_checkinteger(L, 3);
 		break;
+	case side_clipmidtex:
+		if (luaL_checkboolean(L, 3))
+			side->flags |= SIDEFLAG_CLIP_MIDTEX;
+		else
+			side->flags &= ~SIDEFLAG_CLIP_MIDTEX;
+		break;
 	}
 	return 0;
 }
diff --git a/src/p_saveg.c b/src/p_saveg.c
index 5e4d6d0760441e6bc94c6815824b8b7e1ab38c80..d3fd05c731b0d43a3aa32c5e7846790105668288 100644
--- a/src/p_saveg.c
+++ b/src/p_saveg.c
@@ -1393,6 +1393,8 @@ static UINT32 GetSideDiff(const side_t *si, const side_t *spawnsi)
 		diff |= LD_SDBOTSCALEY;
 	if (si->repeatcnt != spawnsi->repeatcnt)
 		diff |= LD_SDREPEATCNT;
+	if (si->flags != spawnsi->flags)
+		diff |= LD_SDFLAGS;
 	return diff;
 }
 
@@ -1436,6 +1438,8 @@ static void ArchiveSide(const side_t *si, UINT32 diff)
 		WRITEFIXED(save_p, si->scaley_bottom);
 	if (diff & LD_SDREPEATCNT)
 		WRITEINT16(save_p, si->repeatcnt);
+	if (diff & LD_SDFLAGS)
+		WRITEUINT16(save_p, si->flags);
 }
 
 static void ArchiveLines(void)
@@ -1493,7 +1497,7 @@ static void ArchiveLines(void)
 			if (diff & LD_DIFF2)
 				WRITEUINT8(save_p, diff2);
 			if (diff & LD_FLAG)
-				WRITEINT16(save_p, li->flags);
+				WRITEUINT32(save_p, li->flags);
 			if (diff & LD_SPECIAL)
 				WRITEINT16(save_p, li->special);
 			if (diff & LD_CLLCOUNT)
@@ -1576,6 +1580,8 @@ static void UnArchiveSide(side_t *si)
 		si->scaley_bottom = READFIXED(save_p);
 	if (diff & LD_SDREPEATCNT)
 		si->repeatcnt = READINT16(save_p);
+	if (diff & LD_SDFLAGS)
+		si->flags = READUINT16(save_p);
 }
 
 static void UnArchiveLines(void)
@@ -1601,7 +1607,7 @@ static void UnArchiveLines(void)
 		li = &lines[i];
 
 		if (diff & LD_FLAG)
-			li->flags = READINT16(save_p);
+			li->flags = READUINT32(save_p);
 		if (diff & LD_SPECIAL)
 			li->special = READINT16(save_p);
 		if (diff & LD_CLLCOUNT)
diff --git a/src/p_setup.c b/src/p_setup.c
index 41487d702f265c7e5164c61be8d0b8f8c059aa38..4ec77f364605b77e0a7b0cd27e413bde4faadeb8 100644
--- a/src/p_setup.c
+++ b/src/p_setup.c
@@ -1363,6 +1363,8 @@ static void P_LoadSidedefs(UINT8 *data)
 		sd->scalex_top = sd->scalex_mid = sd->scalex_bottom = FRACUNIT;
 		sd->scaley_top = sd->scaley_mid = sd->scaley_bottom = FRACUNIT;
 
+		sd->flags = 0;
+
 		P_SetSidedefSector(i, (UINT16)SHORT(msd->sector));
 
 		// Special info stored in texture fields!
@@ -1943,6 +1945,8 @@ static void ParseTextmapSidedefParameter(UINT32 i, const char *param, const char
 		P_SetSidedefSector(i, atol(val));
 	else if (fastcmp(param, "repeatcnt"))
 		sides[i].repeatcnt = atol(val);
+	else if (fastcmp(param, "clipmidtex") && fastcmp("true", val))
+		sides[i].flags |= SIDEFLAG_CLIP_MIDTEX;
 }
 
 static void ParseTextmapLinedefParameter(UINT32 i, const char *param, const char *val)
@@ -2025,10 +2029,10 @@ static void ParseTextmapLinedefParameter(UINT32 i, const char *param, const char
 		lines[i].flags |= ML_MIDPEG;
 	else if (fastcmp(param, "midsolid") && fastcmp("true", val))
 		lines[i].flags |= ML_MIDSOLID;
+	else if (fastcmp(param, "clipmidtex") && fastcmp("true", val))
+		lines[i].flags |= ML_CLIPMIDTEX;
 	else if (fastcmp(param, "wrapmidtex") && fastcmp("true", val))
 		lines[i].flags |= ML_WRAPMIDTEX;
-	/*else if (fastcmp(param, "effect6") && fastcmp("true", val))
-		lines[i].flags |= ML_EFFECT6;*/
 	else if (fastcmp(param, "nonet") && fastcmp("true", val))
 		lines[i].flags |= ML_NONET;
 	else if (fastcmp(param, "netonly") && fastcmp("true", val))
@@ -2615,6 +2619,8 @@ static void P_WriteTextmap(void)
 			fprintf(f, "midpeg = true;\n");
 		if (wlines[i].flags & ML_MIDSOLID)
 			fprintf(f, "midsolid = true;\n");
+		if (wlines[i].flags & ML_CLIPMIDTEX)
+			fprintf(f, "clipmidtex = true;\n");
 		if (wlines[i].flags & ML_WRAPMIDTEX)
 			fprintf(f, "wrapmidtex = true;\n");
 		if (wlines[i].flags & ML_NONET)
@@ -2670,6 +2676,8 @@ static void P_WriteTextmap(void)
 			fprintf(f, "texturemiddle = \"%.*s\";\n", 8, textures[wsides[i].midtexture]->name);
 		if (wsides[i].repeatcnt != 0)
 			fprintf(f, "repeatcnt = %d;\n", wsides[i].repeatcnt);
+		if (wsides[i].flags & SIDEFLAG_CLIP_MIDTEX)
+			fprintf(f, "clipmidtex = true;\n");
 		fprintf(f, "}\n");
 		fprintf(f, "\n");
 	}
@@ -3072,6 +3080,7 @@ static void P_LoadTextmap(void)
 		sd->bottomtexture = R_TextureNumForName("-");
 		sd->sector = NULL;
 		sd->repeatcnt = 0;
+		sd->flags = 0;
 
 		TextmapParse(sidesPos[i], i, ParseTextmapSidedefParameter);
 
diff --git a/src/r_defs.h b/src/r_defs.h
index da4dd2d70e6049479eacd24c51af11b4a995507b..38f67e8e91131dd160e5d4ab649439de259612fc 100644
--- a/src/r_defs.h
+++ b/src/r_defs.h
@@ -584,7 +584,7 @@ typedef struct line_s
 	angle_t angle; // Precalculated angle between dx and dy
 
 	// Animation related.
-	INT16 flags;
+	UINT32 flags;
 	INT16 special;
 	taglist_t tags;
 	INT32 args[NUMLINEARGS];
@@ -614,6 +614,12 @@ typedef struct line_s
 	UINT32 secportal; // transferred sector portal
 } line_t;
 
+// Don't make available to Lua or I will find where you live
+typedef enum
+{
+	SIDEFLAG_CLIP_MIDTEX     = 1 << 0, // Like the line counterpart, but only for this side.
+} sideflags_t;
+
 typedef struct
 {
 	// add this to the calculated texture column
@@ -622,13 +628,16 @@ typedef struct
 	// add this to the calculated texture top
 	fixed_t rowoffset;
 
-	// per-texture offsets for UDMF
+	// per-texture offsets
 	fixed_t offsetx_top, offsetx_mid, offsetx_bottom;
 	fixed_t offsety_top, offsety_mid, offsety_bottom;
 
 	fixed_t scalex_top, scalex_mid, scalex_bottom;
 	fixed_t scaley_top, scaley_mid, scaley_bottom;
 
+	// Rendering-related flags
+	UINT16 flags;
+
 	// Texture indices.
 	// We do not maintain names here.
 	INT32 toptexture, bottomtexture, midtexture;
diff --git a/src/r_segs.c b/src/r_segs.c
index e07ced86a7c6002c7bd127c1e2867bdae5386419..14a4453299617a691f758f8a4e45eab685d8ee9f 100644
--- a/src/r_segs.c
+++ b/src/r_segs.c
@@ -114,6 +114,8 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2)
 	fixed_t wall_scaley;
 	fixed_t scalestep;
 	fixed_t scale1;
+	fixed_t texture_top, texture_bottom, texture_height;
+	boolean clipmidtex;
 
 	// Calculate light table.
 	// Use different light tables
@@ -276,6 +278,9 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2)
 	else
 		back = backsector;
 
+	clipmidtex = (ldef->flags & ML_CLIPMIDTEX) || (sidedef->flags & SIDEFLAG_CLIP_MIDTEX);
+	texture_height = FixedDiv(textureheight[texnum], wall_scaley);
+
 	if (sidedef->repeatcnt)
 		repeats = 1 + sidedef->repeatcnt;
 	else if (ldef->flags & ML_WRAPMIDTEX)
@@ -292,8 +297,8 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2)
 		else
 			low = back->floorheight;
 
-		repeats = (high - low)/textureheight[texnum];
-		if ((high-low)%textureheight[texnum])
+		repeats = (high - low)/texture_height;
+		if ((high-low)%texture_height)
 			repeats++; // tile an extra time to fill the gap -- Monster Iestyn
 	}
 	else
@@ -301,6 +306,33 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2)
 
 	for (times = 0; times < repeats; times++)
 	{
+		fixed_t left_top = 0, left_bottom = 0;
+		fixed_t right_top = 0, right_bottom = 0;
+		fixed_t top_step = 0, bottom_step = 0;
+
+		// Get left and right ends of wall for clipping it
+		if (clipmidtex)
+		{
+			// For this to work correctly with polyobjects, it needs to use its own back sector, rather than the seg's front sector
+			sector_t *sec_front = curline->polyseg ?
+				curline->polyseg->lines[0]->backsector :
+				frontsector;
+
+			// calculate both left ends
+			left_top    = P_GetSectorCeilingZAt(sec_front, ds->leftpos.x, ds->leftpos.y) - viewz;
+			left_bottom = P_GetSectorFloorZAt(sec_front, ds->leftpos.x, ds->leftpos.y) - viewz;
+
+			// calculate right ends now
+			right_top    = P_GetSectorCeilingZAt(sec_front, ds->rightpos.x, ds->rightpos.y) - viewz;
+			right_bottom = P_GetSectorFloorZAt(sec_front, ds->rightpos.x, ds->rightpos.y) - viewz;
+
+			top_step = (right_top - left_top) / range;
+			bottom_step = (right_bottom - left_bottom) / range;
+
+			left_top += top_step * (x1 - ds->x1);
+			left_bottom += bottom_step * (x1 - ds->x1);
+		}
+
 		if (times > 0)
 		{
 			rw_scalestep = scalestep;
@@ -321,7 +353,7 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2)
 		{
 			dc_texturemid = ds->maskedtextureheight[dc_x];
 
-			if (curline->linedef->flags & ML_MIDPEG)
+			if (ldef->flags & ML_MIDPEG)
 				dc_texturemid += (textureheight[texnum])*times + textureheight[texnum];
 			else
 				dc_texturemid -= (textureheight[texnum])*times;
@@ -341,29 +373,53 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2)
 					}
 				}
 				spryscale += rw_scalestep;
+				if (clipmidtex)
+				{
+					left_top += top_step;
+					left_bottom += bottom_step;
+				}
 				continue;
 			}
 
-			// calculate lighting
-			if (dc_numlights)
+			texture_top = dc_texturemid;
+			texture_bottom = texture_top - texture_height;
+
+			// If the texture is meant to be clipped
+			if (clipmidtex)
 			{
-				lighttable_t **xwalllights;
+				texture_top = min(FixedMul(left_top, wall_scaley), texture_top);
+				texture_bottom = max(FixedMul(left_bottom, wall_scaley), texture_bottom);
 
-				sprtopscreen = windowtop = (centeryfrac - FixedMul(dc_texturemid, spryscale));
+				left_top += top_step;
+				left_bottom += bottom_step;
+			}
 
-				realbot = FixedMul(textureheight[texnum], spryscale) + sprtopscreen;
-				dc_iscale = FixedMul(ds->invscale[dc_x], wall_scaley);
+			// NB: sprtopscreen needs to start where dc_texturemid does, so that R_DrawMaskedColumn works correctly.
+			// windowtop however is set so that the column gets clipped properly.
+			sprtopscreen = centeryfrac - FixedMul(dc_texturemid, spryscale);
+			realbot = centeryfrac - FixedMul(texture_bottom, spryscale);
 
+			// set wall bounds if necessary
+			if (dc_numlights || clipmidtex)
+			{
+				windowtop = centeryfrac - FixedMul(texture_top, spryscale);
 				windowbottom = realbot;
+			}
 
-				// draw the texture
-				col = R_GetColumn(texnum, maskedtexturecol[dc_x] >> FRACBITS);
+			dc_iscale = FixedMul(ds->invscale[dc_x], wall_scaley);
+
+			col = R_GetColumn(texnum, maskedtexturecol[dc_x] >> FRACBITS);
 
+			// draw light list if there is one
+			if (dc_numlights)
+			{
 				for (i = 0; i < dc_numlights; i++)
 				{
+					lighttable_t **xwalllights;
+
 					rlight = &dc_lightlist[i];
 
-					if ((rlight->flags & FOF_NOSHADE))
+					if (rlight->flags & FOF_NOSHADE)
 						continue;
 
 					if (rlight->lightnum < 0)
@@ -373,7 +429,7 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2)
 					else
 						xwalllights = scalelight[rlight->lightnum];
 
-					pindex = FixedMul(spryscale, LIGHTRESOLUTIONFIX)>>LIGHTSCALESHIFT;
+					pindex = FixedMul(FixedMul(spryscale, wall_scaley), LIGHTRESOLUTIONFIX)>>LIGHTSCALESHIFT;
 
 					if (pindex >= MAXLIGHTSCALE)
 						pindex = MAXLIGHTSCALE - 1;
@@ -418,7 +474,7 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2)
 			}
 
 			// calculate lighting
-			pindex = FixedMul(spryscale, LIGHTRESOLUTIONFIX)>>LIGHTSCALESHIFT;
+			pindex = FixedMul(FixedMul(spryscale, wall_scaley), LIGHTRESOLUTIONFIX)>>LIGHTSCALESHIFT;
 
 			if (pindex >= MAXLIGHTSCALE)
 				pindex = MAXLIGHTSCALE - 1;
@@ -428,11 +484,7 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2)
 			if (frontsector->extra_colormap)
 				dc_colormap = frontsector->extra_colormap->colormap + (dc_colormap - colormaps);
 
-			sprtopscreen = centeryfrac - FixedMul(dc_texturemid, spryscale);
-			dc_iscale = FixedMul(ds->invscale[dc_x], wall_scaley);
-
 			// draw the texture
-			col = R_GetColumn(texnum, maskedtexturecol[dc_x] >> FRACBITS);
 			colfunc_2s(col, lengthcol);
 
 			spryscale += rw_scalestep;