# Input Buffering

> Input buffering stores a press for ~0.1 s and performs it as soon as the action becomes possible, so slightly early inputs still count.

- Category: Game Feel › Timing
- Level: common
- Tags: input buffer, jump buffer, combo, responsiveness, input forgiveness
- Updated: 2026-11-01
- URL: https://gdcs.coody.app/game-feel/timing/input-buffering

## What it is

Players press buttons slightly **before** an action is allowed: jump a few frames before landing, attack while the previous swing is still recovering, dodge just before a stun ends. If the game only checks the button on the exact frame it is legal, those presses vanish and the controls feel sticky.

An input buffer records the press with a timestamp, or a countdown timer, and keeps checking it for a short window. The first frame the action becomes legal inside that window, it fires and the buffer is cleared. Presses older than the window are discarded, so a stale press does not trigger something the player no longer wants.

The jump buffer is the mirror image of [coyote time](https://gdcs.coody.app/game-feel/timing/coyote-time): coyote time forgives late presses, buffering forgives early ones. Fighting and action games extend the idea to a short queue of commands so combos chain on the first legal frame.

## Key formulas

- **Buffered action fires when:** $\text{legal}(t) \;\land\; t - t_{press} \le T_{buffer}$

## Rules of thumb

- Jump buffers of 0.1–0.15 s (6–9 frames at 60 fps) feel natural. Attack and dodge buffers are often 0.15–0.3 s.
- Buffer one action per type, and let a newer press overwrite an older one.
- Clear the buffer when it is consumed, and on state changes such as death, pause or cutscenes.
- Store time in seconds (or a timestamp), never in frames, so the window does not shrink at high refresh rates.

## Implementation

Jump buffer next to a coyote timer:

```ts
const BUFFER = 0.12 // seconds
let bufferTimer = 0

function update(dt: number, jumpPressed: boolean) {
  if (jumpPressed) bufferTimer = BUFFER
  else bufferTimer = Math.max(0, bufferTimer - dt)

  const canJump = player.grounded || coyoteTimer > 0
  if (bufferTimer > 0 && canJump) {
    player.velocity.y = JUMP_SPEED
    bufferTimer = 0
    coyoteTimer = 0
  }
}
```

A tiny action buffer for combos:

```text
on press(action):  buffered = { action, time: now }
every frame:
  if buffered and now - buffered.time > WINDOW: buffered = null
  if buffered and canPerform(buffered.action):
      perform(buffered.action); buffered = null
```

## Common mistakes

- Reading input in an event handler and acting on it immediately, bypassing the buffer and the state checks.
- Never expiring the buffer, so a jump pressed mid-air fires a second later and feels like a glitch.
- Buffering in frames, which cuts the window from 100 ms to 42 ms on a 144 Hz display.
- Not clearing the buffer when it fires, causing a double jump on the next frame.

## Interactive visual

The cube presses jump in mid-air at the yellow line. The strip below is a zoomed timeline: yellow is the press, white is touchdown, the cyan bar is the buffer window and the grey playhead is now. Runs alternate between buffer on (the press survives until touchdown, the cube turns green and hops again) and off (the press is dropped, it lands red and stops). Push the press earlier than the window to see it expire.

## Prompt

```text
Implement a jump input buffer for a TypeScript Three.js platformer. When jump is pressed, set bufferTimer = 0.12 s; count it down by dt each update. On any update where the character can jump (grounded or coyote time left) and bufferTimer > 0, jump and clear both timers. Clear the buffer on death or pause. Read raw key presses into a per-frame `pressed` flag so the controller never acts inside event handlers. Write Vitest tests at 30, 60 and 144 fps: a press 0.08 s before landing jumps on the landing frame, a press 0.2 s before does not, and one press never gives two jumps.
```

## Related

- [Coyote Time](https://gdcs.coody.app/game-feel/timing/coyote-time.md) — Coyote time keeps a jump available for ~0.1 s after the character walks off an edge, so presses that feel on time are not eaten by the game.
- [Finite State Machine](https://gdcs.coody.app/ai/decision/finite-state-machine.md) — An FSM keeps an AI or character in exactly one state, such as Idle, Patrol, Chase or Attack. A table of events decides every legal transition.
- [Delta Time](https://gdcs.coody.app/game-feel/timing/delta-time.md) — Delta time is the seconds since the last frame. Multiply speeds and rates by it so a game plays the same at 30, 60 or 144 fps.

## Further reading

- [Game Programming Patterns — Command](https://gameprogrammingpatterns.com/command.html)
- [MDN — KeyboardEvent](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent)

---
Source: Game Development Cheat Sheet (https://gdcs.coody.app)
