Angle Decay
Sometimes, the player's pitch angle will naturally decay, very slowly, towards or away from 0. This is normally not noticeable, but can present an issue when attempting to line up precise shots, in particular Seamshots (such as the one on Crazy Box).
There are two types of angle decay: major decay (which decays the pitch angle towards 0) and minor decay (which decays the pitch angle away from 0, downwards, i.e. towards positive pitch values). The latter of these is much slower, and is only present on Windows.
Factors Affecting Decay
Both major and minor decay scale linearly with FPS (i.e. if you have 120FPS, your angle will decay twice as quickly as it would if you had 60FPS). Neither form of decay happens when tabbed out of the game, or with in_forceuser
set to control a different player.
Minor decay is always present while your pitch angle is close to 0. Major decay, however, only manifests itself in certain cases. When passing through a portal with a non-trivial angle difference (this means any portal which isn't wall<->wall or floor<->ceiling), the game will choose one of two methods for correcting the player's angle - either a "quaternion punch" or an "up vector snap". The method used determines whether, after the portal transition, major decay will be present: if a quaternion punch is used, the decay will not happen, but if an up vector snap is used, it will.
Thus, to avoid minor decay (important for e.g. certain seamshots), the transition needs to be performed in such a way that a quaternion punch is used. A consistent method of achieving this is to set your angle before the portal transition such that, after the transition, you will be looking approximately straight up.
Technical Explanation
Minor Decay
The cause of minor angle decay is currently not well understood.
Major Decay
Major angle decay is caused by a floating-point error in the player's m_PlayerLocal.m_Up
vector.
This field represents the up vector of the player's view as it is rotating after a portal transition which uses an up vector snap; it is set by CPortal_Player::SnapCamera
, which is responsible for initiating the viewangle fixup after a portal passthrough. When a quaternion punch is used, m_Up
is immediately set to the value of m_vLocalUp
, which will always be exactly 0,0,1
(the field's existence is a remnant of Adhesion Gel code). However, when an up vector snap is used, m_Up
is snapped to the value your current up vector corresponds to when translated across the portal, and is then decayed across the following ticks to m_vLocalUp
by CPortal_Player::RotateUpVector
.
This method of setting m_Up
raises an issue when RotateUpVector
does not correctly decay the value to precisely 0,0,1
. In fact, it turns out that due to some floating point error in the calculations performed by this function (possibly the call to vEndUp.NormalizeInPlace()
), this value ends up decaying to a vector of around 0,0,0.9999999
. This is the root cause of angle decay; the distance of this value from the expected one dictates how far the player's angle will decay every frame.
The actual decay comes from the C_Paint_Input::ApplyMouse
function. This function tries to calculate mouse movement using the player's m_Up
vector; specifically, the pitch adjustment code calculates the dot product of the forward view vector and m_Up
. For angles close to +/-90, the deviation from the expected value here is enough to cause the pitch angle to decay noticably, particularly on higher framerates.