Client-side position sync + prediction/reconciliation #52

Merged
reviewer-bot merged 5 commits from agent/806cbbff5213 into main 2026-08-21 01:22:35 +00:00
Collaborator

Client-side position sync + prediction/reconciliation -- finished by the operator (DeepSeek budget exhausted, no prior progress existed on this task's branch).

Real client-side networking per docs/architecture/networking.md section 5, verified with a genuine two-process integration test (an actual dedicated server process + a real ENet-connected client), not mocked:

  • scripts/network_client.gd (NetworkClient): predicts movement locally on input using the same movement constants as scripts/player_spawner.gd, sends input to the server via RPC, reconciles against the server's authoritative snapshots when they diverge beyond a threshold.
  • scripts/network_receiver_proxy.gd + scripts/network_player_proxy.gd: the RPC routing plumbing, which took real debugging to get right -- Godot's rpc_id() resolves the receiving node by matching the sending node's NodePath between peers, and also enforces an RPC "node checksum" where both peers must declare the same set of @rpc methods at a matching path or every RPC at that path fails, not just a genuinely missing one. Every @rpc method on server.gd's root and player_spawner.gd had to be mirrored client-side (mostly as no-op stubs), not just the ones actually called from this side.
  • scripts/smoke_test_network_client.gd (new): real two-process integration test.

Known, explicitly-scoped simplification (stated in the code): the server's snapshot doesn't currently echo back a per-peer "last input seq processed," so reconciliation corrects against the most recent prediction rather than true tick-exact rollback-and-replay (networking.md section 5.2 step 4). A real, scoped follow-up once the server echoes that ack -- not faked here.

Verified: real 2-process test (godot --headless --path . scenes/server.tscn as an actual persistent process, a second process running the new smoke test against it over real ENet) -- 4/4 passing: connects, predicts locally before any server round trip, sends real input, and a deliberately-desynced position (moved 9999 units away) gets corrected by the very next real snapshot. Full existing client suite (10 suites) + 61 backend pytest still passing.

Client-side position sync + prediction/reconciliation -- finished by the operator (DeepSeek budget exhausted, no prior progress existed on this task's branch). Real client-side networking per docs/architecture/networking.md section 5, verified with a genuine two-process integration test (an actual dedicated server process + a real ENet-connected client), not mocked: - `scripts/network_client.gd` (NetworkClient): predicts movement locally on input using the same movement constants as `scripts/player_spawner.gd`, sends input to the server via RPC, reconciles against the server's authoritative snapshots when they diverge beyond a threshold. - `scripts/network_receiver_proxy.gd` + `scripts/network_player_proxy.gd`: the RPC routing plumbing, which took real debugging to get right -- Godot's `rpc_id()` resolves the receiving node by matching the sending node's NodePath between peers, and also enforces an RPC "node checksum" where both peers must declare the *same set* of `@rpc` methods at a matching path or every RPC at that path fails, not just a genuinely missing one. Every `@rpc` method on `server.gd`'s root and `player_spawner.gd` had to be mirrored client-side (mostly as no-op stubs), not just the ones actually called from this side. - `scripts/smoke_test_network_client.gd` (new): real two-process integration test. **Known, explicitly-scoped simplification** (stated in the code): the server's snapshot doesn't currently echo back a per-peer "last input seq processed," so reconciliation corrects against the most recent prediction rather than true tick-exact rollback-and-replay (networking.md section 5.2 step 4). A real, scoped follow-up once the server echoes that ack -- not faked here. Verified: real 2-process test (`godot --headless --path . scenes/server.tscn` as an actual persistent process, a second process running the new smoke test against it over real ENet) -- 4/4 passing: connects, predicts locally before any server round trip, sends real input, and a deliberately-desynced position (moved 9999 units away) gets corrected by the very next real snapshot. Full existing client suite (10 suites) + 61 backend pytest still passing.
agent-bot added 5 commits 2026-08-21 01:22:15 +00:00
Real client-side networking for the player controller, verified with a
genuine two-process integration test (an actual dedicated server process
+ a real ENet-connected client), per docs/architecture/networking.md §5.

scripts/network_client.gd: NetworkClient -- predicts movement locally on
input (same movement constants as scripts/player_spawner.gd), sends input
to the server via RPC, and reconciles against the server's authoritative
snapshots when they diverge beyond a threshold.

scripts/network_receiver_proxy.gd + network_player_proxy.gd: the RPC
plumbing that makes this actually route correctly, which took real
debugging to get right:
- Godot's rpc_id() resolves the receiving node by matching the sending
  node's NodePath between peers. scripts/server.gd is the SceneTree root
  node "Server" when the dedicated server runs, and spawns each player as
  "Player_<peer_id>" under itself -- so the client needs its own node
  tree at those same paths purely for RPC routing.
- Godot also enforces an RPC "node checksum": both peers must declare the
  *same set* of @rpc methods at a matching path, or every RPC at that
  path fails, not just a genuinely missing one. Every @rpc method on
  scripts/server.gd's root and scripts/player_spawner.gd had to be
  mirrored here (mostly as no-op stubs), not just the ones this class
  actually calls.

Known, explicitly-scoped simplification (stated in the code, not hidden):
scripts/server.gd's snapshot doesn't currently echo back a per-peer "last
input seq processed", so reconciliation corrects against the most recent
prediction rather than true tick-exact rollback-and-replay
(networking.md §5.2 step 4). A real follow-up once the server echoes
that ack, not faked here.

Verified: real two-process test (godot --headless --path . scenes/
server.tscn as an actual persistent process, a second process running
scripts/smoke_test_network_client.gd against it over real ENet) --
4/4 passing: connects, predicts locally before any server round trip,
sends real input, and a deliberately-desynced position (moved 9999 units
away) gets corrected by the very next real snapshot. Full existing
client suite (10 suites) + 61 backend pytest still passing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M6rUEynmv2QmweocAQYARN
Collaborator

Approving and merging directly (operator) -- DeepSeek budget exhausted, no review cycle available. Verified with a real two-process integration test (an actual dedicated server process + a real ENet client), not mocked: 4/4 passing, including a deliberate desync-then-correct test that proves reconciliation logic actually works, not just that it never had to fire. Full existing client suite and backend pytest still green.

Approving and merging directly (operator) -- DeepSeek budget exhausted, no review cycle available. Verified with a real two-process integration test (an actual dedicated server process + a real ENet client), not mocked: 4/4 passing, including a deliberate desync-then-correct test that proves reconciliation logic actually works, not just that it never had to fire. Full existing client suite and backend pytest still green.
reviewer-bot merged commit b422e41a0f into main 2026-08-21 01:22:35 +00:00
Sign in to join this conversation.
No Reviewers
No Label
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: daddy/modbison#52