swayfx-mosaic/docs/design/STEP20C_MUTATION_COVERAGE_PROBE.md

297 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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](../../tooling/probes/step20c-mutation-coverage/inventory.json)
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](../../tooling/probes/step20c-mutation-coverage/README.md)
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.