The controller and input schemes
The mechanism
A controller is a signal path from the finger to the processor. Let's take it link by link: alphabet → wire → polling clock → latency pipeline → gesture parser.
The D-pad: 8 directions from 4 buttons
Before the cross there was the arcade joystick — bulky, fragile, no good in a pocket. Gunpei Yokoi (Game & Watch, 1982, then the NES) reduced direction to a cross of 4 momentary switches (U/D/L/R). Press between two arms and you close both → a diagonal. How many states 4 bits give and how many of them are legal:
The vertical ∈ {U, D, nothing}, the horizontal ∈ {L, R, nothing} → 3 × 3 = 9 states (neutral plus 8 directions). Opposing pairs (U+D, L+R) are physically impossible or ignored — hence not 16. Cheap (4 penny switches), sturdy (no lever), flat — the key to the handheld. Yokoi's philosophy: "lateral thinking with withered technology" — take cheap mature hardware and use it in an unusual way.
The wire: latch and serial shift
The NES controller is a 4021, an 8-bit shift register. The CPU doesn't run 8 wires from 8 buttons. Instead: a latch signal loads the state of all 8 buttons into the register in parallel at once; then the CPU clocks 8 times, and on each clock one bit falls out of the single wire at $4016 — serially. The order is fixed: A, B, Select, Start, Up, Down, Left, Right. So a poll costs:
Why serial, if parallel is "faster"? Because the cycle isn't expensive, the wire is: 8 conductors in a cable and 8 contacts in a connector cost money and space. One data output + latch + clock = 3 signals for any number of buttons. The same technique as SPI/I²C: serialize to save pins. (The buttons are pulled up: unpressed = 1 in the 4021, inverted for the CPU.)
The polling clock and the latency pipeline
The console usually reads the pad once a frame, during vblank (via NMI). So input is quantized to 1/60 s — between two polls the world knows nothing about a button. From there the press travels the pipeline: poll → frame logic → simulation → rendering → scanout to the screen. Each link is a frame. Total finger-to-pixel latency:
A worked example. The finger presses right after frame N's poll → it waits ~1 frame for the next poll, +1 frame of processing, +1 frame of scanout before it appears on screen:
On a 1980s CRT that was the entire lag; a modern wireless pad plus a buffering TV adds more frames. Polling once a frame versus an edge-triggered interrupt is the classic "poll vs interrupt" choice: the pad is polled (simple, in sync with the frame), though in theory it could raise an interrupt on a press.
The gesture parser: motions and the input buffer
Fighting games turned the 8-direction pad into an alphabet of gestures. Special moves are patterns of directions plus a button:
- Hadoken — quarter-circle-forward:
↓ ↘ →+ punch. - Shoryuken (the "dragon punch", a Z motion):
→ ↓ ↘+ punch. - Tatsumaki — quarter-circle-back:
↓ ↙ ←+ kick.
The engine keeps a sliding window of recent presses (the input buffer, ~8–16 frames) and matches it against gesture templates. The buffer also forgives timing: a command entered a couple of frames early still fires. The Konami code (↑↑↓↓←→←→BA) is the same buffered sequence, and combos are chains within frame windows. This is sequence recognition on a stream: the window is context, a match is a tiny sequence model.
🕹 Games to play — and what to notice
The signal path from "2 buttons and a cross" to "a gesture as a word" and "6 buttons plus shoulders". For each: what the input link does and what to notice with your hands.
A D-pad + 2 buttons (A/B) + Start/Select. The whole "language" is 8 directions and two buttons. The Konami code (↑↑↓↓←→←→BA) is a buffered sequence read by the same shift register; in Contra it gives you 30 lives.
🎮 Play: enter the Konami code on Contra's title screen. Notice: what matters is the order and the window of the input — this is sequence recognition out of the shift register's stream, not a "magic button".
The A button is binary (pressed or not), but jump height depends on how long you hold it: the game counts how many frames A is held and feeds that to the physics. Continuous nuance squeezed out of a single bit — interpretation of input, not new hardware.
🎮 Play: in SMB, tap A briefly and hold it long — the height differs. That is "how many frames the button was held", read by polling every frame; pure interpretation of a binary input.
The pad or stick became an alphabet: ↓↘→ + punch = Hadoken. The engine matches a window of inputs against a template; the window width is the "ease versus false positives" dial. An arcade stick and a pad give different input precision for the same gestures.
🎮 Play: throw some Ryu Hadokens. Catch how a fireball sometimes comes out by accident while walking and jumping (the pattern matched inside the window), and how sometimes a sharp motion "doesn't read". That is the recognition buffer in the flesh.
The NES gave 2 buttons, the SNES 4 face buttons (A/B/X/Y) plus 2 shoulders (L/R). More buttons = more verbs without motions: item switching, a dedicated "run", aiming. The shoulders later opened up control schemes for racing and Mode 7 games.
🎮 Play: in any SNES game, count how many distinct actions hang off 6 buttons versus 2 on the NES. Notice how the shoulders act as a "modifier" (like Shift on a keyboard) — that is an expansion of the alphabet, not of speed.
The cross's main dividend: a direction control flat enough for a pocket. The same 4-switch cross and 2 buttons as on the NES, but in a handheld — precisely Yokoi's "lateral thinking with withered technology".
🎮 Play: fire up any Game Boy emulator and feel that the input scheme is identical to the NES. The cross won not on expressiveness but on form factor — it fit where a joystick could not.
Deep end · engineering: why polling rather than interrupts, and where the extra lag hidesskippable
Input can be read two ways, and the choice is not accidental.
Poll vs interrupt
Polling: read the pad's state once a frame — simple, deterministic, in sync with the simulation (input is tied to the same fixed step as the physics, see m01). Interrupts: fire a handler on every press — instant reaction, but asynchronous, breeding races with state updates and non-determinism (bad for replays and net play). Games take polling precisely for determinism: frame N's input belongs to frame N. The price is that events shorter than a frame are invisible.
Where lag accumulates beyond the minimum
- Polling late in the frame: read the pad late and you lose up to a frame.
- A deep render pipeline: 2–3 frames "in flight" for throughput (like batching in inference: throughput ↑, latency ↑).
- The display/TV buffer: post-processing and an overdrive frame buffer — more frames.
- A wireless pad: the radio stack adds variable latency.
Minimizing finger→pixel = poll early + keep the pipeline shallow + use an honest display. That is a latency budget, the same one any interactive service has.
Polling-rate aliasing
Polling at 60 Hz caps the fastest input you can distinguish: a tap shorter than ~16.7 ms that starts and ends between polls is invisible (Nyquist for fingers). TAS runs exploit this frame by frame; a human has to hold the input across the poll boundary.
Deep end · design: the input buffer as a "precision ↔ forgiveness" dialskippable
The input buffer is a window of the last frames, over which a gesture or command is recognized. The window width is a single but powerful dial.
A wider window — easier input, more false positives
The gesture ↓↘→ matches if all three directions occur within frames. A large : even a dirty, slow motion fires (easier for beginners), but false positives grow — a fireball during an ordinary walk-and-jump. A small : clean, but it demands precision. This is the same precision/recall curve as a wake-word detector: the sensitivity threshold drives the "misses vs false alarms" trade.
Forgiving timing
The same buffer forgives early input: a jump command a couple of frames before landing is queued and fires on the first legal frame. In platformers this is "jump buffering" (see game feel) — but there it is the output side (the feeling of responsiveness), and here it is the input side (the mechanics of recognition from a stream). One buffering technique, two sides of the link.
The connection to combos
A combo is a chain of moves, each with its own cancel window: land a hit → for N frames you can "cancel" the recovery into the next move. Combo timing is a sequence of buffer windows; frame data (see the crystallization of genres) determines which windows exist at all.
ML / AI (your domain): the finger→pixel budget = the inference latency budget; streaming LLMs are measured by TTFT (time to first token) — the same "request → first pixel". The input buffer ⇄ action chunking in imitation learning and robot policies (ACT, VLA: predict a buffer of several actions and play them back over several steps — output buffering). Recognizing a motion within a window ⇄ sequence classification over a sliding window (the buffer window = context; a match = a small seq model; the window width = precision/recall, as with a wake word). And laying actions out across buttons ⇄ action-space design in RL: however you parameterize the actions is what can be learned.
Systems / hardware: the shift register = SPI/I²C and serialization to save pins; the latch = sample-and-hold; poll vs interrupt = busy-poll versus epoll/IRQ and an event loop; poll quantization = sampling rate and Nyquist.
UX / frontend: the input budget = perceived responsiveness (input latency); debounce/throttle = the same input buffer; the action layout = affordances and modifier hotkeys (Shift = the gamepad's shoulder).
The principle: serialize to save channels; poll at a fixed rate and count the whole input→response pipeline; keep a windowed buffer to recognize gestures and forgive timing.
$4016 — you'll catch the latch + 8 clocks of the pad poll once a frame (in NMI/vblank). Turn on the frame latency display. Compare a game that polls the pad early in the frame with one that polls late — the difference in lag is visible. In a fighting game emulator, find the input buffer window: enter a motion "dirty" and "clean" and watch when it matches.dt. Deterministic input = a deterministic loop.Why read the pad serially (1 wire, 8 clocks) if parallel is "faster"?
If input is polled once a frame, what happens to a tap shorter than 16.67 ms?
How does the game tell a deliberate Hadoken (↓↘→) from accidentally passing through those directions while walking?
Why wasn't the D-pad made analog right away — isn't analog strictly better?
The latency pipeline is several frames; why not "poll, process, display" in a single frame and kill the lag?
- NESdev Wiki — Standard controller, 4021, Controller reading: the primary source on latching and serial reads.
- David Sheff, "Game Over" — Gunpei Yokoi and "lateral thinking with withered technology" (the origin of the D-pad).
- The FGC community: "frame data" and "input buffer" — how motions and combos are read.
- Module 2, "Console Wars", the Controllers / Fighting Game Balance sections (
02-console-wars-1985-1993.md).