“Move three pixels each frame” makes a faster computer change the rules. Use speed in pixels per second and pass elapsed seconds into the update. At 150 pixels/second, a .02-second step moves three pixels, while a .01-second step moves one and a half.
The model does not read a clock. The desktop loop calls Clock.tick(60) once per frame and divides its millisecond result by 1000. Requesting a 60-frame cap does not guarantee every frame takes exactly one sixtieth of a second. Tests pass explicit elapsed values.
The lab caps simulated elapsed time at .05 seconds. A breakpoint or dragged window can produce a much longer interval; applying all of it in one movement would make the ball leap. This policy discards the extra time. The game slows during a stall rather than catching up. More advanced games may use an accumulator and fixed substeps; that is a different policy with additional limits to design.
speed, cap = 150, .05
for elapsed in [.01, .02, 2.0]:
simulated = min(elapsed, cap)
print("seconds", elapsed, "simulated", simulated, "pixels", speed * simulated)The paddle coordinate is its center. Keep it between half its width and board width minus half its width. Clamp after computing the candidate position. The ball moves down; its x stays fixed until the scoring transition spawns the next ball.
Positions stay floating-point in the model. Round only when making a render plan. Rounding each update can lose small movements and make speed depend on frame count.
The supplied validator rejects negative, infinite and NaN elapsed values, Boolean axes and axes outside −1/0/+1. The update changes positions only: counters, turn and mode belong to later transitions. A paused world must remain equal to its input.
x, speed, elapsed = 434.0, 260.0, .02
half, width = 45, 480
candidate = x + speed * elapsed
clamped = max(half, min(width - half, candidate))
print("candidate", candidate)
print("center", clamped)
print("right edge", clamped + half)Test .02 seconds, a stall, both board edges, zero elapsed and a paused world. A motion-only partition test is useful before clamping or scoring occurs. Do not generalize it into a claim that all discrete collision outcomes are frame-rate independent.
Primary reference: Pygame Clock.tick. Next we turn state into visible shapes without letting drawing advance the simulation.