ValveSoftware / source-sdk-2013

The 2013 edition of the Source SDK
https://developer.valvesoftware.com/wiki/SDK2013_GettingStarted
Other
3.73k stars 1.99k forks source link

Models go spastic after some time #404

Open ghost opened 7 years ago

ghost commented 7 years ago

This bug has also been reported in the other repository: https://github.com/ValveSoftware/Source-1-Games/issues/2036 .

After some time of playing in Source 2013 mods, the player models will begin to freak out as seen on this youtube video: https://youtu.be/eHjXmGUIpks . The models will freak out enough that it disappears after a while (Some people say it crashes after this point for them).

ghost commented 6 years ago

This also happens to us in GoldenEye: Source (which currently uses Source SDK Base 2007). This issue has come and gone over the years for us, but from my observation it seems most likely to occur:

When touching another player; When using frame limiting software; or When using the Steam overlay (which limits the framerate of the game)

azuisleet commented 6 years ago

This is an old issue, but depending on if these mods use legacy animation or CMultiPlayerAnimState, there might be some obvious causes. Some pose parameters like the eye position may be uninitialized. There may be weird logic in the HL2MP SDK that doesn't set the pose parameter values correctly.

Garry's Mod for sure uses CMultiplayerAnimState, but the video posted only shows the face acting funny. My guess is that the pose parameters could be in some invalid state, whereas the pose parameters like body aim don't appear to be affected (which could be the case with CMultiplayerAnimState or legacy animation)

TotallyMehis commented 6 years ago

I highly doubt it's because of uninitialized vars, especially when it happens randomly and seems to get worse. If it was, causing a full update should be an easy way of recreating it.

The cause of the crash, however, is at some point a pose parameter becomes NaN. This in turn causes the game to stop rendering the model (hence why players disappear). The crash occurs in VPhysics when a ragdoll is created using those pose parameters (hence why the game crashes after a player dies).

Here's some more footage https://www.twitch.tv/videos/235246932 where aim_yaw seems to be the main culprit this time. I've also experienced the same with eyes, but never enough to crash.

If anybody has any ideas or workarounds, I'd appreciate them. Quite considerable chunk of our playerbase is affected by this bug. We're using multiplayer anim state as seen here. In our case limiting framerate will stop this problem. This is really hard to inspect, since there is no reliable way of recreating it, and as soon as you tab out to use a debugger, framerate drops and the problem's gone. A map where you get high fps only helps.

azuisleet commented 6 years ago

Another thing that would be helpful in diagnosing this would be if your local player model is also jittery in third person, because of the localplayer/nonlocalplayer split in the datatable (if the source of the error comes from eye angles for example). When I have some time, I'll take a look for myself and see if there's anything obvious.

azuisleet commented 6 years ago

The OC guys got in contact with me, and it seems like the issue is with the "body_yaw" pose parameter on the player. If you're using CHL2MPPlayerAnimState (correction: CMultiPlayerAnimState uses body_yaw in place of aim_yaw) this pose parameter is not actively being set. This pose "should" be set to its default value, but based on their notes, something is giving weird output values for this particular pose value after interpolation on the client.

I have not done any tests and I haven't actually been able to reproduce it, but I suggested they try setting a default value for body_yaw with the other computed pose values in the animstate. I'll post an update if they tell me anything.

TotallyMehis commented 6 years ago

It is not only the unused pose parameters, it's all of them. Reason: this doesn't work Best bet is to disable interpolation on them, since pose parameters aren't networked at all in the first place. That is if you're using the multiplayer anim state.

azuisleet commented 6 years ago

Yes, so we were able to investigate this further. For the pose parameters that aren't being actively set, their values are static, but end up accumulating a bunch of errors that eventually result in NaN values.

What is happening is that the interpolation logic is trying to interpolate a value on a boundary (eg 0.5 and 0.499999) using Lerp_Hermite (lerp_functions.h). This algorithm is not stable in that it has a significant amount of overshoot. I believe this is made worse by the large number of frames per second. Without any correction applied (by explicitly setting pose parameters more often, or when the network data comes in?), this error can accumulate very quickly and result in a NaN.

The last suggestion I made, but I have not heard back on, was to clamp the delta values (d1, d2) in Lerp_Hermite by simply forcing delta values < 0.00001 to 0. This would hopefully prevent errors from accumulating when the lerp function is just trying to lerp between two very close values.

Here's a very simple simulation of that function to show how those errors accumulate: https://gist.github.com/azuisleet/f05787b61a1cd31470207012a94e853c (t is the value of frac, intentionally chosen as worst case)

azuisleet commented 6 years ago

The OC guys had a chance to test the change to Lerp_Hermite. It sounds like it was sufficient to prevent the issue without introducing any visual artifacts.

Here's an example of that function (specialized for float for this example, because that's what the pose parameters are interpolated as):

lerp_functions.h

template <> inline float Lerp_Hermite<float>( float t, const float& p0, const float& p1, const float& p2 )
{
    float d1 = p1 - p0;
    float d2 = p2 - p1;

    if ( d1 < 0.00001 ) d1 = 0;
    if ( d2 < 0.00001 ) d2 = 0;

    float output;
    float tSqr = t*t;
    float tCube = t*tSqr;

    output = p1 * (2*tCube-3*tSqr+1);
    output += p2 * (-2*tCube+3*tSqr);
    output += d1 * (tCube-2*tSqr+t);
    output += d2 * (tCube-tSqr);

    return output;
}