swayfx-mosaic/docs/design/STEP20C_MUTATION_COVERAGE_PROBE.md

19 KiB
Raw Permalink Blame History

STEP20C mutation coverage and lifetime probe

Status: EXPERIMENTAL / PARTIALLY_FEASIBLE. The protocol preceded the private tests and the findings follow below. STEP20C remains NEXT and production multi-output Mosaic remains NOT YET SUPPORTED. This probe uses disposable private sources and headless sockets; it changes no production compositor, SceneFX ABI, package, user interaction or installed dependency.

Question and observation boundary

Determine which applied mutations can change pixels, scene hit geometry, pointer delivery or only metadata between output-state construction and an executed input test. The existing asi_applied_epoch increments at selected transaction and popup arrangements only. Equality of this partial counter cannot establish a complete scene generation. Native output commit_seq and headless present identify an output submission, not the scene used by a later pointer event or a physically displayed frame.

The candidate minimal boundary is workspace/owner identity, applied scene position, Mosaic tiled and owner-derived popup clips, surface hierarchy, visibility, order, hit geometry and spatial transform. Client buffer content is tracked separately from geometry. Output presentation identity is tracked per output. XDG grab state controls delivery after a scene hit and must be reported separately. These are hypotheses for comparison, not a new production generation contract.

Source and owner audit plan

Inspect pinned Mosaic/SwayFX transaction.c, output.c, xdg_shell.c, cursor.c, seat.c, arrange.c and mosaic.c; private SceneFX clipping; and the wlroots 0.20.2 scene/output/surface paths. Trace callers and callbacks for node position, enabled state, reparent/order, creation and destruction, clips, surface map/unmap, subsurfaces, input region, buffer commits, scale/transform, damage, effects, animations, workspace/output changes, focus and XDG grabs. Record the owner of each mutation and whether it is immediate or transaction applied. A text search alone is insufficient.

The structured inventory in the private laboratory records source function, object, pixel/hit/damage effect, transaction timing, current epoch coverage, trigger and verdict. Every untriggered path stays NOT TESTED. Distinguish pixel only, hit only, both, grab delivery only and metadata only. Preserve monotonic timestamps, applied snapshots, native commits/presents, executed hits and client events without assigning a fictional frame ID to input.

Bounded experiments and stop rules

Use HEADLESS-1 (-800,0) and HEADLESS-2 (0,0), both 800×600 at scale 1 and normal transform. Run an unmodified SwayFX splith control, private splith, and private mosaic with trace-only instrumentation first.

  • T1: camera 400 → 1280 → 400; correlate actual scene/clip mutation sites, partial epoch, output state, commit and present.
  • T2: create grabbed A then B; close B while A remains, then close A; correlate hierarchy and hit changes, including uncovered sites.
  • T3: commit changed client pixels at fixed intended size/position; establish a visible new buffer and corresponding native commit/present, then compare geometry and partial epoch.
  • T4: choose one reproducible input-region, subsurface, order or geometry mutation from the source audit; compare scene hit and client delivery.
  • T5: compare ordinary splith behavior in unmodified and private builds.

Use targeted captures only. Read a true output buffer only if a specific remaining question requires it; time and bound any such copy per output. grim is an independent capture, not a commit-buffer identity proof.

Audit rejected-commit and output-destroy lock cleanup first in source. A safe private headless trigger may be used only if the backend and callback lifetime permit it. Otherwise preserve a clearly synthetic branch test or mark it NOT TESTED; no invalid state or unsafe output destruction is allowed. Count each extra lock/unlock and retain exit status and logs.

Separate verdicts for mapping, runtime mutation scenarios and cleanup. FEASIBLE covers only triggered bounded paths with matching geometry and input and no blocking regression. PARTIALLY_FEASIBLE retains useful evidence with important unqualified paths. INCONCLUSIVE means reliable observation was unavailable. BLOCKED requires a reproduced mismatch, unsafe lifetime, crash or blocking regression. The final architecture answer is UNDECIDED unless the tested and source-audited boundary can actually discriminate native SwayFX consistency from a required additional transaction/generation contract.

Source inventory and ownership findings

The audit used private Mosaic derived from published 1f59cb433bcdad006c23df10496cb4709551c624 and the frozen patch chain, SceneFX 0.5 from 37ccd723bef49e6891156ffafce8f549f01446cc, and wlroots 0.20.2 source tag 0.20.2 (d783533489e1f75d6886c2ab5c5960090ef268f8). The structured inventory is copied into the private lab with the scenario matrix. It distinguishes observed paths from untriggered callers; it is not a claim that all scene mutations have been exercised.

