fix: a zero-width band fires on equality or not at all, and the text now says which

The earlier correction moved the claim rather than fixing it: four places still
said the float-equality test "never fires on a real-valued signal", and a +/-1
square is a real-valued signal -- a standalone check against a band of [1, 1]
fired 19 times, because `inside` is true at +1 and false at -1, so the signal
changes side on every transition.

All four now state the measured behaviour: an exact float-equality test against
the level, which an ordinary signal never satisfies and a signal sitting at the
level satisfies on every transition. Neither answer is a band, which is the
reason the second spin is clamped an increment away rather than the detector
special-cased -- the same conclusion, now resting on a true premise.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Martino FerrariandClaude Opus 4.6 committed 2026-10-08 11:39:22 +02:00
1 parent c8f10f59dc
commit aa8ec1794a
4 files changed
+18 -9

No files matched your search

+5 -2
View File
@@ -119,8 +119,11 @@ double EventDetector::feed_window(double value, double t) noexcept {
if (inside == inside_) return kNoEvent;
inside_ = inside;
// The event is the *change of side*, so `Rising` reads as "into the band" and `Falling`
// as "out of it". A band of zero width reduces the condition to an equality test on a
// real-valued signal, which is a float-equality test and therefore unusable: the panel refuses one.
// as "out of it". A band of zero width is not a band: `inside` reduces to an exact
// float-equality test against the level, so the condition fires on none of an ordinary
// signal's samples and on every transition of one that sits exactly at the level -- a
// +/-1 square against a band of [1, 1] fired 19 times in a standalone check. Neither
// answer is a band, so the panel refuses one.
const bool fired = (cond_.edge == TriggerEdge::Rising && inside) ||
(cond_.edge == TriggerEdge::Falling && !inside) ||
cond_.edge == TriggerEdge::Both;
+4 -2
View File
@@ -34,8 +34,10 @@ share. <b>Arm</b> starts watching; <tt>a</tt> arms and disarms from the keyboard
behaviour. <b>Pulse width</b> — a crossing and its return, qualified by how long the excursion
lasted: less than, greater than, within or outside a pair of widths; the event is timed at the
closing edge. <b>Window</b> — the signal entering or leaving a band between two levels; the
panel keeps the two levels at least one increment apart, because a zero-width band reduces the
condition to a float-equality test that never fires on a real-valued signal. <b>Dropout</b> —
panel keeps the two levels at least one increment apart, because a zero-width band is not a
band: the condition becomes an exact float-equality test against the level, which an ordinary
signal never satisfies and a signal sitting at the level satisfies on every transition.
<b>Dropout</b> —
the absence of a crossing for longer than a timeout; one event per outage, re-arms when a
crossing returns.
• The shape's own rows are the only ones shown; the rest are hidden, so the panel never offers
+4 -3
View File
@@ -6983,9 +6983,10 @@ GtkWidget* build_trigger_panel(App& app) {
app.trigger_level2_spin = spin(-1.0e12, 1.0e12, 0.01, 4);
gtk_widget_set_tooltip_text(app.trigger_level2_spin,
"the band's other edge; it is kept at least one increment away "
"from the level, because a band of zero width reduces the "
"condition to a float-equality test, which on a real-valued "
"signal never fires");
"from the level, because a band of zero width is not a band: the "
"condition becomes an exact float-equality test against the level, "
"which an ordinary signal never satisfies and a signal sitting at "
"the level satisfies on every transition");
app.trigger_level2_row = labeled_box("to level", app.trigger_level2_spin);
gtk_box_append(GTK_BOX(box), app.trigger_level2_row);