Hold a quarter-circle-forward, press punch a frame late, and the fireball still comes out. Every fighting game you've ever loved lies to you like this, constantly, and the whole genre would fall apart without it.
The problem with honest input
A naive implementation checks, on the exact frame the attack button goes down, whether the motion was completed. Miss by one frame at 60 fps — sixteen milliseconds — and you get a standing punch instead of a special. Players don't read that as their own mistake. They read it as the game being broken.
The buffer isn't there to make the game easier. It's there to make the game agree with what the player believes they did.
What a buffer actually is
A ring buffer of the last N frames of input state. Nothing more exotic than that. Each frame you push the current direction and button mask on; anything older than N frames falls off the end.
const BUFFER_FRAMES := 12
var _buf: Array[int] = []
func _physics_process(_delta: float) -> void:
_buf.push_back(_read_input_mask())
if _buf.size() > BUFFER_FRAMES:
_buf.pop_front()
Move recognition then walks that buffer backwards looking for the motion in order, allowing gaps. A quarter-circle-forward isn't "down, then down-forward, then forward on consecutive frames" — it's "forward appears, and somewhere in the twelve frames before it, down-forward appears, and before that, down."
The three numbers that matter
- Motion window — how many frames the whole motion may span. Around 12–15 frames for quarter circles; 30 or more for charge moves and half circles.
- Attack leniency — how many frames after the motion the button may land and still count. Street Fighter II sat near 8; modern games often run 4–6 because online rollback makes long windows feel mushy.
- Cancel window — how early during an existing attack a queued input may be accepted. This one is what turns two moves into a combo, and it's the one players feel most directly.
Negative edge, the trick nobody asks for
Classic Capcom games also fire the check on button release, not just press. It sounds like a bug, and originally may well have been one, but it lets you hold a button through a motion and release to fire. Once a generation of players learned it, removing it felt worse than keeping it.
Tuning it by feel, not by spec
There's no correct value. Build a training-mode overlay that draws the last 30 frames of input and marks which frame a move was recognised on, then hand the controller to someone who doesn't play fighting games. If they can throw a fireball inside five minutes, your windows are roughly right. If they can accidentally throw one, they're too wide.
I walked through the whole implementation, overlay included, in the video version — it's on the videos page.