test: explain why the zoomkeys probe reads its axes synchronously

The probe's doc-comment advised running it inside a pinned state "so the _held
readings mean the other axis did not move" — but the reads happen in the same
callback as the key events, so no frame can intervene and nothing can re-frame.
Worse, the advice hid the real reason the read is synchronous, which is the
opposite of what the pan and zoom probes do and so invites a future reader to
"fix" it: hold_pinned_window() clamps the x span into the pinned window, so a
reading taken a frame later would report that clamp's answer rather than the
restriction's for the ctrl case. Name the actual bug class a deferred read would
confuse it with, and point at the probe that does assert figure adoption.

std::fabs for the two span comparisons, matching the file's existing idiom —
<cstdlib> is also in scope here.
This commit is contained in:
Martino Ferrari committed 2026-10-04 12:14:00 +02:00
1 parent dc7821a504
commit 856f538681
1 file changed
+12 -4
+12 -4
View File
@@ -7182,8 +7182,16 @@ gboolean measures_probe_step(gpointer user) {
}
/// Drives the plot's keyboard zoom with Ctrl and with Alt and reports which axis moved.
/// Run it inside a pinned state (the pause probe makes a stable one) so the `_held`
/// readings mean "the other axis did not move", not "the frame re-framed anyway":
/// The ranges are read **synchronously**, in this callback, which is deliberate and is the
/// opposite of what the pan and zoom probes do. The question here is only "does the
/// controller's modifier restriction reach the app", so the read has to see the
/// controller's own write and nothing else: `hold_pinned_window()` clamps the *x* span
/// into the pinned window, so a reading taken a frame later would report that clamp's
/// answer rather than the restriction's for the ctrl case. Nor can a same-callback read
/// drift past a state boundary, which is the hazard the read-in-the-tick rule exists for.
/// Whether a zoom the controller performed is then *adopted* by the figure is a different
/// question, and `[pan-probe]`'s `zoom-in asked=/group=/member=` line is what asserts it.
/// Pausing first is therefore not required, only tidy — it is how Task 11 runs it:
/// UDPSCOPE_PROBE_PAUSE_MS=1500 UDPSCOPE_PROBE_ZOOMKEYS_MS=2200
gboolean zoomkeys_probe(gpointer user) {
App* a = static_cast<App*>(user);
@@ -7206,13 +7214,13 @@ gboolean zoomkeys_probe(gpointer user) {
static_cast<plot::u32>(plot::input::Mod::Ctrl), true});
const plot::Range x1 = p.x_range(ax);
const plot::Range y1 = p.y_range(ay);
const int y_held = std::abs(y1.span() - y0.span()) < 1e-9 ? 1 : 0;
const int y_held = std::fabs(y1.span() - y0.span()) < 1e-9 ? 1 : 0;
c->on_key({plot::input::kKeyZoomIn,
static_cast<plot::u32>(plot::input::Mod::Alt), true});
const plot::Range x2 = p.x_range(ax);
const plot::Range y2 = p.y_range(ay);
const int x_held = std::abs(x2.span() - x1.span()) < 1e-9 ? 1 : 0;
const int x_held = std::fabs(x2.span() - x1.span()) < 1e-9 ? 1 : 0;
std::fprintf(stderr,
"[zoomkeys] ctrl+plus x=[%.4f..%.4f]->[%.4f..%.4f] y_held=%d | "