swayfx-mosaic/docs/design/STEP20C_SAVED_TRANSACTION_BUFFER_PROBE.md

15 KiB
Raw Permalink Blame History

STEP20C saved transaction buffer clipping and lifetime probe

EXPERIMENTAL / INCONCLUSIVE, preserved after execution. STEP20C remains NEXT and multi-output Mosaic production is NOT YET SUPPORTED. This campaign follows the static R90 popup evidence without changing production Mosaic, SceneFX algorithms, UX or packaging.

Source contract and ownership

Pinned inputs: SwayFX 0.6 fd71a6bdc061bd633b488ae7b83e8a6981d22f86, private Mosaic cd550382b311efdbfe3ed892f4e1d281d852e754, SceneFX 0.5 37ccd723bef49e6891156ffafce8f549f01446cc, wlroots 0.20.2. The private source tree is disposable; installed libraries remain untouched.

transaction_commit() configures a visible changed-size view when should_configure() sees different current/instruction content dimensions, then calls view_save_buffer() for every visible view instruction without an existing saved tree. view_save_buffer() creates saved_surface_tree under view->scene_tree, places it below output_handler, initially disables it, iterates enabled buffers of content_tree, creates new scene buffer nodes, and calls wlr_scene_buffer_set_buffer(sbuf, buffer->buffer). This reuses a referenced source buffer; it does not copy pixels to a new SHM image. SceneFX's setter calls wlr_buffer_lock() on a non-null buffer and unlocks the previously owned buffer when replacing it; its texture conversion path may release that lock after obtaining a texture. Node destruction releases its owned buffer/texture through the same scene lifetime. Pointer equality at creation is therefore evidence of reuse, not proof that one particular buffer lock remains held throughout every rendered frame. The live content tree is then disabled and the saved tree enabled.

The saved tree is a sibling of content_tree, not its descendant. apply_container_state() normally removes it via view_remove_saved_buffer(): destroy saved scene nodes and enable live content. The destroying-container branch skips this immediate removal; close animation and container teardown must be audited separately. XDG commit uses the actual current configure serial to notify transaction readiness; a nonmatching commit can send frame done to a saved view. transaction_progress() applies when all waiting instructions are ready. Its timeout callback sets waiting to zero and uses the same progress path. A new pending transaction cannot commit while a queued transaction exists. -Dtxn-timeout=1200 sets the private test policy; the code default is 200 ms. No -Dtxn-wait or -Dnoatomic is permitted.

view->scene_tree is normally reparented under tiled container content_tree. The private Mosaic clip is set on the owning workspace's layers.tiling; SceneFX's scene_node_intersect_clip() walks all tree ancestors for render visibility and damage, and scene_node_point_inside_clip() guards node-at. Thus inheritance is plausible for the saved sibling, but runtime parentage, enabled state, actual buffer, render contribution and pixels must be observed. The SWAY_SCENE_DESC_SAVED_BUFFER descriptor marks saved nodes. SWAY_SCENE_DESC_VIEW marks the view tree; container descriptors identify ownership. output_handler is an input-rejecting buffer above the saved tree and tracks output updates. A saved scene buffer is not itself a Wayland surface: node-at, surface lookup, seat notification and client delivery are separate observations. An output buffer and an optimized blur cache are different objects with different lifetimes.

For destruction, XDG unmap calls view_unmap(), which starts container destruction. container_destroy() asserts zero transaction references before detaching the view and destroying its scene tree; view_destroy() also asserts the view is unmapped, marked destroying and detached. The destroying branch of apply_container_state() deliberately skips immediate saved-tree removal. transaction_destroy() can defer final container deletion through a close animation, whose completion callback removes the saved tree before destroying the container. The observed ACK/timeout episodes did not exercise mapped-view destruction while a transaction was waiting; that path remains NOT TESTED.

Controlled fixture and evidence gates

Use one C17 XDG SHM client in all modes. It maps a deterministic initial buffer, logs configure serial/size/monotonic time, keeps dispatching pings and input, and accepts hold_next_configure / release_configure over a private control channel. Release ACKs the latest valid serial and commits the corresponding size; it never invents a serial. A normal geometry command (candidate: gaps inner after source and IPC calibration) must create a real visible view instruction, a nonempty saved tree, live-content disablement and a waiting transaction. A camera change alone is insufficient. A source trace without an active nonempty saved buffer does not pass S1.

