Sprites flickering across ledges or lifts
From DoomWiki.org
A thing resting on a ledge of a high sector, its center point hanging in the air, may snap down onto the lower sector and it may then snap back onto the upper sector, sometimes repeatedly. On source ports with higher framerates and movement interpolation, this can look like very quick movement up or down instead of an instantaneous blink. This can happen frequently with monster corpses on the edge of a lift.
This phenomenon is a combination of two separate bugs:
- When a thing is being updated by a moving sector, and it overlaps a blocking thing (like a player or monster), the thing being updated is snapped down to the floor at its center point.
- When a sector is in motion, such as a lift, it updates things in the vicinity to snap to the highest floor of sectors they intersect. The idea is that the things move with the lift, but there are more consequences due to implementation oddities.
Altogether, these two glitches make it possible for things to snap down and up repeatedly. As a common example, if there is a corpse that has its center point hanging off a ledge while a nearby sector is in motion, monsters or the player walking around on the corpse cause a flickering up and down effect for the corpse.
This bug is exploitable to ignore the walls of ledges, such as the collision of a stair; if a player stands with their midpoint off the edge of a stair, and a pain elemental shoots a lost soul onto the player, that lost soul will briefly be a blocking object that overlaps the player. If then a sector moves nearby, the player will snap down to the floor at their center point. This is useful for making the player's center point touch a secret sector that would otherwise be blocked by a ledge's collision, such as Doom II's MAP15: Industrial Zone and The Plutonia Experiment's MAP26: Bunker.
Technical explanation[edit]
The relevant thing movement happens in P_ThingHeightClip, which is called on each thing in the vicinity of moving sectors. In here, a call to P_CheckPosition is made, which finds a floor height that the thing should rest on. When that function finishes, the resulting floor height is set for the thing.
P_ThingHeightClip, p_map.c:
P_CheckPosition (thing, thing->x, thing->y); ... thing->floorz = tmfloorz;
The way that P_CheckPosition finds a floor is that it initially finds the floor under the thing's center point.
P_CheckPosition, p_map.c:
newsubsec = R_PointInSubsector (x,y); ... tmfloorz = newsubsec->sector->floorheight;
Then linedefs in the vicinity are checked. If a line belonging to a ledge is found, such as that of the lift, and if that ledge's has a lower floor than is currently in the tmfloorz variable, the variable is overwritten. Ultimately, the variable will hold the highest floor height.
The problem is that P_CheckPosition is a function that not only finds this floor height variable, but returns true/false for whether or not this thing is overlapping some other blocking thing. This is useless information for the current purpose gets discarded anyway inside P_ThingHeightClip by not using the return value. By returning this value as soon as something blocking is found, this function ends prematurely and avoids checking any linedefs.
...
for (bx = xl; bx <= xh; bx++) {
for (by = yl; by <= yh; by++) {
if (!P_BlockThingsIterator(bx, by, PIT_CheckThing)) {
return false;
}
}
}
...
for (bx = xl; bx <= xh; bx++) {
for (by = yl; by <= yh; by++) {
if (!P_BlockLinesIterator(bx, by, PIT_CheckLine)) {
return false;
}
}
}
...
Overlapped things are checked first, and if an overlapped thing is solid, this leads to an early return, skipping any linedef checking. Once an overlapping solid thing has caused the linedefs to never be checked, the initially set tmfloorz (that has the height of the floor under the thing's center point) is used, snapping the thing down if it was resting on a ledge with its center point in the air.
Because the player is one such solid thing that causes an early return, the player can, while a nearby sector is moving, move in and out of a thing's bounding box if it is hanging with its center point off a ledge and observe that it snaps down and up. Additionally, if the player gets themselves stuck in a blocking thing, the player can get snapped down, which is exploitable for touching a secret sector that would otherwise be obstructed by collision with a ledge.
