fix: dead probe fields are removed and the channel name is chosen once, so arming cannot disagree with enabling

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Martino FerrariandClaude Sonnet 4.6 committed 2026-10-07 18:25:33 +02:00
1 parent 601923f1d1
commit d79810c7ba
1 file changed
+21 -31
+21 -31
View File
@@ -8474,21 +8474,15 @@ gboolean fields_probe(gpointer user) {
/// must be outside the band) and `earlier` (the sample immediately before it, which must be
/// inside). Window 0.2 s, 10% pre — the crossing is one sample before x = 0.
///
/// Shape 3 (dropout): x = 0 is `quiet_at + timeout`, the moment the timeout expired. Scans
/// backwards for the most recent crossing in either direction, because the last sine crossing
/// before the quiet period is downward for the `--quiet-at 4.26` case and an upward-only scan
/// would miss it. `timeout` must exceed the sine's 0.1 s half-period; 0.3 s is used so that
/// an ordinary live sine cannot satisfy the condition. Needs a 1.0 s window with 50% pre
/// because the crossing is `timeout` ≈ 0.3 s before x = 0.
/// Shape 3 (dropout): x = 0 is the moment the timeout expired. Scans backwards for the most
/// recent crossing in either direction, because the last sine crossing before the quiet period
/// may be downward, and an upward-only scan would miss it. `timeout` must exceed the sine's
/// 0.1 s half-period; 0.3 s is used so that an ordinary live sine cannot satisfy the
/// condition. Needs a 1.0 s window with 50% pre because the crossing is `timeout` ≈ 0.3 s
/// before x = 0.
///
/// All legs print one `[condition-probe]` line with `nan` for any unmeasurable field.
struct ConditionProbe {
double glitch_at = 0.25;
double glitch_width = 0.002;
double square_period = 0.4;
/// With `--quiet-at`, the sine stops at a level on the other side of zero, so `quiet_at`
/// is its last crossing and the dropout's event time is exactly `quiet_at + timeout`.
double quiet_at = -1.0;
double timeout = 0.300;
/// 0 = pulse width `<` (the glitch), 1 = pulse width `>`, 2 = window, 3 = dropout. Selected
/// by `UDPSCOPE_PROBE_CONDITION_SHAPE`; the default keeps Task 3's glitch leg.
@@ -8497,6 +8491,13 @@ struct ConditionProbe {
};
ConditionProbe g_condition;
// Returns the channel name required by the given shape: shapes 2 and 3 use the sine (the
// square has no sample inside [-0.5, +0.5] and cannot satisfy a window condition, and the
// dropout relies on the sine going quiet); shapes 0 and 1 use the square.
static const char* condition_probe_channel(int shape) {
return (shape == 2 || shape == 3) ? "sine" : "square";
}
gboolean condition_probe_report(gpointer user) {
App* a = static_cast<App*>(user);
if (a->window == nullptr) return G_SOURCE_REMOVE;
@@ -8507,15 +8508,15 @@ gboolean condition_probe_report(gpointer user) {
std::fflush(stderr);
return G_SOURCE_REMOVE;
}
const double nan = std::numeric_limits<double>::quiet_NaN();
const double kNan = std::numeric_limits<double>::quiet_NaN();
const double level = a->model->trigger().condition.level;
const double level2 = a->model->trigger().condition.level2;
const std::size_t trig_ch = static_cast<std::size_t>(a->model->trigger().channel);
// Shape 2 reports two sample values rather than a measured width.
double at0 = nan;
double earlier = nan;
double measured = nan;
double at0 = kNan;
double earlier = kNan;
double measured = kNan;
if (std::shared_ptr<const EventSnapshot> snap = a->model->display_snapshot()) {
if (snap != nullptr && trig_ch < snap->values.size()) {
@@ -8576,10 +8577,7 @@ gboolean condition_probe_arm(gpointer user) {
const std::vector<ChannelInfo>& channels = a->model->channels();
int sel = -1;
const bool dropout = g_condition.shape == 3;
// Shape 2 (window) and shape 3 (dropout) both use the sine: the square has no sample inside
// [-0.5, +0.5] and cannot produce a band change-of-side, and the dropout relies on the sine
// going quiet. Shapes 0 and 1 (pulse-width) use the square.
const char* want_name = (g_condition.shape == 2 || dropout) ? "sine" : "square";
const char* want_name = condition_probe_channel(g_condition.shape);
for (std::size_t i = 0; i < channels.size(); ++i) {
if (channels[i].name == want_name) sel = static_cast<int>(i);
}
@@ -8598,9 +8596,7 @@ gboolean condition_probe_arm(gpointer user) {
// Window leg uses `falling` (index 1), meaning out-of-band, which makes the latch deterministic:
// x=0 is always a band exit, so at0 is outside and the previous sample is inside.
// Dropout has no direction (index 2, `both`). Pulse-width legs use `rising` (index 0).
guint edge_idx = 0u;
if (g_condition.shape == 2) edge_idx = 1u;
else if (dropout) edge_idx = 2u;
const guint edge_idx = (g_condition.shape == 2) ? 1u : (dropout ? 2u : 0u);
gtk_drop_down_set_selected(GTK_DROP_DOWN(a->trigger_edge_drop), edge_idx);
gtk_spin_button_set_value(GTK_SPIN_BUTTON(a->trigger_width_spin), 0.010);
gtk_spin_button_set_value(GTK_SPIN_BUTTON(a->trigger_timeout_spin), g_condition.timeout);
@@ -8624,18 +8620,12 @@ gboolean condition_probe_arm(gpointer user) {
gboolean condition_probe(gpointer user) {
App* a = static_cast<App*>(user);
if (a->window == nullptr) return G_SOURCE_REMOVE;
// `UDPSCOPE_PROBE_QUIET_AT` mirrors the mock's `--quiet-at`: it selects the dropout leg of
// the probe and tells it where the sine's last crossing is. Absent, the glitch leg runs.
if (const char* q = g_getenv("UDPSCOPE_PROBE_QUIET_AT")) g_condition.quiet_at = std::atof(q);
if (const char* s = g_getenv("UDPSCOPE_PROBE_CONDITION_SHAPE")) {
g_condition.shape = std::atoi(s);
} else if (g_condition.quiet_at >= 0.0) {
g_condition.shape = 3;
}
// `square` is not acquired until it is enabled, and the trigger can only name a channel
// that is: enabling is what starts acquisition. Shape 2 (window) needs the sine because the
// square has no sample inside [-0.5, +0.5] and can never change sides of the band.
const char* want = (g_condition.shape == 2 || g_condition.shape == 3) ? "sine" : "square";
// that is: enabling is what starts acquisition.
const char* want = condition_probe_channel(g_condition.shape);
const std::vector<SignalInfo>& signals = a->model->signals();
for (std::size_t i = 0; i < signals.size(); ++i) {
if (signals[i].name == want) a->model->set_signal_enabled(i, true);