Use H1 800×600, scale 1, normal, origin (-800,0), and H2 800×600, scale 1, normal, origin (0,0). H1 hosts four tiled cells and a Mosaic camera near 400; H2 hosts an ordinary reference surface. Recalculate owner, scene, snapshot and viewport boxes from applied IPC and trace. The clipping gate requires saved_buffer_left < 0 < saved_buffer_right; otherwise partial exclusion is NOT EXERCISED. Capture a full H2 reference before transaction.

One run each: C0 pristine splith, C1 private splith, C2 private mosaic. Controls validate ordinary snapshot/ACK/timeout/lifetime behavior, though standard tiling need not overflow. S0 calibrates outputs, renderer, PNGs, view/owner, viewport, pointer and ordinary input. S1 retains a real configure and proves creation, active buffer and wait. S2 captures active snapshot, render entries, ancestor clip, H1/H2 pixels, damage and commits/presents. S3 samples allowed, forbidden and ordinary H2 hit points, separating node-at from client delivery. S4 releases and observes ACK, commit, apply, saved-tree destruction, restored content, pixels and damage. S5 holds a second configure past the real timeout with the client alive and checks cleanup. S6 may use one camera hide/return pair only if S1–S4 show actual saved rendering and source/runtime prove overlap with the active snapshot. A queued camera request alone proves nothing. Mapped-view destruction is audited; unsafe runtime destruction remains NOT TESTED.

Passive private traces may record tree/buffer creation and destruction, source buffer identity, parent chain, clip, enabled state, render entry, damage, transaction wait/ready/timeout/apply, node-at and output commit/present. Use CLOCK_MONOTONIC and ephemeral identities scoped to lifetime. Logging must not add a buffer lock or change clipping, render, scheduler or ABI behavior. Compare whole PNGs and measured damage in their actual coordinates. A pair of captures cannot qualify every intermediate frame, and grim does not identify the native presented buffer.

Budget and classification

The single persistent locked ledger reserves before execution: at most three main runs, one per mode; two targeted transaction episodes per mode (ACK and natural timeout); one Mosaic camera hide/return pair; 20 captures per run, 60 overall; 150 seconds per run unless explicitly justified before launch. Failed attempts consume reservations. No reset, refund, clone bypass or extra run to repair placement. Historical probe ledgers remain unchanged.

FEASIBLE requires an actual rendered saved buffer crossing x=0 under Mosaic, correct H1/H2 pixels and hits, ACK and timeout removal, no blocking standard regression and supporting measured damage. PARTIALLY_FEASIBLE means a real saved tree works but lifetime, input or pixels remain unqualified. INCONCLUSIVE means the snapshot, boundary or live/saved distinction cannot be exercised. BLOCKED requires a reproducible forbidden pixel/hit, reference defect, crash or blocking standard regression. Stop the affected scenario for a protocol error, uncalibrated geometry or proven leak. Do not patch algorithms to pass.

All builds, sockets, logs, PNGs and results stay outside Git under one private LAB. Every headless run uses a private 0700 runtime, WAYLAND_DISPLAY and explicit SWAYSOCK. The source fixture is under tooling/probes/step20c-saved-transaction-buffer. Saved buffers do not qualify optimized blur, all effects/animations, all transforms/scales/backends, complete intermediate frames, SceneFX ABI/package or production STEP20C.

Measured campaign

INCONCLUSIVE / measured 2026-10-12. One headless run was reserved per mode. The pristine standard control completed held-configure/ACK and natural timeout episodes with -Dtxn-timeout=1200. Four nonempty saved buffers were created per target episode, with source/saved buffer pointers equal and content_enabled=0 saved_enabled=1; all four trees were destroyed about 56 ms after ACK in episode one and about 1200 ms after creation in episode two. The pristine SceneFX installation did not carry the private render-entry trace, so its exact saved-node render contribution is unqualified. H2 stayed identical through both held captures; the final timeout H2 capture changed 253 pixels in box [4,35,15,58), not yet causally attributed; the Mosaic timeout capture has the same count and box.

The private-standard ACK episode created four saved buffers and four of their nodes reached SB_RENDER, but the fixture injected six slow pointer actions during the 1200 ms hold. New XDG serials superseded the first retained serial; the client correctly ACKed the latest serial after the compositor's natural timeout. The runner then failed waiting for a post-ACK destruction that had already occurred. Its single run and ACK reservation remain consumed; its timeout episode is NOT EXERCISED. This is a fixture timing incident, not a demonstrated compositor regression.

