How We Measure Input Lag
We sample the elapsed time between successive controller input events using performance.now() inside a requestAnimationFrame loop, over a 10-second active window with at least 30 valid samples. The result reports polling-loop latency - how quickly the browser observes state changes - not thumb-to-photon end-to-end input lag.
What How We Measure Input Lag means
The exact measurement algorithm
Everything below reflects the shipped code in the browser tester. No black box - the source is open, the constants are documented, and every threshold has a specific rationale rather than being marketing round numbers.
- 01
requestAnimationFrame polling loop
The tester runs a continuous polling loop tied to the display's refresh cycle via requestAnimationFrame. On each frame, it reads the current Gamepad snapshot with navigator.getGamepads(), compares each axis and button against the previous frame's values, and records a timestamp with performance.now() the moment any input change is detected.
- 02
Change detection thresholds
A change qualifies as an event when any axis moves by more than 0.02 in absolute value from the previous reading, or when any button's pressed state flips. The 0.02 axis threshold filters out sensor noise on centered sticks; without it, drift-prone controllers would generate false events every frame and skew the result artificially low.
- 03
Inter-event delta capture
When a change fires, the tester records delta = performance.now() - timeOfPreviousChange. That delta is the interval the browser took to observe the next distinct input from the controller. Deltas above 500ms are dropped as outliers - those represent pauses between input bursts, not measurement latency.
- 04
10-second sample window
The active window runs for exactly 10,000ms after the user presses start. During that window, users are instructed to continuously rotate both sticks and tap buttons - the goal is a dense stream of state changes so we have enough deltas to statistically characterize the controller's real behavior.
- 05
Aggregate metrics
At window close, we require at least 30 valid deltas to report a result. From those we compute min, mean, 95th percentile, and max, plus a 12-bucket histogram of the full distribution. The mean is what gets graded against the threshold table; the histogram surfaces bimodal or jittery behavior that a single average would hide.
How We Measure Input Lag reported latency thresholds
The verdict bands below are GPADLAB interpretation bands used consistently in the tester's result panel and the Controller Health Score latency stage. They apply to this browser-observed polling-loop measurement, not to hardware-instrumented end-to-end latency - a controller reporting 4ms here is not equivalent to a 4ms button-to-screen result.
| Mean inter-event delta | Verdict | Meaning |
|---|---|---|
| < 5ms | Excellent | Excellent browser-observed polling response in this test. Full 150 points in the Controller Health Score latency stage. |
| 5-10ms | Good | A strong browser-observed result. Actual end-to-end input latency can still differ by controller, connection, platform, game, and display. Falls in the 90% health-score bucket. |
| 10-20ms | Acceptable | A moderate browser-observed interval. Connection mode, controller firmware, platform, browser scheduling, and system load can all influence the result. Falls in the partial-severity bucket for scoring. |
| 20-40ms | Degraded | A slower browser-observed interval. Repeat the test under similar conditions and compare connection modes before attributing the result to a specific cause. |
| ≥ 40ms | Problematic | A high browser-observed interval. Re-test under controlled conditions and investigate connection, system load, browser, firmware, and controller behavior. Scores zero in the latency stage. |
Thresholds are hardcoded as T_EXCELLENT=5, T_GOOD=10, T_ACCEPTABLE=20, T_DEGRADED=40 in LatencyTester.tsx and mirrored by scoreLatency() in scoring/index.ts. Both files are the source of truth if there's any doubt.
Fix How We Measure Input Lag issues
Related glossary terms
How We Measure Input Lag questions
No, and this distinction matters. The tester reads navigator.getGamepads() inside a requestAnimationFrame loop and timestamps when successive state changes become observable to the page. Controller reporting behavior, USB or wireless transport, the operating system, browser scheduling, and display cadence can influence when those changes are observed, but browser-side timing cannot isolate or directly sum those stages. The test does not timestamp the physical button press at the controller, transport arrival at the OS, game-engine processing, or the final on-screen response. Hardware-instrumented end-to-end measurements therefore answer a different question and are not directly comparable to this browser-observed result.
Two reasons. First, that equipment costs $500+ and requires a physical setup on every controller - not feasible for a browser-based tool anyone can use in ten seconds. Second, hardware-instrumented tests measure a fundamentally different quantity (thumb-to-photon) that includes display latency, which varies by monitor. Our measurement isolates a component of the input chain - the polling-loop delta - that is comparable across sessions and controllers on the same host machine. Different tool, different job.
The 10-second window balances two competing needs. Long enough to gather 200-500 valid deltas on a controller being actively worked (dense input yields dense samples); short enough that users will actually complete the test without getting bored and stopping early. In testing, extending the window to 30 seconds changed the reported mean by less than 5% on stable controllers - the sample size is already large enough for statistical significance at 10 seconds.
Below 30 samples, the standard error of the mean is large enough that reporting a specific latency number would be misleading precision. If the tester ends the window with under 30 valid deltas, it silently discards the run and returns the user to a ready state - a signal to redo the test with more continuous input. This threshold matches the sampleCount < 30 check in scoreLatency() from the Controller Health Score scoring library.
The Gamepad API reports floating-point axis values that can fluctuate slightly frame-to-frame even on a stationary stick. A very small threshold can cause sensor jitter or stick drift to be counted as repeated input changes, which can skew the observed interval downward. GPADLAB therefore uses 0.02 as an implementation threshold to reduce noise while remaining responsive to deliberate stick movement. It is a practical change-detection threshold, not a hardware calibration value.
In this inter-event method, a delta above 500ms usually reflects a long pause between user-generated state changes rather than a dense sequence of inputs suitable for the calculation. Including those pauses would make the average depend heavily on how continuously the user moved the sticks or pressed buttons. GPADLAB therefore excludes deltas above 500ms from this calculation; the cutoff is an input-sampling rule, not a claim about the maximum possible hardware latency of a controller.
First-party controller latency figures, when published, may be measured with hardware setups or definitions that are not directly comparable to this browser-based method. GPADLAB results are most useful as a comparative signal when the same host, browser, connection conditions, test procedure, and repeated runs are used. A difference between two single runs should not automatically be treated as a hardware-level latency difference because requestAnimationFrame cadence, browser scheduling, user input pattern, and system load can affect the observed values.
Yes - this is the most important caveat. Chrome, Firefox, and Safari all throttle background tabs' requestAnimationFrame callbacks to 1fps or lower. Running the test in a background tab will produce meaningless results. The tester requires an active, foreground tab. We do not currently gate the test on document visibility, but adding that check is on the polish backlog. In the meantime, run the test on the active tab and don't switch tabs during the 10-second window.
Further reading
- Gamepad API - MDN Web Docs · MDN Web Docs
- Performance.now() - high-resolution timestamps · MDN Web Docs
- requestAnimationFrame throttling in background tabs · Chrome for Developers