Class Actual owner and path Effect and current coverage
Camera, tiled clip, owner popup clip sway/desktop/transaction.c:arrange_output, arrange_popups, probe_clip_popup; sway/tree/mosaic.c supplies logical camera Pixels and hit. Transaction arrangement is marked; direct popup creation can call arrangement independently. T1 sampled.
XDG hierarchy and map lifecycle sway/desktop/xdg_shell.c:popup_create, handle_new_popup, popup destroy; wlroots types/scene/subsurface_tree.c map/unmap/new-subsurface listeners Pixels and hit. Creation/destruction themselves have no asi_mark; incidental arrangements may mark later. T2 A/B lifecycle sampled; subsurface mutation open.
Generic scene node and clip SceneFX types/scene/wlr_scene.c:wlr_scene_node_set_position, set_enabled, reparent, place_above/below, wlr_scene_tree_set_clip, scene_node_update Pixels, hit and conditional damage. The current counter covers only selected callers. Uninstrumented effects and SceneFX callbacks remain.
Client content wlroots types/scene/surface.c:handle_scene_surface_surface_commit, surface_reconfigure; SceneFX wlr_scene_buffer_set_buffer_with_options Pixels/damage; hit geometry only if dimensions, crop, transform or related state change. T3 fixed-size recolor sampled with no geometry change.
Client input region wlroots types/scene/surface.c:scene_buffer_point_accepts_input → wlr_surface_point_accepts_input; SceneFX scene_node_at_iterator Hit only for fixed pixels. T4 changed hit with the same partial epoch and unchanged sampled pixel.
Effects and decorations sway/desktop/output.c render update; SceneFX rect/shadow/blur/buffer setters Mostly pixels/damage, sometimes interactive rect geometry. Broad effect paths NOT TESTED.
Output and presentation sway/desktop/output.c:output_repaint_timer_handler, commit/present listeners, destroy; wlroots output commit/state/headless deferred present Per-output submission identity. Neither commit_seq nor present is an input generation. Fixed geometry only.
Grab, focus and IPC sway/input/cursor.c:node_at_coords, sway/input/seat.c:seat_pointer_notify_button, wlroots XDG grab handlers Grab changes delivery after scene hit; focus/IPC can change metadata or decorations without moving hit geometry. Client receipt is separate evidence.

SceneFX's private tree clip intersects both visible render/damage bounds and wlr_scene_node_at() traversal. Its setter calls scene_node_update when the clip changes. wlroots' subsurface tree reconfigures position and order on a surface commit and handles map/unmap/new-subsurface callbacks. A simple search of Mosaic asi_mark sites would miss these transitive changes. transaction_progress() applies queued state, arranges root, then marks the partial epoch; it is not a commit/present barrier. The compositor event loop orders individual callbacks, but an input callback need not observe the same scene that a previously built buffer used.

Runtime matrix and correlations

Lab: /tmp/swayfx-mosaic-nested-grab-OWIvboDB/. All nine T1–T5 runs reported failure=null, used private headless sockets and exited through runner cleanup. The unmodified SwayFX control lacks private native commit/present logging, so its zero trace count is no observation, not zero actual commits. For each private run, the only unmatched accepted native commits were initial sequence 2 on each output; no headless present was observed for those two. No B case used an output-buffer readback.

Scenario Private Mosaic Private splith Unmodified splith
T1/T2 one camera and popup lifecycle cycle 70 accepted commits / 68 presents; two real A/B grabs; A/B created and destroyed 57/55; two grabs; A/B created and destroyed Native counts uninstrumented; two grabs; A/B created and destroyed
T3 same-size recolor 34/32; pixel (35,68,204) → (208,32,192) 36/34; same pixel change Same pixel change; native counts uninstrumented
T4 empty/full input region 42/40; sampled pixel stays (35,68,204) 43/41; same sampled pixel Same sampled pixel; native counts uninstrumented

T1. The private Mosaic output-state stages show 400/1280/400 with applied tiling positions -400/-1280/-400 and tree clips (400,0,800,600)/(1280,0,800,600)/(400,0,800,600). SceneFX logged the corresponding actual clip mutations, including a hidden-owner empty popup clip at 1280. The sampled output stages reached accepted native commits and headless presents. The epoch advanced at the transaction and popup arrangements, but it can also advance when sampled state is unchanged. The controls remained splith without a Mosaic camera.

