Improve multiplayer movement reconciliation

This commit is contained in:
Alexander Sellite 2026-08-23 23:04:14 -04:00
parent 348ae261e1
commit a3aea98982
3 changed files with 240 additions and 89 deletions

View file

@ -13,6 +13,7 @@ const ENET_TIMEOUT_MAXIMUM_MS: int = 120000
const INPUT_INTERVAL: float = 1.0 / 30.0
const IDLE_INPUT_INTERVAL: float = 1.0 / 5.0
const SNAPSHOT_INTERVAL: float = 1.0 / 30.0
const OWNER_SNAPSHOT_DIVISOR: int = 3
const NEAR_REMOTE_SNAPSHOT_DIVISOR: int = 2
const FAR_REMOTE_SNAPSHOT_DIVISOR: int = 6
const DISTANT_REMOTE_SNAPSHOT_DIVISOR: int = 8
@ -122,6 +123,7 @@ var _last_input_state_hash: int = 0
var _pending_movement_inputs: Array[Dictionary] = []
var _snapshot_accumulator: float = 0.0
var _movement_snapshot_tick: int = 0
var _last_local_snapshot_received_msec: int = 0
var _animation_refresh_accumulator: float = 0.0
var _last_animation_state_by_peer: Dictionary[int, Dictionary] = {}
var _pending_animation_state_by_peer: Dictionary[int, Dictionary] = {}
@ -2248,13 +2250,16 @@ func _broadcast_movement_snapshots() -> void:
var subject_avatar: Player = _spawn_service.get_avatar(subject_id)
if subject_avatar == null:
continue
if (
subject_id != recipient_id
and not _should_send_remote_snapshot(
if subject_id == recipient_id:
# The owner already simulates locally. Its authoritative state is
# an audit and acknowledgement, not a presentation stream, so it
# does not need the full 30 Hz observer snapshot rate.
if _movement_snapshot_tick % OWNER_SNAPSHOT_DIVISOR != 0:
continue
elif not _should_send_remote_snapshot(
recipient_avatar.global_position,
subject_avatar.global_position,
)
):
):
continue
var encoded: Array = _encode_movement_snapshot(
subject_avatar.make_network_snapshot(subject_id)
@ -2538,8 +2543,10 @@ func receive_movement_snapshots(encoded_snapshots: Array) -> void:
)
avatar.apply_local_prediction_correction(
snapshot,
_pending_movement_inputs,
_input_sequence,
INPUT_INTERVAL,
estimated_transit_seconds,
_local_snapshot_delta_seconds(),
)
else:
avatar.push_network_snapshot(
@ -2557,6 +2564,20 @@ func _discard_acknowledged_movement_inputs(acknowledged_sequence: int) -> void:
_pending_movement_inputs.pop_front()
func _local_snapshot_delta_seconds() -> float:
var now_msec: int = Time.get_ticks_msec()
if _last_local_snapshot_received_msec <= 0:
_last_local_snapshot_received_msec = now_msec
return 0.0
var elapsed_seconds: float = clampf(
float(now_msec - _last_local_snapshot_received_msec) / 1000.0,
0.0,
Player.LOCAL_PREDICTION_MAX_AUDIT_DELTA_SECONDS,
)
_last_local_snapshot_received_msec = now_msec
return elapsed_seconds
@rpc(
"authority",
"call_remote",
@ -2861,6 +2882,7 @@ func _teardown_peer() -> void:
_pending_movement_inputs.clear()
_snapshot_accumulator = 0.0
_movement_snapshot_tick = 0
_last_local_snapshot_received_msec = 0
_animation_refresh_accumulator = 0.0
_last_animation_state_by_peer.clear()
_pending_animation_state_by_peer.clear()