Telling motion from stillness
Capture several frames at a fixed interval and report what changed between them.
observe answers did it change, and when did it settle, and returns only the final frame. That is a state machine consuming frames to reach a verdict.
realtime answers a different question — what did it look like along the way — and returns every frame. That is a sampling operation that keeps what it took.
eensh session realtime "$SID" --frames 3 --interval 50ms \
--timeout 500ms --base64 --jsonTwo screenshots
You know the screen was in state A, and later in state B. You do not know whether it passed smoothly between them, jumped, or oscillated.
A three-frame stack
Three frames at known offsets. A spinner has visibly rotated; a frozen frame has visibly not.
When each frame is captured
Sample opportunities sit at exact multiples of the interval, measured from the start of the request. Not one interval after the previous capture finished.
This is the difference between a cadence and a chain. If a capture at 50 ms takes 30 ms, the next opportunity is still at 100 ms — 20 ms away, not 50 ms away — so a slow capture cannot push the whole schedule later.
An opportunity that cannot be taken is skipped, never replayed. Replaying missed slots would quietly double the length of the window and destroy the property the caller asked for, which is that the frames are roughly one interval apart.
The three timings each frame carries
Each frame carries three times, and the third is the one to act on:
| Field | Meaning |
|---|---|
| capture_offset_us | When the sample was taken, from the request start. This is what makes the stack a temporal sequence. |
| capture_duration_us | How long the capture itself took. |
| age_us | How long ago the frame was captured, when the response was assembled. The honest number for deciding whether a frame is worth acting on. |
The newest frame already has an age by the time a caller sees it — in the default configuration, comfortably over 100 ms, because every requested encode happens before the response is sent.
A stack is slower than separate captures, deliberately
A three-frame stack takes longer per frame than three separate captures. That is not inefficiency; it is the feature. Three frames spaced 50 ms apart cannot finish before 100 ms, because waiting is exactly what was asked for. Three independent captures return sooner and show three nearly identical moments.
The cost shape of a stack is a schedule, not a throughput figure. At 50 ms cadence it is dominated by sleep, so a machine of half the speed would barely change it.
A stack that runs out of time returns what it captured
--frames 8 --interval 1s --timeout 250ms is a reasonable thing to type by mistake. The answer is a partial stack, not an error:
{
"realtime": {
"result": "partial",
"requested_frames": 8,
"captured_frames": 1,
"skipped_opportunities": 0,
"elapsed_ms": 0
}
}The frames that were obtained are returned, complete, and exit status is 0. Discarding work already done would be worse than reporting it, and result makes the shortfall explicit rather than silent.
Next: doing this repeatedly without paying for a connection every time — persistent sessions.