diff --git a/client/scripts/player.gd b/client/scripts/player.gd index 0cacc9b..ea9f038 100644 --- a/client/scripts/player.gd +++ b/client/scripts/player.gd @@ -28,6 +28,9 @@ const IDLE_MANUAL_CORRECTION_MAX_TICKS: int = 8 ## Horizontal nudge per tick for rim / straddle settle (**`_maybe_idle_rim_settle_nudge`**). const IDLE_RIM_SETTLE_STEP: float = 0.004 const DESCEND_GOAL_Y_MARGIN: float = 0.06 +## Cap floor snap while descending from a step so Jolt cannot snap the capsule back onto the +## upper surface across an internal edge (lip hang). Relaxed once feet are near the goal height. +const DESCEND_LIP_SNAP_CAP: float = 0.12 ## Y lift when blocked by wall-ish slide but nav goal is higher (stepped bumps). const WALK_STEP_ASSIST_DELTA: float = 0.11 ## Skip assist when horizontal velocity already aligns enough with want-dir (corners slide slowly). @@ -254,24 +257,28 @@ func _apply_walk_air_gravity(delta: float) -> void: if not is_on_floor(): velocity += g_step return - # Descending toward a lower surface (e.g. first terrace step → main floor): rim / internal - # edge contact can keep `is_on_floor()` true on the upper surface while the step's vertical - # face zeros horizontal motion. With vy forced to 0 by `_set_horizontal_velocity_toward`, - # no gravity ran and the capsule could hang until a new click retargeted. Shallow 0.3 m - # drops hit this more often than taller platform→floor descents. - var feet_on_floor: float = capsule_feet_y(global_position.y, CAPSULE_HALF_HEIGHT + PLAYER_CAPSULE_RADIUS) + # Descending toward a lower surface: `is_on_floor()` can stay true on the upper step while + # horizontal motion is blocked (internal edge / lip). `is_on_wall()` is unreliable here, and + # a far floor click keeps large horiz delta so wall heuristics never ran. Always integrate + # downward acceleration while on the upper surface; `move_and_slide` + floor collision + # absorbs it on flat spans, and it peels the capsule off the lip when forward motion stalls. + var feet_y: float = capsule_feet_y(global_position.y, CAPSULE_HALF_HEIGHT + PLAYER_CAPSULE_RADIUS) var descending: bool = ( - _has_walk_goal and _auth_walk_goal.y < feet_on_floor - DESCEND_GOAL_Y_MARGIN + _has_walk_goal and _auth_walk_goal.y < feet_y - DESCEND_GOAL_Y_MARGIN ) if descending and g_step.y < 0.0: - var wallish: bool = is_on_wall() - if not wallish: - for i: int in get_slide_collision_count(): - if get_slide_collision(i).get_normal().y < 0.55: - wallish = true - break - if wallish: - velocity.y += g_step.y + velocity.y += g_step.y + + +func _walk_floor_snap_length(feet_y: float) -> float: + var fl: float = WALK_STEP_ASSIST_SNAP if _step_assist_active else FLOOR_SNAP_MOVING + if ( + _has_walk_goal + and _auth_walk_goal.y < feet_y - DESCEND_GOAL_Y_MARGIN + and feet_y > _auth_walk_goal.y + 0.08 + ): + fl = minf(fl, DESCEND_LIP_SNAP_CAP) + return fl ## When the authoritative goal shares XZ with the capsule (e.g. terrace above), horizontal @@ -626,7 +633,9 @@ func _physics_process(delta: float) -> void: if NavigationServer3D.map_get_iteration_id(nav_map) == 0: _set_horizontal_velocity_toward(_auth_walk_goal, want_goal_h) _apply_walk_air_gravity(delta) - floor_snap_length = WALK_STEP_ASSIST_SNAP if _step_assist_active else FLOOR_SNAP_MOVING + floor_snap_length = _walk_floor_snap_length( + capsule_feet_y(global_position.y, CAPSULE_HALF_HEIGHT + PLAYER_CAPSULE_RADIUS) + ) move_and_slide() _after_walk_move_and_slide() if _step_assist_active and ( @@ -647,7 +656,7 @@ func _physics_process(delta: float) -> void: if _auth_walk_goal.y < feet_y - DESCEND_GOAL_Y_MARGIN: _set_horizontal_velocity_toward(_auth_walk_goal, want_goal_h) _apply_walk_air_gravity(delta) - floor_snap_length = WALK_STEP_ASSIST_SNAP if _step_assist_active else FLOOR_SNAP_MOVING + floor_snap_length = _walk_floor_snap_length(feet_y) move_and_slide() _after_walk_move_and_slide() if _step_assist_active and (