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 happens due to a combination of two mechanics:

  • When a sector is in motion, such as a lift, things in the vicinity are in a state of unrest because they expect to be moved by the sector. This area of unrest is a bounding box consisting of blockmap tiles around the moving sector.
  • When a thing in unrest is overlapping a solid thing, the former is snapped down to the floor at its center point.

Solid in this instance refers to the MF_SOLID flag set for mobjs, which just means that a solid thing cannot be walked through. A solid thing could be the player, or monsters walking around.

Therefore, as an example, monsters that overlap corpses dangling off ledges while sectors are in motion cause a flickering up and down effect for the corpse.

Technical explanation[edit]

This bug is closely related to things snapping up to high ledges during sector motion, and can easily be experienced once a thing has snapped up, as entering the thing's bounding box while a nearby sector is in motion will cause this flickering effect.

Just like for things snapping up, the thing movement happens in P_ThingHeightClip. 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_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.

   newsubsec = R_PointInSubsector (x,y);
   ...
   tmfloorz = tmdropoffz = newsubsec->sector->floorheight;

Then things and linedefs in the vicinity are checked. If a line belonging to a ledge is found, such as that of the lift, the saved floor height from earlier is overwritten so the thing can be on the edge of a lift.

   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;

Because functions such as P_CheckPosition are used for multiple things, such as checking if a Nightmare! difficulty monster respawn has enough room, more actions than necessary are often performed that make no sense in the context of where they are used.

In this case, the intent was to only check for overlapped linedefs around the moving sector so that a thing can interact with a lift, but instead overlapped things are checked first, and if an overlapped thing is solid, this leads to an early return, skipping linedef checking. Once an overlapping solid thing has caused the linedefs to never be checked, the initially set 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 to cause thing unrest, move into 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.