Lesson 6 · Essential

Forgiveness

Players press jump a moment too late at ledges, a moment too early before landing, and clip ceiling corners they never saw. Coyote time, jump buffering and corner correction quietly do what they meant; input delay eats into all of it. Each one measured frame by frame.

A controller that does exactly what the player pressed feels unfair. Players act on what they see, react a little late or a little early, and judge collisions by eye rather than by bounding box. When the game takes them literally, they don’t think “I pressed a frame late”; they think the game ignored them.

So platformers cheat in the player’s favour, in small ways that nobody should notice. Maddy Thorson’s article Celeste & Forgiveness lists ten that Celeste uses. This lesson builds the three every platformer needs, coyote time, jump buffering and corner correction, on the previous lesson’s controller, and then adds the thing that makes all of them matter more: input delay.

Each widget runs the same move many times, changing one thing a little each time (when jump is pressed, or where the box starts), and colours every attempt by what happened. They replay one by one; click a cell under the scene to hold one, or focus the row and use the arrow keys.

Coyote time

Named after the cartoon coyote who runs off a cliff and hangs in the air until he looks down. For a short window after the feet leave the ground, a jump still works.

Loading…

The box runs right at a gap. Cell 0 is the last frame its feet are on the ledge; cells to the right are presses after it has left. With Tight’s 100 ms, a press up to 6 frames after leaving still jumps. Turn coyote time off and a press one frame late does nothing: the box drops into the gap. From the player’s side of the screen the box was at the edge when they pressed, so the dropped jump feels like a missed input, not a mistake.

To build it, keep a timer of time since the character was last on the ground, and allow a jump while it’s under the window, but only if the character didn’t leave the ground by jumping, or coyote time becomes a free double jump off the top of every jump:

bool can_jump = grounded || (!jumped_since_grounded && time_since_grounded <= coyote_time);

80 to 150 ms is typical. Celeste uses 0.1 s (JumpGraceTime in its released Player.cs). Much longer and players notice they can run on air.

Jump buffering

The mirror image. Players press jump a little before they land, expecting to jump the moment they touch down. Without a buffer, only a press made on the ground counts.

Loading…

The box runs off a ledge and drops to the floor. Cell 0 is its first frame on the ground; cells to the left are presses made while still falling. With Tight’s 120 ms buffer, a press up to 7 frames (117 ms) before landing jumps on the first frame on the ground. With the buffer off, a press even one frame early is eaten, and the box lands and runs on.

if (jump_pressed) time_since_press = 0;
else              time_since_press += dt;

if (can_jump && time_since_press <= jump_buffer) {
    jump();
    time_since_press = INFINITY;          // one press, one jump
    if (!jump_held) cut_jump();           // a tap that's already been let go is a hop, not a full jump
}

Two details. Clear the press when it’s used, or one press can jump twice. And if jump is no longer held when the buffered jump fires, apply the jump cut at once, or every early tap becomes a full jump. Keep the window short: at 200 ms every press in the widget jumps, and in play a buffer that long starts firing jumps the player has already given up on.

Corner correction

Jump up beside a ledge and catch its corner with the edge of your head, and a plain collision stops the jump dead. The player didn’t see the overlap: it may be a pixel.

Loading…

Each attempt starts a little further under the ledge (cells left of the line start clear of it) and jumps while holding right. With correction off, none of the starts under the corner reach the top. When rising movement is blocked, corner correction tries the same move nudged a little sideways, smallest nudge first, in the direction of travel before the other, and takes the first that’s clear. With Tight’s 0.35 tiles, 7 of 9 make it; with 0.15, 3 of 9.

if (moving_up && collides(x, new_y)) {
    for (float n = step; n <= max_correction; n += step)
        for (float dir : { travel_dir, -travel_dir })
            if (!collides(x + dir * n, new_y)) { x += dir * n; y = new_y; goto moved; }
    vy = 0;                                // a real ceiling: stop
}

Celeste nudges up to 4 pixels (half a tile) for jumps, and a sideways dash that clips a corner is popped up onto the ledge. Thorson’s list goes on in the same spirit: wall jumps work from 2 pixels away, and jumping off a fast platform keeps its momentum for a few frames after it has stopped.

Input delay

Every frame between pressing a button and the game acting on it makes a character feel heavier and less precise, even when nothing else changes. Nobody chooses it: input read after the simulation instead of before it, a frame of rendering in flight, triple buffering, a television’s processing. The frame pipeline lesson measures where it comes from.

Go back to the coyote widget and pick 3 frames of input delay. Every press now reaches the game 3 frames after it’s made. A press made while the box is still on the ledge reaches the game after it has left, so coyote time spends its window covering the delay: late presses are saved only up to 3 frames instead of 6. With 6 frames none are, and with coyote time off as well, every press from 5 frames before the last frame on the ledge onwards is eaten: the player pressed on the ledge, and fell.

That’s why forgiveness windows are tuned on the target hardware, with its real delay, and why they’re usually a little more generous than a developer at a fast monitor would think necessary. To keep delay down: read input as late as possible before the simulation step, run the simulation at a fixed rate (game loops), and measure the whole chain from button to pixel.

Takeaways

  • Players act on what they see, a little late or early. Forgiveness makes the game do what they meant, invisibly.
  • Coyote time: a jump still works for 80 to 150 ms after leaving the ground, unless you left by jumping.
  • Jump buffering: remember a press for about 100 to 150 ms and fire it on the first frame a jump is possible. Clear it when used; cut it if the button is already up.
  • Corner correction: when rising is blocked, try small sideways nudges and take the first that’s clear.
  • Input delay eats into every window. Tune them with the real delay of the target hardware.

In the lab

The lab’s course has a gap, a low ledge with a corner and a run of steps for exactly these three, and its strip marks each saved, buffered, corrected and eaten jump as you play.

Try the gap with no coyote time and 3 frames of delay in the feel lab →

Further reading