diff --git a/src/help/09-cursors.txt b/src/help/09-cursors.txt
new file mode 100644
index 0000000..b2a6a20
--- /dev/null
+++ b/src/help/09-cursors.txt
@@ -0,0 +1,33 @@
+Cursors
+
+Cursors read values off the traces. There are two kinds: one shared vertical pair that
+every row carries at the same time, and horizontal rulers, one pair per row, that belong
+to that row alone.
+
+How to reach it
+
+The cursors button in the toolbar, or c. The pane below the plot shows the readings and
+owns a Reset button. Drag a marker to move it. The mode dropdown offers four modes:
+vertical (the shared time pair), horizontal (the per-row rulers),
+both, and mix.
+
+Worth knowing
+
+• The vertical pair is one time for the whole figure, so moving it on any row moves it on
+ all of them. That is what makes dt meaningful across rows.
+• A ruler belongs to its row and nothing shares it, so two rows' rulers are independent.
+• mix is the reading mode: one line per bound trace, with the trace's own values
+ at each marker and their difference. A slot with a name but no channel behind it is not a
+ trace and gets no line.
+• A marker past the newest sample reads --. With only one marker over data the row
+ reads partial: a difference between a number and nothing is not a number.
+• In mix each marker also places a crossing annotation per trace, which is what lets
+ you see on the plot which sample a number came from.
+• The time row exists only in the modes that show the vertical pair. horizontal has no
+ time row, by design.
+
+Example
+
+Three traces in mix mode with both markers over data: 3 lines and 6 crossing marks,
+each line reading like sine 0.31 V -0.84 V -1.15 V. Move the second marker past the
+newest sample and the third column reads --.
diff --git a/src/help/10-measurements.txt b/src/help/10-measurements.txt
new file mode 100644
index 0000000..59d20d9
--- /dev/null
+++ b/src/help/10-measurements.txt
@@ -0,0 +1,39 @@
+Measurements
+
+A measurement is a statistic of the visible window of one signal, recomputed as that
+window moves. The pane lists them with their values; each can also draw its result on the plot.
+
+How to reach it
+
+The measurements button in the toolbar, or m. The pane's + adds one; clicking
+a row opens the dialog where the signal, the type, the threshold share and the on-plot marks are
+set, with Save and Delete.
+
+Worth knowing
+
+• The types are average, top, bottom, min, max, peak to peak, amplitude, pulse duration, period
+ and frequency. top and bottom are the means of the upper and lower halves, which
+ is why a unit sine reads 0.637 (2/π) rather than 1.
+• The on-plot marks come in two kinds and the mix is the reading: a level mark is a ruler
+ at a value, a time mark is a marker at a position. An average draws one level mark; a
+ max draws a level mark and a time mark at the first sample that reached it.
+• Marks cannot be dragged. A measurement mark says what the measurement measured, so it is
+ created unselectable — as is the trigger marker.
+• A pass is throttled to a cadence rather than run every frame, so a ruler over a live view is
+ stepped. The number and the ruler come from the same pass, so they always agree.
+• An invalid pass keeps its level marks and drops its time marks. A level is a
+ property of the signal and outlives the window; a position inside the window does not. That is
+ what stops the rulers blinking off at a mode change.
+• A window with too few crossings reads invalid / -- rather than a plausible
+ invented number. Frequency over a window holding one edge is the usual case.
+• frequency is reported in Hz and period/pulse duration in seconds;
+ every other type carries the channel's own unit.
+• Cost is bounded by reading the summary pyramid the producer already maintains, not the raw
+ window. The values are still exact: a bucket's sum is a sum.
+
+Example
+
+On a 1 Hz ±1 sine: average=0, top=0.636620, bottom=-0.636620,
+max=1, min=-1, peak to peak=2, amplitude=1,
+pulse duration=0.5 s (duty 0.5), period=1 s, frequency=1 Hz. Over a
+2 M-sample window those two passes read about 0.1% of the samples.
diff --git a/src/help/11-math.txt b/src/help/11-math.txt
new file mode 100644
index 0000000..01b85ab
--- /dev/null
+++ b/src/help/11-math.txt
@@ -0,0 +1,35 @@
+Math channels
+
+A math channel is a signal computed from other signals. You build it as a small graph — source
+nodes, operations, one output — and udpscope then treats the result as an ordinary channel.
+
+How to reach it
+
+The math button in the toolbar, or g. The pane's + adds a channel; opening one
+shows the graph editor: nodes as boxes, click a box to select it and edit its parameters beside
+the canvas, click an output port then an input port to wire them, drag a box to move it. The
+orange dot marks the output node.
+
+Worth knowing
+
+• The graph is evaluated as an ordered list, so a node may only read ones above it. A
+ cycle is impossible by construction rather than rejected by a check.
+• A derived channel is a channel. It appears in the signal panel, can be plotted,
+ measured, exported, and used as a source for another math channel.
+• The math runs on the stored samples — decimated and averaged — so a cutoff you type in
+ Hz means the rate the filter will actually run at. Change the window across a decimation
+ boundary and the coefficients are rebuilt for the new rate.
+• A source may only name an acquired channel. The stream's timestamp signal is not one:
+ a graph sourced from it validates and then reads NaN for ever, so the dropdown does not
+ offer it.
+• The output defaults to the last node, and a node added to a linear graph becomes the
+ output. Choosing an earlier node explicitly is respected.
+• Every numeric field starts at its own identity value — gain 1, offset 0 — not at the bottom
+ of its range.
+
+Example
+
+[sine] → [gain x2] gives a channel reading y=[-2, 2]. A 100 Hz low pass on the
+same source passes 50 Hz at 0.97 and 2 kHz at 0.002; a 1 kHz band pass with Q=2 passes 200 Hz at
+0.05, 1 kHz at 0.95 and 4 kHz at 0.05. Each derived channel must hold exactly as many samples as
+the shared clock has time slots.
diff --git a/src/help/udpscope-help.gresource.xml b/src/help/udpscope-help.gresource.xml
index 7df6621..3a19f3a 100644
--- a/src/help/udpscope-help.gresource.xml
+++ b/src/help/udpscope-help.gresource.xml
@@ -17,5 +17,8 @@
06-navigate.txt
07-pause.txt
08-trigger.txt
+ 09-cursors.txt
+ 10-measurements.txt
+ 11-math.txt
diff --git a/src/help_window.cpp b/src/help_window.cpp
index a9153f7..178d1c9 100644
--- a/src/help_window.cpp
+++ b/src/help_window.cpp
@@ -38,6 +38,9 @@ constexpr HelpPage kPages[] = {
{"Viewing", "Navigating the plot", "/org/udpscope/help/06-navigate.txt"},
{"Viewing", "Pause and play", "/org/udpscope/help/07-pause.txt"},
{"Viewing", "Trigger", "/org/udpscope/help/08-trigger.txt"},
+ {"Analysis", "Cursors", "/org/udpscope/help/09-cursors.txt"},
+ {"Analysis", "Measurements", "/org/udpscope/help/10-measurements.txt"},
+ {"Analysis", "Math channels", "/org/udpscope/help/11-math.txt"},
};
constexpr int kPageCount = static_cast(sizeof(kPages) / sizeof(kPages[0]));