4. Player movement basics

In this chapter, we will focus on the fundamental governing equations for the air and ground player movements, and the exploitation of some of the miscellaneous physical phenomena. This chapter also serves as a prerequisite to Strafing.

4.1. Gravity

Like other entities in the Half-Life universe, the player experiences gravity. Whenever the player is in the air, the waterlevel is less than 2, and not waterjumping (see Waterjump), a constant downward acceleration will be applied. The gravity in the Half-Life universe works in a similar way to the Newtonian gravity in the real world. Namely, a free falling object experiences a constant acceleration of , with value specified by

where is may be called the entity gravity, which is a modifier that scales the default gravity. Typically, , though it can take a fractional value in Xen, for example. The consequence of a constant acceleration is that the velocity and position of the object at time is


Recall that the Half-Life universe runs at quantised time, that is assuming constant frame rate we may write . Or, after one frame. In Half-Life physics, the new position is not updated directly using the position equation above, but rather, it is obtained by a position update step described in Position update.

Looking closely at the code of PM_PlayerMove, we see that the game applies half gravity to the player before acceleration and position update by using PM_AddCorrectGravity, which modifies the vertical velocity and the vertical basevelocity by

where is the vertical component of the basevelocity . After position update, the the second half of gravity will be added using PM_FixupGravityVelocity, altering the vertical velocity by

To see the game splits the gravity computation into halves like so, ignoring basevelocity, we write the vertical velocity after the first half of gravity as

The position update step follows from there, by computing the new vertical position

After this, the second half of gravity is applied to compute the correct final vertical velocity

Now observe that both and are correct in accordance to classical mechanics in (4.1). Had the gravity been calculated in any other way, the final vertical position and velocity would be incorrect. This technique of breaking up the acceleration is a variant of the leapfrog integration in the study of numerical integration. It can be shown that trajectory of player motion is indeed parabolic and independent of the frame rate. That is, the trajectory fits the parabolic curve generated using classical mechanics perfectly. Consequently, the jump height is also independent of frame rate. Vertically launching from a ladder, however, does result in frame rate-dependent heights (see Ladder exit).

On the other hand, the straightforward way of integrating gravity is to calculate the full (as opposed to half) gravity , followed by the position update . Notice that this means

In other words, the new vertical position is incorrect, because the term in red is incorrect compared to (4.1). Essentially, this approach is equivalent to the Euler’s method of integrating a differential equation. Not only would the errors accumulate over time, but also that the jump height will be dependent on the frame rate.

4.2. Ground friction

When the player is moving on the ground, friction will be applied to reduce the horizontal speed. The friction is applied before air and ground movement calculations (see Air and ground movements) in PM_Friction. The player friction differs from the friction applied to all other entities in that different types of friction is applied depending on the horizontal speed.

Let be the stop speed, the value of sv_stopspeed which is typically 100. Let be the value of

which is usually 4 and where is called the entity friction. The entity friction can be modified by a friction entity (see func_friction). The is the edgefriction which will be described in a moment. It is usually 1 but can often be 2. The two dimensional player velocity immediately after applying friction (but before air or ground acceleration) is now


Assuming . Now observe that the player speed is scaled by a constant factor (assuming and are constant) each frame, resulting in an exponential decrease. This may be called geometric friction, because the series of speeds in consecutive frames forms a geometric series. At higher horizontal speeds this type of friction can be devastating, because higher speeds are harder to achieve and maintain (owing to the sublinear growth of speed by pure strafing, see Strafing), but the factor scales down the speed by an amount proportional to it.

Assuming no other influences and the condition for geometric friction is always satisfied. At frame , the speed due to geometric friction is

Since time is discretised in the Half-Life universe, we have . Therefore,

From this equation, it can be shown, assuming sensible positive values for and , that the lower the frame rate, the greater the geometric friction. However, the difference in friction between different frame rates is so minute that it does not make much practical difference.

In the second case in (4.2), the type of friction being applied may be called arithmetic friction, because the speeds of consecutive frames form an arithmetic series. Namely, at frame , we have

This type of friction is independent of the frame rate, unlike the geometric friction.

In the third case of (4.2), where the speed is very low, the speed is simply set to zero. This case makes little practical difference.

4.2.1. Edgefriction