T2. The private client created distinct A and B XDG surfaces with two pointer-derived grab serials. close_b destroyed B; a subsequent right button reached A, then close_a destroyed A. The same A-after-B input result and lifecycle occurred in both standard controls. Direct creation, map, and destruction of the scene objects are not individually covered by asi_applied_epoch; later transaction arrangements caused separate marks. The trace proves the sampled hierarchy/lifecycle and actual A delivery, not a complete mutation-generation boundary.

T3. The private client issued a second wl_surface.attach and commit for the toplevel at the same 420×320 size and unchanged intended position. The new color was visible in a targeted capture after HEADLESS-1 native commit/present 20 in the Mosaic run. Its COMMIT_ACCEPTED stage still had epoch 12, tile -400,0 and clip (400,0,800,600); the prior relevant stage had the same values. Thus a real content change was absent from the partial epoch, as expected for a content-only mutation. The capture is temporally correlated with the native event, not an exact read of its buffer.

T4. No popup or XDG grab was active. At one global point on the toplevel, the executed ASI_HIT changed from its surface to nil after the client committed an empty input region. Both hits recorded epoch 12, tile -400,0 and clip (400,0,800,600). The pixel at that point remained (35,68,204) in all three captures. Before and after restoration the toplevel received a right-button press; while empty it received none. Unmodified and private standard layouts likewise delivered two toplevel presses, before and after the empty region, with unchanged sampled pixels. An incidental later transaction mark does not erase the same-epoch hit counterexample. This is an actual hit-affecting mutation missed by the counter, without a demonstrated clipping defect.

T5. Standard layouts preserved the popup close order, A delivery after B closed, toplevel recolor, and input-region behavior. Private trace logging added native records but no reproduced blocking difference in these cases. No exhaustive timing or effects comparison was made.

Output-buffer lifetime audit

sway/desktop/output.c:output_repaint_timer_handler takes an extra probe lock before wlr_output_commit_state. On a rejected commit it directly unlocks that reference, before the state is finished. On an accepted commit, it installs the lock in the output-scoped slot and starts a timer; a same output buffer replacement releases the old slot. begin_destroy releases an active slot before the output pointers/listeners are removed. The helper clears the slot before removing its timer and unlocking. wlroots 0.20.2 types/output/output.c:wlr_output_commit_state emits the native commit event synchronously after acceptance; types/output/state.c owns a separate buffer reference until wlr_output_state_finish; headless present is deferred and cancellation on output destroy is handled by wlroots.

One controlled real private headless case retained buffer ID 1 after HEADLESS-1 present sequence 30, then stopped the compositor normally. begin_destroy emitted exactly one ASI_RELEASE id=1 reason=output_destroy, age 863,608,059 ns; the compositor exited 0 and the runner removed its private runtime. This qualifies that sampled output-destroy path only. A C17 synthetic model passed once-only release for direct rejected commit and repeated destroy release, but no real rejected commit was triggered. Backend rejected-commit cleanup is NOT TESTED. The synthetic result cannot qualify a real backend.

Verdicts and architectural boundary

  • Mapping: PARTIALLY_FEASIBLE. Source ownership and major classes are identified, but output migration, animations, effects, subsurfaces, stacking and other scene callers remain untriggered.
  • Mutation scenarios T1–T5: FEASIBLE for the bounded matrix. Observed geometry, clip, hits and client delivery agree. T4 disproves completeness of asi_applied_epoch; T3 shows why pixels must not automatically be folded into a geometric generation.
  • Cleanup: PARTIALLY_FEASIBLE. A real held-reference output destruction passed; the backend rejected-commit branch remains NOT TESTED.
  • Overall probe: PARTIALLY_FEASIBLE. No sampled crash, invisible hit, clip leak or standard-layout regression was reproduced. This does not qualify production STEP20C or all SceneFX mutations.

The minimal candidate coherence boundary is an applied geometry/hit identity for workspace and owner, scene position, viewport and tiled/root popup clip, hierarchy, enabled state, order, surface hit geometry and spatial transforms. A separate content identity may track attached buffers and effect pixels without pretending they change hit geometry. Native presentation identity remains (output, commit_seq) with the headless present observation. Grab state must be associated with delivery, not retroactively treated as the scene hit. This separation is a research hypothesis, not an authorized production API or counter design.

