Input Systems — Deep Engineering Guide
Input is the player's handshake: it must be lag-free, remappable, device-agnostic and deterministic. This skill covers device abstraction, action mapping, raw sampling vs event flows, text/IME, touch/gestures, haptics and the input-to-simulation timing that separates "responsive" from "floaty".
1. The Three Layers
device world (keyboard/mouse/stick/controller/gamepad/touch)
→ InputProvider (concrete device driver)
→ InputSystem (maps physical → logical actions, accumulates)
→ Game (consumes actions; sim at fixed tick)
The action abstraction is what makes one input system serve every device + remap UIs.
2. Device Abstraction
struct InputEvent { Device device; DeviceId id; float3 raw; ButtonState state; };
| Device |
Raw data |
Notes |
| Keyboard |
key codes |
layout-aware (AZERTY!) |
| Mouse |
dx/dy, buttons, wheel |
raw vs OS-corrected |
| Gamepad |
axis, buttons, triggers |
analog, deadzone |
| Touch |
touches[], gesture stream |
multitouch |
| VR |
controller linear/angular |
motion |
| Pen/touchpad |
extended |
per-OS |
The abstraction must retain the raw for remap-sensitive consumers — never bake layout into the engine core (see device-abstraction.md).
3. Action Mapping (The Remap Surface)
// logical: Jump (physical: Space | GamepadA)
// triggered by any binding that matches
struct Action { vector<Binding> bindings; Activation mode; };
- Actions express intent (
Move, Attack, Talk) — the game reads actions only, never key codes.
- Bindings:
{key, gamepadButton, axis-min/max, deadzone, doubleTap, hold, drag}.
- Conflicts: an action triggered by multiple bound devices — resolve by priority + first-arrival (never two).
- Remap UI edits the bindings; the engine logic never changes.
4. Sampling vs Events
| Style |
Use |
Tradeoff |
| Poll (sample) every frame / fixed-tick |
deterministic sims, netcode |
1 sample/frame |
| Event (queued) fired on change |
UI, menus, gestures |
lossless, ordering |
| Accumulated (delta) |
mouse movement, scroll |
sums raw deltas |
Gameplay = poll the action state at the fixed tick (deterministic). UI/menus = events. Mouse = accumulated delta between polls (the "eats the delta" contract).
4.1 The Fixed-Tick Braid
// sim tick consumes the latest action state — never processes events mid-tick
frame: poll → build actionSamples[tick] → sim(tick) → render
Determinism requires: input snapshot per fixed tick, ordered, deduplicated (see sampling-and-events.md).
5. Text & IME (The Hidden Beast)
- Text input ≠ keydown: a keyboard produces characters (layout + modifiers), and CJK uses an IME composition window.
- Engine must route through the platform IME:
compositionStart/update/end + the committed string.
- Differentiator: paste handling, dead-keys, numpad, and per-UI-context focus (chat field vs game).
Details in text-and-ime.md — a surprising chunk of "input bugs" live here.
6. Touch & Gestures
- Touch = absolute multitouch stream (down/move/up per touch id).
- Gestures are recognized upstream of game logic: tap, long-press, drag, pinch, double-tap, swipe — each with begin/update/end + a cancel case.
- Never synthesize mouse from touch frames (the mouse is a different device with a cursor concept).
7. Haptics & Feel
| Device |
Haptics |
| Gamepad |
rumble (left/right motor), pulse frequencies |
| PS5/VR |
adaptive triggers, high-fidelity haptics |
| Mobile |
vibration (primitive) |
| Desktop |
— (rare) |
Haptic events are timed impulses ("hit feedback 20 ms, 60% L / 0% R"), remapped per-device, rate-limited (rumble spam ruins feel). See haptics-and-tuning.md.
8. Input-to-Sim Latency (The Feel Metric)
| Stage |
Budget |
| device → provider |
OS-level (~1–4 ms console, up to ~poll-rate) |
| provider → poll |
< 1 ms |
| poll → sim tick |
≤ 1 tick (16.6 ms @60) |
| sim → render |
≤ 1 frame |
| full click-to-result |
≤ ~33–50 ms (imperceptible), p95 < 80 ms |
Threads/queue/timing decisions that add a tick of latency are the feel killers — measure, don't assume.
9. The Input → Simulation Contract (For Netcode)
Serialized online: the polling must be fixed-tick snapshot per tick (see authoritative-server input authority) with a deterministic order per tick, and redundant presses coalesced. Client prediction replays it identically.
10. References
references/device-abstraction.md — drivers, raw data, layout-awareness, device unplug/replug
references/action-mapping.md — bindings, deadzones, modifiers, remap UI, priority/conflicts
references/sampling-and-events.md — poll vs event, accumulated deltas, fixed-tick braid, determinism
references/text-and-ime.md — text input, IME composition, layout, focus routing
references/touch-and-gestures.md — multitouch, gesture recognizers, cancel semantics, areas
references/haptics-and-tuning.md — rumble/haptics model, rate limits, feel calibration
1---2name: input-systems3description: Expert game input systems — device abstraction, action mapping, sampling vs events, text/IME, touch/gestures, haptics and input-to-game timing.4---56# Input Systems — Deep Engineering Guide78Input is the player's handshake: it must be lag-free, remappable, device-agnostic and deterministic. This skill covers device abstraction, action mapping, raw sampling vs event flows, text/IME, touch/gestures, haptics and the input-to-simulation timing that separates "responsive" from "floaty".910## 1. The Three Layers1112```13device world (keyboard/mouse/stick/controller/gamepad/touch)14 → InputProvider (concrete device driver)15 → InputSystem (maps physical → logical actions, accumulates)16 → Game (consumes actions; sim at fixed tick)17```1819The **action abstraction** is what makes one input system serve every device + remap UIs.2021## 2. Device Abstraction2223```cpp24struct InputEvent { Device device; DeviceId id; float3 raw; ButtonState state; };25```2627| Device | Raw data | Notes |28|--------|----------|-------|29| Keyboard | key codes | layout-aware (AZERTY!) |30| Mouse | dx/dy, buttons, wheel | raw vs OS-corrected |31| Gamepad | axis, buttons, triggers | analog, deadzone |32| Touch | touches[], gesture stream | multitouch |33| VR | controller linear/angular | motion |34| Pen/touchpad | extended | per-OS |3536The abstraction must *retain the raw* for remap-sensitive consumers — never bake layout into the engine core (see `device-abstraction.md`).3738## 3. Action Mapping (The Remap Surface)3940```cpp41// logical: Jump (physical: Space | GamepadA)42// triggered by any binding that matches43struct Action { vector<Binding> bindings; Activation mode; };44```4546- Actions express *intent* (`Move`, `Attack`, `Talk`) — the game reads actions only, never key codes.47- Bindings: `{key, gamepadButton, axis-min/max, deadzone, doubleTap, hold, drag}`.48- Conflicts: an action triggered by multiple bound devices — resolve by priority + first-arrival (never two).49- Remap UI edits the bindings; the engine logic never changes.5051## 4. Sampling vs Events5253| Style | Use | Tradeoff |54|-------|-----|----------|55| **Poll (sample)** every frame / fixed-tick | deterministic sims, netcode | 1 sample/frame |56| **Event (queued)** fired on change | UI, menus, gestures | lossless, ordering |57| **Accumulated (delta)** | mouse movement, scroll | sums raw deltas |5859Gameplay = poll the *action state* at the fixed tick (deterministic). UI/menus = events. Mouse = accumulated delta between polls (the "eats the delta" contract).6061### 4.1 The Fixed-Tick Braid6263```cpp64// sim tick consumes the latest action state — never processes events mid-tick65frame: poll → build actionSamples[tick] → sim(tick) → render66```67Determinism requires: input snapshot per fixed tick, ordered, deduplicated (see `sampling-and-events.md`).6869## 5. Text & IME (The Hidden Beast)7071- Text input ≠ keydown: a keyboard produces *characters* (layout + modifiers), and CJK uses an **IME** composition window.72- Engine must route through the platform IME: `compositionStart/update/end` + the committed string.73- Differentiator: paste handling, dead-keys, numpad, and per-UI-context focus (chat field vs game).7475Details in `text-and-ime.md` — a surprising chunk of "input bugs" live here.7677## 6. Touch & Gestures7879- Touch = absolute multitouch stream (down/move/up per touch id).80- **Gestures** are recognized *upstream* of game logic: tap, long-press, drag, pinch, double-tap, swipe — each with begin/update/end + a cancel case.81- Never synthesize mouse from touch frames (the mouse is a different device with a cursor concept).8283## 7. Haptics & Feel8485| Device | Haptics |86|--------|---------|87| Gamepad | rumble (left/right motor), pulse frequencies |88| PS5/VR | adaptive triggers, high-fidelity haptics |89| Mobile | vibration (primitive) |90| Desktop | — (rare) |9192Haptic events are *timed impulses* ("hit feedback 20 ms, 60% L / 0% R"), remapped per-device, rate-limited (rumble spam ruins feel). See `haptics-and-tuning.md`.9394## 8. Input-to-Sim Latency (The Feel Metric)9596| Stage | Budget |97|-------|--------|98| device → provider | OS-level (~1–4 ms console, up to ~poll-rate) |99| provider → poll | < 1 ms |100| poll → sim tick | ≤ 1 tick (16.6 ms @60) |101| sim → render | ≤ 1 frame |102| **full click-to-result** | **≤ ~33–50 ms** (imperceptible), p95 < 80 ms |103104Threads/queue/timing decisions that add a tick of latency are *the* feel killers — measure, don't assume.105106## 9. The Input → Simulation Contract (For Netcode)107108Serialized online: the polling *must* be fixed-tick snapshot per tick (see `authoritative-server` input authority) with a deterministic order per tick, and redundant presses coalesced. Client prediction replays it identically.109110## 10. References111112- `references/device-abstraction.md` — drivers, raw data, layout-awareness, device unplug/replug113- `references/action-mapping.md` — bindings, deadzones, modifiers, remap UI, priority/conflicts114- `references/sampling-and-events.md` — poll vs event, accumulated deltas, fixed-tick braid, determinism115- `references/text-and-ime.md` — text input, IME composition, layout, focus routing116- `references/touch-and-gestures.md` — multitouch, gesture recognizers, cancel semantics, areas117- `references/haptics-and-tuning.md` — rumble/haptics model, rate limits, feel calibration