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
+23
-33
+23
-33
@@ -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()) {
|
||||
@@ -8540,7 +8541,7 @@ gboolean condition_probe_report(gpointer user) {
|
||||
// before the quiet period may be downward, and an upward-only scan would miss it.
|
||||
for (std::size_t i = end; i >= 2; --i) {
|
||||
const double prev = static_cast<double>(vals[i - 2]);
|
||||
const double val = static_cast<double>(vals[i - 1]);
|
||||
const double val = static_cast<double>(vals[i - 1]);
|
||||
const bool crossed = (g_condition.shape == 3)
|
||||
? ((prev < level) != (val < level)) // either direction
|
||||
: (prev < level && val >= level); // upward only
|
||||
@@ -8570,16 +8571,13 @@ gboolean condition_probe_arm(gpointer user) {
|
||||
// Legs 1 and 3 need a 1.0 s window with 50% pre so the opening edge of a 0.2 s pulse and
|
||||
// the last sine crossing before a 0.3 s timeout are both captured.
|
||||
const bool wide = (g_condition.shape == 1 || g_condition.shape == 3);
|
||||
const double win_s = wide ? 1.0 : 0.2;
|
||||
const double win_s = wide ? 1.0 : 0.2;
|
||||
const double pre_pct = wide ? 50.0 : 10.0;
|
||||
apply_time_window(*a, win_s);
|
||||
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);
|
||||
|
||||
Reference in new issue
Block a user