15 KiB
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.