Skip to content

Stop a client authoring the clock its own shots are judged against - #150

Merged
themuffinator merged 2 commits into
mainfrom
claude/mp-lag-compensation-engine
Sep 3, 2026
Merged

Stop a client authoring the clock its own shots are judged against#150
themuffinator merged 2 commits into
mainfrom
claude/mp-lag-compensation-engine

Conversation

@themuffinator

Copy link
Copy Markdown
Owner

Engine half of the multiplayer lag-compensation work. Pairs with openQ4-game #14, which is merged.

The sanitisation

usercmd.gameTime arrives from the client and reaches the game unchanged, so anything that measures a command's age against the server clock becomes a dial the client can turn. Overwriting it with the frame index the server already filed the command under is a no-op for an honest client — that is exactly what idAsyncClient sends — and it removes the dial permanently.

Staleness detection is unaffected: a slot that was never received still holds a stamp from a whole backup window ago, so DuplicateUsercmd still fires. Worth noting the obvious exploit is the mirror of the intuitive one — stamping the value low is self-defeating because DuplicateUsercmd overwrites it, so the live vector was stamping it high.

The contract

Lag compensation was pinned by nothing in either repository, which is how it came to ship with a rewind that could not measure what it was for. tools/tests/mp_lag_compensation_contract.py pins three agreements:

  • the estimate must not be derived from the command's age (a structural constant — the server executes a client's command at that client's own frame index, after RunFrame has advanced time past it);
  • the hit target must be restored before anything acts on the hit — damage, decal, impact effect, the effect's PVS routing, ragdoll;
  • a discontinuity must invalidate the history.

It also pins net_mpLagCompensation at 0, so the default cannot be flipped by accident — only deliberately, by editing the assertion alongside it. Registered in both workflows and the local validation profile.

Pin bump

OPENQ4_GAMELIBS_SHA moves to 6804902, which is the tree these commits were measured against and carries all three merged multiplayer changes (openQ4-game #11, #12, #14). The contract test asserts code that exists only there.

🤖 Generated with Claude Code

themuffinator and others added 2 commits September 3, 2026 12:39
usercmd.gameTime arrives from the client and reaches the game unchanged, where
anything that measures a command's age against the server clock becomes a dial
the client can turn.  Overwriting it with the frame index the server already
filed the command under is a no-op for an honest client - that is exactly what
idAsyncClient sends - and it removes the dial permanently.  Staleness detection
is unaffected: a slot that was never received still holds a stamp from a whole
backup window ago, so DuplicateUsercmd still fires.

Add tools/tests/mp_lag_compensation_contract.py, and register it in the two
workflows and the local validation profile.  Lag compensation was pinned by
nothing in either repository, which is how it came to ship with a rewind that
could not measure what it was for.  The contract pins three agreements: the
estimate must not be derived from the command's age, the hit target must be
restored before anything acts on the hit, and a discontinuity must invalidate the
history.  It also pins net_mpLagCompensation at 0, so the default cannot be
flipped by accident - only deliberately, by editing the assertion with it.

Pairs with openQ4-game "Make the lag-compensation rewind measure something, and
put the target back".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
mp_lag_compensation_contract.py asserts code that exists only in openQ4-game
6804902, so the pin has to move with it or the engine's cross-repo tests would
assert contracts against a tree that predates them.

6804902 is the tree these engine commits were measured against, and it carries
all three merged multiplayer changes: the projectile prestep (#11), remote-player
re-simulation (#12) and the lag-compensation rewind (#14).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@themuffinator
themuffinator merged commit 80b111a into main Sep 3, 2026
21 of 25 checks passed
@themuffinator
themuffinator deleted the claude/mp-lag-compensation-engine branch September 3, 2026 13:54
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.

1 participant