Edgefriction is a an extra friction applied to the player when the player is sufficiently close to an edge that is sufficiently high above from a lower ground. Let be the player position, and the player velocity. Define

Here, is a matrix with diagonal entries of . is half the hull height of the player, which depends on the ducking state.

See Ducking for a descriptions of ducking states. Effectively, this makes level with the player’s feet. With and computed, the game performs a player trace from to . If nothing is in the way between the two points, the game will set to the value of edgefriction. In the default settings of Half-Life, this amounts to , doubling from its normal value.


Fig. 4.1. If the player hull trace from to does not collide with a solid entity, then edgefriction will be applied. Note that is units below .

Although doubling seems minor at first glance, the effect is devastating. Prolonged groundstrafing towards an edge can drastically reduce the horizontal speed, which in turn affects the overall airstrafing acceleration after jumping off the edge. One way to avoid edgefriction is to jump or ducktap before reaching an edge and start airstrafing. In human speedrunning terms, the technique of ducktapping before an edge is sometimes called countjump. However, this is sometimes infeasible due to space or other constraints. The most optimal way to deal with edgefriction is highly dependent on the circumstances. Extensive offline simulations may be desirable.

4.3. Air and ground movements

The physics governing the player’s air and ground movements are of primary importance. With precise inputs, they can be exploited to allow mathematically unbounded speed gain (barring sv_maxvelocity). The consequences of the air and ground physics will be described in detail in Strafing.


All vectors in this section are two dimensional on the plane unless stated otherwise.

The air or ground accelerations are computed before position update (see Position update). Let the initial player velocity in two dimensions, namely the velocity immediately before friction and acceleration are applied. Then the FME is simply

Here, is called the unit acceleration vector, such that

A few notes to be made here. First, the and are the forwardmove and sidemove respectively, described in Forwardmove, sidemove, and upmove. Second, and are the unit forward and side view vectors described in View vectors. But more importantly, they are obtained by setting in the equations, regardless of the player’s actual pitch. Consequently, they do not have a component in the axis.

Define such that

where is sv_maxspeed. Observe that is always capped by sv_maxspeed. Observe also that if and are not sufficiently large, one can end up with a smaller value of below sv_maxspeed, which results in lower accelerations, as we will see later. In addition, if , then and will be smaller compared to that when , and so will also be smaller. Therefore, it is undesirable to have any at all if we want as much horizontal acceleration as possible.

In the FME, we also have the coefficient. This coefficient may be written as


Recall that is the entity friction described in Ground friction. is the value of either sv_accelerate or sv_airaccelerate, used for ground and air movement respectively. is either or , for ground and air movement respectively. is the shortest angle between and .

We can observe that if , there will be no acceleration at all. This occurs when

Now observe that if , then this condition will never hold because the maximum value of is . That is to say, at lower speeds, the player will be able to accelerate regardless of (barring a few zero points). With speeds beyond , acceleration will not occur with angles

This is just one of the consequences of the FME. Exploitations of this equation will be detailed in Strafing.

Having computed the new velocity , the basevelocity will be added to the player velocity as

Then, a position update will be performed as described in Position update. Once the position is updated, the basevelocity will be “removed” from the velocity by

4.4. Water movements

Water movement has less exploitation potential than air and ground movement. Nonetheless, due to its ability to slow the player down, we should strive to understand its physics. Here, all vectors are three dimensional and the waterlevel must be 2 or above to run water physics.

Let be the player velocity and the basevelocity. Then the game first modifies the velocity as such:

Subsequently, the acceleration vector is computed as

Similar, but not identical to that in the air or ground movement physics, is defined to be

The only difference is the presence of the factor. Then, velocity is updated as


and is the value of sv_accelerate, such that

Note that, unlike air and ground movement, the basevelocity is added before acceleration, rather than after the acceleration.

Next, the player tries to swim up a step based on sv_stepsize. This is followed by a position update as explained in Position update. In the final step, the basevelocity is “removed” from the velocity by

To see why it is impossible to accelerate beyond a certain speed without external help, observe that when the speed is sufficiently high, then regardless of view angles or other inputs, will become negative. This always sets , resulting in zero acceleration. In the absence of acceleration, the friction will reduce the speed rapidly.

