test: a probe reads the hidden tab's accumulator, and the docs follow the tabs
`UDPSCOPE_PROBE_SIDE_TAB_MS` is the one reading for the design's §5 rule -- the statistics accumulator keeps running on the hidden page -- which nothing tested before. It switches to Measure, forces one pass so there is a baseline, switches away to Signals and reads again two seconds later: `advanced=4` is the assertion, and `advanced=0` is the defect where the accumulation is gated on the visible tab, which would leave a gap in min/max/avg for exactly as long as the trigger was on screen. Two things the probe has to do for itself, and getting either wrong makes `advanced=0` a lie about the tab: - it **enables its own signal**, because a signal starts disabled (enabling is what starts acquisition) and a probe that waited for one to be on would be reading another probe's side effects -- and `UDPSCOPE_ENABLE_ALL_MS` is not part of this run; - it **assigns that channel to a plot**, because a channel with no trace leaves the live window *still*, and a still window is precisely the state `update_measures()` is supposed to skip. Measured the hard way: without the assignment the probe read `advanced=0 min_n=0` with the view pinned and only four passes in twelve seconds, none of them the tab's doing. Docs: `probe-readings.md` gains the probe's entry and the signals panel's new shape (`rows == signals + 1 + math`, `divider=0` a list that lost its `+`), loses the whole `math panel:` entry, and follows `pane_sensitive=`, the gone `revealed=` field, the six toolbar controls and the tab-reading `m`/`g` key pairs. `verification.md` gains the probe's bullet and the re-measured warning figures. The invariants record the sidebar as a switcher above a stack with a scroller per page, the hidden-page trap that replaces the unrevealed-revealer one for it, that a feature's on/off is sensitivity rather than visibility, that a math channel is a row of the signal list, and that a cursors-off readout blanks to `--` with the panel left on screen. `shortcuts.ui` and the help page's window and sidebar bullets stop describing five revealers. The README records the deviation from `SPECS.md` that names the panes as separate collapsible sections. Verified: `[side-tab-probe] page=signals rows=4 accums=4 advanced=4 min_n=16`; `signals panel: labels=2 list=1 rows=9 signals=4 math=4 divider=1`; `panels: … (page_current=1) (sensitive=0) switcher=1`; `toolbar icons:` six controls with every name `(ok)`, with the Preferences dialog opened in the same run; zero `Gtk-CRITICAL` and no missing icon.
This commit is contained in:
1 parent
cb866774c6
commit
6492bb910f
9 files changed
+197
-41
No files matched your search
@@ -1124,6 +1124,89 @@ gboolean stats_probe(gpointer user) {
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
/// Phase two of the tab-switch probe: the hidden page's accumulator must have advanced. A statistics
|
||||
/// readout is *history*, so min/max/avg would gain a gap for exactly as long as the trigger was on
|
||||
/// screen -- which is the one defect the design's "what runs on a hidden page" rule is there to
|
||||
/// prevent, and the reason no frame-rate code may be gated on which tab is current.
|
||||
gboolean side_tab_probe_report(gpointer user) {
|
||||
App* a = static_cast<App*>(user);
|
||||
if (a->window == nullptr) return G_SOURCE_REMOVE;
|
||||
std::uint64_t advanced = 0;
|
||||
std::uint64_t min_n = 0;
|
||||
bool first = true;
|
||||
for (std::size_t i = 0; i < a->measure_stats.size(); ++i) {
|
||||
const std::uint64_t n = a->measure_stats[i].n;
|
||||
if (i < g_side_tab_n_before.size() && n > g_side_tab_n_before[i]) ++advanced;
|
||||
if (first || n < min_n) {
|
||||
min_n = n;
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
std::fprintf(stderr,
|
||||
"[side-tab-probe] page=%s rows=%zu accums=%zu advanced=%" G_GUINT64_FORMAT
|
||||
" min_n=%" G_GUINT64_FORMAT "\n",
|
||||
a->side_tab.c_str(), a->measures.size(), a->measure_stats.size(), advanced, min_n);
|
||||
std::fflush(stderr);
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
/// Phase one: four measurements on the first channel, statistics on, then switch *away* from the
|
||||
/// Measure tab and come back in two seconds to read what ran while it was not on screen.
|
||||
gboolean side_tab_probe(gpointer user) {
|
||||
App* a = static_cast<App*>(user);
|
||||
if (a->window == nullptr) return G_SOURCE_REMOVE;
|
||||
// One signal on, and on a plot, the way `enable_all_probe` does it and a restored session arrives.
|
||||
// Both halves matter here: a signal starts disabled (enabling is what starts acquisition), and a
|
||||
// channel with no trace leaves the live window *still* -- and a still window is exactly the state
|
||||
// the tick is supposed to skip, so a probe that left it that way would be testing nothing. This
|
||||
// probe is about the case where the window does move while the page is somewhere else.
|
||||
std::size_t pick = a->model->signals().size();
|
||||
{
|
||||
const std::vector<SignalInfo>& signals = a->model->signals();
|
||||
for (std::size_t i = 0; i < signals.size(); ++i) {
|
||||
if (ScopeModel::is_time_signal(signals, i)) continue; // the time base is not acquired
|
||||
pick = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (pick >= a->model->signals().size()) return G_SOURCE_REMOVE;
|
||||
a->model->set_signals_enabled({pick}, true);
|
||||
std::string name;
|
||||
{
|
||||
const std::vector<ChannelInfo>& channels = a->model->channels();
|
||||
if (channels.empty()) return G_SOURCE_REMOVE;
|
||||
name = channels.front().name;
|
||||
a->rows->assign(0, 0, channels.front().name, channels.front().name,
|
||||
signal_color_index(a->model->signals(), channels.front().signal));
|
||||
}
|
||||
a->relayout_pending = true;
|
||||
if (a->measures.empty()) {
|
||||
for (const MeasureType t : {MeasureType::Average, MeasureType::Top, MeasureType::PeakToPeak,
|
||||
MeasureType::Amplitude}) {
|
||||
MeasureOptions options;
|
||||
options.show_on_plot = measure_can_show_on_plot(t);
|
||||
a->measures.push_back({name, t, options});
|
||||
}
|
||||
rebuild_measures_pane(*a);
|
||||
}
|
||||
if (a->measures_stats_check != nullptr) {
|
||||
gtk_check_button_set_active(GTK_CHECK_BUTTON(a->measures_stats_check), TRUE);
|
||||
}
|
||||
select_side_tab(*a, "measure");
|
||||
update_measures(*a, /*force=*/true, /*accumulate=*/true);
|
||||
g_side_tab_n_before.clear();
|
||||
for (const MeasureStatsAccum& s : a->measure_stats) g_side_tab_n_before.push_back(s.n);
|
||||
// Away from the page the measurements are on: its widgets are now unrealized and measure 0x0, and
|
||||
// the tick keeps calling `update_measures()` regardless.
|
||||
select_side_tab(*a, "signals");
|
||||
std::fprintf(stderr, "[side-tab-probe] page=%s accums=%zu baseline recorded\n",
|
||||
a->side_tab.c_str(), g_side_tab_n_before.size());
|
||||
std::fflush(stderr);
|
||||
// Long enough for several throttled recomputes (one per 100 ms while the window moves).
|
||||
g_timeout_add(2000, side_tab_probe_report, a);
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
gboolean pan_probe(gpointer user) {
|
||||
App* a = static_cast<App*>(user);
|
||||
if (a->window == nullptr || a->rows->row_count() <= 0) return G_SOURCE_REMOVE;
|
||||
|
||||
Reference in new issue
Block a user