swayfx-mosaic/docs/design/STEP20C_PRESENTATION_IDENTITY_PROBE.md

13 KiB

STEP20C presentation identity probe

Status: EXPERIMENTAL / PARTIALLY_FEASIBLE. STEP20C remains NEXT; production multi-output Mosaic remains NOT YET SUPPORTED. No production compositor, public ABI, package, patch or UX contract changes.

Question and fixed scope

Can a private headless trace correlate an accepted output commit with its backend present event, the applied Mosaic camera/clip state observed while building that output state, and later pointer delivery? Repeat only scroll_x 400 → 1280 → 400 with the same mapped, genuinely grabbed nested XDG popups A/B. HEADLESS-1 is (-800,0,800,600) and HEADLESS-2 is (0,0,800,600), scale 1, normal transform. Controls are unmodified SwayFX splith, private SwayFX splith, and private Mosaic mosaic. Standard controls have no synthetic camera action.

Audited signal path

The installed wlroots-0.20 package reports 0.20.2. Its installed wlr_output.h exposes a per-output uint32_t commit_seq, events.commit, events.present, and a present event with uint32_t commit_seq, bool presented, timespec when, unsigned seq, int refresh in nanoseconds and uint32_t flags. The sequence may wrap. The pinned upstream wlroots 0.20.2 source is tag commit d783533489e1f75d6886c2ab5c5960090ef268f8. wlr_output_commit_state increments the native sequence only after a successful backend commit, then emits events.commit. Its commit event has a monotonic when and a borrowed state pointer valid during the callback.

The 0.20.2 headless backend creates a presented=true event with commit_seq = wlr_output->commit_seq + 1 for a commit leaving the output enabled, then defers it to an event-loop idle callback. The core fills a zero when with CLOCK_MONOTONIC at delivery. Headless initializes neither hardware refresh sequence nor hardware presentation flags, and it has no physical display. Allocation failure can suppress a deferred event. An accepted commit, an idle present, a rendered buffer, a grim screenshot and photons are distinct observations. The header alone cannot establish buffer content or physical timing. Source references: headless/output.c, types/output/output.c.

SwayFX sway/desktop/output.c calls output_configure_scene, tests wlr_scene_output_needs_frame, builds the output state, and calls wlr_output_commit_state in output_repaint_timer_handler. Its existing present listener updates refresh prediction but ignores unpresented events. The private SceneFX subtree clip and owner visibility guard govern render and hit testing; prior TEMPORAL_DAMAGE lines are output-local bounding extents, not pixman region decompositions. Sway's logical scroll_x, current container geometry and applied scene nodes are separate.

Instrumentation contract

Apply a distinct, hash-checked patch only in a pinned private compositor copy. Log native output identity and commit_seq from events.commit and events.present, the monotonic callback time, present when, presented, seq, refresh, flags, and the result of the repaint commit call. A per-output listener must be removed before its output is destroyed. The trace must not change commit, clip, damage, hit-test or transaction decisions. Logging itself may affect timing and requires a control comparison.

At the last safely observable point before output-state build, record the active workspace, Mosaic logical camera, owner identity and current geometry, scene node position, and any accessible applied clip/owner visibility. Label unavailable fields UNOBSERVED; never replace them with pending values. Record a native sequence only when the callback or accepted commit provides it. A scene snapshot taken before build is an ordered observation, not proof of the pixels within the committed buffer. Event data and object pointers must not be retained beyond callback/object lifetime.

Keep TEMPORAL_DAMAGE, SPIKE_DAMAGE, client XDG logs, real virtual-pointer requests, IPC snapshots, and grim PNGs. The runner records capture start and finish monotonic times, output, SHA-256, ROI and A/B color counts. Only a native (output, commit_seq) match may pair commit and present; timestamp order alone cannot do so. An accepted commit lacking present stays unpresented in the observed trace. Multiple commands can coalesce before a commit. Pointer enter/button and popup_done belong to their own timed event stream and are never assigned a fictional frame number.

Bounded matrix

  1. Validate listener registration, native sequences, timestamps, output separation and removal in one private headless run before repetition.
  2. Open A and B with fresh real button serials; capture settled scene, IPC, clips, XDG mapping, last commit/present and both output PNGs.
  3. From 400, scroll +880; observe full hiding without a click. Scroll -880; check same owner and popup identities, applied clips and restored pixels.
  4. Repeat the settled pair five times, with per-pair event and capture IDs.
  5. Send twelve controlled faster alternating requests; preserve command times, coalesced or missing presentations and unobserved intervals.
  6. In separate runs test B visible, a clipped portion, fully hidden owner and return without prior popup closure. An outside click may dismiss the grab; child-first popup_done is not an invisible-popup hit.
  7. Compare the private standard layout with unmodified SwayFX, then compare private builds with and without new presentation listeners at settled positions. Preserve all failures, timeouts and protocol errors.

Verdict and permanent limits

FEASIBLE requires real headless present events paired by native sequence and demonstrated association of necessary applied state without a repeatable regression. PARTIALLY_FEASIBLE covers partial event/state correlation with unproven buffer identity, input relation or transitions. BLOCKED requires a reproducible leak, invisible hit, lifecycle fault, crash or synchronization violation. INCONCLUSIVE applies when the tested backend/instrumentation cannot supply the needed signal or measurements cannot decide. Untested cells remain NOT TESTED. Even FEASIBLE would not prove every presented pixel, DRM behavior, production readiness, effects, saved buffers, scaling, rotation, migration or SceneFX ABI maintenance.

Measured private headless result

