Skip to content

[HL2MP] Fix/prediction shared weapon recoil rng - #2041

Open
speedvoltage wants to merge 2 commits into
ValveSoftware:masterfrom
speedvoltage:fix/prediction-shared-weapon-recoil-rng
Open

[HL2MP] Fix/prediction shared weapon recoil rng#2041
speedvoltage wants to merge 2 commits into
ValveSoftware:masterfrom
speedvoltage:fix/prediction-shared-weapon-recoil-rng

Conversation

@speedvoltage

Copy link
Copy Markdown
Contributor

Issue:

The .357 and physcannon generate random view-punch components independently on the client and server while running shared predicted attack code. Their recoil state can therefore diverge and cause prediction corrections. Because ViewPunch is not merely a local camera effect, we cannot simply move this to being fully client-sided. This is because it changes predicted, networked player state:

m_Local.m_vecPunchAngleVel += angleOffset * 20;

Therefore, both m_vecPunchAngle and m_vecPunchAngleVel are:

  • maintained authoritatively by the server
  • sent to the owning client
  • stored in client prediction history
  • decayed during movement simulation on both sides
  • used by the server when calculating aim with EyeAngles() + m_vecPunchAngle

Shooting with the current setup will always show the following:

[Tick 1858] 001 CPlayerLocalData::m_vecPunchAngleVel - vec[] differs (1st diff) (net -160.000000 -9.218750 0.000000 - pred -160.000000 2.080131 0.000000) delta(0.000000 11.298881 0.000000)
[Tick 1858] Full latch reset!

[Tick 2198] 001 CPlayerLocalData::m_vecPunchAngleVel - vec[] differs (1st diff) (net -160.000000 -3.625000 0.000000 - pred -160.000000 18.189096 0.000000) delta(0.000000 21.814096 0.000000)
[Tick 2198] Full latch reset!

[Tick 2667] 001 CPlayerLocalData::m_vecPunchAngleVel - vec[] differs (1st diff) (net -160.000000 -39.000000 0.000000 - pred -160.000000 17.259556 0.000000) delta(0.000000 56.259556 0.000000)
[Tick 2667] Full latch reset!

Fix:

Use command-seeded shared random values for their variable recoil components.

Before:

2026-08-30.20-38-09.mp4

After:

2026-08-30.20-39-26.mp4

Note that this is a complementary fix to #954

Use command-seeded shared random values for .357 yaw punch and physcannon recoil. Client and server now produce identical predicted view kick.
Use class-scoped names for the .357 and physcannon recoil random streams.
@speedvoltage speedvoltage changed the title Fix/prediction shared weapon recoil rng [HL2MP] Fix/prediction shared weapon recoil rng Aug 30, 2026
@inco-cc

inco-cc commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Interestingly, the .357 only produces this error with really low network latency (basically the local host). Even with net_fakelag 5, it occurs far less frequently. The Gravity Gun always produces this error, though.

@speedvoltage

Copy link
Copy Markdown
Contributor Author

Interestingly, the .357 only produces this error with really low network latency (basically the local host). Even with net_fakelag 5, it occurs far less frequently. The Gravity Gun always produces this error, though.

I ran some tests. I think that behavior makes sense given #954.

FireBullets() reseeds the random stream from the prediction command seed. On the first client prediction, the .357 then consumes two client-only RandomInt calls I had introduced for the disorientation from #954 before reaching the ViewPunch RandomFloat, while the server does not.

On a subsequent re-prediction, IsFirstTimePredicted() is false, so those two client-only random calls are skipped. Since FireBullets() reseeds the stream again, the ViewPunch value can then line up with the server. That would explain why adding a little latency makes the .357 error much less frequent: the prediction replay can mask the RNG-order mismatch before the command is acknowledged.

The physcannon doesn't have an equivalent deterministic reseed immediately before its two recoil random calls, which would also explain why its error is persistent.

This is why I think SharedRandomFloat is preferable here regardless of latency: the recoil value is derived directly from the command prediction seed and named shared value instead of depending on the current global RNG state/call order.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants