docs(mosaic): preserve optimized blur cache probe evidence

This commit is contained in:
fy59 2026-10-13 05:10:19 +02:00
parent 6fd8945d10
commit e749653ccd
23 changed files with 4061 additions and 0 deletions

1
.gitattributes vendored
View file

@ -1,3 +1,4 @@
# Preserved experimental patches include diff markers and source-context tabs.
tooling/probes/step20c-valid-grab/*.patch -whitespace
tooling/probes/step20c-saved-target-timeout-input/target-hit-trace.patch -whitespace
tooling/probes/step20c-optimized-blur-cache/*.patch -whitespace

View file

@ -4,6 +4,8 @@
### STEP20C experimental evidence
- Preserved optimized blur cache probe: private splith/Mosaic used a real background source and output-scoped GPU cache, naturally reconstructed A/B/A and restored exact central filtered pixels with stable whole H2 RGB. Boundary equivalence still differs by up to six RGB levels in both private modes. Pristine C0 failed before captures; its duplicate initial image was charged after observation, with no retry or numerical budget overrun. PARTIALLY_FEASIBLE, architecture UNDECIDED; neutrality and whole-cache correctness unqualified. No production change; the historical campaign is closed.
- Preserved saved-target natural timeout gate: one Mosaic run used a static SHM H2 reference with four identical whole-RGB captures. The measured target saved node crossed x=0, rendered 182,214 retained-color pixels on H1, contributed no H2 pixels and was removed after a real 2500 ms timeout while the client held serial 50 alive. Two sampled pointer points found the H2 reference and visible H1 saved node. PARTIALLY_FEASIBLE because complete damage, broader input and GPU/texture lifetime remain unqualified; no production change.
- Local target-crossing saved-buffer probe: one measured signed camera delta placed the real target source and saved node across x=0. The target node rendered within H1's clip and retained its SHM pixels; H2 showed no target color but had a repeated 253-pixel reference difference. Mosaic E2 timeout stopped through stale fixture pointer correlation. PARTIALLY_FEASIBLE, architecture UNDECIDED; no production change, commit or push for this campaign.

View file

@ -314,3 +314,18 @@ all input paths or native GPU lifetime. The earlier crossing campaign's own
architectural verdict remains UNDECIDED. The new limited-matrix conclusion
does not authorize a production implementation. Optimized blur/cache and
SceneFX ABI/package remain separate gates.
The preserved [optimized blur cache probe](design/STEP20C_OPTIMIZED_BLUR_CACHE_PROBE.md)
is **PARTIALLY_FEASIBLE**, architecture **UNDECIDED**. A real background
layer under blur_tree triggered natural output-specific cache reconstruction
and fresh central A/B/A filtered pixels in private splith and Mosaic.
Sampled final tiled drawing excluded H2, whose whole RGB remained identical;
output damage extents covered stabilized differences and sampled cache/FBO
teardown completed. Shared boundary differences up to six RGB levels remain
unresolved. Pristine C0 failed before captures through a duplicate configure
fixture; its second initial image was reserved only after observation.
No numerical budget was exceeded or run repeated. Instrumentation neutrality,
whole-cache equivalence and intermediate frames remain unqualified. A
separate pristine/untraced boundary-sampling gate should precede ABI/package
qualification. This evidence freeze changes no production or UX contract.
STEP20C stays **NEXT**, multi-output **NOT YET SUPPORTED**.

View file

@ -255,3 +255,18 @@ unmeasured input paths, seat notification for the saved node and complete
GPU/texture reference lifetime remain **NOT TESTED**. The old Kitty cursor
explanation remains strongly probable, not causally proven. STEP20C remains
**NEXT** and production multi-output Mosaic **NOT YET SUPPORTED**.
The preserved [optimized blur cache probe](design/STEP20C_OPTIMIZED_BLUR_CACHE_PROBE.md)
is **PARTIALLY_FEASIBLE**, architecture **UNDECIDED**. A real background
layer under blur_tree triggered natural output-specific cache reconstruction
and fresh central A/B/A filtered pixels in private splith and Mosaic.
Sampled final tiled drawing excluded H2, whose whole RGB remained identical;
output damage extents covered stabilized differences and sampled cache/FBO
teardown completed. Shared boundary differences up to six RGB levels remain
unresolved. Pristine C0 failed before captures through a duplicate configure
fixture; its second initial image was reserved only after observation.
No numerical budget was exceeded or run repeated. Instrumentation neutrality,
whole-cache equivalence and intermediate frames remain unqualified. A
separate pristine/untraced boundary-sampling gate should precede ABI/package
qualification. This evidence freeze changes no production or UX contract.
STEP20C stays **NEXT**, multi-output **NOT YET SUPPORTED**.

View file

@ -164,3 +164,18 @@ crossing target saved node rendered inside H1 and was removed after a real
timeout with the XDG client alive; sampled H2 input reached the reference.
Complete damage and GPU lifetime remain open. STEP20C stays NEXT and
production multi-output remains NOT YET SUPPORTED.
The preserved [optimized blur cache probe](design/STEP20C_OPTIMIZED_BLUR_CACHE_PROBE.md)
is **PARTIALLY_FEASIBLE**, architecture **UNDECIDED**. A real background
layer under blur_tree triggered natural output-specific cache reconstruction
and fresh central A/B/A filtered pixels in private splith and Mosaic.
Sampled final tiled drawing excluded H2, whose whole RGB remained identical;
output damage extents covered stabilized differences and sampled cache/FBO
teardown completed. Shared boundary differences up to six RGB levels remain
unresolved. Pristine C0 failed before captures through a duplicate configure
fixture; its second initial image was reserved only after observation.
No numerical budget was exceeded or run repeated. Instrumentation neutrality,
whole-cache equivalence and intermediate frames remain unqualified. A
separate pristine/untraced boundary-sampling gate should precede ABI/package
qualification. This evidence freeze changes no production or UX contract.
STEP20C stays **NEXT**, multi-output **NOT YET SUPPORTED**.

View file

@ -259,3 +259,18 @@ unqualified. Its verdict is **PARTIALLY_FEASIBLE**; the earlier probe's own
UNDECIDED conclusion is preserved. The next separate STEP20C gate is
optimized blur/cache, followed by SceneFX ABI/package. STEP20C is still
**NEXT**, production multi-output **NOT YET SUPPORTED**.
The preserved [optimized blur cache probe](design/STEP20C_OPTIMIZED_BLUR_CACHE_PROBE.md)
is **PARTIALLY_FEASIBLE**, architecture **UNDECIDED**. A real background
layer under blur_tree triggered natural output-specific cache reconstruction
and fresh central A/B/A filtered pixels in private splith and Mosaic.
Sampled final tiled drawing excluded H2, whose whole RGB remained identical;
output damage extents covered stabilized differences and sampled cache/FBO
teardown completed. Shared boundary differences up to six RGB levels remain
unresolved. Pristine C0 failed before captures through a duplicate configure
fixture; its second initial image was reserved only after observation.
No numerical budget was exceeded or run repeated. Instrumentation neutrality,
whole-cache equivalence and intermediate frames remain unqualified. A
separate pristine/untraced boundary-sampling gate should precede ABI/package
qualification. This evidence freeze changes no production or UX contract.
STEP20C stays **NEXT**, multi-output **NOT YET SUPPORTED**.

View file

@ -0,0 +1,486 @@
# STEP20C optimized blur cache invalidation probe
**EXPERIMENTAL / PARTIALLY_FEASIBLE; architecture UNDECIDED.**
The protocol below preceded the graphical runs; actual evidence follows. The
previous ticket published its saved-timeout phase at
`6fd8945d10d1072871bf66f639153a717d7ca758`. Its optimized-cache phase
was local until this separate evidence-freeze ticket. No graphical run is
authorized in the closed historical laboratory. STEP20A DONE, STEP20B EXPERIMENTAL, STEP20C NEXT, STEP20D–G
PLANNED; production multi-output Mosaic NOT YET SUPPORTED. No UX change.
## Question, historical evidence and source contract
Does a normal same-size commit from the actual background source update the
optimized cache and the final visibly filtered translucent pixels? Separate
logical dirty state, reconstruction attempt/result, GPU cache copies,
regular blur selection, final drawing clip and output damage. The
[effects/damage probe](STEP20C_EFFECTS_DAMAGE_PROBE.md) established visible
blur and sampled clipping, but did not qualify cache invalidation. Its
FX2 tiled path could already select optimized blur, so FX3 is not an
independent non-optimized control. Its reference/trace difference had
unproven causality. This campaign uses no animated reference.
Pinned inputs: SwayFX 0.6 `fd71a6bdc061bd633b488ae7b83e8a6981d22f86`,
private Mosaic `cd550382b311efdbfe3ed892f4e1d281d852e754`, SceneFX 0.5
`37ccd723bef49e6891156ffafce8f549f01446cc`, installed wlroots 0.20.2
(previous audited source `d783533489e1f75d6886c2ab5c5960090ef268f8`).
Reconstruct archives from these commits and the unchanged preserved clip,
owner guard and passive timing/native-presentation patches. New passive
patches record borrowed identities only. No algorithm, shader, public ABI,
installed library, package or production source changes.
Source audit before instrumentation (pinned SceneFX and SwayFX):
1. `sway/tree/output.c:output_create` creates one optimized node under
staging, initially sized zero. `arrange_root` reparents it to global
`root->layers.blur_tree`, at its own output origin. `arrange_output`
controls its size and enabled state; fullscreen disables it.
2. `root_create` orders shell background, shell bottom, optimized blur,
tiling, floating, shell top, fullscreen, overlay, popup, seat and lock.
Render-list traversal reverses its top-to-bottom list for drawing.
The optimized source is the main output framebuffer at this stage,
containing lower background/bottom composition, not arbitrary tiled
content above the cache node.
3. `handle_surface_commit` in `sway/desktop/layer_shell.c` explicitly marks
the output cache dirty on EVERY background/bottom surface commit,
including ordinary pixel commits without layer-state changes. The
existing comment says “Rerender the optimized blur on change”. Destroy
of those layers also invalidates. This is the tested correctness intent.
4. `cmd_blur` and `cmd_blur_xray` mark all output optimized nodes dirty.
Blur radius/passes/noise/brightness/contrast/saturation setters traverse
and invalidate optimized nodes when their parameter actually changes.
Output reconfiguration directly sets dirty for FBO-dependent changes.
5. `wlr_scene_optimized_blur_mark_dirty` skips disabled nodes, otherwise
sets dirty and calls `scene_node_update`. `set_size` invalidates on a
size change. Generic `scene_node_update` recomputes visibility and damages
old/new regions; it does not universally invalidate optimized caches.
Generic geometry/visibility/buffer mutations are not interchangeable
with the Sway layer-shell commit hook. Other source owners need separate
audit; absent mark calls alone are not proof of stale rendering.
6. `scene_node_visibility` ignores disabled nodes. The inherited private
`scene_node_intersect_clip` restricts tiled leaves. Optimized nodes have
a different global parent and do not inherit the tiled clip.
7. `wlr_scene_output_build_state` initializes FX offscreen buffers before
blur damage expansion. `should_blur_node_extend_damage` sets `has_blur`
for optimized regular blur at full strength; dirty optimized nodes,
full damage and `apply_blur_region` intersections provide other routes.
8. `scene_entry_render` first requires a nonempty visible/damage intersection.
An optimized regeneration then requires `has_blur`, enabled blur data and
dirty. `fx_render_pass_add_optimized_blur` calls `get_main_buffer_blur`
with the current main framebuffer, and copies blurred and unblurred
results into the two output caches. It returns false with no offscreen
buffers or no blur result; dirty is cleared only on true and otherwise
retained for a later visible blur. A true return is not itself pixel proof.
9. `fx_render_pass_read_to_buffer` performs a GPU texture draw/copy, not CPU
readback. It returns void and can skip a copy if texture import fails;
trace attempted and completed draw calls separately from regeneration.
No mass GPU readback is planned.
10. `fx_render_pass_add_blur` selects `optimized_blur_buffer` when optimized
and strength 1; a lower strength reblurs `optimized_no_blur_buffer`.
Ordinary nonoptimized blur instead uses the current main framebuffer.
Sway's `config->blur_xray || !could_container_overlap(con)` explains
optimized selection with xray disabled. Keep opacity 1 and SHM alpha
partial, so buffer alpha and compositor blur strength remain distinct.
11. `fx_offscreen_buffers_try_get` finds/creates an output-owned addon.
`fx_render_pass_init_offscreen_buffers` obtains output-size framebuffers.
`fx_framebuffer_get_or_create_custom` reuses equal sizes, drops/resizes
different sizes and reports allocation failures; failure destroys the
offscreen set. No output resize is exercised here.
12. Framebuffers use allocator buffers, EGL images and GLES FBOs/stencils.
The output addon destructor drops all five buffers, finishes the addon
and frees the set; renderer teardown also owns cleanup. Observed object
destruction is not proof of every driver/GPU reference lifetime.
13. Optimized regeneration copies the output-sized destination region.
Final regular blur receives visible intersect damage and ancestor clip.
Shader sampling expands/clamps to output framebuffer bounds and can
read beyond a tiled draw clip. Such reads are not automatically leaks;
the decisive contract is absence of forbidden final contributions.
14. `apply_blur_region` expands damage for sampling/artifact prevention and
includes it in output-state damage. Client buffer damage, scene damage,
cache copy regions and output damage are separate. Captures and extent
coverage cannot enumerate intermediate frames or native PNG identities.
## Fixed geometry, clients and comparators
H1 `(-800,0)`, H2 `(0,0)`, both physical/logical 800×600, scale 1, normal.
C0: pinned pristine SwayFX splith, pristine private SceneFX without new logs.
C1: private SwayFX splith with inherited clip/guard, private SceneFX plus
passive logs. C2: same private compositor/renderer with Mosaic enabled.
C0/C1 provide a bounded visual instrumentation comparison; source/library
clip differences prevent claiming perfect log-only causal isolation.
Each configuration has exactly one reserved run, with identical client
sources and blur settings. No installed `/usr/bin/sway` is executed.
A background layer-shell SHM client explicitly binds HEADLESS-1, uses layer
BACKGROUND, all four anchors, exclusive zone -1 and no keyboard input. It
remains mapped and submits opaque 8-pixel vertical stripes A (#f5f5f5 /
#0f192d) and B (#20c0e0 / #b02040), same dimensions, by normal attach,
damage_buffer and commit. Generated layer-shell protocol comes from pinned
wlr-protocols `b010a03648b88d143236de193bddbfea0c08bc84`, with its XML hash
and URL retained before code generation. XDG XML is recorded and hashed.
The glass client uses premultiplied ARGB `0x88442010`, no opaque region,
normal XDG configure/ACK and no animation; the H2 XDG static reference
`org.mosaic.StaticReference` uses fixed XRGB #303038. A separate transparent ARGB
filler with blur disabled provides a second tiled cell without occluding
the exposed source ROI. Only the glass receives scoped blur commands. All identities are discovered by app ID.
Before scoring, verify background surface/buffer, output, layer, scene parent
under shell_background below blur_tree, real client and compositor commits,
source pattern visible outside glass and within FX0 alpha blending. Correlate
renderer/FBO/cache identities, actual optimized regular use and reconstruction.
A node alone cannot pass the gate. FX0 blur disabled; FX2 blur enabled,
radius 5, passes 3, noise 0, brightness/contrast/saturation 1, shadows off,
corner radius 0, xray off, opacity 1. Log real renderer (not environment hint).
ROIs predefined in output-local pixels: glass interior `(520,100,720,500)`,
exposed source `(20,100,160,500)`, H1 boundary `(720,50,800,550)` and H2
whole image `(0,0,800,600)`. Record full RGB diffs/bboxes/counts, ROI luminance
standard deviation and mean absolute horizontal adjacent-pixel contrast.
FX0/FX2 must show filtering beyond simple alpha. Geometry is recorded from
IPC and actual scene buffer draw positions; Mosaic camera is calibrated once
from current IPC camera and logical cell geometry to put glass across x=0.
Calibration is not P5 and occurs before P1. No focus or camera change during
P1–P3. Standard cells use the same splith/tiled identities without camera.
## Ordered protocol and gates
P0 map A, filler, glass, H2 reference; settle outputs/geometries. Capture
both outputs FX0, capture H2 a second time >=0.7s later and require complete
RGB equality. Enable FX2, capture both. Stop dependent cases if renderer,
source or visibly optimized filtering gate fails. Reserve capture before grim.
P1 submit B once with a normal same-size background pixel commit. Keep
position, geometry, alpha, focus, camera, output, clip and parameters fixed.
Capture both after stabilization. Observe dirty, attempt, return, copies,
regular use, damage and final pixels separately. No forced dirty in P1.
P2 (private C1/C2 only) perform one normal `blur disable` / `blur enable`
cycle, with no parameter or source change, then capture both. Audit/observe
cmd_blur's dirty route. This is a positive reconstruction control; never
substitute this image for P1. C0 keeps visual natural-mutation control only.
P3 submit A once, same geometry, capture both; compare with P0 FX2 and count
any residual B pixels. P4 uses the C2 boundary and tiled clip observations
from all these stages, verifies final draw bounds and whole H2 stability.
The global output cache is not claimed to be a tiled-owned clipped cache.
P5 optional one hide/return camera pair only if earlier gates pass and it
adds a distinct assertion. Omit if main mutation/cache evidence suffices.
Retain scene damage, physical extents, native output commits/presents where
instrumented, output-buffer identities and capture windows. Compare damage
union on the correct output against stabilized RGB changes, with no claim
of minimal damage, every frame or DRM presentation. Exit compositor through
its explicit private socket and retain teardown events before owned-process
fallback cleanup. Destroy/remove only fixture-owned sockets/process groups.
## Budgets, isolation and stop rules
One independent persistent campaign ledger under LAB, exclusive flock,
fsync and reservation BEFORE launch/action. Failed reservations consume;
no refunds, reset or repeat mode. Max 3 total runs, 1 per configuration,
4 background mutations/run (initial A counts), 1 explicit cycle/private run,
1 optional camera pair globally, 20 PNG/run and 60 globally, 150s/run.
Run deadline enforced by parent watchdog; all subprocess operations bounded.
No migration, resize, transform, hotplug, saved episode, popup, close
animation or scheduling test. Offline ledger tests precede any compositor.
Logs/files are bounded and monitored; only a finite client/renderer matrix.
Private 0700 runtime, explicit WAYLAND_DISPLAY, SWAYSOCK and swaymsg -s,
headless backend, no inherited daily sockets/library overrides. Generated
files/builds/captures/logs outside Git; home filesystem LAB avoids the
observed /tmp quota. Cleanup retains evidence, removes private socket tree.
FEASIBLE: all real renderer/source/optimized/filtering gates, natural required
reconstruction, correct stabilized A/B/A pixels, P2 positive reconstruction,
Mosaic boundary exclusion, stable H2 and supportive damage in measured matrix.
PARTIALLY_FEASIBLE: working subset with unqualified mutation/rebuild/damage.
INCONCLUSIVE: optimized source/use or filtering is not exercised/distinguishable.
BLOCKED: a measured stale image violates the identified source correction
intent, forbidden pixels, lifetime defect/crash or blocking standard regression.
Absent dirty alone cannot classify BLOCKED. Preserve failed fixture facts;
never spend an extra run to repair a calibration. No algorithmic correction.
Broader sources, partial damage, failure paths, GPU references, every frame,
other outputs/renderers and ABI/package remain NOT TESTED unless measured.
## C0 fixture failure and pre-run amendment
The sole C0 reservation stopped before captures (0.97s): two same-size
layer configures caused the original client to submit A twice. Actual
mutations are 2, with the second charged after observation, not reserved
before submission. This protocol deviation is retained; no budget is
refunded and C0 is never relaunched. Pristine graphical cache/filtering
control is NOT EXERCISED. The exact C0 runner/client and binary hash are
retained in its run directory. The remaining C1/C2 clients ACK configures
without drawing and submit every image only on an explicitly reserved
runner command. This is fixture correction, not a renderer/SceneFX change.
The standard library/renderer identity from C0 remains usable; its missing
visual comparison cannot be replaced by private source claims.
## Retained campaign and actual observations
LAB: `/home/fy59/swayfx-mosaic-optimized-blur-_ppdud_y/`. No prior `/tmp`
laboratory is a reconstruction dependency. `prepare.py` archives the pinned
project commits, clones SceneFX at its pin, applies unchanged historical
patches, adds separate passive traces and builds privately. New XML inputs,
protocol hashes, current and failed-C0 client/runner sources are preserved
in the [source fixture](../../tooling/probes/step20c-optimized-blur-cache/README.md).
Generated protocols, binaries, logs, PNGs and builds remain outside Git.
| Configuration | Duration | Actual background images | PNGs | Explicit cycles | Result |
| --- | ---: | ---: | ---: | ---: | --- |
| C0 pristine splith, pristine SceneFX | 0.972 s | 2 × A | 0 | 0 | Fixture failure before graphical gates; no repeat |
| C1 private splith | 4.075 s | A/B/A (3) | 11 | 1 | Real cache update, central pixel equality; boundary equivalence unresolved |
| C2 private Mosaic | 4.434 s | A/B/A (3) | 11 | 1 | Same update; measured final clip excludes H2; boundary equivalence unresolved |
All three run reservations are consumed. Actual totals: 8 background images,
22 PNGs of 60, one cycle per private mode, zero camera pairs, one computed
C2 calibration, no migration/resize/transform. C0's second image was charged
only after observation; this is a retained reservation-protocol deviation,
although no numerical limit was exceeded. Every C1/C2 image was explicitly
reserved before its draw command. The shared ticket ledger refuses another
LAB, another run per mode, refunds and corrupt/empty state. No extra
untraced graphical run was possible after C0 failed. Instrumentation
neutrality is **NOT TESTED**, not inferred from similar private outcomes.
### Renderer, layer source and actual use
All three compositors report SceneFX FX, OpenGL ES **3.2**, Mesa
**26.2.4-arch1.1**, AMD Radeon **780M**. `/proc/PID/maps` records the correct
private SceneFX library and installed wlroots 0.20 library. C0 uses a fresh
pristine SceneFX build; C1/C2 use the same private clip/timing/cache-trace
library. Environment `WLR_RENDERER=gles2` is not the renderer evidence.
The FX pass is additionally proven in C1/C2 by successful offscreen setup,
GPU blur/copy and regular optimized-use traces. C0 has renderer identity
but no qualifying graphical/native-cache control.
C1/C2 background server commits show surface resource 3, layer 0
BACKGROUND, mapped=true, global `(-800,0)`, full buffer damage 800×600,
and the namespace `mosaic-dynamic-background`. Their actual scene parent
is the output shell-background tree, whose parent is the global
shell-background tree. The optimized node's actual parent is blur_tree.
The pinned root's applied layer order draws background/bottom before blur
and tiling afterwards. `OPT_BUFFER_DRAW` correlates the surface and its
buffer to a full-output draw before `OPT_GPU_REBUILD`. Client logs record
A/B/A, normal SHM buffers, attach/damage/commit and release. Server sequences
2/3/4 correlate to those three content commits; resource/buffer pointers can
be reused after release and are never treated as permanent image IDs.
The exposed source ROI contains the exact A or B stripe colors. Thus the
mutated surface is a real lower cache source, not a tiled proxy above it.
The glass buffer is fixed premultiplied ARGB `0x88442010`. Its compositor
opacity/blur strength stays 1; alpha blending and blur strength are distinct.
Only the glass receives scoped blur enable/disable. Shadow and corners are
zero; radius 5, passes 3, noise 0, brightness/contrast/saturation 1 and xray
false remain fixed. Both private runs record `optimized=1`, strength 1,
non-null selected cached framebuffer FBO 11 in `OPT_BLUR_USE`. This proves
actual optimized selection independently of xray and node presence.
### Natural invalidation, explicit control and cache ownership
For each private mode, P1 B and P3 A each show one real source commit,
one dirty set for H1, one H1 regeneration attempt/result=1, GPU source blur,
two cache copy draw completions, dirty clear to false and regular optimized
use. H2 has **zero regeneration** in these natural-mutation windows.
The source-only Sway layer-shell hook provides the natural dirty; no blur
command, camera, focus, geometry or parameter mutation is inserted in P1.
P2's one normal disable/enable cycle records six dirty-set observations,
two successful H1 and two successful H2 regenerations, then optimized glass
use on H1. A global dirty consequence of scoped cmd_blur is source-defined;
H2 rebuilds in P2 do not demonstrate contamination. H2 pixels remain stable.
Successful regeneration, completed GPU texture-draw calls and final pixels
remain separate evidence. `fx_render_pass_read_to_buffer` has no success
return or driver fence proof. No GPU readback was added. Initial optimized
nodes start dirty=false, are invalidated by normal setup and source changes,
and clear to false on observed successful regenerations. P3's last stable
H1 render observes dirty=true before regeneration and dirty=false after it;
teardown invalidation is a separate lifetime phase.
| Mode | Output | Offscreen set | Optimized framebuffer | Size | Reuses |
| --- | --- | --- | --- | --- | ---: |
| C1 | H1 | `0x563e16fee600` | `0x563e16fe4750`, FBO 11 | 800×600 | 18 |
| C1 | H2 | `0x563e16f3d430` | `0x563e16f43950`, FBO 5 | 800×600 | 10 |
| C2 | H1 | `0x55e94e85cee0` | `0x55e94e852310`, FBO 11 | 800×600 | 19 |
| C2 | H2 | `0x55e94e6a8f00` | `0x55e94e7b13e0`, FBO 5 | 800×600 | 10 |
Each set retains one blur object and one distinct unblurred object during
its observed lifetime. Normal explicit private compositor exit logs cache
set destruction and exactly one destructor event for each optimized blur
and unblurred FBO object. No resize/allocation-failure path is exercised.
This demonstrates sampled object cleanup, not every GPU/driver reference.
Clients exit with Wayland EPIPE (32) after controlled compositor shutdown;
the compositor exit code is 0 in every run, with no observed compositor
crash. Every private runtime directory/socket is removed and owned child
processes are reaped. There is no daily-session IPC or global kill command.
### Pixels, filtering and the retained boundary anomaly
| Measurement, identical in C1/C2 central ROI | Value |
| --- | ---: |
| FX0 luminance standard deviation | 51.165618 |
| FX2 luminance standard deviation | 0.132211 |
| FX0 horizontal adjacent luminance contrast | 12.351341 |
| FX2 horizontal adjacent luminance contrast | 0.015453 |
| B natural versus B explicit, central changed pixels | 0 |
| A initial versus A returned, central changed pixels | 0 |
| H2 whole RGB differences, every comparison | 0 |
FX0/FX2 changes 240,000 H1 pixels in C1 and 192,000 in C2. These contrast
measurements establish actual filtering beyond fixed alpha compositing.
A→B changes all 480,000 H1 pixels, including the exposed source and glass.
The 80,000-pixel central glass ROI changes with B, equals the independent
explicit reconstruction result exactly and returns exactly to measured A.
This establishes freshness in that central ROI; it is not a whole-cache PASS.
All six H2 PNGs per private run are whole-RGB identical, without masks.
| Whole H1 comparison | C1 changed pixels | C2 changed pixels | Maximum RGB channel delta |
| --- | ---: | ---: | ---: |
| B natural → B explicit | 36,776 | 33,915 | 6 |
| A initial → A returned | 38,308 | 35,340 | 6 |
The B comparison bboxes are `[400,800)×[0,600)` in C1 and
`[480,800)×[0,600)` in C2; A return has the same respective bboxes.
Differences concentrate near output/filter boundaries; central/source ROIs
are exactly unchanged in these equivalence comparisons. The predefined
right boundary ROI changes **19,007** pixels for B natural/explicit and
**20,486** for A return in BOTH private modes, with maximum differences
4 and 5 respectively. These nonzero differences are preserved, never
masked, and cannot be promoted to exact full-image restoration.
This is a shared private standard/Mosaic anomaly, not a demonstrated
Mosaic-only defect. Pristine comparison is missing, so an unmodified
SceneFX runtime attribution remains unproven.
A bounded offline audit of `render_blur_segments`, `get_main_buffer_blur`,
`blur1.frag`, `blur2.frag` and FX texture creation found a plausible history
mechanism: downsample writes only a reduced top-left domain in full-size
ping-pong FBOs, while upsample taps can reach beyond that freshly written
domain; GL_CLAMP_TO_EDGE bounds the whole texture rather than the reduced
valid domain. `edge-model.py` applies the pinned kernels with ideal bilinear
sampling and 8-bit rounding to the retained regeneration/source sequence.
It exhibits history dependence without any renderer edit. In the predefined
boundary ROI, absolute model/native mean channel error is 0.199–0.518,
with 96.67–97.91% of channels within one level; maximum residual is 4.
This supports the hypothesis but does not establish native intermediate
FBO tail contents, GLES precision or causality. No model threshold turns
the anomaly into a PASS, and no shader/algorithm correction is authorized.
A proven stale-image invariant violation is not claimed on that model alone.
### Mosaic clipping and damage
C2 calibration derives delta **-320** from its current camera **480** and
actual glass geometry; applied camera becomes **160**. These coincident
values are measured here, not historical IDs/constants in the fixture.
The regular glass blur is actually at global `(-320,0)`, size 640×600,
so its geometric right edge is +320 on H2. Its final H1 draw is
`[480,800)×[0,600)` output-local, equivalent to global `[-320,0)`.
`PI_SCENE` records applied tiling scene `(-160,0)`, local clip
`(160,0,800,600)` under the H1 output parent. The global tiled viewport is
`[-800,0)×[0,600)`. No target regular-blur draw on H2 is observed; complete
H2 RGB stays unchanged throughout. The cache still covers its full 800×600
output, with a different owner than the tiled final clip. No input event or
universal shader-sampling confinement is asserted.
`OPT_OUTPUT_BUFFER` identifies successful rendered output-state buffers and
physical output damage extents. Native commit/present traces retain their
own identities; no PNG is assigned to a particular native presented buffer.
For all four stabilized H1 transitions in each private mode, the union of
available output-damage extents leaves **zero changed pixels uncovered**.
Client damage, scene TEMPORAL_DAMAGE, cache full-output GPU copies and
output-state damage are recorded separately. Extent coverage proves neither
minimum damage nor every intervening frame. P5 hide/return is NOT EXERCISED:
P1/P3 already address the distinct source mutation, and a camera pair would
not resolve missing pristine control or the boundary source/sampling issue.
## Decision and next gate
`SWAYFX_MOSAIC_OPTIMIZED_BLUR_CACHE_PROBE=PARTIALLY_FEASIBLE`.
Architectural conclusion: **UNDECIDED**. Actual output-scoped cache source,
optimized selection, natural dirty/reconstruction, visible filtering,
central A/B/A freshness, sampled final Mosaic clipping, stable H2 and
sampled teardown are established in C1/C2. Whole-cache correctness is not
established: boundary differences remain, C0 graphical control failed, and
instrumentation neutrality is NOT TESTED. There is no basis here to merge a
private patch or to require a Mosaic-specific redesign.
The next separate rendering gate should complete pristine and untraced
controls and discriminate reduced-domain edge sampling/history at fixed
geometry before the ABI/package gate. Retain this campaign's exact pixels,
source traces and offline model; do not repeat V1 runs or silently strengthen
its result. A later targeted native observation may be justified only by a
predefined bounded hypothesis. Other source layers, partial damage, hidden
owner/camera transitions, output resize/migration, scaling/rotation, failure
paths, driver references and intermediate frames remain NOT TESTED.
STEP20C stays NEXT, production multi-output NOT YET SUPPORTED.
## Offline validation and preservation
Python AST (all current and original fixtures, no bytecode), XML parsing,
controlled protocol regeneration byte equivalence, both C clients compiled
separately as C17 with `-Wall -Wextra -Werror -Wno-unused-parameter`, locked
ledger exhaustion/concurrency/cross-LAB/corruption tests, pinned private
patch applicability and full reconstructed source equivalence passed.
Pristine SwayFX source was independently compared with its pinned archive.
Final private SceneFX/Mosaic and pristine builds pass; Meson `mosaic-model`
passes 1/1. Existing pinned SwayFX libinput switch warnings are retained;
the new trace's pre-run radius-format warning was corrected before graphics.
The two passive instrumentation insertion failures and C0 fixture failure
remain in raw preparation/run evidence. No failure was hidden by retrying
an already-consumed graphical configuration.
Local Markdown targets, complete tracked/new-file diff, whitespace and
bytecode/generated-artifact protection were checked. Production `sway/`,
`include/`, Meson files, cumulative patch, PKGBUILD, UX and AGENTS match the
pre-ticket baseline. Installed compositor/libraries, daily IPC/session,
QuickShell and dotfiles were not modified or targeted by any command.
Cache/source/state/output JSON categories, all run logs, client sources,
configs, captures, native events and exit statuses remain in LAB/results
and LAB/runs. The failed C0 has zero images; no visual result is invented.
All owned processes are reaped and private runtime/socket trees removed.
That validation preceded publication of this historical probe. The new
evidence freeze does not upgrade STEP20C.
Final source manifest: **1006 entries**, SHA-256
`0067ad29b7363d2850f8a7418b271c4e5e7044f6a08fd8e85f39b63413a2d152`.
Final evidence manifest: **106 entries**, SHA-256
`3a83bbf303217d020b5dadb29e357836975a08055540bae006b5c770feb1d34c`.
Every entry was available and matched its recorded digest. The earlier A
913/46-entry historical manifests were reverified after B and remain
unchanged. The B H2 PNG digest in both private modes is
`4471e93ccb6cf3ff4970f61fce405431a228af598d2f1f030a3b7586962ecd7a`;
all six images per mode share it and match complete RGB without a mask.
## Independent evidence freeze (2026-10-13)
The new freeze audited the initial main at `6fd8945d10d1072871bf66f639153a717d7ca758`
and directly queried both remotes at that same commit. All **1006 source**
and **106 evidence** manifest entries were available and matched, before
publication-only documentation edits. The two original manifests and the
historical laboratory remain byte-identical; neither analyzer nor runner was
executed there. A copy of every pre-freeze fixture file is retained in
`/home/fy59/swayfx-mosaic-cache-freeze-audit-m7o528qk/fixture-before-freeze/`.
The historical source manifest names the old README at its repository path;
after this freeze its matching original bytes are in that snapshot. This
provenance distinction is explicit, not a regenerated historical manifest.
An independent reconstruction in the same audit directory applied the full
private patch chain to the pinned archives, then compared all relevant source
bytes to the historical compiled trees. All patches apply without forced
hunks. Both new trace patch hashes match the recorded values:
`a0fd5a64156a79b1605ff3b52bd9accb20390199b2b36e1970389f5086b68f39`
and `b83ff12bf24ee879cc713a134b0c312e9b8bd1b4034ecc2aea124d55a743ebb7`.
C17 builds of the corrected and failed-C0 clients with fatal warnings, both
SceneFX builds, pristine SwayFX, private Mosaic and Meson mosaic-model passed.
The actual corrected callback, extracted verbatim for a bounded offline
harness, ACKed duplicate 800×600 configures without incrementing image
submissions. Locked ledger tests passed; no historical reservation changed.
Independent whole-RGB recalculation reproduces 36,776/33,915 changed B pixels,
38,308/35,340 changed A-return pixels, maximum channel delta 6, zero central
differences and stable H2. The offline model was rerun into a separate output
directory and reproduces its historical JSON exactly. These checks establish
evidence integrity, not native FBO causality or instrumentation neutrality.
The narrow `.gitattributes` patch rule preserves original unified-diff bytes:
blank context lines carry a required leading space and context source tabs
follow a diff marker. C, Python and Markdown retain ordinary whitespace
checks. Source-context applicability and reconstructed source equivalence
were checked independently of the patch-file whitespace exclusion.

View file

@ -0,0 +1,78 @@
# Optimized blur cache fixture
**EXPERIMENTAL / PARTIALLY_FEASIBLE; preserved evidence, campaign closed.** Read the
[protocol](../../../docs/design/STEP20C_OPTIMIZED_BLUR_CACHE_PROBE.md) before
running. Reconstruct pinned sources in one new LAB outside Git. Do not use
or reset a historical saved-buffer/effects ledger. No production source,
package, installed library, shader or clipping algorithm changes.
The preparation builds only; the locked ledger's offline tests must pass
before the three single-mode runs. All generated protocols, binaries,
private libraries, PNGs and raw logs remain in LAB. Record every failed
reserved launch/action; no retry for a consumed mode. Analyze offline after
owned-process/socket cleanup. The separate freeze ticket publishes this source-only historical evidence.
## Execution and reconstruction
The V1 campaign is closed in
`/home/fy59/swayfx-mosaic-optimized-blur-_ppdud_y`. C0 failed before captures
through duplicate same-size background configures, without a retry. Its
original `clients-c0-original.c` and `run-c0-original.py` are preserved as
failed fixture evidence. The current client ACKs background configures
without submitting pixels; all A/B images are runner-commanded after a
persistent reservation. C1/C2 completed central cache mutation, filtered
pixels, H2 stability and sampled cleanup; boundary differences remain.
The verdict is **PARTIALLY_FEASIBLE**, architecture **UNDECIDED**. The
freeze publishes this fixture/report without new graphical execution.
Build-only reconstruction in a fresh LAB requires the pinned project
commits in a full clone, Meson/Ninja/C17, installed compatible wlroots
0.20.2/SceneFX build dependencies, Wayland scanner, and network access to
the pinned SceneFX and protocol sources. No system install is used.
```sh
PYTHONDONTWRITEBYTECODE=1 python tooling/probes/step20c-optimized-blur-cache/prepare.py
PYTHONDONTWRITEBYTECODE=1 python tooling/probes/step20c-optimized-blur-cache/test-budget.py
```
The preparer prints LAB and builds only. It retains failed preflight source
copies/logs and supports a pre-graphics preparation continuation in that LAB;
Meson build continuation should use the existing build directories. The
new `scenefx-cache-trace.patch` and `sway-source-trace.patch` are passive
standalone deltas after the historical clip/timing/guard/presentation chain.
`instrument.py` deterministically generates them from those sources;
no preserved historical patch is rewritten. XML provenance is in
`protocol-provenance.json`: layer-shell is pinned to wlr-protocols
`b010a03648b88d143236de193bddbfea0c08bc84`; the retained XDG XML came from
wayland-protocols 1.49 and has a fixed checked digest. Generated files remain
in LAB. The current and original C clients compile separately with C17 and
fatal warnings.
The already-consumed graphic invocations were `run.py LAB C0`, `C1`, `C2`.
**Do not execute them again for this ticket.** The ledger at
`~/swayfx-mosaic-optimized-blur-v1-budget.json` is shared across clones and
LABs, locks atomically and refuses a second mode run or another LAB.
It has no reset/refund operation. C0's second unreserved initial image is
charged and documented as a reservation-protocol deviation; numerical
limits were not exceeded. A new authorized ticket must define its own
protocol/budget before new graphics, rather than reset this one.
Offline analysis remains repeatable:
```sh
PYTHONDONTWRITEBYTECODE=1 python tooling/probes/step20c-optimized-blur-cache/edge-model.py "$LAB"
PYTHONDONTWRITEBYTECODE=1 python tooling/probes/step20c-optimized-blur-cache/analyze.py "$LAB"
```
These analyzers write derived results and manifests. Never target the closed
historical LAB: use a separate offline copy/output tree. The freeze audit
preserved the exact pre-freeze fixture in the external snapshot named in the
report; its README is the byte-identical source-manifest input. Raw logs/PNGs and per-run executed sources stay
immutable. The offline model is a hypothesis about reduced-domain sampling,
not native GPU readback or a shader fix. The complete source/evidence
manifest digests and validation are recorded in the report after the final
analysis. Full H1 differences are retained alongside the predefined ROIs;
no boundary anomaly is excluded to obtain a PASS. No PNG/native-buffer
identity, all-frame damage, GPU reference release or instrumentation
neutrality is claimed. Runtime directories are removed and all owned
processes reaped. No production patch, package or desktop change follows.

View file

@ -0,0 +1,80 @@
#!/usr/bin/env python3
"""Offline facts from immutable logs/PNGs; category evidence stays separate."""
import hashlib,json,re,sys
from pathlib import Path
import numpy as np
from PIL import Image
lab=Path(sys.argv[1]).resolve();out=lab/'results';fixture=Path(__file__).resolve().parent
ROIS={'glass':[520,100,720,500],'source':[20,100,160,500],'boundary':[720,50,800,550]}
def write(name,data):(out/(name+'.json')).write_text(json.dumps(data,indent=2,sort_keys=True)+'\n')
def sha(p):return hashlib.sha256(p.read_bytes()).hexdigest()
def fields(line):return {k:v for k,v in re.findall(r'([A-Za-z_]+)=([^ ]+)',line)}
def parsed(lines,name):return [{'event':name,**fields(x),'raw':x} for x in lines if re.search(r'\b'+name+r'\b',x)]
def rgb(p):return np.array(Image.open(p).convert('RGB'))
def diff(a,b):
d=np.abs(a.astype(int)-b.astype(int));mask=np.any(d,axis=2);ys,xs=np.where(mask)
return {'pixels':int(mask.sum()),'bbox':[int(xs.min()),int(ys.min()),int(xs.max()+1),int(ys.max()+1)] if mask.any() else None,'max_channel':int(d.max()),'mean_abs_channel':float(d.mean())}
def roi(a,box):l,t,r,b=box;return a[t:b,l:r]
def comparisons(a,b):return {'whole':diff(a,b),**{n:diff(roi(a,box),roi(b,box)) for n,box in ROIS.items()}}
data={};renderers={};pixels={};contrast={};phases={};source={};lifetimes={};damage={};budgets={}
for mode in ['C0','C1','C2']:
run=lab/'runs'/mode;status=json.loads((run/'exit-status.json').read_text());events=[json.loads(x) for x in (run/'events.jsonl').read_text().splitlines()];lines=(run/'compositor.log').read_text().splitlines()
traces={name:parsed(lines,name) for name in ['OPT_NODE_CREATE','OPT_NODE_RENDER','OPT_DIRTY_SET','OPT_REGEN_ATTEMPT','OPT_REGEN_RESULT','OPT_DIRTY_CLEAR','OPT_CACHE_ALLOC','OPT_CACHE_STATE','OPT_CACHE_DESTROY','OPT_FBO_DESTROY','OPT_GPU_REBUILD','OPT_GPU_RESULT','OPT_GPU_COPY','OPT_GPU_COPY_DRAWN','OPT_BLUR_USE','OPT_REGULAR_NODE','OPT_BACKGROUND_COMMIT','OPT_BUFFER_DRAW','OPT_OUTPUT_BUFFER','TEMPORAL_DAMAGE','PI_OUTPUT_COMMIT','PI_OUTPUT_PRESENT','PI_SCENE','PI_OWNER']}
data[mode]={'status':status,'events':events,'traces':traces}
renderers[mode]={'gles':[x for x in lines if 'Using OpenGL' in x or 'GL renderer:' in x or 'GL vendor:' in x or 'EGL vendor:' in x],'loaded_libraries':[x for x in (run/'library-maps.txt').read_text().splitlines() if 'libscenefx' in x or 'libwlroots' in x],'FX_pass_observed':bool(traces['OPT_CACHE_STATE']),'native_control':mode!='C0'}
cs=parsed((run/'background.log').read_text().splitlines(),'CLIENT_COMMIT');source[mode]={'client_commits':cs,'server_commits':traces['OPT_BACKGROUND_COMMIT'],'draws':traces['OPT_BUFFER_DRAW'],'layer_paths_valid':all(e['layer']=='0' and e['parent']==e['background'] and e['background_parent']==e['global_background'] and e['opt_parent']==e['blur_tree'] for e in traces['OPT_BACKGROUND_COMMIT'] if e['mapped']=='1') if traces['OPT_BACKGROUND_COMMIT'] else None,'pointer_identity_warning':'Buffer/resource IDs can be reused after release; phase, sequence and native lifetime delimit identity.'}
lifetime=[]
for allocation in traces['OPT_CACHE_ALLOC']:
ns=int(allocation['ns']);cid=allocation['cache'];destroy=next((e for e in traces['OPT_CACHE_DESTROY'] if e['cache']==cid and int(e['ns'])>ns),None)
states=[e for e in traces['OPT_CACHE_STATE'] if e['cache']==cid and int(e['ns'])>=ns and (not destroy or int(e['ns'])<int(destroy['ns']))]
lifetime.append({'allocation':allocation,'destroy':destroy,'states':states,'distinct_blur_objects':sorted(set(e['blur'] for e in states)),'reuse_count':sum(e['reuse']=='1' for e in states),'observed_sizes':sorted(set(e['size'] for e in states)),'fbos_dropped_then_destroyed':[{k:destroy[k],'events':[e for e in traces['OPT_FBO_DESTROY'] if e['fbo_object']==destroy[k] and int(e['ns'])>=int(destroy['ns'])]} for k in ['blur','plain']] if destroy else None})
lifetimes[mode]=lifetime
if status['failure']:
pixels[mode]={'status':'NOT EXERCISED','reason':status['failure']};contrast[mode]={'status':'NOT EXERCISED'};continue
imgs={e['tag']+'-'+e['output']:rgb(run/e['path']) for e in events if e['kind']=='capture'}
pairs=[('P0-FX0','P0-FX2'),('P0-FX2','P1-B-natural'),('P1-B-natural','P2-B-explicit'),('P0-FX2','P3-A-natural')]
pixels[mode]={'capture_hashes':{p.name:sha(p) for p in run.glob('*.png')},'comparisons':{a+' -> '+b:comparisons(imgs[a+'-HEADLESS-1'],imgs[b+'-HEADLESS-1']) for a,b in pairs if b+'-HEADLESS-1' in imgs},'H2_full':{k:diff(imgs['P0-FX0-HEADLESS-2'],v) for k,v in imgs.items() if 'HEADLESS-2' in k}}
contrast[mode]={}
for name,img in imgs.items():
if 'HEADLESS-1' not in name:continue
r=roi(img,ROIS['glass']).astype(float);lum=r@[.2126,.7152,.0722]
contrast[mode][name]={'luminance_std':float(lum.std()),'luminance_adjacent_mean':float(np.abs(np.diff(lum,axis=1)).mean()),'rgb_mean':r.mean(axis=(0,1)).tolist(),'RGB_adjacent_mean':float(np.abs(np.diff(r,axis=1)).mean())}
phase={}
for tag,kind in [('P0-FX2','enable'),('P1-B-natural','B'),('P2-B-explicit','cycle'),('P3-A-natural','A')]:
capture=next(e for e in events if e['kind']=='capture' and e['tag']==tag and e['output']=='HEADLESS-1');end=capture['after_ns']
if kind=='enable':start=next(e['after_ns'] for e in events if e['kind']=='capture' and e['tag']=='P0-reference')
elif kind=='cycle':start=next(e['ns'] for e in events if e['kind']=='explicit_cycle_start')
else:start=next(e['ns'] for e in events if e['kind']=='mutation_reserved' and e['pattern']==kind and (kind!='A' or e['ns']>next(c['after_ns'] for c in events if c['kind']=='capture' and c['tag']=='P1-B-natural')))
phase[tag]={'start_ns':start,'end_ns':end,'trace':{n:[e for e in v if 'ns' in e and start<=int(e['ns'])<=end] for n,v in traces.items()}}
phases[mode]=phase
damage[mode]={}
for old,new in [('P0-FX0','P0-FX2'),('P0-FX2','P1-B-natural'),('P1-B-natural','P2-B-explicit'),('P2-B-explicit','P3-A-natural')]:
oldcap=next(e for e in events if e['kind']=='capture' and e['tag']==old and e['output']=='HEADLESS-1');newcap=next(e for e in events if e['kind']=='capture' and e['tag']==new and e['output']=='HEADLESS-1')
mask=np.any(imgs[old+'-HEADLESS-1']!=imgs[new+'-HEADLESS-1'],axis=2);cover=np.zeros((600,800),bool);extents=[]
for e in traces['OPT_OUTPUT_BUFFER']:
if e['output']!='HEADLESS-1' or not oldcap['before_ns']<=int(e['ns'])<=newcap['after_ns']:continue
b=[int(x) for x in e['damage'].split(',')];extents.append(e)
l,t,r,bottom=b;cover[max(0,t):min(600,bottom),max(0,l):min(800,r)]=True
damage[mode][old+' -> '+new]={'changed_pixels':int(mask.sum()),'uncovered_by_observed_output_damage_extents':int((mask&~cover).sum()),'output_damage':extents,'limitations':'Extent union only; not exact region/minimal damage, every frame, native PNG buffer identity or physical presentation.'}
write('renderer-identity',renderers);write('background-source',source);write('source-layers',source);write('pixel-comparisons',pixels);write('blur-contrast',contrast);write('offscreen-buffer-lifetime',lifetimes);write('damage-analysis',damage);write('phase-events',phases)
for file,names in [('blur-node-identities',['OPT_NODE_CREATE','OPT_NODE_RENDER','OPT_REGULAR_NODE']),('blur-mode-selection',['OPT_BLUR_USE','OPT_REGULAR_NODE']),('optimized-node-state',['OPT_NODE_RENDER','OPT_DIRTY_SET','OPT_DIRTY_CLEAR']),('dirty-events',['OPT_DIRTY_SET','OPT_DIRTY_CLEAR']),('cache-rebuild-events',['OPT_REGEN_ATTEMPT','OPT_REGEN_RESULT','OPT_GPU_REBUILD','OPT_GPU_RESULT','OPT_GPU_COPY','OPT_GPU_COPY_DRAWN']),('regular-blur-use',['OPT_BLUR_USE','OPT_REGULAR_NODE']),('output-clips',['PI_SCENE','PI_OWNER','OPT_REGULAR_NODE','OPT_BUFFER_DRAW']),('output-commits',['OPT_OUTPUT_BUFFER','PI_OUTPUT_COMMIT']),('presentations',['PI_OUTPUT_PRESENT'])]:write(file,{m:{k:d['traces'][k] for k in names} for m,d in data.items()})
write('background-mutations',{m:{'submissions':source[m]['client_commits'],'actual_count':len(source[m]['client_commits'])} for m in data})
budget=json.loads((out/'campaign-budget.json').read_text());assert sum(s['run'] for s in budget['modes'].values())==3
for mode in data:
assert len(source[mode]['client_commits'])==budget['modes'][mode]['mutation']
assert len(data[mode]['status']['captures'])==budget['modes'][mode]['capture']
assert data[mode]['status']['duration']<150 and data[mode]['status']['runtime_removed']
write('matrix',{'C0':'NOT EXERCISED: failed duplicate initial configure fixture, no retry','C1':'central natural invalidation PASS; boundary history differences unresolved','C2':'central natural invalidation and sampled final clip PASS; boundary history differences unresolved','instrumentation_neutrality':'NOT TESTED: C0 has no captures','P5':'NOT EXERCISED: no extra camera pair','budget_deviation':'C0 second initial image reserved only after observation; actual 2 charged, no refund'})
write('summary',{'verdict':'PARTIALLY_FEASIBLE','architecture':'UNDECIDED','private_central_updates':True,'full_H2_stable':True,'boundary_equivalence':False,'reason':['C0 graphical control missing','instrumentation neutrality unqualified','shared C1/C2 boundary differences up to six RGB levels, source-only cache equivalence unqualified'],'lab':str(lab)})
# Source manifest records actual compiled trees and each executed runner/client.
# Evidence excludes its own digest and source list; never touches saved manifests.
sources=list(fixture.glob('*'))
for name in ['scene0','scenefx','mosaic','pristine']:
sources.extend(p for p in (lab/name).rglob('*') if p.is_file() and 'subprojects' not in p.parts)
sources.extend(p for run in (lab/'runs').iterdir() for p in [run/'fixture-source.py',run/'client-source.c'] if p.is_file())
sources.extend(p for p in lab.glob('*') if p.suffix in ['.h','.c','.xml'])
sources=sorted(set(p for p in sources if p.is_file()))
(out/'source-sha256.txt').write_text(''.join(f'{sha(p)} {p}\n' for p in sources))
evidence=sorted([p for p in (lab/'runs').rglob('*') if p.is_file()]+[p for p in out.iterdir() if p.is_file() and not p.name.endswith('-sha256.txt')]+[lab/'build.log'])
(out/'evidence-sha256.txt').write_text(''.join(f'{sha(p)} {p}\n' for p in evidence))
print('PARTIALLY_FEASIBLE sources',len(sources),'evidence',len(evidence),'source_manifest',sha(out/'source-sha256.txt'),'evidence_manifest',sha(out/'evidence-sha256.txt'))

View file

@ -0,0 +1,31 @@
"""Fail-closed persistent campaign reservations; shared across clones/LABs."""
import fcntl,json,os,tempfile
from pathlib import Path
MODES=('C0','C1','C2')
CAMPAIGN='SWAYFX_MOSAIC_STEP20C_SAVED_TIMEOUT_FREEZE_AND_OPTIMIZED_BLUR_CACHE_V1'
DEFAULT=Path.home()/'swayfx-mosaic-optimized-blur-v1-budget.json'
def reserve(lab,mode,resource,path=DEFAULT):
if mode not in MODES or resource not in ('run','mutation','capture','cycle','camera_pair','calibration'):raise ValueError('unknown reservation')
lab=str(Path(lab).resolve());path=Path(path)
with path.with_suffix('.lock').open('a+') as lock:
fcntl.flock(lock,fcntl.LOCK_EX)
state=json.loads(path.read_text()) if path.exists() else {'campaign':CAMPAIGN,'lab':lab,'modes':{m:{k:0 for k in ('run','mutation','capture','cycle','camera_pair','calibration')} for m in MODES}}
if state.get('campaign')!=CAMPAIGN or state.get('lab')!=lab:raise RuntimeError('campaign already bound to another LAB')
for m in MODES:
for k,v in state['modes'][m].items():
if type(v) is not int or v<0:raise RuntimeError('corrupt budget')
counts=state['modes'][mode]
limits={'run':1,'mutation':4,'capture':20,'cycle':0 if mode=='C0' else 1,'camera_pair':1 if mode=='C2' else 0,'calibration':1 if mode=='C2' else 0}
if resource!='run' and counts['run']!=1:raise RuntimeError('reserve run first')
if counts[resource]>=limits[resource]:raise RuntimeError('budget exhausted: '+resource)
if resource=='capture' and sum(s['capture'] for s in state['modes'].values())>=60:raise RuntimeError('global captures exhausted')
counts[resource]+=1
fd,tmp=tempfile.mkstemp(prefix=path.name+'.',dir=path.parent)
try:
with os.fdopen(fd,'w') as out:json.dump(state,out,sort_keys=True);out.write('\n');out.flush();os.fsync(out.fileno())
os.replace(tmp,path)
directory=os.open(path.parent,os.O_DIRECTORY);os.fsync(directory);os.close(directory)
finally:
if os.path.exists(tmp):os.unlink(tmp)
return state

View file

@ -0,0 +1,289 @@
#define _POSIX_C_SOURCE 200809L
#include "layer-shell-client.h"
#include "xdg-shell-client.h"
#include <errno.h>
#include <poll.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <time.h>
#include <unistd.h>
#include <wayland-client.h>
/* WHY: one persistent background supplies real A/B commits below blur_tree.
* CONTRACT: fixed opaque background, premultiplied ARGB glass and static H2.
* INVARIANT: no animation, synthetic configure, popup or held transaction. */
static struct wl_display *display;
static struct wl_compositor *compositor;
static struct wl_shm *shm;
static struct xdg_wm_base *wm;
static struct zwlr_layer_shell_v1 *shell;
static struct wl_output *h1;
static struct wl_surface *surface;
static struct xdg_surface *xdg;
static struct xdg_toplevel *top;
static struct zwlr_layer_surface_v1 *layer;
static const char *mode;
static int width = 420, height = 320;
static unsigned commits, live_buffers;
static char pattern = 'A';
static volatile sig_atomic_t running = 1;
struct pixels {
struct wl_buffer *buffer;
void *map;
size_t size;
struct pixels *next;
};
static struct pixels *buffers;
static long long stamp(void) {
struct timespec t;
clock_gettime(CLOCK_MONOTONIC, &t);
return (long long)t.tv_sec * 1000000000LL + t.tv_nsec;
}
static void free_pixels(struct pixels *p) {
struct pixels **it = &buffers;
while (*it && *it != p)
it = &(*it)->next;
if (*it)
*it = p->next;
wl_buffer_destroy(p->buffer);
munmap(p->map, p->size);
free(p);
--live_buffers;
}
static void release(void *data, struct wl_buffer *b) {
fprintf(stderr, "RELEASE ns=%lld buffer=%u\n", stamp(),
wl_proxy_get_id((struct wl_proxy *)b));
free_pixels(data);
}
static const struct wl_buffer_listener buffer_listener = {.release = release};
static void draw(void) {
if (width <= 0 || height <= 0 || width > 4096 || height > 4096 ||
live_buffers >= 8)
exit(2);
size_t size = (size_t)width * (size_t)height * 4;
/* CONTRACT: each mapping stays immutable until native wl_buffer.release.
* Bounds keep pool/stride integers valid, with at most eight outstanding. */
char name[512];
const char *runtime = getenv("XDG_RUNTIME_DIR");
if (!runtime || snprintf(name, sizeof(name), "%s/blur-shm-XXXXXX", runtime) >=
(int)sizeof(name))
exit(2);
int fd = mkstemp(name);
if (fd < 0)
exit(2);
unlink(name);
if (ftruncate(fd, (off_t)size) < 0)
exit(2);
struct pixels *p = calloc(1, sizeof(*p));
if (!p)
exit(2);
p->map = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
p->size = size;
if (p->map == MAP_FAILED)
exit(2);
uint32_t *pixels = p->map;
for (int y = 0; y < height; ++y)
for (int x = 0; x < width; ++x) {
uint32_t color = 0x00303038;
if (!strcmp(mode, "background"))
color = pattern == 'A' ? (x / 8 % 2 ? 0x000f192d : 0x00f5f5f5)
: (x / 8 % 2 ? 0x00b02040 : 0x0020c0e0);
else if (!strcmp(mode, "glass"))
color = 0x88442010;
else if (!strcmp(mode, "filler"))
color = 0x00000000;
pixels[(size_t)y * (size_t)width + (size_t)x] = color;
}
struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, (int)size);
p->buffer = wl_shm_pool_create_buffer(
pool, 0, width, height, width * 4,
(!strcmp(mode, "glass") || !strcmp(mode, "filler")) ? WL_SHM_FORMAT_ARGB8888 : WL_SHM_FORMAT_XRGB8888);
p->next = buffers;
buffers = p;
++live_buffers;
wl_buffer_add_listener(p->buffer, &buffer_listener, p);
wl_surface_attach(surface, p->buffer, 0, 0);
wl_surface_damage_buffer(surface, 0, 0, width, height);
wl_surface_commit(surface);
fprintf(stderr,
"CLIENT_COMMIT ns=%lld mode=%s surface=%u buffer=%u index=%u "
"pattern=%c size=%dx%d damage=0,0,%d,%d\n",
stamp(), mode, wl_proxy_get_id((struct wl_proxy *)surface),
wl_proxy_get_id((struct wl_proxy *)p->buffer), ++commits, pattern,
width, height, width, height);
wl_shm_pool_destroy(pool);
close(fd);
}
static void ping(void *d, struct xdg_wm_base *b, uint32_t s) {
xdg_wm_base_pong(b, s);
}
static const struct xdg_wm_base_listener wm_listener = {.ping = ping};
static void top_configure(void *d, struct xdg_toplevel *t, int32_t w, int32_t h,
struct wl_array *a) {
if (w > 0)
width = w;
if (h > 0)
height = h;
}
static void top_close(void *d, struct xdg_toplevel *t) { running = 0; }
static const struct xdg_toplevel_listener top_listener = {
.configure = top_configure, .close = top_close};
static void configure(void *d, struct xdg_surface *s, uint32_t serial) {
xdg_surface_ack_configure(s, serial);
draw();
}
static const struct xdg_surface_listener xdg_listener = {.configure =
configure};
static void layer_configure(void *d, struct zwlr_layer_surface_v1 *s,
uint32_t serial, uint32_t w, uint32_t h) {
if (w > 4096 || h > 4096)
exit(2);
width = (int)w;
height = (int)h;
zwlr_layer_surface_v1_ack_configure(s, serial);
draw();
}
static void layer_close(void *d, struct zwlr_layer_surface_v1 *s) {
running = 0;
}
static const struct zwlr_layer_surface_v1_listener layer_listener = {
.configure = layer_configure, .closed = layer_close};
static void output_name(void *d, struct wl_output *o, const char *name) {
if (!strcmp(name, "HEADLESS-1"))
h1 = o;
}
static void output_geometry(void *d, struct wl_output *o, int32_t x, int32_t y,
int32_t w, int32_t h, int32_t sp, const char *m,
const char *n, int32_t tr) {}
static void output_mode(void *d, struct wl_output *o, uint32_t flags, int32_t w,
int32_t h, int32_t refresh) {}
static void output_done(void *d, struct wl_output *o) {}
static void output_scale(void *d, struct wl_output *o, int32_t scale) {}
static void output_description(void *d, struct wl_output *o, const char *desc) {
}
static const struct wl_output_listener output_listener = {
.geometry = output_geometry,
.mode = output_mode,
.done = output_done,
.scale = output_scale,
.name = output_name,
.description = output_description};
static void global(void *d, struct wl_registry *r, uint32_t id,
const char *name, uint32_t v) {
if (!strcmp(name, "wl_compositor"))
compositor =
wl_registry_bind(r, id, &wl_compositor_interface, v < 4 ? v : 4);
else if (!strcmp(name, "wl_shm"))
shm = wl_registry_bind(r, id, &wl_shm_interface, 1);
else if (!strcmp(name, "xdg_wm_base"))
wm = wl_registry_bind(r, id, &xdg_wm_base_interface, v < 3 ? v : 3);
else if (!strcmp(name, "zwlr_layer_shell_v1"))
shell =
wl_registry_bind(r, id, &zwlr_layer_shell_v1_interface, v < 2 ? v : 2);
else if (!strcmp(name, "wl_output") && v >= 4) {
struct wl_output *o = wl_registry_bind(r, id, &wl_output_interface, 4);
wl_output_add_listener(o, &output_listener, NULL);
}
}
static void removed(void *d, struct wl_registry *r, uint32_t id) {}
static const struct wl_registry_listener registry_listener = {
.global = global, .global_remove = removed};
static void stop(int s) { running = 0; }
int main(int argc, char **argv) {
if (argc != 2)
return 2;
mode = argv[1];
if (strcmp(mode, "background") && strcmp(mode, "glass") &&
strcmp(mode, "filler") && strcmp(mode, "reference"))
return 2;
signal(SIGTERM, stop);
display = wl_display_connect(NULL);
if (!display)
return 1;
struct wl_registry *r = wl_display_get_registry(display);
wl_registry_add_listener(r, &registry_listener, NULL);
if (wl_display_roundtrip(display) < 0 || wl_display_roundtrip(display) < 0)
return 1;
if (!compositor || !shm || !wm)
return 2;
xdg_wm_base_add_listener(wm, &wm_listener, NULL);
surface = wl_compositor_create_surface(compositor);
if (!strcmp(mode, "background")) {
if (!shell || !h1)
return 2;
layer = zwlr_layer_shell_v1_get_layer_surface(
shell, surface, h1, ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND,
"mosaic-dynamic-background");
zwlr_layer_surface_v1_add_listener(layer, &layer_listener, NULL);
zwlr_layer_surface_v1_set_size(layer, 0, 0);
zwlr_layer_surface_v1_set_anchor(layer,
ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP |
ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM |
ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT |
ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT);
zwlr_layer_surface_v1_set_exclusive_zone(layer, -1);
zwlr_layer_surface_v1_set_keyboard_interactivity(layer, 0);
} else {
xdg = xdg_wm_base_get_xdg_surface(wm, surface);
xdg_surface_add_listener(xdg, &xdg_listener, NULL);
top = xdg_surface_get_toplevel(xdg);
xdg_toplevel_add_listener(top, &top_listener, NULL);
const char *app = !strcmp(mode, "reference") ? "org.mosaic.StaticReference"
: !strcmp(mode, "glass") ? "org.mosaic.BlurGlass"
: "org.mosaic.BlurFiller";
xdg_toplevel_set_app_id(top, app);
xdg_toplevel_set_title(top, app);
}
wl_surface_commit(surface);
while (running) {
if (wl_display_dispatch_pending(display) < 0)
break;
if (wl_display_flush(display) < 0 && errno != EAGAIN)
break;
struct pollfd fds[2] = {
{.fd = wl_display_get_fd(display), .events = POLLIN},
{.fd = STDIN_FILENO, .events = POLLIN}};
int n = poll(fds, 2, 100);
if (n < 0) {
if (errno == EINTR)
continue;
break;
}
if (fds[0].revents & (POLLIN | POLLERR | POLLHUP))
if (wl_display_dispatch(display) < 0)
break;
if (fds[1].revents & POLLIN) {
char cmd[64];
if (!fgets(cmd, sizeof(cmd), stdin))
break;
if (!strcmp(mode, "background") && (cmd[0] == 'A' || cmd[0] == 'B') &&
cmd[1] == '\n') {
pattern = cmd[0];
draw();
fprintf(stderr, "COMMAND %c DONE ns=%lld\n", pattern, stamp());
} else if (!strncmp(cmd, "close", 5))
running = 0;
}
}
int error = wl_display_get_error(display);
fprintf(stderr, "EXIT ns=%lld mode=%s error=%d commits=%u\n", stamp(), mode,
error, commits);
if (layer)
zwlr_layer_surface_v1_destroy(layer);
if (top)
xdg_toplevel_destroy(top);
if (xdg)
xdg_surface_destroy(xdg);
wl_surface_destroy(surface);
wl_display_flush(display);
while (buffers)
free_pixels(buffers);
wl_registry_destroy(r);
wl_display_disconnect(display);
return error ? 1 : 0;
}

View file

@ -0,0 +1,297 @@
#define _POSIX_C_SOURCE 200809L
#include "layer-shell-client.h"
#include "xdg-shell-client.h"
#include <errno.h>
#include <poll.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <time.h>
#include <unistd.h>
#include <wayland-client.h>
/* WHY: one persistent background supplies real A/B commits below blur_tree.
* CONTRACT: fixed opaque background, premultiplied ARGB glass and static H2.
* INVARIANT: no animation, synthetic configure, popup or held transaction. */
static struct wl_display *display;
static struct wl_compositor *compositor;
static struct wl_shm *shm;
static struct xdg_wm_base *wm;
static struct zwlr_layer_shell_v1 *shell;
static struct wl_output *h1;
static struct wl_surface *surface;
static struct xdg_surface *xdg;
static struct xdg_toplevel *top;
static struct zwlr_layer_surface_v1 *layer;
static const char *mode;
static int width = 420, height = 320;
static unsigned commits, live_buffers;
static char pattern = 'A';
static volatile sig_atomic_t running = 1;
struct pixels {
struct wl_buffer *buffer;
void *map;
size_t size;
struct pixels *next;
};
static struct pixels *buffers;
static long long stamp(void) {
struct timespec t;
clock_gettime(CLOCK_MONOTONIC, &t);
return (long long)t.tv_sec * 1000000000LL + t.tv_nsec;
}
static void free_pixels(struct pixels *p) {
struct pixels **it = &buffers;
while (*it && *it != p)
it = &(*it)->next;
if (*it)
*it = p->next;
wl_buffer_destroy(p->buffer);
munmap(p->map, p->size);
free(p);
--live_buffers;
}
static void release(void *data, struct wl_buffer *b) {
fprintf(stderr, "RELEASE ns=%lld buffer=%u\n", stamp(),
wl_proxy_get_id((struct wl_proxy *)b));
free_pixels(data);
}
static const struct wl_buffer_listener buffer_listener = {.release = release};
static void draw(void) {
if (width <= 0 || height <= 0 || width > 4096 || height > 4096 ||
live_buffers >= 8)
exit(2);
size_t size = (size_t)width * (size_t)height * 4;
/* CONTRACT: each mapping stays immutable until native wl_buffer.release.
* Bounds keep pool/stride integers valid, with at most eight outstanding. */
char name[512];
const char *runtime = getenv("XDG_RUNTIME_DIR");
if (!runtime || snprintf(name, sizeof(name), "%s/blur-shm-XXXXXX", runtime) >=
(int)sizeof(name))
exit(2);
int fd = mkstemp(name);
if (fd < 0)
exit(2);
unlink(name);
if (ftruncate(fd, (off_t)size) < 0)
exit(2);
struct pixels *p = calloc(1, sizeof(*p));
if (!p)
exit(2);
p->map = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
p->size = size;
if (p->map == MAP_FAILED)
exit(2);
uint32_t *pixels = p->map;
for (int y = 0; y < height; ++y)
for (int x = 0; x < width; ++x) {
uint32_t color = 0x00303038;
if (!strcmp(mode, "background"))
color = pattern == 'A' ? (x / 8 % 2 ? 0x000f192d : 0x00f5f5f5)
: (x / 8 % 2 ? 0x00b02040 : 0x0020c0e0);
else if (!strcmp(mode, "glass"))
color = 0x88442010;
else if (!strcmp(mode, "filler"))
color = 0x00000000;
pixels[(size_t)y * (size_t)width + (size_t)x] = color;
}
struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, (int)size);
p->buffer = wl_shm_pool_create_buffer(
pool, 0, width, height, width * 4,
(!strcmp(mode, "glass") || !strcmp(mode, "filler"))
? WL_SHM_FORMAT_ARGB8888
: WL_SHM_FORMAT_XRGB8888);
p->next = buffers;
buffers = p;
++live_buffers;
wl_buffer_add_listener(p->buffer, &buffer_listener, p);
wl_surface_attach(surface, p->buffer, 0, 0);
wl_surface_damage_buffer(surface, 0, 0, width, height);
wl_surface_commit(surface);
fprintf(stderr,
"CLIENT_COMMIT ns=%lld mode=%s surface=%u buffer=%u index=%u "
"pattern=%c size=%dx%d damage=0,0,%d,%d\n",
stamp(), mode, wl_proxy_get_id((struct wl_proxy *)surface),
wl_proxy_get_id((struct wl_proxy *)p->buffer), ++commits, pattern,
width, height, width, height);
wl_shm_pool_destroy(pool);
close(fd);
}
static void ping(void *d, struct xdg_wm_base *b, uint32_t s) {
xdg_wm_base_pong(b, s);
}
static const struct xdg_wm_base_listener wm_listener = {.ping = ping};
static void top_configure(void *d, struct xdg_toplevel *t, int32_t w, int32_t h,
struct wl_array *a) {
if (w > 0)
width = w;
if (h > 0)
height = h;
}
static void top_close(void *d, struct xdg_toplevel *t) { running = 0; }
static const struct xdg_toplevel_listener top_listener = {
.configure = top_configure, .close = top_close};
static void configure(void *d, struct xdg_surface *s, uint32_t serial) {
xdg_surface_ack_configure(s, serial);
draw();
}
static const struct xdg_surface_listener xdg_listener = {.configure =
configure};
/* CONTRACT: configures ACK geometry, never submit an unreserved image.
* WHY: duplicate layer configures must not spend autonomous mutations.
* INVARIANT: the runner reserves then sends every A/B draw command. */
static void layer_configure(void *d, struct zwlr_layer_surface_v1 *s,
uint32_t serial, uint32_t w, uint32_t h) {
if (w == 0 || h == 0 || w > 4096 || h > 4096)
exit(2);
if (commits && ((int)w != width || (int)h != height))
exit(2);
width = (int)w;
height = (int)h;
zwlr_layer_surface_v1_ack_configure(s, serial);
fprintf(stderr, "BACKGROUND_CONFIGURED ns=%lld serial=%u size=%dx%d\n",
stamp(), serial, width, height);
}
static void layer_close(void *d, struct zwlr_layer_surface_v1 *s) {
running = 0;
}
static const struct zwlr_layer_surface_v1_listener layer_listener = {
.configure = layer_configure, .closed = layer_close};
static void output_name(void *d, struct wl_output *o, const char *name) {
if (!strcmp(name, "HEADLESS-1"))
h1 = o;
}
static void output_geometry(void *d, struct wl_output *o, int32_t x, int32_t y,
int32_t w, int32_t h, int32_t sp, const char *m,
const char *n, int32_t tr) {}
static void output_mode(void *d, struct wl_output *o, uint32_t flags, int32_t w,
int32_t h, int32_t refresh) {}
static void output_done(void *d, struct wl_output *o) {}
static void output_scale(void *d, struct wl_output *o, int32_t scale) {}
static void output_description(void *d, struct wl_output *o, const char *desc) {
}
static const struct wl_output_listener output_listener = {
.geometry = output_geometry,
.mode = output_mode,
.done = output_done,
.scale = output_scale,
.name = output_name,
.description = output_description};
static void global(void *d, struct wl_registry *r, uint32_t id,
const char *name, uint32_t v) {
if (!strcmp(name, "wl_compositor"))
compositor =
wl_registry_bind(r, id, &wl_compositor_interface, v < 4 ? v : 4);
else if (!strcmp(name, "wl_shm"))
shm = wl_registry_bind(r, id, &wl_shm_interface, 1);
else if (!strcmp(name, "xdg_wm_base"))
wm = wl_registry_bind(r, id, &xdg_wm_base_interface, v < 3 ? v : 3);
else if (!strcmp(name, "zwlr_layer_shell_v1"))
shell =
wl_registry_bind(r, id, &zwlr_layer_shell_v1_interface, v < 2 ? v : 2);
else if (!strcmp(name, "wl_output") && v >= 4) {
struct wl_output *o = wl_registry_bind(r, id, &wl_output_interface, 4);
wl_output_add_listener(o, &output_listener, NULL);
}
}
static void removed(void *d, struct wl_registry *r, uint32_t id) {}
static const struct wl_registry_listener registry_listener = {
.global = global, .global_remove = removed};
static void stop(int s) { running = 0; }
int main(int argc, char **argv) {
if (argc != 2)
return 2;
mode = argv[1];
if (strcmp(mode, "background") && strcmp(mode, "glass") &&
strcmp(mode, "filler") && strcmp(mode, "reference"))
return 2;
signal(SIGTERM, stop);
display = wl_display_connect(NULL);
if (!display)
return 1;
struct wl_registry *r = wl_display_get_registry(display);
wl_registry_add_listener(r, &registry_listener, NULL);
if (wl_display_roundtrip(display) < 0 || wl_display_roundtrip(display) < 0)
return 1;
if (!compositor || !shm || !wm)
return 2;
xdg_wm_base_add_listener(wm, &wm_listener, NULL);
surface = wl_compositor_create_surface(compositor);
if (!strcmp(mode, "background")) {
if (!shell || !h1)
return 2;
layer = zwlr_layer_shell_v1_get_layer_surface(
shell, surface, h1, ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND,
"mosaic-dynamic-background");
zwlr_layer_surface_v1_add_listener(layer, &layer_listener, NULL);
zwlr_layer_surface_v1_set_size(layer, 0, 0);
zwlr_layer_surface_v1_set_anchor(layer,
ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP |
ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM |
ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT |
ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT);
zwlr_layer_surface_v1_set_exclusive_zone(layer, -1);
zwlr_layer_surface_v1_set_keyboard_interactivity(layer, 0);
} else {
xdg = xdg_wm_base_get_xdg_surface(wm, surface);
xdg_surface_add_listener(xdg, &xdg_listener, NULL);
top = xdg_surface_get_toplevel(xdg);
xdg_toplevel_add_listener(top, &top_listener, NULL);
const char *app = !strcmp(mode, "reference") ? "org.mosaic.StaticReference"
: !strcmp(mode, "glass") ? "org.mosaic.BlurGlass"
: "org.mosaic.BlurFiller";
xdg_toplevel_set_app_id(top, app);
xdg_toplevel_set_title(top, app);
}
wl_surface_commit(surface);
while (running) {
if (wl_display_dispatch_pending(display) < 0)
break;
if (wl_display_flush(display) < 0 && errno != EAGAIN)
break;
struct pollfd fds[2] = {
{.fd = wl_display_get_fd(display), .events = POLLIN},
{.fd = STDIN_FILENO, .events = POLLIN}};
int n = poll(fds, 2, 100);
if (n < 0) {
if (errno == EINTR)
continue;
break;
}
if (fds[0].revents & (POLLIN | POLLERR | POLLHUP))
if (wl_display_dispatch(display) < 0)
break;
if (fds[1].revents & POLLIN) {
char cmd[64];
if (!fgets(cmd, sizeof(cmd), stdin))
break;
if (!strcmp(mode, "background") && (cmd[0] == 'A' || cmd[0] == 'B') &&
cmd[1] == '\n') {
pattern = cmd[0];
draw();
fprintf(stderr, "COMMAND %c DONE ns=%lld\n", pattern, stamp());
} else if (!strncmp(cmd, "close", 5))
running = 0;
}
}
int error = wl_display_get_error(display);
fprintf(stderr, "EXIT ns=%lld mode=%s error=%d commits=%u\n", stamp(), mode,
error, commits);
if (layer)
zwlr_layer_surface_v1_destroy(layer);
if (top)
xdg_toplevel_destroy(top);
if (xdg)
xdg_surface_destroy(xdg);
wl_surface_destroy(surface);
wl_display_flush(display);
while (buffers)
free_pixels(buffers);
wl_registry_destroy(r);
wl_display_disconnect(display);
return error ? 1 : 0;
}