The private laboratory /tmp/swayfx-mosaic-nested-grab-FQLULvot/ contains two identity runs (the second adds a B click after camera return), one complete A/B cycle per configuration, separate Mosaic hidden and outside clicks, and four fixed negative-origin input controls. An earlier listener availability pilot remains at /tmp/swayfx-mosaic-nested-grab-TSp2JWyA/. The structured seven-run presentation analysis reports 482 native accepted commits, 470 headless present events, 470 pairs by (output, commit_seq), 12 commits without an observed present, no orphan present and no rejected repaint call. All present events have presented=true, seq=0, refresh=0, flags=0. Every native commit callback captured a scene snapshot. The unmodified SwayFX control has no private event listeners and is correctly NOT TESTED for native event correlation. None of the 12 unpaired commits is labelled presented. Both HEADLESS-1 and HEADLESS-2 emitted paired events; no paired present callback or monotonic event timestamp preceded its own commit. The 12 unpaired accepted commits are exactly native sequence 2 on both outputs in six instrumented runs (Mosaic cycles, hidden click, outside click, two identity runs, and private standard cycles). Each reports a new buffer and workspace=none during initial output setup. Their cause is not established: UNEXPLAINED / NO PRESENT OBSERVED. They are neither counted as presented nor declared lost. The unmodified control has no native listener.

In the final identity run, ten settled transitions form five complete 400 → 1280 → 400 pairs. At the target-valued HEADLESS-1 commits, the applied tiled scene translation is -scroll_x and its local clip starts at scroll_x (56/56 target-valued commits, all with a paired headless present). With A/B mapped, the root popup clip is nonempty at 400 and empty at 1280. Nested B inherits that root clip; its own mapping identity comes from client logs, not a separate root scene tree. All ten stable left captures alternate zero A/B pixels when hidden and 42,200 A-yellow / 17,940 B-green pixels on return; right output has no sampled popup color. The same owner IPC ID 8 and single A/B map each persist through the identity run. A fresh B button after the final return was delivered to B before explicit closure.

Each identity run issued twelve faster alternating commands. Four command intervals in the pilot and three in the final run contained no HEADLESS-1 commit or present, while later intervals could contain more than one. This is observable coalescence or deferral, not evidence that a command's target was displayed. The final run had 125 commits and 123 associated presents; the pilot had 121 and 119. Of 220 sampled captures across the seven-run analysis, 200 overlapped one present callback during the capture call and 20 overlapped none. Even a single overlapping event does not identify the buffer read by grim.

During initial Mosaic layout setup, five private Mosaic runs observed one presented commit with logical scroll_x=1760 while the applied scene was translated by -1120 and its clip began at 1120. This is a reproducible logical-to-applied lag before the target experiment. The applied scene and clip agreed with each other; the record does not establish a wrong rendered pixel or hit target. No mismatch of those applied values was found in the target 400/1280 commits. It illustrates why logical camera cannot be used as a substitute for an applied scene snapshot.

Separate fixed-geometry input controls used real XDG button serials. Both standard configurations delivered B buttons on the crossing portion of HEADLESS-2 and had 15,860 B-green pixels there. Private Mosaic delivered B inside the viewport; a click on the clipped crossing portion delivered no B button and dismissed B then A under the grab. The separate hidden-owner click likewise delivered no popup button and dismissed in child-first order. Client event lines lack a common monotonic timestamp, so only runner request times, callback times, event order and observed delivery are known; the compositor's internal hit-test result at repaint time is NOT TESTED. No pointer event is assigned to a specific frame. These clicks are not a universal click-through UX decision.

An instrumentation-impact check compared one no-new-listener temporal cycle with the new private cycle. Popup color counts at visible, hidden, returned and closed sampled states, right-output absence, owner IPC ID/geometry/focus, two valid grabs, popup mapping and absence of popup_done all agreed. This does not exclude timing perturbations between samples. No crash, protocol error, failed grab or reproducible invisible popup hit appeared in the tested matrix.

Verdict: PARTIALLY_FEASIBLE. Native headless present is available and can be paired with native output commits. Applied scene state can be read at the commit callback, after output-state construction. Neither callback proves which pixels occupy that commit's buffer; grim captures lack an exact commit identity. The smallest next experiment is a private, bounded buffer fingerprint tied to the actual output-state buffer at build/commit and retained safely until its matching headless present callback, then compared with a capture from that same buffer. It must establish buffer lifetime and readback semantics first, without changing the production renderer or SceneFX ABI.

The retained presentation-results/presentation-artifacts-sha256.txt lists 653 raw and derived files and has SHA-256 d37dd26a95b895a115518c9b40b288822f71bac41dd08a2fda59cd30e0013e03. All 653 entries passed sha256sum -c. The five-input presentation-source-sha256.txt has SHA-256 40477a991f294b85c2828a8124e24379fba985695a583435a11de91a9005e92f and passed its five source checks. The distinct instrumentation patch has SHA-256 dbf1f1d11c9c741b641916d25ac22bcb13c3455ee3002e1f534aa6794cd8a538. The lab retains its private build log and Meson test log. Generated binaries, PNG, sockets and logs remain outside Git.

Protocol history

  • 2026-10-12: API/backend/repaint source audit and protocol recorded before a new private patch or experimental run. Measurements follow below.
  • 2026-10-12: private event listener and applied-scene trace passed the bounded headless matrix above. The result remains PARTIALLY_FEASIBLE.
  • 2026-10-12: independently rebuilt the pinned private stack in /tmp/swayfx-mosaic-nested-grab-tMVZBojD/ and ran one 400 → 1280 → 400 cycle with A/B in private Mosaic, plus unmodified and private splith controls. The three-run analysis found 127 accepted native commits, 123 matching headless presents, zero orphan presents, four initial sequence-2 commits without observed present (two per instrumented run), 70 captures, and zero runner or protocol failures. The model Meson test passed. This bounded replay confirms event correlation and settled sampled behavior; it does not identify committed buffer pixels.