acron.slide

The collide-and-slide core both character movers run on.

One algorithm, engine-agnostic: sweep a shape along the desired motion; on a hit, advance to the contact (plus a small skin along the normal), classify the surface against up (floor / wall / ceiling), project the blocked component out of the remaining motion, and go again, for at most max_slides iterations. Floor contact also projects the into-floor component out of the velocity (Godot’s move_and_slide does this implicitly). A WALL does not touch velocity: the motion slides along it for this step, but the caller’s stored velocity still points into the wall, so a body held against one keeps whatever speed it was given. Godot cancels that component; matching it is a behaviour change several games would feel, so it is a deliberate difference to settle before any upstream contribution.

The core knows nothing about Bullet, masks, or planes. Callers hand in:

  • sweep(from_pos, to_pos, motion) – returns an object with .fraction (0..1 along the sweep) and .normal (world space), or None when the path is clear. The 2D mover folds its one-way-platform mask selection into this closure (motion is the remaining motion for exactly that purpose).

  • constrain_normal(normal) (optional) – adjust a hit normal to the caller’s constraint space, or return None to treat the hit as clear (the plane-constrained mover drops out-of-plane prism-cap normals).

snap_to_floor() is the ground-glue probe: a downward sweep that pulls a body that just left the ground back onto slopes/steps. The gate – only when grounded last step and not moving upward, never during a jump or a free fall – is the single most important correctness detail of the movers and is owned by the callers, who know their grounded history.

This module is self-contained on purpose: it is the upstream-contribution candidate (Acron has no character controller; see UPSTREAM_PRS.md).

SlideResult(position, velocity)

What one collide_and_slide() produced.

Global Functions

collide_and_slide(sweep: Callable, position: LVector3f, velocity: LVector3f, dt: float, up: LVector3f, *, max_slides: int = 6, floor_max_angle: float = 0.7853981633974483, skin: float = 0.02, constrain_normal: Callable | None = None) → SlideResult

Move position by velocity * dt, sliding along obstacles.

floor_max_angle is in radians; a hit whose (constrained) normal makes normal . up > cos(floor_max_angle) is a floor, < -cos a ceiling, anything between a wall.

snap_to_floor(sweep: Callable, position: LVector3f, up: LVector3f, reach: float, *, floor_max_angle: float = 0.7853981633974483, skin: float = 0.02, constrain_normal: Callable | None = None)

The ground-glue probe: sweep down reach along -up.

Returns (snapped_position, floor_normal) when a floor-angle surface is within reach, else None. Callers gate this on their own grounded history (grounded last step, not moving upward) – an ungated snap cancels jumps and teleports the tail of every free fall.