Quick answer: Floor snap never applies while the body is moving along up_direction, so a correct jump always leaves the ground. If yours does not, something zeroes or overrides velocity.y before move_and_slide() — usually an if is_on_floor(): velocity.y = 0 placed after the jump code — or up_direction does not point up.
You press jump, the character twitches upwards for a frame and is pulled straight back down. CharacterBody2D's floor snapping gets the blame, but in Godot 4 snapping is designed to switch off during a jump. The bug is almost always in the order of the movement code.
How floor snap works
floor_snap_length (1 px by default) keeps the body glued to slopes and steps while it walks. Snapping only applies while the body is on the floor and not moving along up_direction. Any upward velocity disables it for that call to move_and_slide().
Cause 1: velocity.y is reset after the jump
This is the classic version:
func _physics_process(delta):
if Input.is_action_just_pressed("jump") and is_on_floor():
velocity.y = JUMP_VELOCITY
if is_on_floor():
velocity.y = 0 # undoes the jump in the same frame
else:
velocity.y += gravity * delta
move_and_slide()
is_on_floor() is still true until move_and_slide() runs, so the second block wipes the jump. Apply gravity first and the jump last, and let move_and_slide() zero the vertical velocity when you land:
func _physics_process(delta):
if not is_on_floor():
velocity.y += gravity * delta
if Input.is_action_just_pressed("jump") and is_on_floor():
velocity.y = JUMP_VELOCITY # negative = up
velocity.x = Input.get_axis("left", "right") * SPEED
move_and_slide()
Cause 2: the jump velocity points the wrong way
In 2D, +Y is down. A jump needs a negative velocity.y. A positive value pushes the body into the floor, and snapping holds it there.
Cause 3: up_direction is wrong
up_direction defaults to Vector2.UP. If it was changed (for example, for wall-walking or gravity flips) and not updated, the body thinks "up" is sideways, floor detection breaks, and snapping applies at the wrong time.
Cause 4: input read outside the physics step
If jump input is read in _process() and stored, then cleared before _physics_process() runs, the jump can be lost or applied twice. Read movement input in _physics_process(), or buffer it and consume it there.
Cause 5: a very large floor snap
A floor_snap_length of 30 px or more can pull a body back onto ledges and slopes during small hops. Keep it just large enough for your steepest slope.
If you snap manually
apply_floor_snap() snaps the body down immediately. Call it only when you are sure the character is grounded, never in the same frame as a jump.