フレームでなく、時間で進める
ball.y += ball.vy のようにフレームごとに進めると、速い画面では速く、遅い画面では遅く落ちる。速度に経過時間 dt を掛けて ball.y += ball.vy * dt にすると、1フレームが長くても短くても進む距離が時間に比例して、環境に依らず同じ速さになる。
可変 dt には穴がある。タブを切り替えて戻った直後など dt が跳ねると、一回の更新で進みすぎてボールが壁をすり抜ける。固定タイムステップは、経過時間をアキュムレータに貯め、決まった刻み STEP(標準で 1/60 秒)ぶん溜まるたびに物理を一回回す。可変な現実の時間を一定の刻みに均してから渡すので、dt が荒れても物理に届くのは常に STEP。同じ入力から同じ結果が出る決定性の入口で、Glenn Fiedler の "Fix Your Timestep!" が原典。
経過時間 acc に毎フレームの dt を足し、while (acc >= STEP) で貯まったぶんだけ固定の刻みで step() を回す。重い1フレームは複数回まわして帳尻を合わせ、軽いフレームは一回も回さず次に持ち越す。下では dt をわざと不規則に揺らして、たまに大きく跳ねさせている。ボールは横一列に並び、それぞれ違う速度で落ちて床で跳ね返る。
跳ねるフレームが来ても、ボールは床をすり抜けずに弾む。acc が STEP の何倍貯まっていても、step() に渡る刻みは常に STEP で、跳ねたぶんは複数回の更新に割れる。while の steps < 8 は、極端に重いフレームで更新が無限に積み上がるのを止める上限。STEP を 1/30 に粗くすると一回あたりの落下が大きくなって床の判定が荒れ、1/120 に細かくすると貯まりが速く消えて動きが滑らかになる。