diff --git a/client/scripts/player.gd b/client/scripts/player.gd index fda87b0..b8df20f 100644 --- a/client/scripts/player.gd +++ b/client/scripts/player.gd @@ -31,6 +31,8 @@ const NEXT_WAYPOINT_MIN_HORIZ_SQ: float = 0.01 var _has_walk_goal: bool = false var _auth_walk_goal: Vector3 = Vector3.ZERO +var _footprint_cache_frame: int = -1 +var _footprint_cache: Dictionary = {} @onready var _nav_agent: NavigationAgent3D = $NavigationAgent3D @@ -94,7 +96,7 @@ func _floor_y_under_body() -> float: ## One ray pass: max floor Y under footprint + whether any hit is an NS-19 bump (`ns19_bump` group). -func _footprint_step_down_state() -> Dictionary: +func _compute_footprint_step_down_state() -> Dictionary: var p: Vector3 = global_position var h: float = STEP_FOOTPRINT_HALF var oxz: Array[Vector3] = [ @@ -140,6 +142,14 @@ func _footprint_step_down_state() -> Dictionary: return {"max_y": best_y, "touches_ns19_bump": on_bump} +func _footprint_step_down_state() -> Dictionary: + var f: int = Engine.get_physics_frames() + if f != _footprint_cache_frame: + _footprint_cache = _compute_footprint_step_down_state() + _footprint_cache_frame = f + return _footprint_cache + + ## Footing under goal xz (surface Y). Server goal.y may be capsule center (e.g. 0.9) or pick surface ## (e.g. 0) — do not compare that directly to `_floor_y_under_body()` (always surface). func _destination_footing_y() -> float: @@ -190,9 +200,12 @@ func _physics_process(_delta: float) -> void: var full_to_goal: Vector3 = _auth_walk_goal - global_position var horiz_dist: float = Vector2(full_to_goal.x, full_to_goal.z).length() var vert_err_capsule: float = absf(full_to_goal.y) - var footing_match: bool = ( - absf(_floor_y_under_body() - _destination_footing_y()) <= VERT_ARRIVE_EPS - ) + var dest_foot_y: float = _destination_footing_y() + var fp: Dictionary = _footprint_step_down_state() + var max_foot_y: float = float(fp["max_y"]) + # Same lip issue as step-off: `_floor_y_under_body()` can read floor early and match `dest_foot_y` + # while xz is within ARRIVE_EPS — false “arrived”, goal clears. Use footprint max_y. + var footing_match: bool = absf(max_foot_y - dest_foot_y) <= VERT_ARRIVE_EPS if horiz_dist <= ARRIVE_EPS and (vert_err_capsule <= VERT_ARRIVE_EPS or footing_match): velocity = Vector3.ZERO _has_walk_goal = false