View file

@ -0,0 +1,73 @@
"""Offline model of the audited blur1/blur2 kernels, never a GPU run.
WHY: determine whether a scaled written domain and full-size sampler clamp
can explain the observed history-dependent boundary pixels.
CONTRACT: hypotheses use 8-bit rounding and ideal bilinear samples, not GLES
precision, native framebuffer readback or proof of actual tail contents.
INVARIANT: historical logs/PNGs stay immutable; no renderer algorithm edits.
"""
import json,re,sys
from pathlib import Path
import numpy as np
from PIL import Image
lab=Path(sys.argv[1]);results={}
W,H=800,600
def sample(src,x,y):
x=np.clip(x,0,W-1);y=np.clip(y,0,H-1)
x0=np.floor(x).astype(int);y0=np.floor(y).astype(int)
x1=np.minimum(x0+1,W-1);y1=np.minimum(y0+1,H-1)
ax=(x-x0)[...,None];ay=(y-y0)[...,None]
return ((1-ax)*src[y0,x0]+ax*src[y0,x1])*(1-ay)+((1-ax)*src[y1,x0]+ax*src[y1,x1])*ay
def rebuild(source,ping,pong):
current=source
for down,i in [(True,i) for i in range(3)]+[(False,i) for i in [2,1,0]]:
dst=pong if current is ping else ping
w=W//(2**(i+1) if down else 2**i);h=H//(2**(i+1) if down else 2**i)
yy,xx=np.mgrid[:h,:w]
if down:
x=(xx+.5)*2-.5;y=(yy+.5)*2-.5
v=sample(current,x,y)*4
for dx,dy in [(-5,-5),(5,5),(5,-5),(-5,5)]:v+=sample(current,x+dx,y+dy)
v/=8
else:
x=(xx+.5)/2-.5;y=(yy+.5)/2-.5
v=np.zeros((h,w,3))
for dx,dy,weight in [(-2.5,0,1),(-1.25,1.25,2),(0,2.5,1),(1.25,1.25,2),(2.5,0,1),(1.25,-1.25,2),(0,-2.5,1),(-1.25,-1.25,2)]:v+=sample(current,x+dx,y+dy)*weight
v/=12
dst[:h,:w]=np.round(v)
current=dst
return current.copy()
def pattern(c):
colors=np.array([[245,245,245],[15,25,45]] if c=='A' else [[32,192,224],[176,32,64]],dtype=float)
return np.broadcast_to(colors[(np.arange(W)//8)%2],(H,W,3)).copy()
for mode in ['C1','C2']:
run=lab/'runs'/mode;lines=(run/'compositor.log').read_text().splitlines();client=(run/'background.log').read_text().splitlines()
commits=[]
for line in client:
if line.startswith('CLIENT_COMMIT '):commits.append((int(re.search(r'ns=(\d+)',line)[1]),re.search(r'pattern=([AB])',line)[1]))
rebuilds=[int(re.search(r'ns=(\d+)',x)[1]) for x in lines if x.startswith('OPT_GPU_REBUILD ') and 'output=HEADLESS-1' in x]
ping=np.zeros((H,W,3));pong=ping.copy();cache=ping.copy();states=[]
for ns in rebuilds:
current=next((c for t,c in reversed(commits) if t<ns),None)
src=pattern(current) if current else np.zeros((H,W,3))
cache=rebuild(src,ping,pong);states.append((ns,current,cache))
events=[json.loads(x) for x in (run/'events.jsonl').read_text().splitlines()]
metrics={}
for e in events:
if e['kind']!='capture' or e['output']!='HEADLESS-1' or e['tag']=='P0-FX0':continue
ns,c,cache=next(v for v in reversed(states) if v[0]<e['before_ns'])
final=np.round(cache*(119/255)+[68,32,16])
actual=np.array(Image.open(run/e['path']).convert('RGB')).astype(float)
# Predefined boundary ROI is compared without fitting offsets or kernels.
expected=final[50:550,720:800];observed=actual[50:550,720:800]
d=np.abs(expected-observed)
metrics[e['tag']]={'source':c,'model_mae_boundary':float(d.mean()),'model_max_channel_error':float(d.max()),'within_one_channel':float((d<=1).mean())}
# Pure same-input repeated regeneration must be invariant for a correct source-only model.
ping=np.zeros((H,W,3));pong=ping.copy()
aa=rebuild(pattern('A'),ping,pong);bb=rebuild(pattern('B'),ping,pong);bb2=rebuild(pattern('B'),ping,pong);aa2=rebuild(pattern('A'),ping,pong)
metrics['ideal_model_history']={'B_repeat_changed_pixels':int(np.any(bb!=bb2,axis=2).sum()),'A_return_changed_pixels':int(np.any(aa!=aa2,axis=2).sum()),'explanation':'Upsample taps exceed the freshly written reduced domain; texture clamp is at the full-size FBO boundary. This is a source-supported hypothesis, not native GPU tail proof.'}
results[mode]=metrics
out=lab/'results/edge-model.json';out.write_text(json.dumps(results,indent=2)+'\n');print(json.dumps(results,indent=2))

View file

@ -0,0 +1,136 @@
"""Generate passive private deltas after the documented source audit.
WHY: dirty, regeneration return, cache copies and final use are separate facts.
CONTRACT: insert logs only, never change a render/scene/ownership decision.
INVARIANT: borrowed pointers are read synchronously; no retained GPU references.
"""
from pathlib import Path
import difflib
HEADER = r'''#ifndef OPT_PROBE_H
#define OPT_PROBE_H
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
/* WHY: correlate passive observations with the runner's CLOCK_MONOTONIC.
* CONTRACT: no driver query/readback or retained object; opt-in logs only.
* INVARIANT: native rendering and lifetime decisions are unchanged. */
static inline long long opt_probe_ns(void) {
struct timespec t;
clock_gettime(CLOCK_MONOTONIC, &t);
return (long long)t.tv_sec * 1000000000LL + t.tv_nsec;
}
#define OPT_LOG(event, format, ...) do { if (getenv("OPT_TRACE")) \
fprintf(stderr, event " ns=%lld " format "\n", opt_probe_ns(), __VA_ARGS__); } while (0)
#endif
'''
def instrument(scene: Path, mosaic: Path, fixture: Path):
originals = {}
def change(root, name, old, new):
p=root/name
originals.setdefault(p, p.read_text() if p.exists() else '')
s=p.read_text()
if s.count(old)!=1:raise RuntimeError(f'nonunique insertion {name}: {old[:60]} count={s.count(old)}')
p.write_text(s.replace(old,new,1))
header=scene/'include/render/opt_probe.h'; originals[header]='';header.write_text(HEADER)
for name in ['types/scene/wlr_scene.c','render/fx_renderer/fx_pass.c','render/fx_renderer/fx_offscreen_buffers.c','render/fx_renderer/fx_framebuffer.c']:
p=scene/name; originals[p]=p.read_text();p.write_text('#include "render/opt_probe.h"\n'+p.read_text())
name='types/scene/wlr_scene.c'
change(scene,name,'\tscene_blur->dirty = false;\n\n\tscene_node_update(&scene_blur->node, NULL);','\tscene_blur->dirty = false;\n\n\tscene_node_update(&scene_blur->node, NULL);\n\tOPT_LOG("OPT_NODE_CREATE", "node=%p parent=%p size=%dx%d dirty=0", (void *)scene_blur, (void *)parent, width, height);')
change(scene,name,'\tblur_node->dirty = true;','\tOPT_LOG("OPT_DIRTY_SET", "node=%p enabled=%d before=%d size=%dx%d", (void *)blur_node, blur_node->node.enabled, blur_node->dirty, blur_node->width, blur_node->height);\n\tblur_node->dirty = true;')
change(scene,name,'\t\t// Re-render the optimized blur buffer when needed.',r''' OPT_LOG("OPT_NODE_RENDER", "output=%s node=%p enabled=%d dirty=%d has_blur=%d size=%dx%d visible=%d,%d,%d,%d draw=%d,%d,%d,%d radius=%.3f passes=%d",
data->output->output->name, (void *)node, node->enabled, scene_blur->dirty, fx_pass->has_blur, scene_blur->width, scene_blur->height,
node->visible.extents.x1, node->visible.extents.y1, node->visible.extents.x2, node->visible.extents.y2,
render_region.extents.x1, render_region.extents.y1, render_region.extents.x2, render_region.extents.y2,
scene->blur_data.radius, scene->blur_data.num_passes);
// Re-render the optimized blur buffer when needed.'''.replace('\\t','\t'))
change(scene,name,'\t\t\tbool result = fx_render_pass_add_optimized_blur(fx_pass, &blur_options);',r''' OPT_LOG("OPT_REGEN_ATTEMPT", "output=%s node=%p dirty=%d", data->output->output->name, (void *)node, scene_blur->dirty);
bool result = fx_render_pass_add_optimized_blur(fx_pass, &blur_options);
OPT_LOG("OPT_REGEN_RESULT", "output=%s node=%p result=%d", data->output->output->name, (void *)node, result);'''.replace('\\t','\t'))
change(scene,name,'\t\t\t\tscene_blur->dirty = false;','\t\t\t\tscene_blur->dirty = false;\n OPT_LOG("OPT_DIRTY_CLEAR", "output=%s node=%p dirty=%d", data->output->output->name, (void *)node, scene_blur->dirty);')
change(scene,name,'\t\tfx_render_pass_add_blur(fx_pass, &blur_options);',r''' OPT_LOG("OPT_REGULAR_NODE", "output=%s node=%p parent=%p optimized=%d strength=%.3f enabled=%d global=%d,%d size=%dx%d draw=%d,%d,%d,%d mask=%p",
data->output->output->name, (void *)node, (void *)node->parent, blur_options.use_optimized_blur, blur->strength, node->enabled,
entry->x, entry->y, blur->width, blur->height,
render_region.extents.x1, render_region.extents.y1, render_region.extents.x2, render_region.extents.y2, (void *)mask);
fx_render_pass_add_blur(fx_pass, &blur_options);'''.replace('\\t','\t'))
# Main-output buffer is the source at the optimized node, distinct from final output submission.
change(scene,name,'\tint x = entry->x - data->logical.x;',r''' if (node->type == WLR_SCENE_NODE_BUFFER) {
struct wlr_scene_buffer *sb = wlr_scene_buffer_from_node(node);
struct wlr_scene_surface *ss = wlr_scene_surface_try_from_buffer(sb);
OPT_LOG("OPT_BUFFER_DRAW", "output=%s node=%p buffer=%p surface=%p global=%d,%d dest=%dx%d source=%dx%d draw=%d,%d,%d,%d",
data->output->output->name, (void *)node, (void *)sb->buffer, ss ? (void *)ss->surface : NULL,
entry->x, entry->y, sb->dst_width, sb->dst_height, sb->buffer ? sb->buffer->width : 0, sb->buffer ? sb->buffer->height : 0,
render_region.extents.x1, render_region.extents.y1, render_region.extents.x2, render_region.extents.y2);
}
int x = entry->x - data->logical.x;''')
change(scene,name,'\twlr_output_state_set_buffer(state, buffer);\n\twlr_buffer_unlock(buffer);',r''' wlr_output_state_set_buffer(state, buffer);
OPT_LOG("OPT_OUTPUT_BUFFER", "output=%s buffer=%p submitted=1 damage=%d,%d,%d,%d committed=%u",
output->name, (void *)buffer, state->damage.extents.x1, state->damage.extents.y1, state->damage.extents.x2, state->damage.extents.y2, state->committed);
wlr_buffer_unlock(buffer);''')
name='render/fx_renderer/fx_pass.c'
change(scene,name,'\t// Update the buffers if needed','\tstruct fx_framebuffer *opt_before = pass->fx_offscreen_buffers->optimized_blur_buffer;\n\t// Update the buffers if needed')
change(scene,name,'\t// Bind back to the default buffer\n\tfx_framebuffer_bind(pass->buffer);\n\n\tif (failed)',r''' OPT_LOG("OPT_CACHE_STATE", "output=%s cache=%p blur=%p plain=%p blur_fbo=%u plain_fbo=%u size=%dx%d failed=%d reuse=%d",
output->name, (void *)pass->fx_offscreen_buffers,
(void *)pass->fx_offscreen_buffers->optimized_blur_buffer,
(void *)pass->fx_offscreen_buffers->optimized_no_blur_buffer,
pass->fx_offscreen_buffers->optimized_blur_buffer ? pass->fx_offscreen_buffers->optimized_blur_buffer->fbo : 0,
pass->fx_offscreen_buffers->optimized_no_blur_buffer ? pass->fx_offscreen_buffers->optimized_no_blur_buffer->fbo : 0,
width, height, failed, opt_before != NULL && opt_before == pass->fx_offscreen_buffers->optimized_blur_buffer);
// Bind back to the default buffer
fx_framebuffer_bind(pass->buffer);
if (failed)'''.replace('\\t','\t'))
change(scene,name,'\tif (!buffer) {\n\t\tgoto finish;',r''' OPT_LOG("OPT_BLUR_USE", "output=%s cache=%p chosen=%p optimized=%d strength=%.3f fbo=%u",
((const struct wlr_output *)pass->fx_offscreen_buffers->addon.owner)->name,
(void *)pass->fx_offscreen_buffers, (void *)buffer, fx_options->use_optimized_blur, fx_options->blur_strength,
buffer ? buffer->fbo : 0);
if (!buffer) {
goto finish;'''.replace('\\t','\t'))
change(scene,name,'\tstruct fx_framebuffer *fx_buffer = get_main_buffer_blur(pass, &blur_options);',r''' OPT_LOG("OPT_GPU_REBUILD", "output=%s cache=%p source=%p source_fbo=%u region=%d,%d,%d,%d",
((const struct wlr_output *)pass->fx_offscreen_buffers->addon.owner)->name,
(void *)pass->fx_offscreen_buffers, (void *)pass->buffer, pass->buffer->fbo,
clip.extents.x1, clip.extents.y1, clip.extents.x2, clip.extents.y2);
struct fx_framebuffer *fx_buffer = get_main_buffer_blur(pass, &blur_options);'''.replace('\\t','\t'))
change(scene,name,'\treturn fx_buffer != NULL;','\tOPT_LOG("OPT_GPU_RESULT", "output=%s cache=%p result=%d blurred=%p", ((const struct wlr_output *)pass->fx_offscreen_buffers->addon.owner)->name, (void *)pass->fx_offscreen_buffers, fx_buffer != NULL, (void *)fx_buffer);\n\treturn fx_buffer != NULL;')
change(scene,name,'\t// Draw onto the dst_buffer',r''' OPT_LOG("OPT_GPU_COPY", "output=%s cache=%p dst=%p src=%p dst_fbo=%u src_fbo=%u region=%d,%d,%d,%d",
pass->fx_offscreen_buffers ? ((const struct wlr_output *)pass->fx_offscreen_buffers->addon.owner)->name : "none",
(void *)pass->fx_offscreen_buffers, (void *)dst_buffer, (void *)src_buffer, dst_buffer->fbo, src_buffer->fbo,
region.extents.x1, region.extents.y1, region.extents.x2, region.extents.y2);
// Draw onto the dst_buffer'''.replace('\\t','\t'))
change(scene,name,'\twlr_texture_destroy(src_tex);','\tOPT_LOG("OPT_GPU_COPY_DRAWN", "dst=%p src=%p", (void *)dst_buffer, (void *)src_buffer);\n\twlr_texture_destroy(src_tex);')
name='render/fx_renderer/fx_offscreen_buffers.c'
change(scene,name,'\t// Make sure to free the buffers','\tOPT_LOG("OPT_CACHE_DESTROY", "output=%s cache=%p blur=%p plain=%p", ((const struct wlr_output *)fbos->addon.owner)->name, (void *)fbos, (void *)fbos->optimized_blur_buffer, (void *)fbos->optimized_no_blur_buffer);\n\t// Make sure to free the buffers')
change(scene,name,'\twl_list_insert(&renderer->offscreen_buffers, &fbos->link);','\tOPT_LOG("OPT_CACHE_ALLOC", "output=%s cache=%p", output->name, (void *)fbos);\n\twl_list_insert(&renderer->offscreen_buffers, &fbos->link);')
name='render/fx_renderer/fx_framebuffer.c'
change(scene,name,'\t// Release the framebuffer','\tOPT_LOG("OPT_FBO_DESTROY", "fbo_object=%p native_buffer=%p fbo=%u", (void *)fx_buffer, (void *)fx_buffer->buffer, fx_buffer->fbo);\n\t// Release the framebuffer')
# Add a timestamped source/scene-layer trace on the existing Sway commit hook.
name='sway/desktop/layer_shell.c'
p=mosaic/name;originals[p]=p.read_text();p.write_text('#include <stdio.h>\n#include <time.h>\n#include "sway/tree/root.h"\n'+p.read_text())
change(mosaic,name,'\t// Rerender the optimized blur on change',r''' /* WHY: verify actual source ownership before interpreting cache invalidation.
* CONTRACT: passive private trace; reads borrowed scene/surface pointers.
* INVARIANT: no extra dirty call or change to commit/arrangement order. */
if (getenv("OPT_TRACE")) {
struct timespec t; clock_gettime(CLOCK_MONOTONIC, &t);
struct wlr_surface *s = layer_surface->surface;
int x = 0, y = 0; bool valid = wlr_scene_node_coords(&surface->scene->tree->node, &x, &y);
fprintf(stderr, "OPT_BACKGROUND_COMMIT ns=%lld output=%s surface=%p resource=%u buffer=%p seq=%u layer=%u mapped=%d coords=%d,%d valid=%d tree=%p parent=%p layer_parent=%p background=%p background_parent=%p global_background=%p bottom=%p blur_tree=%p opt_node=%p opt_parent=%p damage=%d,%d,%d,%d namespace=%s\n",
(long long)t.tv_sec * 1000000000LL + t.tv_nsec, surface->output ? surface->output->wlr_output->name : "none",
(void *)s, wl_resource_get_id(s->resource), (void *)s->buffer, s->current.seq, layer_surface->current.layer, s->mapped, x, y, valid,
(void *)surface->scene->tree, (void *)surface->scene->tree->node.parent, (void *)surface->tree->node.parent,
surface->output ? (void *)surface->output->layers.shell_background : NULL,
surface->output ? (void *)surface->output->layers.shell_background->node.parent : NULL, (void *)root->layers.shell_background,
surface->output ? (void *)surface->output->layers.shell_bottom : NULL,
(void *)root->layers.blur_tree, surface->output ? (void *)surface->output->layers.blur_layer : NULL,
surface->output ? (void *)surface->output->layers.blur_layer->node.parent : NULL,
s->buffer_damage.extents.x1, s->buffer_damage.extents.y1, s->buffer_damage.extents.x2, s->buffer_damage.extents.y2,
layer_surface->namespace);
}
// Rerender the optimized blur on change'''.replace('\\t','\t'))
for root,name in [(scene,'scenefx-cache-trace.patch'),(mosaic,'sway-source-trace.patch')]:
delta=''
for p,old in originals.items():
if not p.is_relative_to(root):continue
rel=p.relative_to(root)
delta+=''.join(difflib.unified_diff(old.splitlines(True),p.read_text().splitlines(True),fromfile='a/'+str(rel),tofile='b/'+str(rel)))
(fixture/name).write_text(delta)

View file

@ -0,0 +1,63 @@
#!/usr/bin/env python3
"""Build pinned private sources only; never launch a compositor or install."""
import hashlib,json,os,subprocess,tempfile,urllib.request,sys
from pathlib import Path
from instrument import instrument
repo=Path(__file__).resolve().parents[3]; fixture=Path(__file__).resolve().parent
lab=Path(sys.argv[1]).resolve() if len(sys.argv)==2 else Path(tempfile.mkdtemp(prefix='swayfx-mosaic-optimized-blur-',dir=Path.home()))
if (lab/'runs').exists() and any((lab/'runs').iterdir()):raise RuntimeError('never rebuild a consumed campaign')
print('LAB='+str(lab),flush=True)
(lab/'results').mkdir(exist_ok=True); (lab/'runs').mkdir(exist_ok=True)
(lab/'build.log').touch()
def run(argv,env=None,cwd=None):
with (lab/'build.log').open('ab') as f:
subprocess.run(argv,check=True,env=env,cwd=cwd,stdout=f,stderr=subprocess.STDOUT)
def archive(git,rev,dst):
if dst.exists():
if dst.name in ('scene0','pristine'):return
suffix=1
while dst.with_name(dst.name+f'-preflight-failed-{suffix}').exists():suffix+=1
dst.rename(dst.with_name(dst.name+f'-preflight-failed-{suffix}'))
dst.mkdir()
data=subprocess.check_output(['git','-C',str(git),'archive',rev])
subprocess.run(['tar','-xf','-','-C',str(dst)],input=data,check=True)
def patch(dst,path):
run(['git','apply','--check',str(path)],cwd=dst);run(['git','apply',str(path)],cwd=dst)
scene_rev='37ccd723bef49e6891156ffafce8f549f01446cc'
if not (lab/'scenefx-git').exists():run(['git','clone','--quiet','https://github.com/wlrfx/scenefx.git',str(lab/'scenefx-git')])
archive(lab/'scenefx-git',scene_rev,lab/'scene0')
archive(lab/'scenefx-git',scene_rev,lab/'scenefx')
archive(repo,'fd71a6bdc061bd633b488ae7b83e8a6981d22f86',lab/'pristine')
archive(repo,'cd550382b311efdbfe3ed892f4e1d281d852e754',lab/'mosaic')
for p in ['step20c-valid-grab/scenefx-popup-plus-base.patch','step20c-temporal-damage/scenefx-timing.patch']:
patch(lab/'scenefx',repo/'tooling/probes'/p)
for p in ['step20c-valid-grab/mosaic-popup-plus-base.patch','step20c-valid-grab/hidden-guard.patch','step20c-temporal-damage/mosaic-output-timing.patch','step20c-presentation-identity/presentation-events.patch']:
patch(lab/'mosaic',repo/'tooling/probes'/p)
# WHY: new passive deltas are separate from immutable historical patches.
instrument(lab/'scenefx',lab/'mosaic',fixture)
for src,build in [('scene0','scene0-build'),('scenefx','scenefx-build')]:
run(['meson','setup',str(lab/build),str(lab/src),'-Dexamples=false','-Dwerror=false'])
run(['meson','compile','-C',str(lab/build),'-j','8'])
for src,build,scene in [('pristine','pristine-build','scene0-build'),('mosaic','mosaic-build','scenefx-build')]:
env={**os.environ,'PKG_CONFIG_PATH':str(lab/scene/'meson-uninstalled')}
args=['meson','setup',str(lab/build),str(lab/src),'-Ddefault-wallpaper=false','-Dswaybar=false','-Dswaynag=false','-Dman-pages=disabled','-Dsd-bus-provider=libsystemd','-Dwerror=false']
if src=='mosaic':args.append('-Dmosaic-package=true')
run(args,env=env);run(['meson','compile','-C',str(lab/build),'-j','8'],env=env)
# XML is fetched at a fixed revision. Generated protocol code stays outside Git.
url='https://raw.githubusercontent.com/swaywm/wlr-protocols/b010a03648b88d143236de193bddbfea0c08bc84/unstable/wlr-layer-shell-unstable-v1.xml'
xml=urllib.request.urlopen(url,timeout=30).read();(fixture/'wlr-layer-shell-unstable-v1.xml').write_bytes(xml)
xdg=fixture/'xdg-shell.xml'
assert hashlib.sha256(xdg.read_bytes()).hexdigest()=='7ba7f9c8473deee674cb1f154a18abd0bb0cc072604fc055b0c15e459fc4c7df'
assert hashlib.sha256(xml).hexdigest()=='996b8c66e1c2276c443336d4cb174634e8e5948823779042f244930525b79cc2'
(lab/'xdg-shell.xml').write_bytes(xdg.read_bytes())
provenance={'layer':{'url':url,'commit':'b010a03648b88d143236de193bddbfea0c08bc84','sha256':hashlib.sha256(xml).hexdigest()},'xdg':{'path':str(xdg),'sha256':hashlib.sha256(xdg.read_bytes()).hexdigest(),'wayland_protocols':subprocess.check_output(['pkg-config','--modversion','wayland-protocols'],text=True).strip()}}
(fixture/'protocol-provenance.json').write_text(json.dumps(provenance,indent=2)+'\n')
for name,p in [('layer-shell',fixture/'wlr-layer-shell-unstable-v1.xml'),('xdg-shell',lab/'xdg-shell.xml')]:
run(['wayland-scanner','client-header',str(p),str(lab/(name+'-client.h'))])
run(['wayland-scanner','private-code',str(p),str(lab/(name+'-protocol.c'))])
flags=subprocess.check_output(['pkg-config','--cflags','--libs','wayland-client'],text=True).split()
run(['cc','-std=c17','-Wall','-Wextra','-Werror','-Wno-unused-parameter','-I'+str(lab),str(fixture/'clients.c'),str(lab/'xdg-shell-protocol.c'),str(lab/'layer-shell-protocol.c'),'-o',str(lab/'clients'),*flags])
run(['meson','test','-C',str(lab/'mosaic-build'),'mosaic-model','--print-errorlogs'])
(lab/'results/versions.json').write_text(json.dumps({'SceneFX':scene_rev,'Mosaic':'cd550382b311efdbfe3ed892f4e1d281d852e754','pristine':'fd71a6bdc061bd633b488ae7b83e8a6981d22f86','wlroots':subprocess.check_output(['pkg-config','--modversion','wlroots-0.20'],text=True).strip(),'protocols':provenance},indent=2)+'\n')
print('BUILD_PASS LAB='+str(lab),flush=True)

View file

@ -0,0 +1,12 @@
{
"layer": {
"url": "https://raw.githubusercontent.com/swaywm/wlr-protocols/b010a03648b88d143236de193bddbfea0c08bc84/unstable/wlr-layer-shell-unstable-v1.xml",
"commit": "b010a03648b88d143236de193bddbfea0c08bc84",
"sha256": "996b8c66e1c2276c443336d4cb174634e8e5948823779042f244930525b79cc2"
},
"xdg": {
"path": "/usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml",
"sha256": "7ba7f9c8473deee674cb1f154a18abd0bb0cc072604fc055b0c15e459fc4c7df",
"wayland_protocols": "1.49"
}
}

View file

@ -0,0 +1,162 @@
#!/usr/bin/env python3
"""One reserved, deadline-limited run per mode; only owned headless processes."""
import argparse,json,os,re,shutil,signal,subprocess,time
from pathlib import Path
import numpy as np
from PIL import Image
from budget import reserve,DEFAULT
p=argparse.ArgumentParser();p.add_argument('lab',type=Path);p.add_argument('mode',choices=['C0','C1','C2']);a=p.parse_args()
lab=a.lab.resolve();mode=a.mode
if not (lab/'clients').is_file():p.error('prepared LAB required')
reserve(lab,mode,'run')
run=lab/'runs'/mode;run.mkdir(mode=0o700)
runtime=run/'runtime';runtime.mkdir(mode=0o700)
(run/'fixture-source.py').write_text(Path(__file__).read_text())
(run/'config').write_text('''output HEADLESS-1 resolution 800x600 scale 1 transform normal position -800 0
output HEADLESS-2 resolution 800x600 scale 1 transform normal position 0 0
workspace 1 output HEADLESS-1
workspace 2 output HEADLESS-2
focus_follows_mouse no
mouse_warping none
default_border none
font monospace 8
shadows disable
blur disable
blur_xray disable
blur_radius 5
blur_passes 3
blur_noise 0
blur_brightness 1
blur_contrast 1
blur_saturation 1
corner_radius 0
animation_duration_ms 0
''')
env={k:v for k,v in os.environ.items() if k not in ['SWAYSOCK','I3SOCK','WAYLAND_DISPLAY','LD_LIBRARY_PATH','OPT_TRACE','SAVED_TRACE','SAVED_HIT_TRACE','FX_TRACE']}
env.update(XDG_RUNTIME_DIR=str(runtime),WLR_BACKENDS='headless',WLR_HEADLESS_OUTPUTS='2',WLR_RENDERER='gles2')
private=mode!='C0'
binary=lab/('mosaic-build/sway/swayfx-mosaic' if private else 'pristine-build/sway/sway')
env['LD_LIBRARY_PATH']=str(lab/('scenefx-build' if private else 'scene0-build'))
if private:env['OPT_TRACE']='1'
children=[];files=[];comp=None;sock=None;failure=None;started=time.monotonic();captures=[]
def event(kind,**data):
with (run/'events.jsonl').open('a') as out:out.write(json.dumps({'kind':kind,'ns':time.monotonic_ns(),**data})+'\n')
def guard():
if time.monotonic()-started>150:raise TimeoutError('run deadline')
for q in run.glob('*.log'):
if q.stat().st_size>25_000_000:raise RuntimeError('bounded log exceeded')
def spawn(argv,name,stdin=False):
guard();f=(run/name).open('w');files.append(f)
child=subprocess.Popen(argv,env=env,stdin=subprocess.PIPE if stdin else subprocess.DEVNULL,stdout=f,stderr=subprocess.STDOUT,text=stdin,start_new_session=True);children.append(child)
event('spawn',argv=argv,pid=child.pid);return child
def wait(fn,seconds=8):
end=time.monotonic()+seconds
while time.monotonic()<end:
guard();result=fn()
if result:return result
if comp and comp.poll() is not None:raise RuntimeError('compositor exited')
time.sleep(.025)
raise TimeoutError('fixture wait')
def ipc(*args):
guard();r=subprocess.run(['swaymsg','-s',str(sock),*args],env=env,capture_output=True,text=True,timeout=5,check=True)
data=json.loads(r.stdout);event('ipc',args=args,data=data);return data
def command(s):
r=ipc(s)
if not r or not all(v.get('success') for v in r):raise RuntimeError('IPC rejected '+s)
def nodes(n):
yield n
for c in n.get('nodes',[])+n.get('floating_nodes',[]):yield from nodes(c)
def find(app):return next((n for n in nodes(ipc('-t','get_tree')) if n.get('app_id')==app),None)
def log(name):return (run/name).read_text()
def snapshot(tag,only_h2=False):
outs=ipc('-t','get_outputs');expected={'HEADLESS-1':(-800,0,800,600),'HEADLESS-2':(0,0,800,600)}
actual={o['name']:tuple(o['rect'][k] for k in ['x','y','width','height']) for o in outs if o['active']}
if actual!=expected or any(o['scale']!=1 or o['transform']!='normal' for o in outs if o['active']):raise RuntimeError('output geometry gate')
(run/(tag+'-outputs.json')).write_text(json.dumps(outs,indent=2)+'\n')
(run/(tag+'-tree.json')).write_text(json.dumps(ipc('-t','get_tree'),indent=2)+'\n')
for name in (['HEADLESS-2'] if only_h2 else ['HEADLESS-1','HEADLESS-2']):
reserve(lab,mode,'capture');q=run/(tag+'-'+name+'.png');before=time.monotonic_ns()
subprocess.run(['grim','-s','1','-o',name,str(q)],env=env,timeout=5,check=True,stdout=subprocess.DEVNULL)
if Image.open(q).size!=(800,600):raise RuntimeError('capture size gate')
record={'tag':tag,'output':name,'path':q.name,'before_ns':before,'after_ns':time.monotonic_ns()};captures.append(record);event('capture',**record)
def image(tag,out='HEADLESS-1'):return np.array(Image.open(run/(tag+'-'+out+'.png')).convert('RGB'))
def native_gate():
text=log('compositor.log')
return 'OPT_BLUR_USE' in text and 'OPT_REGEN_RESULT' in text and any('OPT_REGEN_RESULT' in x and 'output=HEADLESS-1' in x and 'result=1' in x for x in text.splitlines()) and any('OPT_BLUR_USE' in x and 'output=HEADLESS-1' in x and 'optimized=1' in x and 'chosen=(nil)' not in x for x in text.splitlines())
def mutate(pattern):
reserve(lab,mode,'mutation');before=log('background.log').count('CLIENT_COMMIT ');event('mutation_reserved',pattern=pattern)
background.stdin.write(pattern+'\n');background.stdin.flush()
wait(lambda:log('background.log').count('CLIENT_COMMIT ')>before)
count=log('background.log').count('CLIENT_COMMIT ')
if count!=before+1:raise RuntimeError('unexpected background commits')
event('mutation_received_client',pattern=pattern,count=count);time.sleep(.35)
def alarm(s,f):raise TimeoutError('150 second watchdog')
signal.signal(signal.SIGALRM,alarm);signal.setitimer(signal.ITIMER_REAL,150)
try:
f=(run/'compositor.log').open('w');files.append(f)
comp=subprocess.Popen([str(binary),'-d','-c',str(run/'config')],env=env,stdout=f,stderr=subprocess.STDOUT,start_new_session=True)
def sockets():
s=next(iter(runtime.glob('sway-ipc.*.sock')),None);w=next((q for q in runtime.glob('wayland-*') if q.is_socket()),None)
return (s,w) if s and w else None
sock,way=wait(sockets);env.update(SWAYSOCK=str(sock),WAYLAND_DISPLAY=way.name)
(run/'library-maps.txt').write_text(Path(f'/proc/{comp.pid}/maps').read_text())
event('renderer_start',pid=comp.pid,binary=str(binary),library=env['LD_LIBRARY_PATH'])
reserve(lab,mode,'mutation');event('mutation_reserved',pattern='A',initial=True)
background=spawn([str(lab/'clients'),'background'],'background.log',True)
wait(lambda:'CLIENT_COMMIT ' in log('background.log'))
command('workspace 1');command('layout mosaic' if mode=='C2' else 'layout splith')
filler=spawn([str(lab/'clients'),'filler'],'filler.log',True);wait(lambda:find('org.mosaic.BlurFiller'))
glass=spawn([str(lab/'clients'),'glass'],'glass.log',True);wait(lambda:find('org.mosaic.BlurGlass'))
command('workspace 2');reference=spawn([str(lab/'clients'),'reference'],'reference.log',True);wait(lambda:find('org.mosaic.StaticReference'))
command('[app_id="org.mosaic.BlurGlass"] focus');time.sleep(.5)
if mode=='C2':
tree=ipc('-t','get_tree');ws=next(n for n in nodes(tree) if n.get('type')=='workspace' and n.get('name')=='1');owner=next(n for n in nodes(ws) if n.get('app_id')=='org.mosaic.BlurGlass')
current=ws['mosaic']['rows'][0]['scroll_x'];left=owner['rect']['x']+owner['window_rect']['x']-current
delta=int(left+owner['window_rect']['width']//2)
reserve(lab,mode,'calibration');event('camera_calibration',current=current,logical=owner['rect'],window=owner['window_rect'],delta=delta)
command('layout mosaic scroll '+str(delta));time.sleep(.35)
if log('background.log').count('CLIENT_COMMIT ')!=1:raise RuntimeError('unexpected initial background image count')
snapshot('P0-FX0');time.sleep(.75);snapshot('P0-reference',True)
if not np.array_equal(image('P0-FX0','HEADLESS-2'),image('P0-reference','HEADLESS-2')):raise RuntimeError('H2 preflight instability')
# Source A must be visible outside glass before interpretation of any cache.
source=image('P0-FX0')[100:500,20:160]
colors={tuple(x) for x in source.reshape(-1,3)}
if not {(245,245,245),(15,25,45)}<=colors:raise RuntimeError('unblurred source gate')
command('[app_id="org.mosaic.BlurGlass"] blur enable');time.sleep(.35);snapshot('P0-FX2')
roi0=image('P0-FX0')[100:500,520:720].astype(float);roi2=image('P0-FX2')[100:500,520:720].astype(float)
adjacent0=np.abs(np.diff(roi0,axis=1)).mean();adjacent2=np.abs(np.diff(roi2,axis=1)).mean()
if adjacent0<=5 or adjacent2>=adjacent0*.8 or not np.any(roi0!=roi2):raise RuntimeError('visible filtering gate')
if private and not native_gate():raise RuntimeError('optimized native use gate')
event('P0_gate',source=True,filtering=True,native_optimized=bool(private),adjacent_fx0=adjacent0,adjacent_fx2=adjacent2)
mutate('B');snapshot('P1-B-natural')
if private:
reserve(lab,mode,'cycle');event('explicit_cycle_start')
command('[app_id="org.mosaic.BlurGlass"] blur disable');time.sleep(.15)
command('[app_id="org.mosaic.BlurGlass"] blur enable');time.sleep(.35);snapshot('P2-B-explicit')
mutate('A');snapshot('P3-A-natural')
event('complete',camera_pair='NOT EXERCISED: main mutation evidence is distinct and sufficient')
except Exception as exc:
failure=repr(exc);event('failure',error=failure)
finally:
signal.setitimer(signal.ITIMER_REAL,0)
# WHY: request normal compositor teardown first so cache destructors can log.
# CONTRACT: explicit private socket, then only recorded owned process groups.
if comp and comp.poll() is None and sock:
try:command('exit');comp.wait(timeout=5)
except (subprocess.SubprocessError,RuntimeError,OSError):pass
for c in children+([comp] if comp else []):
if c.poll() is None:
try:os.killpg(c.pid,signal.SIGTERM)
except ProcessLookupError:pass
for c in children+([comp] if comp else []):
try:c.wait(timeout=3)
except subprocess.TimeoutExpired:
os.killpg(c.pid,signal.SIGKILL);c.wait(timeout=3)
for f in files:f.close()
shutil.rmtree(runtime)
(run/'exit-status.json').write_text(json.dumps({'failure':failure,'duration':time.monotonic()-started,'compositor':comp.returncode if comp else None,'children':[{'args':c.args,'pid':c.pid,'code':c.returncode} for c in children],'runtime_removed':not runtime.exists(),'captures':captures},indent=2)+'\n')
shutil.copy2(DEFAULT,lab/'results/campaign-budget.json')
event('cleanup',runtime_removed=not runtime.exists())
print(mode,'FAILURE='+str(failure),'RUN='+str(run),flush=True)
if failure:raise SystemExit(1)

View file

@ -0,0 +1,163 @@
#!/usr/bin/env python3
"""One reserved, deadline-limited run per mode; only owned headless processes."""
import argparse,json,os,re,shutil,signal,subprocess,time
from pathlib import Path
import numpy as np
from PIL import Image
from budget import reserve,DEFAULT
p=argparse.ArgumentParser();p.add_argument('lab',type=Path);p.add_argument('mode',choices=['C0','C1','C2']);a=p.parse_args()
lab=a.lab.resolve();mode=a.mode
if not (lab/'clients').is_file():p.error('prepared LAB required')
reserve(lab,mode,'run')
run=lab/'runs'/mode;run.mkdir(mode=0o700)
runtime=run/'runtime';runtime.mkdir(mode=0o700)
(run/'fixture-source.py').write_text(Path(__file__).read_text())
shutil.copy2(Path(__file__).with_name('clients.c'),run/'client-source.c')
(run/'config').write_text('''output HEADLESS-1 resolution 800x600 scale 1 transform normal position -800 0
output HEADLESS-2 resolution 800x600 scale 1 transform normal position 0 0
workspace 1 output HEADLESS-1
workspace 2 output HEADLESS-2
focus_follows_mouse no
mouse_warping none
default_border none
font monospace 8
shadows disable
blur disable
blur_xray disable
blur_radius 5
blur_passes 3
blur_noise 0
blur_brightness 1
blur_contrast 1
blur_saturation 1
corner_radius 0
animation_duration_ms 0
''')
env={k:v for k,v in os.environ.items() if k not in ['SWAYSOCK','I3SOCK','WAYLAND_DISPLAY','LD_LIBRARY_PATH','OPT_TRACE','SAVED_TRACE','SAVED_HIT_TRACE','FX_TRACE']}
env.update(XDG_RUNTIME_DIR=str(runtime),WLR_BACKENDS='headless',WLR_HEADLESS_OUTPUTS='2',WLR_RENDERER='gles2')
private=mode!='C0'
binary=lab/('mosaic-build/sway/swayfx-mosaic' if private else 'pristine-build/sway/sway')
env['LD_LIBRARY_PATH']=str(lab/('scenefx-build' if private else 'scene0-build'))
if private:env['OPT_TRACE']='1'
children=[];files=[];comp=None;sock=None;failure=None;started=time.monotonic();captures=[]
def event(kind,**data):
with (run/'events.jsonl').open('a') as out:out.write(json.dumps({'kind':kind,'ns':time.monotonic_ns(),**data})+'\n')
def guard():
if time.monotonic()-started>150:raise TimeoutError('run deadline')
for q in run.glob('*.log'):
if q.stat().st_size>25_000_000:raise RuntimeError('bounded log exceeded')
def spawn(argv,name,stdin=False):
guard();f=(run/name).open('w');files.append(f)
child=subprocess.Popen(argv,env=env,stdin=subprocess.PIPE if stdin else subprocess.DEVNULL,stdout=f,stderr=subprocess.STDOUT,text=stdin,start_new_session=True);children.append(child)
event('spawn',argv=argv,pid=child.pid);return child
def wait(fn,seconds=8):
end=time.monotonic()+seconds
while time.monotonic()<end:
guard();result=fn()
if result:return result
if comp and comp.poll() is not None:raise RuntimeError('compositor exited')
time.sleep(.025)
raise TimeoutError('fixture wait')
def ipc(*args):
guard();r=subprocess.run(['swaymsg','-s',str(sock),*args],env=env,capture_output=True,text=True,timeout=5,check=True)
data=json.loads(r.stdout);event('ipc',args=args,data=data);return data
def command(s):
r=ipc(s)
if not r or not all(v.get('success') for v in r):raise RuntimeError('IPC rejected '+s)
def nodes(n):
yield n
for c in n.get('nodes',[])+n.get('floating_nodes',[]):yield from nodes(c)
def find(app):return next((n for n in nodes(ipc('-t','get_tree')) if n.get('app_id')==app),None)
def log(name):return (run/name).read_text()
def snapshot(tag,only_h2=False):
outs=ipc('-t','get_outputs');expected={'HEADLESS-1':(-800,0,800,600),'HEADLESS-2':(0,0,800,600)}
actual={o['name']:tuple(o['rect'][k] for k in ['x','y','width','height']) for o in outs if o['active']}
if actual!=expected or any(o['scale']!=1 or o['transform']!='normal' for o in outs if o['active']):raise RuntimeError('output geometry gate')
(run/(tag+'-outputs.json')).write_text(json.dumps(outs,indent=2)+'\n')
(run/(tag+'-tree.json')).write_text(json.dumps(ipc('-t','get_tree'),indent=2)+'\n')
for name in (['HEADLESS-2'] if only_h2 else ['HEADLESS-1','HEADLESS-2']):
reserve(lab,mode,'capture');q=run/(tag+'-'+name+'.png');before=time.monotonic_ns()
subprocess.run(['grim','-s','1','-o',name,str(q)],env=env,timeout=5,check=True,stdout=subprocess.DEVNULL)
if Image.open(q).size!=(800,600):raise RuntimeError('capture size gate')
record={'tag':tag,'output':name,'path':q.name,'before_ns':before,'after_ns':time.monotonic_ns()};captures.append(record);event('capture',**record)
def image(tag,out='HEADLESS-1'):return np.array(Image.open(run/(tag+'-'+out+'.png')).convert('RGB'))
def native_gate():
text=log('compositor.log')
return 'OPT_BLUR_USE' in text and 'OPT_REGEN_RESULT' in text and any('OPT_REGEN_RESULT' in x and 'output=HEADLESS-1' in x and 'result=1' in x for x in text.splitlines()) and any('OPT_BLUR_USE' in x and 'output=HEADLESS-1' in x and 'optimized=1' in x and 'chosen=(nil)' not in x for x in text.splitlines())
def mutate(pattern):
reserve(lab,mode,'mutation');before=log('background.log').count('CLIENT_COMMIT ');event('mutation_reserved',pattern=pattern)
background.stdin.write(pattern+'\n');background.stdin.flush()
wait(lambda:log('background.log').count('CLIENT_COMMIT ')>before)
count=log('background.log').count('CLIENT_COMMIT ')
if count!=before+1:raise RuntimeError('unexpected background commits')
event('mutation_received_client',pattern=pattern,count=count);time.sleep(.35)
def alarm(s,f):raise TimeoutError('150 second watchdog')
signal.signal(signal.SIGALRM,alarm);signal.setitimer(signal.ITIMER_REAL,150)
try:
f=(run/'compositor.log').open('w');files.append(f)
comp=subprocess.Popen([str(binary),'-d','-c',str(run/'config')],env=env,stdout=f,stderr=subprocess.STDOUT,start_new_session=True)
def sockets():
s=next(iter(runtime.glob('sway-ipc.*.sock')),None);w=next((q for q in runtime.glob('wayland-*') if q.is_socket()),None)
return (s,w) if s and w else None
sock,way=wait(sockets);env.update(SWAYSOCK=str(sock),WAYLAND_DISPLAY=way.name)
(run/'library-maps.txt').write_text(Path(f'/proc/{comp.pid}/maps').read_text())
event('renderer_start',pid=comp.pid,binary=str(binary),library=env['LD_LIBRARY_PATH'])
background=spawn([str(lab/'clients'),'background'],'background.log',True)
wait(lambda:'BACKGROUND_CONFIGURED ' in log('background.log'))
mutate('A')
command('workspace 1');command('layout mosaic' if mode=='C2' else 'layout splith')
filler=spawn([str(lab/'clients'),'filler'],'filler.log',True);wait(lambda:find('org.mosaic.BlurFiller'))
glass=spawn([str(lab/'clients'),'glass'],'glass.log',True);wait(lambda:find('org.mosaic.BlurGlass'))
command('workspace 2');reference=spawn([str(lab/'clients'),'reference'],'reference.log',True);wait(lambda:find('org.mosaic.StaticReference'))
command('[app_id="org.mosaic.BlurGlass"] focus');time.sleep(.5)
if mode=='C2':
tree=ipc('-t','get_tree');ws=next(n for n in nodes(tree) if n.get('type')=='workspace' and n.get('name')=='1');owner=next(n for n in nodes(ws) if n.get('app_id')=='org.mosaic.BlurGlass')
current=ws['mosaic']['rows'][0]['scroll_x'];left=owner['rect']['x']+owner['window_rect']['x']-current
delta=int(left+owner['window_rect']['width']//2)
reserve(lab,mode,'calibration');event('camera_calibration',current=current,logical=owner['rect'],window=owner['window_rect'],delta=delta)
command('layout mosaic scroll '+str(delta));time.sleep(.35)
if log('background.log').count('CLIENT_COMMIT ')!=1:raise RuntimeError('unexpected initial background image count')
snapshot('P0-FX0');time.sleep(.75);snapshot('P0-reference',True)
if not np.array_equal(image('P0-FX0','HEADLESS-2'),image('P0-reference','HEADLESS-2')):raise RuntimeError('H2 preflight instability')
# Source A must be visible outside glass before interpretation of any cache.
source=image('P0-FX0')[100:500,20:160]
colors={tuple(x) for x in source.reshape(-1,3)}
if not {(245,245,245),(15,25,45)}<=colors:raise RuntimeError('unblurred source gate')
command('[app_id="org.mosaic.BlurGlass"] blur enable');time.sleep(.35);snapshot('P0-FX2')
roi0=image('P0-FX0')[100:500,520:720].astype(float);roi2=image('P0-FX2')[100:500,520:720].astype(float)
adjacent0=np.abs(np.diff(roi0,axis=1)).mean();adjacent2=np.abs(np.diff(roi2,axis=1)).mean()
if adjacent0<=5 or adjacent2>=adjacent0*.8 or not np.any(roi0!=roi2):raise RuntimeError('visible filtering gate')
if private and not native_gate():raise RuntimeError('optimized native use gate')
event('P0_gate',source=True,filtering=True,native_optimized=bool(private),adjacent_fx0=adjacent0,adjacent_fx2=adjacent2)
mutate('B');snapshot('P1-B-natural')
if private:
reserve(lab,mode,'cycle');event('explicit_cycle_start')
command('[app_id="org.mosaic.BlurGlass"] blur disable');time.sleep(.15)
command('[app_id="org.mosaic.BlurGlass"] blur enable');time.sleep(.35);snapshot('P2-B-explicit')
mutate('A');snapshot('P3-A-natural')
event('complete',camera_pair='NOT EXERCISED: main mutation evidence is distinct and sufficient')
except Exception as exc:
failure=repr(exc);event('failure',error=failure)
finally:
signal.setitimer(signal.ITIMER_REAL,0)
# WHY: request normal compositor teardown first so cache destructors can log.
# CONTRACT: explicit private socket, then only recorded owned process groups.
if comp and comp.poll() is None and sock:
try:command('exit');comp.wait(timeout=5)
except (subprocess.SubprocessError,RuntimeError,OSError):pass
for c in children+([comp] if comp else []):
if c.poll() is None:
try:os.killpg(c.pid,signal.SIGTERM)
except ProcessLookupError:pass
for c in children+([comp] if comp else []):
try:c.wait(timeout=3)
except subprocess.TimeoutExpired:
os.killpg(c.pid,signal.SIGKILL);c.wait(timeout=3)
for f in files:f.close()
shutil.rmtree(runtime)
(run/'exit-status.json').write_text(json.dumps({'failure':failure,'duration':time.monotonic()-started,'compositor':comp.returncode if comp else None,'children':[{'args':c.args,'pid':c.pid,'code':c.returncode} for c in children],'runtime_removed':not runtime.exists(),'captures':captures},indent=2)+'\n')
shutil.copy2(DEFAULT,lab/'results/campaign-budget.json')
event('cleanup',runtime_removed=not runtime.exists())
print(mode,'FAILURE='+str(failure),'RUN='+str(run),flush=True)
if failure:raise SystemExit(1)

View file

@ -0,0 +1,245 @@
--- a/include/render/opt_probe.h
+++ b/include/render/opt_probe.h
@@ -0,0 +1,16 @@
+#ifndef OPT_PROBE_H
+#define OPT_PROBE_H
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+/* WHY: correlate passive observations with the runner's CLOCK_MONOTONIC.
+ * CONTRACT: no driver query/readback or retained object; opt-in logs only.
+ * INVARIANT: native rendering and lifetime decisions are unchanged. */
+static inline long long opt_probe_ns(void) {
+ struct timespec t;
+ clock_gettime(CLOCK_MONOTONIC, &t);
+ return (long long)t.tv_sec * 1000000000LL + t.tv_nsec;
+}
+#define OPT_LOG(event, format, ...) do { if (getenv("OPT_TRACE")) \
+ fprintf(stderr, event " ns=%lld " format "\n", opt_probe_ns(), __VA_ARGS__); } while (0)
+#endif
--- a/types/scene/wlr_scene.c
+++ b/types/scene/wlr_scene.c
@@ -1,3 +1,4 @@
+#include "render/opt_probe.h"
#include <assert.h>
#include <inttypes.h>
#include <time.h>
@@ -1432,6 +1433,7 @@
scene_blur->dirty = false;
scene_node_update(&scene_blur->node, NULL);
+ OPT_LOG("OPT_NODE_CREATE", "node=%p parent=%p size=%dx%d dirty=0", (void *)scene_blur, (void *)parent, width, height);
return scene_blur;
}
@@ -1455,6 +1457,7 @@
return;
}
+ OPT_LOG("OPT_DIRTY_SET", "node=%p enabled=%d before=%d size=%dx%d", (void *)blur_node, blur_node->node.enabled, blur_node->dirty, blur_node->width, blur_node->height);
blur_node->dirty = true;
scene_node_update(&blur_node->node, NULL);
@@ -2108,7 +2111,15 @@
return;
}
- int x = entry->x - data->logical.x;
+ if (node->type == WLR_SCENE_NODE_BUFFER) {
+ struct wlr_scene_buffer *sb = wlr_scene_buffer_from_node(node);
+ struct wlr_scene_surface *ss = wlr_scene_surface_try_from_buffer(sb);
+ OPT_LOG("OPT_BUFFER_DRAW", "output=%s node=%p buffer=%p surface=%p global=%d,%d dest=%dx%d source=%dx%d draw=%d,%d,%d,%d",
+ data->output->output->name, (void *)node, (void *)sb->buffer, ss ? (void *)ss->surface : NULL,
+ entry->x, entry->y, sb->dst_width, sb->dst_height, sb->buffer ? sb->buffer->width : 0, sb->buffer ? sb->buffer->height : 0,
+ render_region.extents.x1, render_region.extents.y1, render_region.extents.x2, render_region.extents.y2);
+ }
+ int x = entry->x - data->logical.x;
int y = entry->y - data->logical.y;
struct wlr_box dst_box = {
@@ -2300,7 +2311,12 @@
break;
case WLR_SCENE_NODE_OPTIMIZED_BLUR:;
struct wlr_scene_optimized_blur *scene_blur = wlr_scene_optimized_blur_from_node(node);
- // Re-render the optimized blur buffer when needed. Retry rendering
+ OPT_LOG("OPT_NODE_RENDER", "output=%s node=%p enabled=%d dirty=%d has_blur=%d size=%dx%d visible=%d,%d,%d,%d draw=%d,%d,%d,%d radius=%.3f passes=%d",
+ data->output->output->name, (void *)node, node->enabled, scene_blur->dirty, fx_pass->has_blur, scene_blur->width, scene_blur->height,
+ node->visible.extents.x1, node->visible.extents.y1, node->visible.extents.x2, node->visible.extents.y2,
+ render_region.extents.x1, render_region.extents.y1, render_region.extents.x2, render_region.extents.y2,
+ scene->blur_data.radius, scene->blur_data.num_passes);
+ // Re-render the optimized blur buffer when needed. Retry rendering
// until there's a visible blur_node.
if (fx_pass->has_blur && is_scene_blur_enabled(&scene->blur_data)
&& scene_blur->dirty) {
@@ -2323,9 +2339,12 @@
.blur_data = &scene->blur_data,
.blur_strength = 1.0f,
};
- bool result = fx_render_pass_add_optimized_blur(fx_pass, &blur_options);
+ OPT_LOG("OPT_REGEN_ATTEMPT", "output=%s node=%p dirty=%d", data->output->output->name, (void *)node, scene_blur->dirty);
+ bool result = fx_render_pass_add_optimized_blur(fx_pass, &blur_options);
+ OPT_LOG("OPT_REGEN_RESULT", "output=%s node=%p result=%d", data->output->output->name, (void *)node, result);
if (result) {
scene_blur->dirty = false;
+ OPT_LOG("OPT_DIRTY_CLEAR", "output=%s node=%p dirty=%d", data->output->output->name, (void *)node, scene_blur->dirty);
}
}
break;
@@ -2369,7 +2388,11 @@
.ignore_transparent = mask != NULL,
.blur_strength = blur->strength,
};
- fx_render_pass_add_blur(fx_pass, &blur_options);
+ OPT_LOG("OPT_REGULAR_NODE", "output=%s node=%p parent=%p optimized=%d strength=%.3f enabled=%d global=%d,%d size=%dx%d draw=%d,%d,%d,%d mask=%p",
+ data->output->output->name, (void *)node, (void *)node->parent, blur_options.use_optimized_blur, blur->strength, node->enabled,
+ entry->x, entry->y, blur->width, blur->height,
+ render_region.extents.x1, render_region.extents.y1, render_region.extents.x2, render_region.extents.y2, (void *)mask);
+ fx_render_pass_add_blur(fx_pass, &blur_options);
break;
}
@@ -3623,8 +3646,10 @@
return false;
}
- wlr_output_state_set_buffer(state, buffer);
- wlr_buffer_unlock(buffer);
+ wlr_output_state_set_buffer(state, buffer);
+ OPT_LOG("OPT_OUTPUT_BUFFER", "output=%s buffer=%p submitted=1 damage=%d,%d,%d,%d committed=%u",
+ output->name, (void *)buffer, state->damage.extents.x1, state->damage.extents.y1, state->damage.extents.x2, state->damage.extents.y2, state->committed);
+ wlr_buffer_unlock(buffer);
if (scene_output->in_timeline != NULL) {
wlr_output_state_set_wait_timeline(state, scene_output->in_timeline,
--- a/render/fx_renderer/fx_pass.c
+++ b/render/fx_renderer/fx_pass.c
@@ -1,3 +1,4 @@
+#include "render/opt_probe.h"
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
@@ -67,6 +68,7 @@
return false;
}
+ struct fx_framebuffer *opt_before = pass->fx_offscreen_buffers->optimized_blur_buffer;
// Update the buffers if needed
struct fx_renderer *renderer = pass->buffer->renderer;
const int width = pass->buffer->buffer->width;
@@ -83,10 +85,17 @@
fx_framebuffer_get_or_create_custom(renderer, output->allocator, width, height, false,
&pass->fx_offscreen_buffers->optimized_no_blur_buffer, &failed);
- // Bind back to the default buffer
- fx_framebuffer_bind(pass->buffer);
-
- if (failed) {
+ OPT_LOG("OPT_CACHE_STATE", "output=%s cache=%p blur=%p plain=%p blur_fbo=%u plain_fbo=%u size=%dx%d failed=%d reuse=%d",
+ output->name, (void *)pass->fx_offscreen_buffers,
+ (void *)pass->fx_offscreen_buffers->optimized_blur_buffer,
+ (void *)pass->fx_offscreen_buffers->optimized_no_blur_buffer,
+ pass->fx_offscreen_buffers->optimized_blur_buffer ? pass->fx_offscreen_buffers->optimized_blur_buffer->fbo : 0,
+ pass->fx_offscreen_buffers->optimized_no_blur_buffer ? pass->fx_offscreen_buffers->optimized_no_blur_buffer->fbo : 0,
+ width, height, failed, opt_before != NULL && opt_before == pass->fx_offscreen_buffers->optimized_blur_buffer);
+ // Bind back to the default buffer
+ fx_framebuffer_bind(pass->buffer);
+
+ if (failed) {
fx_offscreen_buffers_destroy(pass->fx_offscreen_buffers);
pass->fx_offscreen_buffers = NULL;
wlr_log(WLR_ERROR, "Failed to create effect framebuffers");
@@ -1128,8 +1137,12 @@
}
buffer = get_main_buffer_blur(pass, &blur_options);
}
- if (!buffer) {
- goto finish;
+ OPT_LOG("OPT_BLUR_USE", "output=%s cache=%p chosen=%p optimized=%d strength=%.3f fbo=%u",
+ ((const struct wlr_output *)pass->fx_offscreen_buffers->addon.owner)->name,
+ (void *)pass->fx_offscreen_buffers, (void *)buffer, fx_options->use_optimized_blur, fx_options->blur_strength,
+ buffer ? buffer->fbo : 0);
+ if (!buffer) {
+ goto finish;
}
struct wlr_texture *wlr_texture =
fx_texture_from_buffer(&renderer->wlr_renderer, buffer->buffer);
@@ -1223,7 +1236,11 @@
struct fx_render_blur_pass_options blur_options = *fx_options;
blur_options.current_buffer = pass->buffer;
blur_options.tex_options.base.clip = &clip;
- struct fx_framebuffer *fx_buffer = get_main_buffer_blur(pass, &blur_options);
+ OPT_LOG("OPT_GPU_REBUILD", "output=%s cache=%p source=%p source_fbo=%u region=%d,%d,%d,%d",
+ ((const struct wlr_output *)pass->fx_offscreen_buffers->addon.owner)->name,
+ (void *)pass->fx_offscreen_buffers, (void *)pass->buffer, pass->buffer->fbo,
+ clip.extents.x1, clip.extents.y1, clip.extents.x2, clip.extents.y2);
+ struct fx_framebuffer *fx_buffer = get_main_buffer_blur(pass, &blur_options);
if (fx_buffer != NULL) {
// Render the newly blurred content into the blur_buffer
fx_render_pass_read_to_buffer(pass, &clip,
@@ -1238,6 +1255,7 @@
pop_fx_debug(renderer);
TRACY_BOTH_ZONES_END;
+ OPT_LOG("OPT_GPU_RESULT", "output=%s cache=%p result=%d blurred=%p", ((const struct wlr_output *)pass->fx_offscreen_buffers->addon.owner)->name, (void *)pass->fx_offscreen_buffers, fx_buffer != NULL, (void *)fx_buffer);
return fx_buffer != NULL;
}
@@ -1259,7 +1277,11 @@
goto done;
}
- // Draw onto the dst_buffer
+ OPT_LOG("OPT_GPU_COPY", "output=%s cache=%p dst=%p src=%p dst_fbo=%u src_fbo=%u region=%d,%d,%d,%d",
+ pass->fx_offscreen_buffers ? ((const struct wlr_output *)pass->fx_offscreen_buffers->addon.owner)->name : "none",
+ (void *)pass->fx_offscreen_buffers, (void *)dst_buffer, (void *)src_buffer, dst_buffer->fbo, src_buffer->fbo,
+ region.extents.x1, region.extents.y1, region.extents.x2, region.extents.y2);
+ // Draw onto the dst_buffer
fx_framebuffer_bind(dst_buffer);
wlr_render_pass_add_texture(&pass->base, &(struct wlr_render_texture_options) {
.texture = src_tex,
@@ -1279,6 +1301,7 @@
.height = src_buffer->buffer->height,
},
});
+ OPT_LOG("OPT_GPU_COPY_DRAWN", "dst=%p src=%p", (void *)dst_buffer, (void *)src_buffer);
wlr_texture_destroy(src_tex);
// Bind back to the main WLR buffer
--- a/render/fx_renderer/fx_offscreen_buffers.c
+++ b/render/fx_renderer/fx_offscreen_buffers.c
@@ -1,3 +1,4 @@
+#include "render/opt_probe.h"
#include <stdlib.h>
#include <wlr/types/wlr_output.h>
#include <wlr/util/addon.h>
@@ -10,6 +11,7 @@
static void addon_handle_destroy(struct wlr_addon *addon) {
struct fx_offscreen_buffers *fbos = wl_container_of(addon, fbos, addon);
+ OPT_LOG("OPT_CACHE_DESTROY", "output=%s cache=%p blur=%p plain=%p", ((const struct wlr_output *)fbos->addon.owner)->name, (void *)fbos, (void *)fbos->optimized_blur_buffer, (void *)fbos->optimized_no_blur_buffer);
// Make sure to free the buffers
if (fbos->optimized_blur_buffer != NULL) {
wlr_buffer_drop(fbos->optimized_blur_buffer->buffer);
@@ -87,6 +89,7 @@
free(fbos);
return NULL;
}
+ OPT_LOG("OPT_CACHE_ALLOC", "output=%s cache=%p", output->name, (void *)fbos);
wl_list_insert(&renderer->offscreen_buffers, &fbos->link);
return fbos;
}
--- a/render/fx_renderer/fx_framebuffer.c
+++ b/render/fx_renderer/fx_framebuffer.c
@@ -1,3 +1,4 @@
+#include "render/opt_probe.h"
#include <stdlib.h>
#include <wlr/interfaces/wlr_buffer.h>
#include <wlr/render/allocator.h>
@@ -170,6 +171,7 @@
return;
}
+ OPT_LOG("OPT_FBO_DESTROY", "fbo_object=%p native_buffer=%p fbo=%u", (void *)fx_buffer, (void *)fx_buffer->buffer, fx_buffer->fbo);
// Release the framebuffer
wl_list_remove(&fx_buffer->link);
wlr_addon_finish(&fx_buffer->addon);

