Compare FPS
Lower FPS means a larger delta_time. One-frame movement increases, but the total over one real second remains equal to the chosen speed.
0.0167 s 0.1000 u 6.00 u/s Movement in cub3D is applied from intent, not directly from raw key events.
The engine first computes frame-based displacement, then resolves collision
against the map so the player never crosses blocking geometry.
Frame timing
FPS varies with the machine and rendering workload. The engine measures elapsed time since the previous frame, then multiplies speed by that time to keep movement consistent.
Lower FPS means a larger delta_time. One-frame movement increases, but the total over one real second remains equal to the chosen speed.
0.0167 s 0.1000 u 6.00 u/s Without delta_time, adding a fixed amount each frame makes the player faster on a computer that renders more frames.
360 u 6 u Time comes from gettimeofday. The first frame uses a default value, and large spikes are clamped with FRAME_DT_MAX.
now = get_time_seconds();
app->delta_time = now - app->last_frame_time;
if (app->delta_time > FRAME_DT_MAX)
app->delta_time = FRAME_DT_MAX;
app->last_frame_time = now; `MOVE_SPEED` is a speed in units per second. Multiplying it by `delta_time` gives the small distance to apply this frame.
move_with_collision(app,
app->dir_x * MOVE_SPEED * app->delta_time,
app->dir_y * MOVE_SPEED * app->delta_time); Number of frames produced per second. It varies by machine.
Elapsed time since the previous frame.
Desired player speed in units per second.
movement = 6 * 0.0167 = 0.1000 u per frame Collision pipeline
Movement starts as a desired `(dx, dy)` from input and `delta_time`. Collision then validates each axis independently, which is what allows smooth wall sliding instead of hard rejection at corners.
compute intended movement Movement helpers build frame-based displacement from camera vectors.
// move_forward / move_backward along dir
dx = dir_x * move_speed * delta_time
dy = dir_y * move_speed * delta_time
// strafe_left / strafe_right use the perpendicular direction
dx = dir_y * move_speed * delta_time
dy = -dir_x * move_speed * delta_time resolve collision per axis X and Y are validated separately inside small movement steps, so blocked corners still allow sliding.
new_x = player.x + dx apply X only if center and radius samples stay clear
new_y = player.y + dy apply Y only if center and radius samples stay clear
Click a floor cell to toggle a wall. The moving dot keeps trying to advance and demonstrates why split X/Y checks slide along geometry.
player (1.50, 3.00) solidity source stays shared Collision and raycasting must agree on what blocks the world.
`1` or any mandatory solid blocks movement immediately
bonus door logic reports it as solid
door query returns passable state
collision depends on current open progress
post-move invariants The player remains in navigable space, corner cases are stable, and blocking logic stays aligned with the render path.
Movement is driven by input flags and frame timing through app->delta_time.
The main motion components are:
This keeps controls stable across frame rates.
The current code uses frame-based displacement, sub-stepping, and radius sampling before committing player position.
Core flow:
dx and dyThis prevents wall crossing, reduces corner clipping, and keeps movement stable
when delta_time changes.
The engine does not validate one combined (x + dx, y + dy) jump only.
Instead, it resolves axes independently.
That design matters because:
This is why the player can “glide” along a wall instead of sticking the moment one side collides.
Collision logic must stay aligned with rendering logic.
That means blocking queries take into account:
If collision and raycasting disagree on solidity, the player would see geometry that does not match gameplay behavior.
After each collision-safe update:
srcs/input/move_player.csrcs/input/move_collision.csrcs/input/input_update.csrcs/validation/validate_map_closed.csrcs_bonus/doors/