This analysis was done using the OpenArena code. Some code may differ from Quake III Arena.
The overbounce bug happens when a player impacts with the ground and then flies off in another direction at the pre-collision speed. Many Defrag trick maps have places where this can be done. Usually they are setup so that the player can drop off a ledge, hit the ground, and then bounce back up to the ledge (vertical overbounce). There is usually space to do a horizontal overbounce as well, which is when the player moves in a direction before hitting the ground. After collision with the ground, the player moves in the chosen direction at high speed. This can also be done on some Q3 maps, but it is usually much harder. This sounds like an exotic bug (which it is), but it happens surprisingly frequently. Most people don't seem to notice it or think it's just a map bug. Spawning is a good example. If you've ever bounced once or a few times after spawning, that was overbounce. The bug is still pretty minor in this case, but it can affect gameplay in a major way. Estatica seems to be a pretty well known map. There is a building with a large tower that is surrounded by a pit. On one side of the tower, it is possible to fall into the void, but there is an open area on the same side to help avoid that. When I land in this area I am typically safe from falling into the pit, but sometimes I overbounce. I hit the ground and friction doesn't stop me. I fly horizontally into the void. In this case, overbounce can greatly affect the score of the game. What makes it worse is that it seems to happen randomly, and I play OA because it's supposed to be skill based.
Overbounce seems to be very much a mystery on the internet. Since there seems to be nobody who can explain it to me, I might as well come up with my own explanation.
Here is what appears to be the all important section of code:
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;
}
}
This function is littered all over bg_pmove.c and bg_slidemove.c. It's purpose is to force objects to collide with each other. How it works is actually pretty simple.
backoff = DotProduct (in, normal);
This line determines how much of the player's velocity is moving into the plane.
for ( i=0 ; i<3 ; i++ ) {
change = normal[i]*backoff;
out[i] = in[i] - change;
}
Here the backoff value is multiplied by the normal and then taken off of the original velocity. This should result in a proper clip as shown in the figure below.
However, we have this code.
if ( backoff < 0 ) {
backoff *= overbounce;
}
else {
backoff /= overbounce;
}
The overbounce variable is clearly where we get the name for the term. What this does is cause the player to bounce away from the surface slightly. The overbounce value is always 1.001, so the overall bounce is a truely tiny value of 0.001 times the vertical speed. But, if you fall from certain heights and don't jump, this small amount of overbounce will somehow bounce you all the way back to your original height. If you remove the overbounce code, this effect will disappear completely. However, if you hop around in this overbounceless world, you may notice that this is not completely true. Vertical overbounce (VOB) disappears, but horizontal overbounce (HOB) remains untouched. So maybe the overbounce variable isn't as related to the bug as we first thought?
Let's analyze how PM_ClipVelocity reacts when a player drops onto the ground from a height. Let us assume that the horizontal speed is zero since that seems to be the situation that triggers a VOB. Let's also assume that the ground is flat. In this case, the vertical velocity will be negative and the surface normal will be straight upward. If we perform the first step and take the dot product, our result will be the vertical velocity. If this value is immediately subtracted from the velocity, the player's velocity will be zero. If we add overbounce into it, the player should bounce back slightly. That's it. Nothing special happens. Clearly something else must be happening.
There are three conditions that lead to an overbounce:
The player must not jump when landing.
The player falls from a certain height.
The player's horizontal velocity is zero (in the case of VOB).
These lead to the effect that the player's vertical velocity is reversed.
After a lot of printf debugging, I managed to determine that the overbounce effect is a combination of a bug and two quirks. That bug is completely unrelated to overbounce.
If the player jumps as he touched the ground, an overbounce does not occur. The only obvious place in the code where this can happen is PM_WalkMove. If jump is held, then PM_WalkMove will call PM_AirMove and then return and skip the ground movement code.
The second requirement I am unsure about, but I will make an educated guess as to what is happening. The frame before the overbounce, PM_AirMove is called like the many frames before it. The last line is PM_StepSlideMove( qfalse );
. My suspicion is that this line is what typically breaks a fall. Because of how the code is set up, it may be possible for that function call to complete it's full movement yet still end up on the ground. If PM_StepSlideMove completes it's full movement, it has no reason to clip the fall velocity. If this unusual situation occurs, PM_WalkMove will be executed the next frame, and pm->ps->velocity
will still have it's full fall velocity. This is never supposed to happen, but happens anyway because of the discrete nature of computers (and possibly network snapping). All this to say, at certain heights, PM_WalkMove is forced to deal with the collision instead of PM_AirMove.
Only this last requirement triggers the overbounce bug.
Based on this guess, PM_WalkMove is the place to look for the offending code. Since PM_ClipVelocity seems to play a role sometimes, the code must use that function as well. PM_ClipVelocity is used three times in PM_WalkMove, but it only clips the player's velocity in one place.
vel = VectorLength(pm->ps->velocity);
// slide along the ground plane
PM_ClipVelocity (pm->ps->velocity, pml.groundTrace.plane.normal, pm->ps->velocity, OVERCLIP );
// don't decrease velocity when going up or down a slope
VectorNormalize(pm->ps->velocity);
VectorScale(pm->ps->velocity, vel, pm->ps->velocity);
And here we are at the heart. The code does what it says it does. If we are walking up or down a slope, the velocity will not decrease, however, it may cause overbounce. The first line saves the current speed. This could be a couple thousand u/s. Next PM_ClipVelocity is called to deal with any collisions. This kills all vertical speed assuming the player is on a level surface, but then a miniscule amount of overbounce is added. This could be a couple u/s. The post-collision velocity is then normalized, and if the horizontal speed was zero, the vertical speed will be one. The normalized velocity is then scaled to the same magnitude as the pre-collision velocity. The velocity has been reversed, and the player soars back into the air.
Horizontal overbounce happens the same way. The reason that it isn't as noticable as vertical overbounce is that friction slows the player down within a second. A HOB is much easier to perform than a VOB because an overbounce will occur at any horizontal speed, and the tiny vertical velocity will be effectively ignored. Diagonal overbounce (DOB) is much more difficult to achieve than the other types because the horizontal speed must be close to a thousandth of the vertical speed. Given this relationship, it should also be the case that the higher the fall, the easier it should be to achieve a diagonal overbounce. If the player can gain 30,000 u/s of downward speed, a horizontal speed of 30 u/s should be sufficient to cause a DOB of 45 degrees.
As a side note, the reason that the velocity displayed is very close to the true velocity is network snapping. At the end of each PmoveSingle the velocity is snapped to the nearest integer. If the display shows zero, it probably is zero because of snapping. OpenArena has a pmove_float cvar which prevents snapping by sending floats over the network. If this is enabled, overbounce is still possible, but it will be much more difficult to get a VOB. This could mean that diagonal overbounce is easier to achieve with pmove_float enabled. I've noticed multiple VOBs in a row sometimes occur when pmove_float is enabled. I'm not sure why this happens when pmove_float is enabled but not when it is disabled.
It's a bug, so how do we fix it? The easiest way would probably be to set OVERCLIP to 1. This only kills vertical overbounce. A better alternative would be to prevent PM_WalkMove from handling aerial collisions. If the player is in the air this frame, set a variable. If the player clips a surface while in the air, clear the variable. When PM_WalkMove is called, if the variable is set, instead of bothering with the slope code, skip it and call PM_AirMove instead. How to do this is explained in my ProMode physics tutorial.
Scratch work (txt)