From 1944eeafe8eb74a57645dc4986af631652494f91 Mon Sep 17 00:00:00 2001 From: VinPropane Date: Sat, 11 Apr 2026 22:30:07 -0400 Subject: [PATCH] NEON-29: fix step assist triggering on floor bumps (directional wall check) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The is_on_wall() short-circuits in _step_assist_has_support() and _step_assist_wallish_blocks() caused the assist to fire whenever the capsule touched any wall contact — including the sides of random floor bumps while crossing flat ground toward a distant elevated goal. After a 0.11 m lift, floor_snap_length = 0.09 m could not reach the original floor (0.11 m away) and _step_assist_active never cleared, so the capsule floated permanently. Fixes: - Remove is_on_wall() blanket short-circuit from both functions. - _step_assist_has_support() now takes want_dir_xz and accepts wall-ish contacts only when they oppose the direction of travel (dot < -0.2), i.e. genuine step faces, not sideways bump contacts. - _step_assist_wallish_blocks() relies solely on the directional collision loop (dot < -0.04); no is_on_wall() bypass. - _try_walk_step_assist() reordered to compute want before calling both functions so the same direction vector is used throughout. - Added "floating free" fallback: if _step_assist_active and no collisions and not on floor, clear the flag so FLOOR_SNAP_MOVING restores on the next tick. Applied to all three physics paths. --- client/scripts/player.gd | 52 ++++++++++++++++++++++++++++++---------- 1 file changed, 40 insertions(+), 12 deletions(-) diff --git a/client/scripts/player.gd b/client/scripts/player.gd index 9b04efb..4283bee 100644 --- a/client/scripts/player.gd +++ b/client/scripts/player.gd @@ -139,18 +139,33 @@ func snap_to_server(world_pos: Vector3) -> void: reset_physics_interpolation() -func _step_assist_has_support() -> bool: - if is_on_floor() or is_on_wall(): +## Returns true when the capsule has something to stand on **or** is pressing against a step face +## in the direction of travel. `want_dir_xz` is the normalised horizontal goal direction. +## Using `is_on_wall()` as a blanket short-circuit was too broad: any floor-bump side-contact +## satisfied it, causing the assist to fire prematurely and then be unable to snap back. +func _step_assist_has_support(want_dir_xz: Vector3) -> bool: + if is_on_floor(): return true + var w2 := Vector2(want_dir_xz.x, want_dir_xz.z) + var have_dir: bool = w2.length_squared() > 1e-8 + if have_dir: + w2 = w2.normalized() for i: int in get_slide_collision_count(): - if get_slide_collision(i).get_normal().y > 0.35: - return true + var n: Vector3 = get_slide_collision(i).get_normal() + if n.y > 0.35: + return true # upward-facing = floor support + # Wall-ish contact opposing forward direction = step face immediately ahead. + if have_dir and abs(n.y) < 0.52: + var n2 := Vector2(n.x, n.z) + if n2.length_squared() > 1e-8 and n2.normalized().dot(w2) < -0.2: + return true return false +## Returns true when slide collisions contain a wall-ish contact that opposes forward motion. +## The `is_on_wall()` short-circuit was removed: it returned true for any wall contact regardless +## of direction, firing the assist for sideways bump-skin contacts unrelated to step climbing. func _step_assist_wallish_blocks(want_dir_xz: Vector3) -> bool: - if is_on_wall(): - return true var w2 := Vector2(want_dir_xz.x, want_dir_xz.z) if w2.length_squared() < 1e-8: return false @@ -183,16 +198,17 @@ func _try_walk_step_assist() -> bool: var actual_feet_y: float = global_position.y - CAPSULE_HALF_HEIGHT - PLAYER_CAPSULE_RADIUS if _auth_walk_goal.y < actual_feet_y + 0.025: return false - if not _step_assist_has_support(): - return false var pos: Vector3 = global_position var to_h: Vector3 = Vector3(_auth_walk_goal.x - pos.x, 0.0, _auth_walk_goal.z - pos.z) if to_h.length_squared() < 0.03: return false + # Compute want direction before the support/blocking checks so both can use it. var want: Vector3 = to_h.normalized() var vel_h: Vector3 = Vector3(velocity.x, 0.0, velocity.z) if not _step_assist_wallish_blocks(want) or vel_h.dot(want) > WALK_STEP_ASSIST_MAX_FORWARD_DOT: return false + if not _step_assist_has_support(want): + return false # Target capsule centre once standing on the goal surface. var target_center_y: float = _auth_walk_goal.y + CAPSULE_HALF_HEIGHT + PLAYER_CAPSULE_RADIUS var lift: float = clampf(target_center_y - global_position.y, 0.0, WALK_STEP_ASSIST_DELTA) @@ -545,7 +561,10 @@ func _physics_process(_delta: float) -> void: floor_snap_length = WALK_STEP_ASSIST_SNAP if _step_assist_active else FLOOR_SNAP_MOVING move_and_slide() _after_walk_move_and_slide() - if _step_assist_active and is_on_floor() and not is_on_wall(): + if _step_assist_active and ( + (is_on_floor() and not is_on_wall()) + or (not is_on_floor() and get_slide_collision_count() == 0) + ): _step_assist_active = false _debug_trace_transform("physics") _snap_capsule_upright() @@ -557,7 +576,10 @@ func _physics_process(_delta: float) -> void: floor_snap_length = FLOOR_SNAP_MOVING move_and_slide() _after_walk_move_and_slide() - if _step_assist_active and is_on_floor() and not is_on_wall(): + if _step_assist_active and ( + (is_on_floor() and not is_on_wall()) + or (not is_on_floor() and get_slide_collision_count() == 0) + ): _step_assist_active = false _debug_trace_transform("physics") _snap_capsule_upright() @@ -578,7 +600,13 @@ func _physics_process(_delta: float) -> void: floor_snap_length = WALK_STEP_ASSIST_SNAP if _step_assist_active else FLOOR_SNAP_MOVING move_and_slide() _after_walk_move_and_slide() - if _step_assist_active and is_on_floor() and not is_on_wall(): - _step_assist_active = false + if _step_assist_active: + if is_on_floor() and not is_on_wall(): + # Landed cleanly on a surface — done climbing. + _step_assist_active = false + elif not is_on_floor() and get_slide_collision_count() == 0: + # Floating free with no contacts: assist must have fired spuriously. + # Clear the flag so FLOOR_SNAP_MOVING restores normal gravity next tick. + _step_assist_active = false _debug_trace_transform("physics") _snap_capsule_upright()