Architecture answer: UNDECIDED. The current SwayFX/SceneFX live scene was consistent with rendering and input in the sampled states, including the input-region mutation. The partial epoch is demonstrably insufficient as a generation identity. The evidence does not show whether existing event-loop ordering alone can enforce the required cross-time relation between an already built output buffer and a later hit during all relevant Mosaic mutations. The smallest discriminating next experiment is a controlled private mutation of hit geometry between a known output-state build and its deferred headless present, with scene state sampled at build, immediately before and after the mutation, at present, and at an executed hit. It must retain one output's native (output, commit_seq) and client delivery evidence. It should first use the proven input-region path, then a Mosaic clip/owner change if the first case cannot distinguish the contracts. Broader output/effect coverage follows only after this temporal question is resolved.

Reproduction, hashes and validation

The fixture README provides exact rebuild and replay commands. mutation-results/ contains the structured scenario matrix, lifetime record, copied inventory, and source/artifact SHA-256 manifests. All generated builds, buffers, logs, captures, sockets and manifests stay outside Git. The private client patch applies on the frozen nested-client source; it does not touch Mosaic or installed SceneFX/wlroots. The trace-only scenarios use no probe locks.

The private client patch SHA-256 is abee78ffa80729c395374c037f71b4a3e1158b81df6079ce6effc36afdf5eef6. The 15-source manifest SHA-256 is 2875c22de8bb3a86d8d2f05296e9e141033980bb765e5e8958d5565ef47530ae. The 420-artifact manifest SHA-256 is f0761406414f3131017fc135c01caa4c643463117ae3f7a135d2b12ffa6ae2bc. The scenario matrix and lifetime record SHA-256 values are db2924f300fa139c9d16729ee62a17782698ecb517d42110e9507821c53d5aa1 and 3a1e4900a83ebdcd9b9725caf18519f05de99aad8ab0b58104bd8c73f9bb1658. Both manifests passed sha256sum --quiet -c in the retained lab.

The original source manifest records two wlroots paths under the historical /tmp/mosaic-wlroots-0202-source/; it remains unchanged evidence, not a replay instruction. The corrected preparator fetches the audited wlroots 0.20.2 commit d783533489e1f75d6886c2ab5c5960090ef268f8 into each new lab and checks the two source hashes before analysis. The corrected analyzer reads LAB/wlroots/types/scene/{surface,subsurface_tree}.c. New runs generate new manifests with their own lab paths; the historical manifest hashes above are preserved. This changes source provenance handling only, not T1–T5 instrumentation or the verdict.

An independent clone of main at 1f59cb433bcdad006c23df10496cb4709551c624 with this source fixture overlaid rebuilt private SceneFX and Mosaic, then replayed all nine T1–T5 mode/scenario runs and one output-destroy run in /tmp/swayfx-mosaic-nested-grab-no8fPnyy/. All ten runners reported failure=null. The three T1/T2 controls each made two real grabs. T3's sampled color changed (35,68,204) → (208,32,192) in all three modes; T4's sampled color remained (35,68,204) and each mode delivered exactly two toplevel presses around the empty-region interval. Private Mosaic had 76/74 native commits/presents in T1/T2, 40/38 in T3 and 42/40 in T4; private standard had 56/54, 33/31 and 42/40. Unmodified control native counts remain uninstrumented. The output-destroy replay retained one lock, released it once with reason=output_destroy, and exited 0; the C17 model passed separately. The replay does not upgrade rejected-commit status.

The corrected prepare.sh SHA-256 is eb8482fc4cb6f96636d89a9510cd1e5de9750ebd16d55630e3af6585fe8933e6; the corrected analyze.py SHA-256 is be96ed839d957a51e724be79b78b0124b63a4c67691d5d409f88ab05dd60c4ab. The new 15-source and 364-artifact manifests hash to 61a4e856e0f6cc26bccf661d7ec81f88256a0a3ee34e5bc7cbdf4063faa44f53 and 11233d9e2f08f9c503d74e43157b787423bda50ef507b16a6670e0d64e235471; every entry passed sha256sum -c. The new scenario matrix and lifetime record hash to 001ed3f324156692ae4b1e71298ac4c00f631b5d0a6839db024bc2e6489eed90 and 8e862792d15b021a61f88b0e926b4d67678ada5a8e2d5ee930dc68511365a0b5. Artifact counts differ because the bounded replay produced different native timings and capture files; the historical 420-file manifest was not rewritten. The remaining NOT TESTED paths are not upgraded by a successful compile or a synthetic model.