NEON-29: fix step assist triggering on floor bumps (directional wall check)

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.
pull/41/head
VinPropane 2026-04-11 22:30:07 -04:00
parent 453000d721
commit 1944eeafe8
1 changed files with 40 additions and 12 deletions

View File

@ -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()