View file

@ -0,0 +1,37 @@
--- a/sway/desktop/layer_shell.c
+++ b/sway/desktop/layer_shell.c
@@ -1,3 +1,6 @@
+#include <stdio.h>
+#include <time.h>
+#include "sway/tree/root.h"
#include <scenefx/types/wlr_scene.h>
#include <stdbool.h>
#include <stdlib.h>
@@ -340,7 +343,26 @@
struct wlr_layer_surface_v1 *layer_surface = surface->layer_surface;
- // Rerender the optimized blur on change
+ /* WHY: verify actual source ownership before interpreting cache invalidation.
+ * CONTRACT: passive private trace; reads borrowed scene/surface pointers.
+ * INVARIANT: no extra dirty call or change to commit/arrangement order. */
+ if (getenv("OPT_TRACE")) {
+ struct timespec t; clock_gettime(CLOCK_MONOTONIC, &t);
+ struct wlr_surface *s = layer_surface->surface;
+ int x = 0, y = 0; bool valid = wlr_scene_node_coords(&surface->scene->tree->node, &x, &y);
+ fprintf(stderr, "OPT_BACKGROUND_COMMIT ns=%lld output=%s surface=%p resource=%u buffer=%p seq=%u layer=%u mapped=%d coords=%d,%d valid=%d tree=%p parent=%p layer_parent=%p background=%p background_parent=%p global_background=%p bottom=%p blur_tree=%p opt_node=%p opt_parent=%p damage=%d,%d,%d,%d namespace=%s\n",
+ (long long)t.tv_sec * 1000000000LL + t.tv_nsec, surface->output ? surface->output->wlr_output->name : "none",
+ (void *)s, wl_resource_get_id(s->resource), (void *)s->buffer, s->current.seq, layer_surface->current.layer, s->mapped, x, y, valid,
+ (void *)surface->scene->tree, (void *)surface->scene->tree->node.parent, (void *)surface->tree->node.parent,
+ surface->output ? (void *)surface->output->layers.shell_background : NULL,
+ surface->output ? (void *)surface->output->layers.shell_background->node.parent : NULL, (void *)root->layers.shell_background,
+ surface->output ? (void *)surface->output->layers.shell_bottom : NULL,
+ (void *)root->layers.blur_tree, surface->output ? (void *)surface->output->layers.blur_layer : NULL,
+ surface->output ? (void *)surface->output->layers.blur_layer->node.parent : NULL,
+ s->buffer_damage.extents.x1, s->buffer_damage.extents.y1, s->buffer_damage.extents.x2, s->buffer_damage.extents.y2,
+ layer_surface->namespace);
+ }
+ // Rerender the optimized blur on change
if (layer_surface->current.layer ==
ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND ||
layer_surface->current.layer == ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM) {

View file

@ -0,0 +1,36 @@
"""Offline exhaustion, corruption, cross-LAB refusal and lock contention."""
import json,multiprocessing,tempfile
from pathlib import Path
from budget import reserve
def denied(call):
try:call()
except (ValueError,RuntimeError):return
raise AssertionError('accepted invalid reservation')
with tempfile.TemporaryDirectory() as tmp:
p=Path(tmp)/'budget.json'
denied(lambda:reserve(tmp,'C2','capture',p))
for mode in ('C0','C1','C2'):
reserve(tmp,mode,'run',p)
denied(lambda:reserve(tmp,mode,'run',p))
for _ in range(4):reserve(tmp,mode,'mutation',p)
denied(lambda:reserve(tmp,mode,'mutation',p))
for _ in range(20):reserve(tmp,mode,'capture',p)
denied(lambda:reserve(tmp,mode,'capture',p))
if mode!='C0':reserve(tmp,mode,'cycle',p)
denied(lambda:reserve(tmp,mode,'cycle',p))
reserve(tmp,'C2','camera_pair',p);denied(lambda:reserve(tmp,'C2','camera_pair',p))
reserve(tmp,'C2','calibration',p);denied(lambda:reserve(tmp,'C2','calibration',p))
denied(lambda:reserve(tmp+'new','C0','run',p))
s=json.loads(p.read_text());assert sum(v['capture'] for v in s['modes'].values())==60
p.write_text('');denied(lambda:reserve(tmp,'C0','run',p))
print('PASS exhaustion, no refunds/reset, cross-LAB protection, corruption')
def contender(root,q):
try:reserve(root,'C0','run',Path(root)/'lock-test.json');q.put('accepted')
except RuntimeError:q.put('denied')
with tempfile.TemporaryDirectory() as tmp:
ctx=multiprocessing.get_context('fork');q=ctx.Queue();ps=[ctx.Process(target=contender,args=(tmp,q)) for _ in range(2)]
for p in ps:p.start()
for p in ps:p.join(5);assert p.exitcode==0
assert sorted(q.get(timeout=1) for p in ps)==['accepted','denied']
print('PASS concurrent reservation')

View file

@ -0,0 +1,390 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="wlr_layer_shell_unstable_v1">
<copyright>
Copyright © 2017 Drew DeVault
Permission to use, copy, modify, distribute, and sell this
software and its documentation for any purpose is hereby granted
without fee, provided that the above copyright notice appear in
all copies and that both that copyright notice and this permission
notice appear in supporting documentation, and that the name of
the copyright holders not be used in advertising or publicity
pertaining to distribution of the software without specific,
written prior permission. The copyright holders make no
representations about the suitability of this software for any
purpose. It is provided "as is" without express or implied
warranty.
THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.
</copyright>
<interface name="zwlr_layer_shell_v1" version="4">
<description summary="create surfaces that are layers of the desktop">
Clients can use this interface to assign the surface_layer role to
wl_surfaces. Such surfaces are assigned to a "layer" of the output and
rendered with a defined z-depth respective to each other. They may also be
anchored to the edges and corners of a screen and specify input handling
semantics. This interface should be suitable for the implementation of
many desktop shell components, and a broad number of other applications
that interact with the desktop.
</description>
<request name="get_layer_surface">
<description summary="create a layer_surface from a surface">
Create a layer surface for an existing surface. This assigns the role of
layer_surface, or raises a protocol error if another role is already
assigned.
Creating a layer surface from a wl_surface which has a buffer attached
or committed is a client error, and any attempts by a client to attach
or manipulate a buffer prior to the first layer_surface.configure call
must also be treated as errors.
After creating a layer_surface object and setting it up, the client
must perform an initial commit without any buffer attached.
The compositor will reply with a layer_surface.configure event.
The client must acknowledge it and is then allowed to attach a buffer
to map the surface.
You may pass NULL for output to allow the compositor to decide which
output to use. Generally this will be the one that the user most
recently interacted with.
Clients can specify a namespace that defines the purpose of the layer
surface.
</description>
<arg name="id" type="new_id" interface="zwlr_layer_surface_v1"/>
<arg name="surface" type="object" interface="wl_surface"/>
<arg name="output" type="object" interface="wl_output" allow-null="true"/>
<arg name="layer" type="uint" enum="layer" summary="layer to add this surface to"/>
<arg name="namespace" type="string" summary="namespace for the layer surface"/>
</request>
<enum name="error">
<entry name="role" value="0" summary="wl_surface has another role"/>
<entry name="invalid_layer" value="1" summary="layer value is invalid"/>
<entry name="already_constructed" value="2" summary="wl_surface has a buffer attached or committed"/>
</enum>
<enum name="layer">
<description summary="available layers for surfaces">
These values indicate which layers a surface can be rendered in. They
are ordered by z depth, bottom-most first. Traditional shell surfaces
will typically be rendered between the bottom and top layers.
Fullscreen shell surfaces are typically rendered at the top layer.
Multiple surfaces can share a single layer, and ordering within a
single layer is undefined.
</description>
<entry name="background" value="0"/>
<entry name="bottom" value="1"/>
<entry name="top" value="2"/>
<entry name="overlay" value="3"/>
</enum>
<!-- Version 3 additions -->
<request name="destroy" type="destructor" since="3">
<description summary="destroy the layer_shell object">
This request indicates that the client will not use the layer_shell
object any more. Objects that have been created through this instance
are not affected.
</description>
</request>
</interface>
<interface name="zwlr_layer_surface_v1" version="4">
<description summary="layer metadata interface">
An interface that may be implemented by a wl_surface, for surfaces that
are designed to be rendered as a layer of a stacked desktop-like
environment.
Layer surface state (layer, size, anchor, exclusive zone,
margin, interactivity) is double-buffered, and will be applied at the
time wl_surface.commit of the corresponding wl_surface is called.
Attaching a null buffer to a layer surface unmaps it.
Unmapping a layer_surface means that the surface cannot be shown by the
compositor until it is explicitly mapped again. The layer_surface
returns to the state it had right after layer_shell.get_layer_surface.
The client can re-map the surface by performing a commit without any
buffer attached, waiting for a configure event and handling it as usual.
</description>
<request name="set_size">
<description summary="sets the size of the surface">
Sets the size of the surface in surface-local coordinates. The
compositor will display the surface centered with respect to its
anchors.
If you pass 0 for either value, the compositor will assign it and
inform you of the assignment in the configure event. You must set your
anchor to opposite edges in the dimensions you omit; not doing so is a
protocol error. Both values are 0 by default.
Size is double-buffered, see wl_surface.commit.
</description>
<arg name="width" type="uint"/>
<arg name="height" type="uint"/>
</request>
<request name="set_anchor">
<description summary="configures the anchor point of the surface">
Requests that the compositor anchor the surface to the specified edges
and corners. If two orthogonal edges are specified (e.g. 'top' and
'left'), then the anchor point will be the intersection of the edges
(e.g. the top left corner of the output); otherwise the anchor point
will be centered on that edge, or in the center if none is specified.
Anchor is double-buffered, see wl_surface.commit.
</description>
<arg name="anchor" type="uint" enum="anchor"/>
</request>
<request name="set_exclusive_zone">
<description summary="configures the exclusive geometry of this surface">
Requests that the compositor avoids occluding an area with other
surfaces. The compositor's use of this information is
implementation-dependent - do not assume that this region will not
actually be occluded.
A positive value is only meaningful if the surface is anchored to one
edge or an edge and both perpendicular edges. If the surface is not
anchored, anchored to only two perpendicular edges (a corner), anchored
to only two parallel edges or anchored to all edges, a positive value
will be treated the same as zero.
A positive zone is the distance from the edge in surface-local
coordinates to consider exclusive.
Surfaces that do not wish to have an exclusive zone may instead specify
how they should interact with surfaces that do. If set to zero, the
surface indicates that it would like to be moved to avoid occluding
surfaces with a positive exclusive zone. If set to -1, the surface
indicates that it would not like to be moved to accommodate for other
surfaces, and the compositor should extend it all the way to the edges
it is anchored to.
For example, a panel might set its exclusive zone to 10, so that
maximized shell surfaces are not shown on top of it. A notification
might set its exclusive zone to 0, so that it is moved to avoid
occluding the panel, but shell surfaces are shown underneath it. A
wallpaper or lock screen might set their exclusive zone to -1, so that
they stretch below or over the panel.
The default value is 0.
Exclusive zone is double-buffered, see wl_surface.commit.
</description>
<arg name="zone" type="int"/>
</request>
<request name="set_margin">
<description summary="sets a margin from the anchor point">
Requests that the surface be placed some distance away from the anchor
point on the output, in surface-local coordinates. Setting this value
for edges you are not anchored to has no effect.
The exclusive zone includes the margin.
Margin is double-buffered, see wl_surface.commit.
</description>
<arg name="top" type="int"/>
<arg name="right" type="int"/>
<arg name="bottom" type="int"/>
<arg name="left" type="int"/>
</request>
<enum name="keyboard_interactivity">
<description summary="types of keyboard interaction possible for a layer shell surface">
Types of keyboard interaction possible for layer shell surfaces. The
rationale for this is twofold: (1) some applications are not interested
in keyboard events and not allowing them to be focused can improve the
desktop experience; (2) some applications will want to take exclusive
keyboard focus.
</description>
<entry name="none" value="0">
<description summary="no keyboard focus is possible">
This value indicates that this surface is not interested in keyboard
events and the compositor should never assign it the keyboard focus.
This is the default value, set for newly created layer shell surfaces.
This is useful for e.g. desktop widgets that display information or
only have interaction with non-keyboard input devices.
</description>
</entry>
<entry name="exclusive" value="1">
<description summary="request exclusive keyboard focus">
Request exclusive keyboard focus if this surface is above the shell surface layer.
For the top and overlay layers, the seat will always give
exclusive keyboard focus to the top-most layer which has keyboard
interactivity set to exclusive. If this layer contains multiple
surfaces with keyboard interactivity set to exclusive, the compositor
determines the one receiving keyboard events in an implementation-
defined manner. In this case, no guarantee is made when this surface
will receive keyboard focus (if ever).
For the bottom and background layers, the compositor is allowed to use
normal focus semantics.
This setting is mainly intended for applications that need to ensure
they receive all keyboard events, such as a lock screen or a password
prompt.
</description>
</entry>
<entry name="on_demand" value="2" since="4">
<description summary="request regular keyboard focus semantics">
This requests the compositor to allow this surface to be focused and
unfocused by the user in an implementation-defined manner. The user
should be able to unfocus this surface even regardless of the layer
it is on.
Typically, the compositor will want to use its normal mechanism to
manage keyboard focus between layer shell surfaces with this setting
and regular toplevels on the desktop layer (e.g. click to focus).
Nevertheless, it is possible for a compositor to require a special
interaction to focus or unfocus layer shell surfaces (e.g. requiring
a click even if focus follows the mouse normally, or providing a
keybinding to switch focus between layers).
This setting is mainly intended for desktop shell components (e.g.
panels) that allow keyboard interaction. Using this option can allow
implementing a desktop shell that can be fully usable without the
mouse.
</description>
</entry>
</enum>
<request name="set_keyboard_interactivity">
<description summary="requests keyboard events">
Set how keyboard events are delivered to this surface. By default,
layer shell surfaces do not receive keyboard events; this request can
be used to change this.
This setting is inherited by child surfaces set by the get_popup
request.
Layer surfaces receive pointer, touch, and tablet events normally. If
you do not want to receive them, set the input region on your surface
to an empty region.
Keyboard interactivity is double-buffered, see wl_surface.commit.
</description>
<arg name="keyboard_interactivity" type="uint" enum="keyboard_interactivity"/>
</request>
<request name="get_popup">
<description summary="assign this layer_surface as an xdg_popup parent">
This assigns an xdg_popup's parent to this layer_surface. This popup
should have been created via xdg_surface::get_popup with the parent set
to NULL, and this request must be invoked before committing the popup's
initial state.
See the documentation of xdg_popup for more details about what an
xdg_popup is and how it is used.
</description>
<arg name="popup" type="object" interface="xdg_popup"/>
</request>
<request name="ack_configure">
<description summary="ack a configure event">
When a configure event is received, if a client commits the
surface in response to the configure event, then the client
must make an ack_configure request sometime before the commit
request, passing along the serial of the configure event.
If the client receives multiple configure events before it
can respond to one, it only has to ack the last configure event.
A client is not required to commit immediately after sending
an ack_configure request - it may even ack_configure several times
before its next surface commit.
A client may send multiple ack_configure requests before committing, but
only the last request sent before a commit indicates which configure
event the client really is responding to.
</description>
<arg name="serial" type="uint" summary="the serial from the configure event"/>
</request>
<request name="destroy" type="destructor">
<description summary="destroy the layer_surface">
This request destroys the layer surface.
</description>
</request>
<event name="configure">
<description summary="suggest a surface change">
The configure event asks the client to resize its surface.
Clients should arrange their surface for the new states, and then send
an ack_configure request with the serial sent in this configure event at
some point before committing the new surface.
The client is free to dismiss all but the last configure event it
received.
The width and height arguments specify the size of the window in
surface-local coordinates.
The size is a hint, in the sense that the client is free to ignore it if
it doesn't resize, pick a smaller size (to satisfy aspect ratio or
resize in steps of NxM pixels). If the client picks a smaller size and
is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the
surface will be centered on this axis.
If the width or height arguments are zero, it means the client should
decide its own window dimension.
</description>
<arg name="serial" type="uint"/>
<arg name="width" type="uint"/>
<arg name="height" type="uint"/>
</event>
<event name="closed">
<description summary="surface should be closed">
The closed event is sent by the compositor when the surface will no
longer be shown. The output may have been destroyed or the user may
have asked for it to be removed. Further changes to the surface will be
ignored. The client should destroy the resource after receiving this
event, and create a new surface if they so choose.
</description>
</event>
<enum name="error">
<entry name="invalid_surface_state" value="0" summary="provided surface state is invalid"/>
<entry name="invalid_size" value="1" summary="size is invalid"/>
<entry name="invalid_anchor" value="2" summary="anchor bitfield is invalid"/>
<entry name="invalid_keyboard_interactivity" value="3" summary="keyboard interactivity is invalid"/>
</enum>
<enum name="anchor" bitfield="true">
<entry name="top" value="1" summary="the top edge of the anchor rectangle"/>
<entry name="bottom" value="2" summary="the bottom edge of the anchor rectangle"/>
<entry name="left" value="4" summary="the left edge of the anchor rectangle"/>
<entry name="right" value="8" summary="the right edge of the anchor rectangle"/>
</enum>
<!-- Version 2 additions -->
<request name="set_layer" since="2">
<description summary="change the layer of the surface">
Change the layer that the surface is rendered on.
Layer is double-buffered, see wl_surface.commit.
</description>
<arg name="layer" type="uint" enum="zwlr_layer_shell_v1.layer" summary="layer to move this surface to"/>
</request>
</interface>
</protocol>

File diff suppressed because it is too large Load diff