The private Mosaic run completed both episodes. Each created four saved buffers; two saved nodes reached the private render-entry trace before each held capture, and all four saved trees were destroyed after ACK or natural timeout. The layout mosaic scroll 400 command was relative to the existing camera: applied IPC/scene state settled at scroll 1760, with H1's tiled scene at -1760 and clip (1760,0,800,600). The target owner's IPC rect was [-160,480) and its camera-adjusted box was [-1920,-1280), wholly outside H1. Its deterministic SHM colors had zero pixels on both H1 and H2 through the Mosaic captures. Consequently, the target snapshot did not exercise saved_buffer_left < 0 < saved_buffer_right, and the central partial clipping test is NOT EXERCISED. Other visible saved nodes rendered; the trace does not identify them as the target. H2 was identical to reference through the held/ACK captures, then changed 253 pixels in the final timeout capture, in the same [4,35,15,58) box as the pristine control. This is unqualified without a full causal frame trace. No forbidden target pixel or crash was demonstrated. Input, node-at, damage coverage, camera hide while a target snapshot is active and mapped-view destruction remain NOT TESTED.

The campaign budget ended at 3/3 runs and 5/6 targeted episodes: control ACK+timeout, private standard ACK attempt, Mosaic ACK+timeout. Captures were 10/20, 4/20 and 10/20 respectively, 24/60 total. No camera hide/return pair was used. Failed reservations were not refunded. The private LAB is /tmp/swayfx-mosaic-nested-grab-KYJ2pj1o; results/ contains raw-derived JSON and source/evidence manifests. No additional run is authorized by this campaign. Verdict: SWAYFX_MOSAIC_SAVED_TRANSACTION_BUFFER_PROBE=INCONCLUSIVE and architectural conclusion UNDECIDED. The next saved-buffer gate needs a new explicitly authorized campaign with a pre-run applied camera/owner crossing calibration; this campaign gives no production STEP20C authorization.

The final source manifest contains 906 entries, SHA-256 e11a0800b84f6a7b12a383faa87d9d04bf8d53232fb83e6d12574b8569cc384c. The evidence manifest contains 90 entries, SHA-256 5c00b78b19fcffacb3922bdb600300188972e85b83fe8356aebbc65280dc7e30. Both passed entry-by-entry verification. The passive Sway trace patch is 106544aade58e05e7245601ee380e763ac19521f7d51e1ada410b4e1e7a19997; the SceneFX trace patch is 82ebdd4fb003be1729ecef443a92190b010f65b62ed858c546c019743998037e. These hashes identify the local source fixture; no phase-B commit or push was made.

Executed runner provenance and camera correction

The exact runner used for each run is retained in its runs/*/fixture-source.py, covered by the evidence manifest. Their SHA-256 values are: pristine bc041eb7be89da9e198bb9ca2e25cfe86467c264e3687e6d9d3f3836e82ce40c, private standard 67aa3fb94901739bea0b1d0e0cf881081d2388a02256dddc45282d7a4bc86b25, and private Mosaic cdece69cf956e550779bd992b0d38234629ca1e611dfb3fb0348c04841c47f59. The checked-in run.py matches only the final private Mosaic copy. The pristine copy had no pointer injector. The private-standard copy added the pointer daemon and six move/right actions during the retained ACK episode. The Mosaic copy retained the daemon but removed those six actions after they consumed the 1200 ms window in the private-standard run. The last standard ACK used a real serial, but followed the natural timeout; the fixture then waited for an already completed saved-tree destruction. This is a fixture failure, not a demonstrated SwayFX regression. The corresponding raw run source and timeline must be used for historical reconstruction.

sway/commands/layout.c computes next = row->state.scroll_x + delta. Consequently layout mosaic scroll 400 added 400 to the existing camera; the observed applied scroll_x was 1760. The target IPC owner box [-160,480) would translate to [-1920,-1280), wholly outside the viewport. This owner estimate is not a measurement of the source buffer's scene box. A new campaign must measure that buffer, calculate one signed correction from the applied camera, and reject the run before any transaction reservation if the buffer itself does not cross x=0.

The final timeout H2 images of pristine and Mosaic both differ from their references by 253 pixels in [4,35,15,58). The common extent does not identify the cause. A cursor or reference artifact remains a hypothesis; the captures were not altered and no Mosaic attribution follows.

The historic ledger is exhausted at 3/3 runs, 5/6 reserved episodes, 24/60 captures and 0/1 camera pairs. The unused episode cannot replay an old run. Saved-tree removal in completed episodes establishes node cleanup, not exhaustive GPU texture/reference release. Transactional input, damage, mapped-view destruction and distinct effects/caches remain NOT TESTED. The private Mosaic trace retained 57 output commits and 55 headless presents over the whole run; private standard retained 100/98. These are run totals, including startup, capture and teardown activity, and do not identify the exact native buffer content of a particular screenshot. The pristine control lacked the private commit/present trace.