Client authoritive multiplayer physics to get low latency gameplay#28
Client authoritive multiplayer physics to get low latency gameplay#28
Conversation
…d player inputs, controlled by `SIMULATED_LATENCY_MS` and documented.
… to prevent race conditions
…r consistent physics.
…-authoritative goal concessions, serer relayes state.
There was a problem hiding this comment.
💡 Codex Review
Lines 295 to 299 in 16d6b4b
With client-authoritative physics, the server no longer calls collision logic (see ServerGame.fixedUpdate), so this.score on the server never increments. Each gameState message then overwrites the client’s locally accumulated score via this.score = state.score, which will stay at 0. This makes the HUD score and goalConceded payload (this.score) incorrect for any online match. Unless the server also updates score from client events, this assignment should be gated (e.g., only when server is authoritative) or the server should handle score updates.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…ynchronize local paddle position with server.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9c70d306f8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // Client-side physics: only ignore ball updates during PLAYING | ||
| // Accept ball updates during SCORING, COUNTDOWN, etc. | ||
| if (state.gameState !== 'PLAYING') { | ||
| this.ball.x = state.ball.x; | ||
| this.ball.y = state.ball.y; |
There was a problem hiding this comment.
Apply server ball state on mid-game join
Because the client ignores gameState updates while state.gameState === 'PLAYING', a player who joins or reconnects mid-match never applies the server’s current ball position/velocity. That means their local simulation starts from stale values (e.g., whatever was left from a previous local game), so they immediately diverge from the rest of the room. This only occurs when a client joins an already-playing room or reconnects during PLAYING, but in that case it breaks synchronization for that client.
Useful? React with 👍 / 👎.
| updateOnlinePhysics(dt) { | ||
| if (!this.socket) return; | ||
|
|
||
| super.updateGameRules(dt); | ||
|
|
There was a problem hiding this comment.
Avoid client-authoritative COUNTDOWN transitions
In online mode updateOnlinePhysics calls super.updateGameRules(dt), which decrements the COUNTDOWN timer and can flip gameState to PLAYING locally. With latency or delayed server updates, the client can enter PLAYING early, run physics, and emit paddleHit/goalConceded before the server transitions. That makes client state advance ahead of the authoritative server and can desync or trigger premature scoring. This only shows up when there’s noticeable network delay or slow server state delivery.
Useful? React with 👍 / 👎.
…ion with silent hooks, and clients fully accept server ball state on first connection.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 14dc4b4913
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // Inform server of goal | ||
| this.socket.emit('goalConceded', { | ||
| edgeIndex: i, | ||
| score: this.score, | ||
| time: Math.floor(this.timeElapsed) |
There was a problem hiding this comment.
Prevent stale score in goalConceded under latency
With client-authoritative hits, this.onPaddleHit() increments this.score locally, but the next gameState update still overwrites it via this.score = state.score in the handler (see Game.js around the gameState listener). Under network delay, a stale server score can arrive between a local hit and a miss, rolling the score back; the subsequent goalConceded then reports the lower value, so the server ends the round with an undercounted final score. This only shows up when server state lags behind local hits, but it corrupts the round result when it does.
Useful? React with 👍 / 👎.
…ating score with the maximum value.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 94b92f6688
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (state.gameState !== 'PLAYING' || !this.firstStateReceived) { | ||
| this.ball.x = state.ball.x; | ||
| this.ball.y = state.ball.y; | ||
| if (state.ball.vx !== undefined) { | ||
| this.ball.vx = state.ball.vx; |
There was a problem hiding this comment.
Reset firstStateReceived before reconnects
The new firstStateReceived gate only allows server ball updates on the very first gameState or outside PLAYING. However, this flag is never reset in rejoinMultiplayer, so a player who disconnects and rejoins mid-match will still have firstStateReceived === true and will ignore the first PLAYING state from the server. That leaves the ball position/velocity stale after a reconnect, causing immediate client desync. This only happens when rejoining an in-progress room without calling stopMultiplayer/resetLocalGame first, but in that case the fix here doesn’t actually restore server truth.
Useful? React with 👍 / 👎.
…AYING in online mode.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 04fcf32602
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| console.log(`Player on edge ${edgeIndex} conceded goal. Score: ${score}, Time: ${time}`); | ||
|
|
||
| // Trigger game over | ||
| this.triggerScore(score, edgeIndex); |
There was a problem hiding this comment.
Use server score when handling goalConceded
Because handleGoalConceded calls triggerScore(score, …) with the client-supplied score, any divergence in that client’s optimistic simulation (or a malicious client) can inflate the server’s final score. This can happen when the conceding client locally counted paddle hits that the server didn’t (e.g., due to latency or prediction errors), and the server will then broadcast a higher lastScore than it has actually tracked. Consider using the server’s authoritative this.score (or validating the reported score) when ending the round.
Useful? React with 👍 / 👎.
…nstead of the client-reported score.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2f58f9be34
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| console.log(`Player on edge ${edgeIndex} conceded goal. Server Score: ${this.score}, (Client reported: ${score}), Time: ${time}`); | ||
|
|
||
| // Trigger game over using the server's authoritative score | ||
| this.triggerScore(this.score, edgeIndex); |
There was a problem hiding this comment.
Guard goalConceded against non-PLAYING state
handleGoalConceded always calls triggerScore once the edge matches, without checking the server’s current gameState. If a client is out of sync (e.g., still in PLAYING while the server is already in COUNTDOWN/SCORING due to latency) or is malicious, it can end the round early by sending goalConceded at any time. Please ignore/defers these events unless the server is currently in PLAYING (and not already celebrating).
Useful? React with 👍 / 👎.
| // Increment server score | ||
| this.score++; |
There was a problem hiding this comment.
Ignore paddleHit events outside active play
onClientPaddleHit increments this.score with no guard on gameState, so late/duplicate packets or an out-of-sync client can bump the score during COUNTDOWN/SCORING. That inflates the HUD and the final score even when no active rally is in progress. Consider verifying this.gameState === 'PLAYING' (and possibly that a celebration isn’t active) before accepting the hit.
Useful? React with 👍 / 👎.
…t in the PLAYING state.
…e on hits, refining server hit detection, and adding client-side escaped ball detection.
Second attempt.
alternative to #27