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:
1 parent
601923f1d1
commit
d79810c7ba
1 file changed
+21
-31
+21
-31
@@ -8474,21 +8474,15 @@ gboolean fields_probe(gpointer user) {
|
|||||||
/// must be outside the band) and `earlier` (the sample immediately before it, which must be
|
/// 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.
|
/// 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
|
/// Shape 3 (dropout): x = 0 is the moment the timeout expired. Scans backwards for the most
|
||||||
/// backwards for the most recent crossing in either direction, because the last sine crossing
|
/// recent crossing in either direction, because the last sine crossing before the quiet period
|
||||||
/// before the quiet period is downward for the `--quiet-at 4.26` case and an upward-only scan
|
/// may be downward, and an upward-only scan would miss it. `timeout` must exceed the sine's
|
||||||
/// would miss it. `timeout` must exceed the sine's 0.1 s half-period; 0.3 s is used so that
|
/// 0.1 s half-period; 0.3 s is used so that an ordinary live sine cannot satisfy the
|
||||||
/// an ordinary live sine cannot satisfy the condition. Needs a 1.0 s window with 50% pre
|
/// condition. Needs a 1.0 s window with 50% pre because the crossing is `timeout` ≈ 0.3 s
|
||||||
/// because the crossing is `timeout` ≈ 0.3 s before x = 0.
|
/// before x = 0.
|
||||||
///
|
///
|
||||||
/// All legs print one `[condition-probe]` line with `nan` for any unmeasurable field.
|
/// All legs print one `[condition-probe]` line with `nan` for any unmeasurable field.
|
||||||
struct ConditionProbe {
|
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;
|
double timeout = 0.300;
|
||||||
/// 0 = pulse width `<` (the glitch), 1 = pulse width `>`, 2 = window, 3 = dropout. Selected
|
/// 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.
|
/// by `UDPSCOPE_PROBE_CONDITION_SHAPE`; the default keeps Task 3's glitch leg.
|
||||||
@@ -8497,6 +8491,13 @@ struct ConditionProbe {
|
|||||||
};
|
};
|
||||||
ConditionProbe g_condition;
|
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) {
|
gboolean condition_probe_report(gpointer user) {
|
||||||
App* a = static_cast<App*>(user);
|
App* a = static_cast<App*>(user);
|
||||||
if (a->window == nullptr) return G_SOURCE_REMOVE;
|
if (a->window == nullptr) return G_SOURCE_REMOVE;
|
||||||
@@ -8507,15 +8508,15 @@ gboolean condition_probe_report(gpointer user) {
|
|||||||
std::fflush(stderr);
|
std::fflush(stderr);
|
||||||
return G_SOURCE_REMOVE;
|
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 level = a->model->trigger().condition.level;
|
||||||
const double level2 = a->model->trigger().condition.level2;
|
const double level2 = a->model->trigger().condition.level2;
|
||||||
const std::size_t trig_ch = static_cast<std::size_t>(a->model->trigger().channel);
|
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.
|
// Shape 2 reports two sample values rather than a measured width.
|
||||||
double at0 = nan;
|
double at0 = kNan;
|
||||||
double earlier = nan;
|
double earlier = kNan;
|
||||||
double measured = nan;
|
double measured = kNan;
|
||||||
|
|
||||||
if (std::shared_ptr<const EventSnapshot> snap = a->model->display_snapshot()) {
|
if (std::shared_ptr<const EventSnapshot> snap = a->model->display_snapshot()) {
|
||||||
if (snap != nullptr && trig_ch < snap->values.size()) {
|
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();
|
const std::vector<ChannelInfo>& channels = a->model->channels();
|
||||||
int sel = -1;
|
int sel = -1;
|
||||||
const bool dropout = g_condition.shape == 3;
|
const bool dropout = g_condition.shape == 3;
|
||||||
// Shape 2 (window) and shape 3 (dropout) both use the sine: the square has no sample inside
|
const char* want_name = condition_probe_channel(g_condition.shape);
|
||||||
// [-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";
|
|
||||||
for (std::size_t i = 0; i < channels.size(); ++i) {
|
for (std::size_t i = 0; i < channels.size(); ++i) {
|
||||||
if (channels[i].name == want_name) sel = static_cast<int>(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:
|
// 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.
|
// 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).
|
// Dropout has no direction (index 2, `both`). Pulse-width legs use `rising` (index 0).
|
||||||
guint edge_idx = 0u;
|
const guint edge_idx = (g_condition.shape == 2) ? 1u : (dropout ? 2u : 0u);
|
||||||
if (g_condition.shape == 2) edge_idx = 1u;
|
|
||||||
else if (dropout) edge_idx = 2u;
|
|
||||||
gtk_drop_down_set_selected(GTK_DROP_DOWN(a->trigger_edge_drop), edge_idx);
|
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_width_spin), 0.010);
|
||||||
gtk_spin_button_set_value(GTK_SPIN_BUTTON(a->trigger_timeout_spin), g_condition.timeout);
|
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) {
|
gboolean condition_probe(gpointer user) {
|
||||||
App* a = static_cast<App*>(user);
|
App* a = static_cast<App*>(user);
|
||||||
if (a->window == nullptr) return G_SOURCE_REMOVE;
|
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")) {
|
if (const char* s = g_getenv("UDPSCOPE_PROBE_CONDITION_SHAPE")) {
|
||||||
g_condition.shape = std::atoi(s);
|
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
|
// `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
|
// that is: enabling is what starts acquisition.
|
||||||
// square has no sample inside [-0.5, +0.5] and can never change sides of the band.
|
const char* want = condition_probe_channel(g_condition.shape);
|
||||||
const char* want = (g_condition.shape == 2 || g_condition.shape == 3) ? "sine" : "square";
|
|
||||||
const std::vector<SignalInfo>& signals = a->model->signals();
|
const std::vector<SignalInfo>& signals = a->model->signals();
|
||||||
for (std::size_t i = 0; i < signals.size(); ++i) {
|
for (std::size_t i = 0; i < signals.size(); ++i) {
|
||||||
if (signals[i].name == want) a->model->set_signal_enabled(i, true);
|
if (signals[i].name == want) a->model->set_signal_enabled(i, true);
|
||||||
|
|||||||
Reference in new issue
Block a user