From 6bd1acd9e7f576527f569c055d7fd5591aef5e27 Mon Sep 17 00:00:00 2001 From: VinPropane Date: Sun, 12 Apr 2026 18:08:01 -0400 Subject: [PATCH] NEON-29: Require forward floor probe to keep walk snap/gravity sane off ledges MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Walk support used any ray hit under the foot disk, so the center sample could still see the platform while the leading edge was over a void. is_on_floor() stayed true from the lip, snap stayed strong, and vy was zeroed each tick — horizontal skate until XZ arrival. Now require a center hit plus at least one forward-biased sample in the movement direction when horizontal intent is non-zero; use velocity XZ for probe direction so nav/path steering matches the rays. --- client/scripts/player.gd | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/client/scripts/player.gd b/client/scripts/player.gd index 8d7f320..119ee94 100644 --- a/client/scripts/player.gd +++ b/client/scripts/player.gd @@ -365,16 +365,30 @@ func _walk_ray_hit_up_floor(space: PhysicsDirectSpaceState3D, off_xz: Vector2) - return n.dot(Vector3.UP) > WALK_SUPPORT_PROBE_MIN_UP_DOT -## True if **any** sample under the foot disk hits walk-like ground within [member WALK_SUPPORT_PROBE_DEPTH]. -## Uses [param move_dir_xz] so the **leading** edge off a deck is checked (center-only rays stayed over mesh). +## Walkable support for **continued** forward motion within [member WALK_SUPPORT_PROBE_DEPTH]. +## Requires a hit under the foot origin **and** at least one sample **ahead** in [param move_dir_xz] +## (same construction as [method _walk_probe_xz_offsets]). If only the center hits while the leading +## foot is over a void, [method CharacterBody3D.is_on_floor] can stay true from a lip — returning +## true here would suppress gravity and keep moving snap until XZ arrival (ledge skate). +## When [param move_dir_xz] is ~zero (idle / no horizontal intent), center-only is enough. func _walk_has_close_floor_probe_below(move_dir_xz: Vector2) -> bool: var w3d := get_world_3d() if w3d == null: return true var space: PhysicsDirectSpaceState3D = w3d.direct_space_state + if not _walk_ray_hit_up_floor(space, Vector2.ZERO): + return false + var len2: float = move_dir_xz.length_squared() + if len2 < 1e-10: + return true + var inv_len: float = 1.0 / sqrt(len2) + var md := Vector2(move_dir_xz.x * inv_len, move_dir_xz.y * inv_len) for off2: Vector2 in _walk_probe_xz_offsets(move_dir_xz): - if _walk_ray_hit_up_floor(space, off2): - return true + if off2.length_squared() < 1e-12: + continue + if off2.dot(md) > 0.0: + if _walk_ray_hit_up_floor(space, off2): + return true return false @@ -922,9 +936,10 @@ func _physics_process(delta: float) -> void: else: _set_horizontal_velocity_toward(_auth_walk_goal, want_goal_h) - var walk_move_dir_xz := Vector2(want_goal_h.x, want_goal_h.z) + # Prefer actual horizontal velocity so probes match nav/path steering, not only click bearing. + var walk_move_dir_xz := Vector2(velocity.x, velocity.z) if walk_move_dir_xz.length_squared() < 1e-10: - walk_move_dir_xz = Vector2(velocity.x, velocity.z) + walk_move_dir_xz = Vector2(want_goal_h.x, want_goal_h.z) _apply_walk_air_gravity(delta, walk_move_dir_xz) floor_snap_length = _walk_floor_snap_length(feet_y, want_goal_h, walk_move_dir_xz)