From 856f538681f5ad19381597aab1492ca212b470a4 Mon Sep 17 00:00:00 2001 From: Martino Ferrari Date: Sun, 4 Oct 2026 12:14:00 +0200 Subject: [PATCH] test: explain why the zoomkeys probe reads its axes synchronously MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 — is also in scope here. --- src/ui.cpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/ui.cpp b/src/ui.cpp index d4e8532..9932eea 100644 --- a/src/ui.cpp +++ b/src/ui.cpp @@ -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(user); @@ -7206,13 +7214,13 @@ gboolean zoomkeys_probe(gpointer user) { static_cast(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::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 | "