Why the HUD cannot answer this
The receiver reports each stage between two stamps on one clock, which is honest and deliberately not the whole story. What none of it captures:
- the panel's own response time
- the compositor's final scanout after
requestAnimationFrame fires
- anything before ScreenCaptureKit hands over a frame
So the sum of the HUD's rows is a floor, not the latency a person feels. The gap between them is unknown, and it is the number that actually matters.
The method
The standard one, and it needs no instrumentation at all:
- Put a millisecond timer on the Mac's own screen — a stopwatch, or a page that prints
performance.now().
- Drag it onto the second display, so both the Mac's screen and the receiver show it.
- Film both screens in one frame with a high-frame-rate camera. A recent phone at 240 fps gives ~4 ms resolution, which is plenty.
- Step through the footage and read the difference. Do it across a few dozen frames — the distribution matters more than any single reading, and the worst case is what gets noticed.
What to report
- Median and worst glass-to-glass, in milliseconds
- The camera's frame rate, since it bounds the precision
- The link, the resolution, the frame rate
- The HUD rows at the same moment, so the two can be compared
That last point is the whole value. With both, the difference between measured-and-felt and measured-by-the-app is finally a known quantity instead of an assumption.
Why it is worth the trouble
Every latency change in this project so far has been argued from mechanism — a queue that was unbounded, a Nagle delay, an encoder flag that was never set. All of them are sound reasoning and none of them has been confirmed to have moved the number a person experiences. This is how that gets settled.
Why the HUD cannot answer this
The receiver reports each stage between two stamps on one clock, which is honest and deliberately not the whole story. What none of it captures:
requestAnimationFramefiresSo the sum of the HUD's rows is a floor, not the latency a person feels. The gap between them is unknown, and it is the number that actually matters.
The method
The standard one, and it needs no instrumentation at all:
performance.now().What to report
That last point is the whole value. With both, the difference between measured-and-felt and measured-by-the-app is finally a known quantity instead of an assumption.
Why it is worth the trouble
Every latency change in this project so far has been argued from mechanism — a queue that was unbounded, a Nagle delay, an encoder flag that was never set. All of them are sound reasoning and none of them has been confirmed to have moved the number a person experiences. This is how that gets settled.