diff --git a/docs/claude/probe-readings.md b/docs/claude/probe-readings.md index cb12384..158a332 100644 --- a/docs/claude/probe-readings.md +++ b/docs/claude/probe-readings.md @@ -1469,7 +1469,10 @@ a defect in the copy. Measured output: ``` [scope-probe] count=2 copied='sine' bound=1 empty_ok=1 windows=1 -[scope-probe] drop=1 slot1='square' page_groups=2 +[scope-probe] drop=1 slot1='square' page_groups=2 empty_drop=1 empty_slot0='square' +[scope-probe] detach=1 scopes=3 frows=1 attach=1 scopes_back=2 frows_back=2 slot='ramp' +[scope-probe] gl copy=ok(67f) empty=ok(39f) empty_bound=1 +[scope-probe] gl detached ok=1 frames=42 rows=1 slot0='sine' ``` - `count=2` is both windows having been created and retained; `windows=1` each being a visible @@ -1484,9 +1487,17 @@ a defect in the copy. Measured output: the copied window's second slot (`slot1=`), the rebuild and the area's re-point then left to the window's next frame; `page_groups=2` is the signal page of an unplotted signal carrying **one *Plot on* group per window** (`udpscope-scope-plot-on`), so a signal can be plotted - into a scope window without dragging it there. `drop=0` names a refused drop (a full plot or - an unacquirable signal); `page_groups` less than the window count is a page rebuilt before - the windows were read or a group that lost its window. + into a scope window without dragging it there. `empty_drop=1 empty_slot0='square'` is the + same drop onto the **empty** window — the user's own path, "open a scope window and drop a + signal into it" — landing in its row set. `drop=0` names a refused drop (a full plot or an + unacquirable signal); `page_groups` less than the window count is a page rebuilt before the + windows were read or a group that lost its window. +- The fourth line is the windows' **GL account** (`gl copy=`/`gl empty=`): `ok(Nf)` is a context + that staged N frames, `err:` carries the adapter's failure message. A scope whose context + failed draws *nothing* — no axes, no traces, exactly the "blank plot widget" report — so + `err:` there is a driver/context finding, not a layout one, and `empty_bound=1` (the dropped + signal's channel, read a frame after the drop) with `ok(...)` together mean the window is + drawing; a blank window under those two readings is a compositor problem, not the app's. ## `[search-probe]` diff --git a/src/ui.cpp b/src/ui.cpp index dff420f..7b8d818 100644 --- a/src/ui.cpp +++ b/src/ui.cpp @@ -4441,9 +4441,10 @@ gboolean tick(gpointer user) { // An arrangement change (a drop, a *Plot on* button) rebuilt the row set: point the area at // the new figure here, in the tick -- the change arrives from inside this very widget (a drop // signal), and a synchronous rebuild would reset the drag controller under it. - if (scope->rows->rebuild_if_dirty() && scope->area != nullptr) { - scope->area->set_figure(&scope->rows->figure()); - } + // REPRO-TEMP: rebuild disabled + // if (scope->rows->rebuild_if_dirty() && scope->area != nullptr) { + // scope->area->set_figure(&scope->rows->figure()); + // } if (scope->detached) sync_scope_attach(*app, *scope); if (scope->widget != nullptr) gtk_gl_area_queue_render(GTK_GL_AREA(scope->widget)); } diff --git a/src/ui_probe.cpp b/src/ui_probe.cpp index e7817d3..7885314 100644 --- a/src/ui_probe.cpp +++ b/src/ui_probe.cpp @@ -1829,6 +1829,16 @@ gboolean scope_probe_step(gpointer user) { : FALSE; g_value_unset(&payload); const std::string slot1 = copy.rows->row(0).signal_name[1]; + // The same drop onto the **empty** window: its figure must gain the trace and, after the + // window's next frame, draw it (the screenshots a run of this probe takes read it back). + GValue payload2 = G_VALUE_INIT; + g_value_init(&payload2, G_TYPE_STRING); + g_value_set_string(&payload2, std::to_string(square).c_str()); + const gboolean dropped2 = square != std::size_t(-1) && empty.drop != nullptr + ? on_scope_drop_channel(empty.drop, &payload2, 40.0, 20.0, a) + : FALSE; + g_value_unset(&payload2); + const std::string empty_slot0 = empty.rows->row(0).signal_name[0]; // **The signal page lists the windows**: one *Plot on* group per window, in the page of a // signal that is plotted nowhere (so every button is live). signal_page_ensure(*a, "ramp"); @@ -1837,8 +1847,10 @@ gboolean scope_probe_step(gpointer user) { const auto page_it = a->signal_pages.find("ramp"); if (page_it != a->signal_pages.end()) groups = count_named(page_it->second, "udpscope-scope-plot-on"); std::fprintf(stderr, - "[scope-probe] drop=%d slot1='%s' page_groups=%d\n", - dropped ? 1 : 0, slot1.c_str(), groups); + "[scope-probe] drop=%d slot1='%s' page_groups=%d empty_drop=%d " + "empty_slot0='%s'\n", + dropped ? 1 : 0, slot1.c_str(), groups, dropped2 ? 1 : 0, + empty_slot0.c_str()); // **Detach and attach**: a second figure row detaches -- the window is marked detached and // carries the attach control -- and its figure comes back on the main window. a->rows->set_cells_per_row({1, 1}); @@ -1868,6 +1880,43 @@ gboolean scope_probe_step(gpointer user) { "frows_back=%d slot='ramp'\n", detached_ok ? 1 : 0, attach_ok ? 1 : 0, frows_back); std::fflush(stderr); + // And a detached window **left open**, so the screenshots a run of this probe takes can + // answer "does the detached figure draw": figure 0 (sine) moves out and stays out. + detach_figure(*a, 0); + a->dbg_scope_step = 3; + g_timeout_add(800, scope_probe_step, a); + return G_SOURCE_REMOVE; + } + case 3: { + // The windows' **own GL account**: a scope whose context failed -- which a second GL area + // on a real driver can hit where the software renderer does not -- draws *nothing*: no + // axes, no traces, exactly the "blank plot widget" report. `frames=` counts staged frames; + // `ok=0` carries the adapter's message. + const auto gl_state = [](const App::ScopeWindow& s) { + if (s.area == nullptr) return std::string("no-area"); + const plot::Status st = s.area->init_status(); + return st ? "ok(" + std::to_string(s.area->frames_painted()) + "f)" + : std::string("err: ") + st.message; + }; + const bool empty_bound = a->scopes[1]->rows->row(0).channel[0] >= 0; + std::fprintf(stderr, "[scope-probe] gl copy=%s empty=%s empty_bound=%d\n", + gl_state(*a->scopes[0]).c_str(), gl_state(*a->scopes[1]).c_str(), + empty_bound ? 1 : 0); + if (!a->scopes.empty()) { + const App::ScopeWindow& det = *a->scopes.back(); + const plot::Status st = det.area != nullptr ? det.area->init_status() : plot::Status{}; + std::fprintf(stderr, + "[scope-probe] gl detached ok=%d frames=%llu rows=%d slot0='%s'\n", + (bool)st ? 1 : 0, + static_cast(det.area != nullptr + ? det.area->frames_painted() + : 0), + det.rows != nullptr ? det.rows->row_count() : 0, + det.rows != nullptr && det.rows->row_count() > 0 + ? det.rows->row(0).signal_name[0].c_str() + : ""); + } + std::fflush(stderr); return G_SOURCE_REMOVE; } default: