diff --git a/client/scripts/main.gd b/client/scripts/main.gd index 97e6ed5..38c9652 100644 --- a/client/scripts/main.gd +++ b/client/scripts/main.gd @@ -30,11 +30,19 @@ const STREAM_IDLE_VEL_HZ_SQ: float = 0.0004 const AUTH_SNAP_MIN_HORIZONTAL: float = 0.08 const AUTH_SNAP_MIN_VERTICAL: float = 0.04 +## While moving, ignore stream snaps smaller than this (XZ m): responses are often one RTT behind +## integrated `move_and_slide`, so snapping causes rubber-band / bounciness. +const AUTH_SNAP_LOCOMOTION_SKIP_MAX_DH: float = 0.55 +## Horizontal speed² above this (m²/s²) counts as “moving” for snap suppression (~0.35 m/s). +const AUTH_SNAP_LOCOMOTION_VEL_HZ_SQ: float = 0.12 + ## Bump on each rejection so older one-shot timers do not clear a newer message. var _move_reject_msg_token: int = 0 var _stream_physics_counter: int = 0 ## After the first `apply_as_snap` authority update, micro-snaps may be skipped (boot always applies). var _allow_authority_soft_snap_skip: bool = false +## Next `apply_as_snap` must not be suppressed (e.g. `move-stream` 400 → `sync_from_server`). +var _authority_force_snap_next: bool = false ## [InputMap] [code]dev_toggle_occluder_obstacle[/code] (default **Ctrl+Shift+K**): ## Restored when the obstacle is shown again. @@ -125,10 +133,19 @@ func _on_target_chosen(world: Vector3) -> void: func _on_authoritative_position(world: Vector3, apply_as_snap: bool) -> void: if apply_as_snap: - if _allow_authority_soft_snap_skip: - var p: Vector3 = _player.global_position - var dh: float = Vector2(world.x - p.x, world.z - p.z).length() - var dy: float = absf(world.y - p.y) + var force: bool = _authority_force_snap_next + _authority_force_snap_next = false + var p: Vector3 = _player.global_position + var dh: float = Vector2(world.x - p.x, world.z - p.z).length() + var dy: float = absf(world.y - p.y) + if _allow_authority_soft_snap_skip and not force: + var wish_len2: float = _player._locomotion_wish_world_xz.length_squared() + var vel_hz_sq: float = Vector2(_player.velocity.x, _player.velocity.z).length_squared() + var locomoting: bool = ( + wish_len2 > 1e-10 or vel_hz_sq > AUTH_SNAP_LOCOMOTION_VEL_HZ_SQ + ) + if locomoting and dh < AUTH_SNAP_LOCOMOTION_SKIP_MAX_DH: + return if dh < AUTH_SNAP_MIN_HORIZONTAL and dy < AUTH_SNAP_MIN_VERTICAL: return _allow_authority_soft_snap_skip = true @@ -138,6 +155,7 @@ func _on_authoritative_position(world: Vector3, apply_as_snap: bool) -> void: func _on_move_rejected(reason_code: String) -> void: + _authority_force_snap_next = true # Rejected POST never reached VERIFY_GET, so no new nav target was applied; do not clear an # in-flight path from a prior successful move. push_warning("Move rejected: %s" % reason_code)