It is worth noting that there is no restriction in the magnitude of player velocity while the waterlevel is 2 or above. This is because PM_CheckVelocity is never called at any point in the code path associated with water physics. Although it is rare for the player to achieve great speeds under water, it is possible with means such as rapid ducking over push trigger, as described in trigger_push.

4.4.1. Waterlevel

The player has a waterlevel ranging from 0 to 3, inclusive. Higher levels indicate being more “immersed” in water. The setting of waterlevel is done by PM_CheckWater, which is called from various points in the player movement code, a notable one being PM_CatagorizePosition. As a high level description,

waterlevel is 1

When 1 unit above the player’s feet, or equivalently, , is under water, where is half the height of the player hull

waterlevel is 2

When the centre of the player hull, equivalent to the position , is under water

waterlevel is 3

When is under water, where is the player’s view offset

Note that the conditions for a particular level must also encompass the conditions for earlier levels. For example, the waterlevel will not be 2 if the conditions for waterlevel to be 1 is not met. If none of these conditions are satisfied, the waterlevel is 0.

4.4.2. Sharking

When the waterlevel is 2, and the jump key is held, then PM_Jump sets the vertical velocity to , and leaving the horizontal components intact. This means that and will not be scaled down unlike the case where . A good thing about pressing the jump key instead of +moveup to swim up is that the jump key sets the vertical velocity upwards instantaneously, while +moveup takes time to accelerate the player up.

When the jump key is held while the waterlevel is bordering between 1 and 2, the player will likely be less submerged in the water, and therefore getting a waterlevel of 1. Suppose a frame such that, at the end of the frame, the waterlevel changes from 2 to 1 due to holding down the jump key. Despite leaving the water at the end of frame, the normal water physics would still be run, because the game does not detect the change until a PM_CatagorizePosition or PM_CheckWater is called. There is no such call between PM_Jump and PM_WaterMove.

After leaving the water, the normal air movement physics will take over, and gravity will be exerted onto the player. Due to the small vertical speed resulting from jumping, gravity will quickly bring the player back into water again. Suppose at some frame , the player falls back into the water. Then, the PM_CatagorizePosition immediately after PM_FlyMove will set the waterlevel to 2 or above. In the next frame , PM_Jump will set the player vertical velocity again, and normal water physics will run, which applies some amount of water friction to the player. It is likely that at frame , the player will be back in air again. The cycle will repeat, and this is sometimes called “sharking” in speedrunning.

4.4.3. Waterjump

Waterjumping refers to the phenomenon where the player vertical velocity is set to without any movement inputs or actually issuing +jump when being near a wall. This phenomenon is not to be confused with sharking (Sharking) or the literal “jumping out of the water” or basevelocity related techniques (trigger_push). The function responsible of initiating waterjumping is PM_CheckWaterJump in the SDK. This function is only called when the waterlevel is exactly 2, implying that waterjumping will not be initiated when the waterlevel is not 2. As a high level description, set

where is the player’s unit forward vector, and is a diagonal matrix with as the diagonal entries. Define further

where is simply the normalised . The game then performs a player trace using the point hull from to . If this collides with a wall, defined as a plane with normal or roughly, one which is slanted by more than , then define

where is half the player hull height. The game then performs a trace from to . If there is no obstruction, waterjumping will be initiated by setting and various other flags and properties.

4.5. Position update

Having a high speed is useless if the player position does not actually get updated. The player position is updated in the PM_FlyMove function in the SDK. This function is also responsible of handling collisions, which usually causes velocity to change as described in Collision. In the simplest case, which is what is assumed in most simulations and analyses of player movement, is to compute the new position as

where is the current position, is the player frame time (see Frame rate), and is the velocity computed after the acceleration step.

At a higher level, in each iteration the function performs a player trace from to . This trace will produce a trace fraction within , and the position will be set to the end position of the trace. If , which implies the player does not collide with any entity, then the iteration will be stopped. Otherwise, the general collision equation described in Collision will be used to modify the velocity. The iteration will continue, for a total of four iterations.

Whenever a collision occurs, recall from (2.1) that the new velocity depends on the bounce coefficient . The exact form of the bounce coefficient depends on various conditions. Given a plane the player collides with, if


then we have

If (4.3) is not met, then we always have .