<b>What udpscope is</b>

udpscope plots a live UDPStreamer source. One window holds up to 8 rows of plots, each with up
to 4 traces, and every row shares one time axis, so the whole figure scrolls together.
Acquisition runs on its own thread and writes a ring buffer sized from <tt>--buffer-mb</tt>;
the drawing thread reads whatever that ring holds when a frame starts, which is why a trace
never makes the window stutter.

The stream is the clock. Every interval you read on screen is measured between timestamps the
source sent, never your machine's, so a stalled source freezes the view instead of scrolling it
-- and a source whose clock steps still gets the window you asked for.

The time axis is <i>relative to now</i>. While streaming it is a fixed <tt>[-window .. 0]</tt>
with the newest sample at 0, so x reads as an age: <tt>-1.5s</tt> is a sample a second and a half
old. In trigger mode 0 is the event's edge instead, and a paused view keeps the ages it was
frozen with. The source's own clock is still there when you need it -- an export writes it as a
second column.

<b>How to reach it</b>

Start the app with a source (<tt>udpscope --server 127.0.0.1 --port 8000</tt>), or start it with
no <tt>--server</tt> and pick one from the chooser, which lists the sessions you have opened
before. A unicast source serves one client at a time: if a second viewer connects, they take
turns and the status bar says so. To watch one source from several instances, use
<tt>--multicast</tt> with the group and <tt>--interface</tt> with the local address.

The signals arrive <i>disabled</i>. Nothing is acquired until you turn one on in the signal
list, and enabling is what starts acquisition -- so a freshly opened scope with an idle side
panel is working correctly, not failing to connect.

<b>The window</b>

• <b>The header bar</b> -- left to right: the time window (<tt>2 s</tt>), the trigger and the
  pause, the events, cursors and side-panel switches, and the menu, which holds Fit to data, the
  layout editor, Export, Keyboard shortcuts, Help and About.

• <b>The side panel</b> -- two tabs. <b>Measure</b> holds the cursors readout and the
  measurements derived from it; <b>Signals</b> holds the trigger and the channel list, where a
  math channel is a row of that one list under a <tt>MATH</tt> heading whose <tt>+</tt> creates
  one. One row per channel: its colour, an enable switch, its name and its unit. Channels are
  dragged from here onto a plot row.

• <b>The plot</b> -- up to 8 rows, each with its own y axis and up to 4 traces, all on one
  shared time axis. Right-click a row for its own menu (fit, add a signal, remove a trace).

• <b>The status bar</b>, directly above the plot's bottom edge -- the state word, the stored
  sample rate and the window, and the ⓘ beside it for packets, gaps and the buffer's own
  figures. The second screenshot above is what a held capture looks like: <tt>event held</tt>,
  the axis pinned to the event, and the trigger marker at the edge.

<b>The first five minutes</b>

1. <b>Connect.</b> With no <tt>--server</tt>, pick a stored session or type a host and port.
2. <b>Enable a signal</b> in the signal list. The plot stays empty for the first fraction of a
   second while the first window fills -- the buffer holds only what has arrived.
3. <b>Drag it onto a row.</b> Drop a second signal on another row, or the same signal again for
   a second view of it. The colour is per signal, so it is the same one wherever it appears.
4. <b>Set the window</b> with the control in the header bar: type a number, pick a unit, or step
   a decade. The trigger's event window is a share of this span, so a window change moves it.
5. <b>Pause</b> (<tt>space</tt>) to look at a still picture; the producer keeps filling the
   buffer behind it, and resuming shows what arrived meanwhile rather than a hole.
6. <b>Arm the trigger</b> (<tt>a</tt>) and wait for a latch: the view freezes on one event, and
   you can zoom in on it. Disarming mid-capture keeps what the capture got.
7. <b>Measure it</b> -- the measurements pane computes a statistic of the visible window, and
   <b>show on plot</b> marks it as a ruler or a pair of markers.
8. <b>Save the data</b> with Export visible range, which writes the plotted signals over the
   window at the stored rate to <tt>.npy</tt>, <tt>.csv</tt> or <tt>.mf3</tt> (MDF 3.30).

<b>Worth knowing</b>

• A signal nobody enables costs nothing -- no buffer, no socket work, no measurements.
• The window remembers itself. Rows, traces, the time window, calibration, which signals were
  enabled, the math channels with their graphs, and the measurements are written per host and
  port, and reloaded the next time you open that source.
• A very wide window on a fast stream is drawn from averaged groups of samples, so the rate in
  the status bar falls: that is the anti-alias filter, not a fault -- striding would fold a fast
  signal back as a slow one, which looks entirely plausible on screen.
• Every instrument the app computed -- the trigger marker, the measurement marks -- is locked to
  the pointer's reach. The cursors are yours to drag; those are not.
