From 2dff83bae3532239aaf528fa41c9e68b4f44a5d1 Mon Sep 17 00:00:00 2001 From: coder-bot Date: Fri, 21 Aug 2026 01:17:13 +0000 Subject: [PATCH] WIP checkpoint: Client-side position sync + prediction/reconciliation --- .godot/editor/filesystem_cache10 | 6 ++--- scripts/network_client.gd | 43 +++++++++++++++---------------- scripts/network_receiver_proxy.gd | 1 + 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/.godot/editor/filesystem_cache10 b/.godot/editor/filesystem_cache10 index 94fe177..af08089 100644 --- a/.godot/editor/filesystem_cache10 +++ b/.godot/editor/filesystem_cache10 @@ -1,5 +1,5 @@ 63f7b34db8d8cdea90c76aacccf841ec -::res://::1787274081 +::res://::1787274847 export_presets.cfg::TextFile::-1::1787272930::0::1::::<><><>0<>0<><>:: README.md::TextFile::-1::1787272930::0::1::::<><><>0<>0<><>:: ::res://addons/::1787272930 @@ -44,7 +44,7 @@ server_player.tscn::PackedScene::-1::1787274081::0::1::::<><><>0<>0<><>::res://s world_list.tscn::PackedScene::-1::1787272930::0::1::::<><><>0<>0<><>::res://scripts/world_list.gd ::res://scenes/worlds/::1787272930 test_level.tscn::PackedScene::-1::1787272930::0::1::::<><><>0<>0<><>:: -::res://scripts/::1787274812 +::res://scripts/::1787274976 api_client.gd::GDScript::5412288119287126975::1787272930::0::1::::<>Node<><>0<>0<><>:: chunk.gd::GDScript::7364239597441807588::1787272930::0::1::::Chunk<>Node3D<><>0<>0<><>:: chunk_data_decoder.gd::GDScript::3922068956043581888::1787272930::0::1::::ChunkDataDecoder<>RefCounted<><>0<>0<><>:: @@ -52,7 +52,7 @@ chunk_scene.gd::GDScript::9207602004456600331::1787272930::0::1::::ChunkScene<>N chunk_stream_manager.gd::GDScript::2366645676568508746::1787272930::0::1::::ChunkStreamManager<>Node<><>0<>0<><>:: dynamic_object_node.gd::GDScript::8863349716576659118::1787272930::0::1::::DynamicObjectNode<>Node3D<><>0<>0<><>:: main_menu.gd::GDScript::2834562513277893052::1787272930::0::1::::<>Control<><>0<>0<><>:: -network_client.gd::GDScript::5408296350353838185::1787274408::0::1::::NetworkClient<>Node<><>0<>0<><>:: +network_client.gd::GDScript::5408296350353838185::1787274976::0::1::::NetworkClient<>Node<><>0<>0<><>:: network_player_proxy.gd::GDScript::4787436336771232797::1787274812::0::1::::<>Node<><>0<>0<><>:: network_receiver_proxy.gd::GDScript::6742760436861380520::1787274804::0::1::::<>Node<><>0<>0<><>:: player.gd::GDScript::4510795778937304264::1787274081::0::1::::Player<>CharacterBody3D<><>0<>0<><>:: diff --git a/scripts/network_client.gd b/scripts/network_client.gd index c5738b4..c9a1df4 100644 --- a/scripts/network_client.gd +++ b/scripts/network_client.gd @@ -200,36 +200,35 @@ func _handle_snapshot(snapshot_data: Dictionary) -> void: return var authoritative_pos: Vector3 = (my_state as Dictionary).get("position", position) - var acked_seq: int = snapshot_data.get("tick", 0) - # Find our own predicted position at (approximately) the acknowledged - # tick -- the buffer is seq-ordered, so the first entry >= acked_seq is - # the comparison point. If the server hasn't caught up to any of our - # sent input yet (empty/very early buffer), nothing to reconcile. - var predicted_entry: Dictionary = {} - for entry in _prediction_buffer: - if entry["seq"] >= acked_seq: - predicted_entry = entry - break - if predicted_entry.is_empty(): + # Real limitation, noted explicitly rather than silently faked: the + # server's snapshot_state (scripts/server.gd's _broadcast_snapshot) does + # not currently echo back "last input seq processed for this peer" -- + # its `tick` field is the server's own internal simulation tick counter, + # a completely different clock from this client's per-input `_seq`. True + # tick-correlated rollback (docs/architecture/networking.md §5.2 step 4, + # "re-simulate any inputs that occurred after the server's tick") needs + # that ack echoed back per-peer -- a real, scoped follow-up, not + # something to fake here. Until then, reconcile against the MOST RECENT + # prediction (not a tick-matched one): still a real error measurement + # and a real correction, just without precise replay-from-exact-tick. + if _prediction_buffer.is_empty(): return + var latest_entry: Dictionary = _prediction_buffer[-1] - var error: float = (predicted_entry["position"] as Vector3).distance_to(authoritative_pos) + var error: float = (latest_entry["position"] as Vector3).distance_to(authoritative_pos) if error <= RECONCILE_ERROR_THRESHOLD: return # Close enough -- prediction was correct, nothing to do. - # Correct: snap to the authoritative position, then replay every input - # sent after the acknowledged tick so the visible result still reflects - # input the server hasn't processed yet (§5.2 step 4). + # Correct: snap to the authoritative position. Without a real ack-seq + # from the server there's no reliable "replay inputs after this point" + # window to recompute, so the buffer is cleared and prediction resumes + # fresh from the corrected position on the next input -- still visibly + # responsive (no waiting for a round trip before the *next* input + # applies), just without this pass replaying in-flight inputs. position = authoritative_pos velocity = Vector3.ZERO - var replay_from_index := _prediction_buffer.find(predicted_entry) + 1 - for i in range(replay_from_index, _prediction_buffer.size()): - var entry: Dictionary = _prediction_buffer[i] - var inp: Dictionary = entry["input"] - _apply_movement(1.0 / 30.0, inp.get("move_x", 0.0), inp.get("move_z", 0.0), inp.get("jump", false)) - entry["position"] = position - entry["velocity"] = velocity + _prediction_buffer.clear() reconciled.emit(error) diff --git a/scripts/network_receiver_proxy.gd b/scripts/network_receiver_proxy.gd index c4ad4b5..d200cc2 100644 --- a/scripts/network_receiver_proxy.gd +++ b/scripts/network_receiver_proxy.gd @@ -20,6 +20,7 @@ var owner_client: Node = null ## config, a mismatch here would silently drop the call. @rpc("any_peer", "call_remote", "reliable") func _receive_snapshot(snapshot_data: Dictionary) -> void: + print("DEBUG proxy received snapshot: ", snapshot_data) if owner_client and is_instance_valid(owner_client): owner_client._handle_snapshot(snapshot_data)