How to fix the overbounce bug

There are trick jumps that can be done in certain places by “overbounce” which increase velocity to a huge value and throws the player back into the air after hitting the ground.

Anyway, I mainly test the game on q3dm1 and I got really tired of just jumping in q3dm1 court yard causing overbounce. Jump and when the player lands, they jump again.

To get rid of it, in code/game/bg_pmove.c there is two places with the following (PM_WalkMove, PM_WaterMove)

	VectorScale(pm->ps->velocity, vel, pm->ps->velocity);

I fixed overbounce in Spearmint by changing them to the below

	if ( pm->pmove_overbounce || VectorLength(pm->ps->velocity) > 1 ) {
		VectorScale(pm->ps->velocity, vel, pm->ps->velocity);

I added pm->pmove_overbounce which is set from a cvar (“pmove_overbounce”) before calling Pmove() in game/cgame to allow enabling/disabling it. You can remove “pm->pmove_overbounce ||” to just disable overbounce.


In 2017, I wrote an explanation that I never finished/posted.


Causing the overbounce bug is simple. Stand still on a flat surface and jump repeatedly. It may take 1 to 10 jumps or so. If the player automatically jumps again when they hit the ground, congratulations you’ve experienced the overbounce bug! It shouldn’t matter where the player is but I usually test in the courtyard of the q3dm1 map.

People have found ways to exploit the overbounce bug by falling from certain locations but that’s outside my area of interest. The key point is no horizontal movement when the player hits the ground.

What went wrong

Player walking and running maintains velocity force when going up and down slopes. The following code is from PM_WalkMove() in code/game/bg_pmove.c.

#define    OVERCLIP                1.001f

	vel = VectorLength(pm->ps->velocity);

	// slide along the ground plane

	// don't decrease velocity when going up or down a slope
	VectorScale(pm->ps->velocity, vel, pm->ps->velocity);

The velocity is clipped against the ground plane. The clipped velocity is then normalized to a unit length of 1 and scaled back to the original velocity’s vector length. This allows the original velocity vector length to be maintained in the clipped direction.

The problem is PM_ClipVelocity() can clip too much and incorrectly change the direction. The falling velocity (0, 0, -270) clipped against an upward facing surface (0, 0, 1) becomes velocity (0, 0, 0.27). The velocity is then normalized to (0, 0, 1) and multiplied by the original velocity vector length (270) resulting in final velocity (0, 0, 270). The player is then launched back into the air.

Let’s have a look at PM_ClipVelocity() in code/game/bg_pmove.c.

void PM_ClipVelocity( vec3_t in, vec3_t normal, vec3_t out,
			float overbounce ) {
	float	backoff;
	float	change;
	int		i;
	backoff = DotProduct (in, normal);
	if ( backoff < 0 ) {
		backoff *= overbounce;
	} else {
		backoff /= overbounce;

	for ( i=0 ; i<3 ; i++ ) {
		change = normal[i]*backoff;
		out[i] = in[i] - change;

(I think there should be more written here? 2017 me didn’t write it.)


My solution for the problem is to only normalize and rescale the velocity if the clipped velocity length is more than 1 unit. It works unless the player’s falling velocity is over 1000 which I don’t think can happen.

    if ( pmove_overbounce || VectorLength(pm->ps->velocity) > 1 ) {
        // don't decrease velocity when going up or down a slope
        VectorScale(pm->ps->velocity, vel, pm->ps->velocity);

Note: Quake III Arena bg_pmove.c source code is available under the Q3SDK end user license agreement and the GNU General Public License version 2 or later.

2023 tl;rd if clipped velocity VectorLength(pm->ps->velocity) is less than 1, it may of flipped direction and basically impacted directly into the surface normal and velocity should probably be 0 (which trap_SnapVector() probably does).

1 Like

(I think there should be more written here? 2017 me didn’t write it.)

There are three things at work here. As you mentioned, VectorNormalize and PM_ClipVelocity are involved in the bug. The last is that PM_GroundTrace says you are walking if 1) you are within 0.25 units of the ground, 2) you are not moving upward or away from the ground, and 3) the ground is not a steep slope. Colliding with the ground is not required. So if you are falling on to flat ground and are within 0.25 units of the ground, but haven’t actually touched the ground plane yet, then you are on the ground. PM_AirMove is designed to perform all collisions with the ground and PM_WalkMove is only designed for movement after PM_AirMove has already handled the collision, but in this uncommon case, PM_WalkMove has to handle the collision.

The “proper” way to fix this is to set pml.walking in PM_SlideMove right after a collision with the ground has occurred. This requires a lot of changes, especially to stair stepping, so a workaround like you wrote above is the best option IMO. Another workaround is to prevent PM_WalkMove from handling ground collisions by setting a flag that indicates when the player was just in air.

And here’s a third way. It’s been a while, but I think this is all I had to do in OpenArena’s Ratmod:

// set groundentity
if (pml.groundPlane && (pm->pmove_ratflags & RAT_NOOVERBOUNCE)) {
	PM_OneSidedClipVelocity(pm->ps->velocity, pml.groundTrace.plane.normal, pm->ps->velocity, OVERCLIP);

It handles the collision before it gets to the movement functions.
PmoveSingle calls PM_GroundTrace twice. This is the first call to it. pmove_ratflags is set from a cvar. PM_OneSidedClipVelocity only projects the velocity if it is moving against the normal. PM_ClipVelocity might work in its place, but I don’t recall if I’ve tested it.

1 Like