diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b7f611..341fc89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ ### STEP20C experimental evidence +- Preserved optimized-edge gate: four completed controls reproduce the same boundary differences in pristine SceneFX, private splith and Mosaic without OPT traces. Same-binary Mosaic trace-off/on and pristine/private standard match full RGB at six states; central freshness, H2 stability and sampled clipping hold. PARTIALLY_FEASIBLE, architecture UNDECIDED: reduced-domain FBO history has source/model support but no native causal observation. Separate ABI/package audit justified; optional bounded GPU study remains unexecuted. No production change; historical graphical campaign closed. + - 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. diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index cc4c6ee..329f9cf 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -329,3 +329,16 @@ 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**. + +The preserved [optimized edge causality gate](design/STEP20C_OPTIMIZED_BLUR_EDGE_CAUSALITY_PROBE.md) +is **PARTIALLY_FEASIBLE**, architecture **UNDECIDED**. Four completed fixed +negative-origin runs qualify pristine/private splith and same-binary Mosaic +OPT-off/on controls. Pristine/private full images and Mosaic trace-off/on +full images are exact at six homologous states; common glass across layouts +is also exact. Historical right/bottom differences persist without new traces, +with max RGB delta 6, fresh central pixels and stable whole H2. Native FBO +history causality remains unproved despite source/model support. R3 damage +extents cover stabilized differences only. No clipping regression or required +invariant violation is established. A separate SceneFX ABI/package audit is +now justified, retaining this shared rendering limitation; optional selective +GPU observation requires a new scoped ticket. This evidence is preserved without a production change. STEP20C stays NEXT, production multi-output NOT YET SUPPORTED. diff --git a/docs/KNOWN_LIMITATIONS.md b/docs/KNOWN_LIMITATIONS.md index 21175a6..3fe7a82 100644 --- a/docs/KNOWN_LIMITATIONS.md +++ b/docs/KNOWN_LIMITATIONS.md @@ -270,3 +270,16 @@ 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**. + +The preserved [optimized edge causality gate](design/STEP20C_OPTIMIZED_BLUR_EDGE_CAUSALITY_PROBE.md) +is **PARTIALLY_FEASIBLE**, architecture **UNDECIDED**. Four completed fixed +negative-origin runs qualify pristine/private splith and same-binary Mosaic +OPT-off/on controls. Pristine/private full images and Mosaic trace-off/on +full images are exact at six homologous states; common glass across layouts +is also exact. Historical right/bottom differences persist without new traces, +with max RGB delta 6, fresh central pixels and stable whole H2. Native FBO +history causality remains unproved despite source/model support. R3 damage +extents cover stabilized differences only. No clipping regression or required +invariant violation is established. A separate SceneFX ABI/package audit is +now justified, retaining this shared rendering limitation; optional selective +GPU observation requires a new scoped ticket. This evidence is preserved without a production change. STEP20C stays NEXT, production multi-output NOT YET SUPPORTED. diff --git a/docs/README.md b/docs/README.md index 3185aec..6abd089 100644 --- a/docs/README.md +++ b/docs/README.md @@ -179,3 +179,19 @@ 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**. + +The preserved [optimized edge causality gate](design/STEP20C_OPTIMIZED_BLUR_EDGE_CAUSALITY_PROBE.md) +is **PARTIALLY_FEASIBLE**, architecture **UNDECIDED**. Four completed fixed +negative-origin runs qualify pristine/private splith and same-binary Mosaic +OPT-off/on controls. Pristine/private full images and Mosaic trace-off/on +full images are exact at six homologous states; common glass across layouts +is also exact. Historical right/bottom differences persist without new traces, +with max RGB delta 6, fresh central pixels and stable whole H2. Native FBO +history causality remains unproved despite source/model support. R3 damage +extents cover stabilized differences only. No clipping regression or required +invariant violation is established. A separate SceneFX ABI/package audit is +now justified, retaining this shared rendering limitation; optional selective +GPU observation requires a new scoped ticket. This evidence is preserved without a production change. STEP20C stays NEXT, production multi-output NOT YET SUPPORTED. + +The [preserved edge source fixture](../tooling/probes/step20c-optimized-blur-edge/README.md) +records pinned reconstruction, offline checks and the exhausted four-slot ledger. diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 5f03cdd..85eaf00 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -274,3 +274,16 @@ 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**. + +The preserved [optimized edge causality gate](design/STEP20C_OPTIMIZED_BLUR_EDGE_CAUSALITY_PROBE.md) +is **PARTIALLY_FEASIBLE**, architecture **UNDECIDED**. Four completed fixed +negative-origin runs qualify pristine/private splith and same-binary Mosaic +OPT-off/on controls. Pristine/private full images and Mosaic trace-off/on +full images are exact at six homologous states; common glass across layouts +is also exact. Historical right/bottom differences persist without new traces, +with max RGB delta 6, fresh central pixels and stable whole H2. Native FBO +history causality remains unproved despite source/model support. R3 damage +extents cover stabilized differences only. No clipping regression or required +invariant violation is established. A separate SceneFX ABI/package audit is +now justified, retaining this shared rendering limitation; optional selective +GPU observation requires a new scoped ticket. This evidence is preserved without a production change. STEP20C stays NEXT, production multi-output NOT YET SUPPORTED. diff --git a/docs/design/STEP20C_OPTIMIZED_BLUR_EDGE_CAUSALITY_PROBE.md b/docs/design/STEP20C_OPTIMIZED_BLUR_EDGE_CAUSALITY_PROBE.md new file mode 100644 index 0000000..666f5bc --- /dev/null +++ b/docs/design/STEP20C_OPTIMIZED_BLUR_EDGE_CAUSALITY_PROBE.md @@ -0,0 +1,500 @@ +# STEP20C optimized blur edge history and instrumentation gate + +**EXPERIMENTAL / protocol recorded before new fixture development.** +The separate cache evidence freeze passed at +`e749653ccda42abad9a66a11009be6043b5b6f62`, directly verified on Forgejo +and GitHub with a clean main. The completed experimental evidence is preserved for publication. STEP20A DONE, STEP20B EXPERIMENTAL, STEP20C NEXT, STEP20D–G +PLANNED. Production multi-output Mosaic is NOT YET SUPPORTED. +No UX, production source, cumulative patch, PKGBUILD, system dependency, +shader, rendering algorithm or ABI change is authorized. + +## Evidence and competing hypotheses + +The [V1 cache probe](STEP20C_OPTIMIZED_BLUR_CACHE_PROBE.md) already established +actual FX/GLES 3.2, BACKGROUND cache source, optimized selection, natural +A/B/A dirty/rebuild/copy, fresh central 80,000 pixels, stable whole H2, +sampled clipping/damage/teardown. Its whole-H1 B-natural/B-explicit differences +were 36,776 private splith and 33,915 Mosaic; A-initial/A-return differences +38,308 and 35,340, maximum RGB delta 6. Common boundary ROI counts were +19,007 and 20,486. These are not excluded or dismissed as rounding. +C0 failed before captures through duplicate configure-driven initial images; +its second image was charged after observation, a reservation deviation. +The corrected client ACKs configures without drawing. Historical sources, +PNGs, logs, manifests and ledger stay intact; no V1 run is repeated. +Instrumentation neutrality and pristine graphical behavior were NOT TESTED. + +| Hypothesis | Discriminating evidence | Limit | +| --- | --- | --- | +| H1 reduced-domain FBO history | Source-domain audit, spatial distributions, offline pinned-kernel predictions, identical-source repeat | Native tail content is not read; correlation cannot establish GPU causality | +| H2 new cache traces | R2/R3 same source/library/binary/client/config, OPT_TRACE alone varies | Independent timing; equality is sampled neutrality, not universal proof | +| H3 shared SceneFX property | Completed R0 pristine versus R1 private splith and R2/R3 | Private clipping and older passive traces differ from pristine | +| H4 blur command cycle state | B unchanged during one disable/enable, client events, configuration, native R3 dirty/rebuild | Cycle rearranges blur and dirties all outputs; not identical to source-only commit | +| H5 another observed rendering factor | Geometry, alpha, source colors, renderer, timing, filtering, presentation evidence | Do not invent an unobserved explanation | + +## Fixed comparative matrix and binary differences + +| Run | Compositor / layout | New cache traces | +| --- | --- | --- | +| R0 | pristine SwayFX 0.6 / splith + pristine SceneFX 0.5 | absent, no private patches | +| R1 | private historical compositor / splith | OPT_TRACE absent | +| R2 | same private compositor / mosaic | OPT_TRACE absent | +| R3 | identical private compositor / mosaic | OPT_TRACE=1 | + +Pinned pristine SwayFX `fd71a6bdc061bd633b488ae7b83e8a6981d22f86`, Mosaic +`cd550382b311efdbfe3ed892f4e1d281d852e754`, SceneFX +`37ccd723bef49e6891156ffafce8f549f01446cc`, compatible installed wlroots 0.20. +Private builds apply unchanged inherited clip/popup/guard/timing/presentation +patches and the exact V1 cache/source trace patches. R1/R2/R3 load the same +library and binary. Their old SPIKE/TEMPORAL/PI traces are always active; +"trace-off" means the new OPT cache/source logs are disabled, not all logging. +OPT_LOG tests getenv presence, so OPT_TRACE=0 is not trace-off. Only presence +changes for R2/R3. Borrowed trace-only reads outside OPT_LOG remain in both. +No neutral instrumentation claim precedes the graphics. B does not modify +those patches or add renderer instrumentation. + +## Geometry, graphical inputs and parameters + +Two headless outputs only: H1 HEADLESS-1 at (-800,0), H2 HEADLESS-2 at (0,0), +both 800×600 physical/logical, scale=1, transform=normal. Record actual IPC +rect/mode/scale/transform and PNG dimensions. No rotation, scaling, hotplug, +resize, migration, popup, saved-buffer episode, animation or stress test. +At most one initial Mosaic camera correction calculated from actual applied +camera and glass geometry; no hide/return pair or P1–P3 camera change. + +Reuse V1 deterministic SHM patterns exactly: BACKGROUND layer on H1, opaque +8-pixel stripes A=(245,245,245)/(15,25,45), B=(32,192,224)/(176,32,64). +Same 800×600 XRGB image for each reserved commit. Glass premultiplied ARGB +0x88442010, transparent ARGB filler, static XDG SHM reference H2. No terminal +cursor or animation. XDG configure-driven size submissions are logged separately +from the three explicitly reserved background images. No background configure +may submit pixels. Require two independent whole-H2 captures equal before FX2. + +Fixed blur radius=5, passes=3, noise=0, brightness=contrast=saturation=1, +xray=off, shadows=off, corners=0, compositor opacity=1. Audit pinned command +syntax/source; save applied container/tree and startup config. Acceptance is +not effect evidence: exposed stripe colors calibrate the source; FX0/FX2 +central contrast reduction demonstrates actual filtering. R3 must additionally +show FX renderer, optimized node under blur_tree, BACKGROUND path, optimized=1, +selected FBO, successful rebuild and cache copies. Other runs provide config, +source and pixel evidence without fabricated absent OPT traces. + +## Pre-registered regions and comparisons + +All coordinates below are output-local half-open physical rectangles: + +| Region | Rectangle | +| --- | --- | +| glass center | (520,100,720,500), 80,000 pixels | +| exposed source | (20,100,160,500) | +| historical right boundary | (720,50,800,550) | +| physical right diagnostic | (750,0,800,600) | +| physical bottom diagnostic | (0,550,800,600) | +| whole H1 / whole H2 | (0,0,800,600) | + +No diagnostic region is an exclusion. Every full-equality comparison uses all +800×600 RGB pixels. Intra-run full images always remain reported. Inter-layout +comparisons use only actually homologous source/common glass rectangles and +explicitly retain distinct glass extents (historically splith x=400, Mosaic +x=480); R0/R1 and R2/R3 full-image equality requires matching applied geometry. +Measure changed pixels, max channel delta, mean absolute channel delta, +per-channel amplitude histogram, bounding box, changed-pixel count per column +and per row, every ROI, H2 full differences, capture hashes and timestamps. + +## One deterministic sequence per reserved run + +P0: connect and map background with initial bufferless commit; wait/ACK all +layer configures, assert no image submitted, reserve then command A once. +Map filler/glass/static H2, settle actual geometry/focus and initial camera. +Capture FX0 H1/H2, later independent H2. Assert source colors and stable H2. +Enable scoped glass blur, settle, capture FX2 H1/H2; verify filtering. +P1: reserve and command B exactly once, keep blur/geometry unchanged, settle, +capture H1/H2. P2: no client image; reserve one explicit cycle, scoped blur +disable then enable with ordinary commands, settle and capture H1/H2. +P3: reserve and command A once, keep all else fixed, settle, capture H1/H2; +one later H1 confirmation must match whole RGB. Total 12 PNG per run. + +Settling uses client command receipt, unchanged applied IPC geometry, at least +350 ms without intentional mutation before each measured capture; last H1 +has an independent 350 ms confirmation. Native R3 logs are checked for source +commit/rebuild before capture and competing events in capture windows. Earlier +states lack duplicate H1 PNGs under the finite budget; that limits claims of +stability, especially if event/capture windows disagree. No screenshot gains +a native output-buffer identity from proximity to a commit or present. + +Comparisons: FX0 A/FX2 A, P0 FX2/P3 A, P1 B/P2 B, R0/R1 homologous states, +R2/R3 homologous states, and common edge regions across layouts. Inspect +source A/B/A submission order, sizes, events and frame timing before attribution. +No deliberate focus, resize, camera, alpha, radius, passes or source mutation +outside this sequence. If a second unreserved A submission appears: abort, +retain evidence, consume reservation, no rerun, dependent results INCONCLUSIVE. + +## Sampling and command audit before graphics + +Inspect pinned render_blur_segments/get_main_buffer_blur, blur1.frag/blur2.frag, +texture filter/wrap, FBO allocation and written/sampled regions. For each of +six passes document full framebuffer size, freshly written domain, mapping, +radius/taps, bilinear texels beyond the valid domain, possible historical value +and expected final influence. Domain sizes for 800×600 are down 400×300, +200×150,100×75 then up 200×150,400×300,800×600, inside full-size FBOs. +Full-texture GL_CLAMP_TO_EDGE is distinct from reduced valid-domain clamp. +The offline model uses ideal bilinear sampling and 8-bit rounding, not native +mediump GLES precision, and must be reported as hypothesis only. + +Audit cmd_blur: scoped container blur flag and arrange_container, then dirty +all output nodes. Natural BACKGROUND commit dirties its own output. Therefore +P1 and P2 need not traverse identical scene/rebuild sequences. Preserve native +R3 evidence of H1/H2 counts and scene/damage. No shader modification, forced +FBO clearing or GPU readback is authorized. If needed, propose only a later +bounded selective-readback protocol after results; do not execute it here. + +## Budgets, isolation and preflight gate + +One new LAB, one persistent campaign ledger in the home directory, exclusive +flock, atomic durable replacement, exact schema validation. Four run slots +R0–R3, each once; at most 3 background images, 1 blur cycle, 12 PNG, 150 s. +Global max 12 images,4 cycles,48 PNG; one initial calibration per Mosaic run, +zero camera pairs. Reserve before every launch/action; failed reservations +remain consumed, no refund/reset/new-LAB bypass/fifth run. + +Before any compositor launch: Python AST, shell syntax if applicable, offline +ledger exhaustion/corruption/concurrency/cross-LAB tests, actual layer callback +ACK/no-draw harness, explicit-command audit, SHM widths/formats/bounds/lifetimes, +Wayland globals/listeners/output naming/protocol hashes, C17 -Werror, both +SceneFX and compositor builds, Meson mosaic-model, all pinned patch checks, +private linker identities, cleanup/timeouts. Fixtures are standalone and +corrected; C0 originals and historical LAB remain untouched. + +Each run has private mode-0700 XDG_RUNTIME_DIR, private WAYLAND_DISPLAY, +explicit SWAYSOCK and swaymsg -s, WLR_BACKENDS=headless. Strip inherited daily +socket/display/library/trace variables; use private SceneFX LD_LIBRARY_PATH. +Record binary/library hashes, /proc/PID/maps, renderer logs, IPC, configs, +client sources, runner exact source, PNGs, capture windows, logs and exits. +Terminate only owned process groups; controlled IPC exit first, preserve +return codes, reap clients, remove only owned runtime/socket directory. +25 MB per raw log and watchdog bound resource use. No live desktop IPC, +killall/pkill, install, sudo, dotfiles, QuickShell or Codex changes. + +Stop consumed failing controls; continue other predefined independent slots +only if safe/meaningful, never relaunch to get a favorable result. Critical +fixture/budget/isolation violation stops affected dependent execution. Compiler +or offline preflight issues may be corrected before the first reserved run. + +## Result evidence and classification + +Keep build/renderer-identities, private-trace-comparison, pristine-control, +source-commits, graphic-states, output-geometries, blur-settings, cache-use, +dirty-events, regeneration-events, edge-pixel-analysis, column/row-distribution, +sampling-domain-audit, edge-model-comparison, damage-analysis, capture-windows, +campaign-budget, matrix, summary and source/evidence SHA-256 manifests in +LAB/results. Generated files/builds/logs/PNG/socket/bytecode never enter Git. +The source-only fixture must rebuild from the pinned versions. + +FEASIBLE requires complete comparable pristine and trace controls, coherent +cache contract, absent or natively explained compatible edges, correct allowed +Mosaic pixels and no forbidden contribution/regression. PARTIALLY_FEASIBLE +means valid majority of controls but native cause or edge property remains +open. INCONCLUSIVE means controls/fixture cannot distinguish hypotheses. +BLOCKED needs a demonstrated required invariant violation, reproducible crash, +forbidden contribution or blocking standard regression; low RGB amplitude +alone is insufficient. Architecture: OPTIMIZED_EDGE_CACHE_FEASIBLE_FOR_TESTED_MATRIX, +OPTIMIZED_EDGE_CACHE_REQUIRES_REDESIGN or UNDECIDED. Distinguish a shared SceneFX +issue from Mosaic clipping. Answer Q1–Q17 from the retained evidence after +execution; recommend minimal GPU study or ABI/package gate explicitly. + +## Retained execution and measured result (2026-10-13) + +`SWAYFX_MOSAIC_OPTIMIZED_BLUR_EDGE_CAUSALITY_PROBE=PARTIALLY_FEASIBLE`. +Architecture: **UNDECIDED**. LAB: +`/home/fy59/swayfx-mosaic-optimized-edge-a55yjb8n/`. +All four controls completed and were comparable in their defined domains. +The old pristine failure did not recur: duplicate layer configures were ACKed +without another image. No run failed, was repeated or exceeded a budget. +The original pre-result protocol is retained as LAB/protocol-before-results.md. + +| Run | Duration | A/B images | PNG | Blur cycles | Initial camera corrections | Compositor exit | +| --- | ---: | ---: | ---: | ---: | ---: | ---: | +| R0 pristine splith | 4.995 s | 3 | 12 | 1 | 0 | 0 | +| R1 private splith, OPT absent | 4.293 s | 3 | 12 | 1 | 0 | 0 | +| R2 private Mosaic, OPT absent | 4.638 s | 3 | 12 | 1 | 1 | 0 | +| R3 same private Mosaic, OPT=1 | 4.652 s | 3 | 12 | 1 | 1 | 0 | + +Total 4/4 runs,12/12 images,48/48 PNG,4/4 cycles,2/2 initial corrections, +zero camera pairs. The home-directory ledger is permanently exhausted for +run slots. Every mutation/capture/cycle/calibration reservation preceded its +operation; failed-reservation tests used independent offline temporary ledgers. +Clients report Wayland EPIPE=32/code 1 after controlled compositor exit; these +teardown statuses are retained, not compositor crashes. Owned children were +reaped and all four runtime/socket directories removed. + +### Actual sources, geometry and rendering + +Every run uses the real FX renderer, OpenGL ES 3.2, Mesa 26.2.4-arch1.1, +AMD Radeon 780M. /proc/PID/maps establishes the private pristine SceneFX in R0 +and the same private patched SceneFX in R1/R2/R3, together with installed +wlroots 0.20.2. Installed package/library series are SceneFX 0.5.0, Wayland +client 1.26.0 and wayland-protocols 1.49. The build-identity JSON records +all archive revisions, exact patch hashes, library and executable digests. +Private compositor SHA-256: +`0c87ece70aa012207de951e9d558c81c5c4b3ea78548b943477bf12543264042`; +private SceneFX: +`78daf506d9aacdd0b1a05605f73b4103ee48cd0b5900a3574671b5ec11f07fe7`. +Pristine compositor: +`8ac865481bc0487344210cd0e16d8e6c7655b29db887cd58ddc11b0ca22fe494`; +pristine SceneFX: +`7a07c1f239043f7910039670006a2f6e9bb15608bf2a840576476c5f7b1378b3`. + +IPC/output records and all PNGs confirm fixed 800×600 scale-1 normal outputs +at (-800,0)/(0,0). Standard glass is 400×600, global x=-400; Mosaic source +scene glass is 640×600 at global x=-320, crossing x=0 by 320. Mosaic's initial +camera 480 receives the measured delta -320 and becomes 160. Tiled scene +translation/clip and regular blur final draw yield output-local +[480,800)×[0,600); no target regular blur draw is seen on H2 in R3. The +private scene-state traces in R2 agree. Neither P1–P3 focus, camera, geometry, +alpha nor blur parameters are deliberately changed. Stored IPC trees confirm +stable applied geometry/focus/camera during measured states. + +Exposed source ROI matches every stripe pixel exactly for A/B/B/A; glass +ARGB, source image dimensions and SHM lifetime remain fixed. Container opacity +1 is source-defined (container_create alpha=1), with no opacity mutation; +ordinary IPC here does not expose an opacity/blur-parameter getter. R3 actual +regular-blur strength=1, radius=5, passes=3 and optimized=1/selected FBO=11 +corroborate source/config/pixel evidence. No getter observation is fabricated. +FX0/FX2 central luminance standard deviation falls 51.165618→0.132211 and +adjacent luminance contrast 12.351341→0.015453 in every run. Actual filtering +is visible beyond alpha compositing. Source mutation changes the central +80,000 pixels and whole 480,000 H1 pixels; B's center equals explicit B exactly, +and returned A's center equals initial A exactly. Six whole-H2 captures in +each run, including two independent preflight images, are pixel-identical. +All 24 H2 PNG hashes equal the historical static-reference hash +`4471e93ccb6cf3ff4970f61fce405431a228af598d2f1f030a3b7586962ecd7a`. + +### Full-image and pre-registered edge comparisons + +| Comparison | R0 pristine | R1 private splith | R2 Mosaic OPT absent | R3 Mosaic OPT=1 | +| --- | ---: | ---: | ---: | ---: | +| B natural → B explicit, whole H1 | 36,776 | 36,776 | 33,915 | 33,915 | +| A initial → A returned, whole H1 | 38,308 | 38,308 | 35,340 | 35,340 | +| Maximum channel delta, both comparisons | 6 | 6 | 6 | 6 | +| Center changed pixels, both comparisons | 0 | 0 | 0 | 0 | +| H2 changed pixels, every comparison | 0 | 0 | 0 | 0 | +| Last A → independent H1 confirmation | 0 | 0 | 0 | 0 | + +R0/R1 have zero whole-H1 RGB differences at all six homologous capture states. +R2/R3 likewise have zero at all six states; H2 also matches across runs. +Runner bytes, config, compiled client, private binary and loaded private library +are identical in R2/R3. New OPT logs are absent in R0/R1/R2 and present in R3; +old private logs remain active in R1/R2/R3. Thus no measured graphical effect +of activating new traces exists in this matrix. Different frame timing remains +observable, and universal instrumentation neutrality is not established. +Standard/Mosaic whole-H1 differs by 48,000 pixels at every state because glass +coverage is x=[400,800) versus [480,800). The entire common glass +[480,800)×[0,600) and exposed source are exact across layouts at every state. +This comparison does not exclude any intra-run full-image difference. + +| Pre-registered edge region | B natural/explicit standard | B natural/explicit Mosaic | A initial/return standard | A initial/return Mosaic | +| --- | ---: | ---: | ---: | ---: | +| Historical boundary (720,50,800,550) | 19,007 | 19,007 | 20,486 | 20,486 | +| Physical right x=[750,800) | 23,119 | 23,119 | 22,948 | 22,948 | +| Physical bottom y=[550,600) | 15,992 | 13,139 | 15,793 | 12,825 | + +Historical boundary max delta is 4 for B and 5 for A; physical bands reach 6. +Whole-H1 mean absolute channel error is 0.083278/0.083215 standard (B/A) and +0.076682/0.076858 Mosaic. Right-band means are 0.866344/0.857756; historical +boundary means 0.515933/0.524808. These small per-channel means do not erase +the tens of thousands of different pixels. Whole-image bounding boxes are +[400,800)×[0,600) standard and [480,800)×[0,600) Mosaic. Column/row arrays +and amplitude histograms retain every changed pixel. In Mosaic only 103 B +and 1,963 A differences lie outside the union of the pre-registered right +50 columns and bottom 50 rows; no pixels are discarded. Rows near the bottom +and columns near the right reach all visible glass pixels; fine quantization +also leaves sparse differences just inside the bands. + +For Mosaic B, changed-pixel maximum amplitudes 1/2/3/4/5/6 occur +17,174/8,705/3,825/4,073/130/8 times. For Mosaic A return they occur +19,009/8,177/5,093/2,394/652/15 times. Channel-level histograms and standard +counts are in edge-pixel-analysis.json; this is not an assertion of ordinary +rounding as the cause. No perceptual or daily-session assessment was run. + +### Natural versus explicit path, native timing and damage + +R3 P1 B and P3 A each contain one mapped BACKGROUND source commit, +one H1 dirty/rebuild/result=1, two completed cache texture-copy draw calls, +one dirty clear and optimized glass use. H2 has zero reconstruction in those +windows. P2 contains no background image commit, six dirty-set observations, +two successful H1 and two successful H2 reconstructions, eight completed copy +draw calls and four dirty clears. This is consistent with scoped cmd_blur +arrangement and its unconditional all-output invalidation, not a claim that +natural and explicit updates execute the same GPU sequence. The fixed cache +objects/FBO sizes are reused; source/configuration do not change radius/passes. +Copy draw completion is not a driver fence or per-pixel intermediate readback. + +All R3 relevant reconstructions finish at least 357.899 ms before H1 capture; +no source commit/reconstruction trace occurs inside the measured capture +windows. The final H1 confirmation is identical after another 350 ms. +Earlier states do not have duplicate H1 PNGs under the finite budget, and +untraced controls lack OPT quiescence evidence. Their deterministic inputs, +stable applied state, settled windows and exact cross-run pixels support the +comparisons without claiming every intervening frame. + +For four stabilized R3 H1 transitions, union of available native output-state +damage extents leaves zero changed pixels uncovered. Old private scene damage +in R1/R2 is retained separately and is not promoted to equivalent output-buffer +damage proof. Pristine has no new native damage trace. Commit/present traces +keep their own IDs; no screenshot is assigned an exact native presented buffer. +No all-frame, minimum-damage, physical-presentation or input claim follows. + +### Sampling domain and fixed offline model + +Pinned shaders are byte-identical pristine/private. FBOs stay 800×600; the +written domains are down 400×300,200×150,100×75 and up +200×150,400×300,800×600. Down taps use 2*(p+0.5)-0.5 with ±5-pixel offsets; +up taps use (p+0.5)/2-0.5 with axial ±2.5 and diagonal ±1.25. GL_LINEAR +requires neighboring texels. First downpass sees a complete source, with +full-texture clamp; later downpasses and all upsample passes can reach past +the fresh reduced domain while remaining inside the texture. No full color +clear appears between these blur passes. Alternating full-size ping-pong FBOs +can retain earlier regions. The audited source demonstrates this possibility; +it does not identify the native value actually read there. + +The exact V1 functions are reused without executing its historical writer, +with the actual eight H1 R3 rebuild timestamps and source order. No kernel, +offset or rounding fitting is performed. Model/native historical-edge mean +absolute channel error spans 0.198700–0.518242; 96.668–97.911% of channels +are within one level, maximum residual 4 in that ROI. The broader physical +right and glass-bottom domains reach model/native residual 6. For B, model +predicts 32,129 changed visible-glass pixels versus native 33,915, mask Jaccard +0.861863. For A, 37,701 versus 35,340, Jaccard 0.805086; predicted maximum +7 versus native 6. Delta residual means in visible glass are 0.049901 and +0.056941. The residual and spatial distributions are preserved, not hidden. +This is strong history compatibility, not equivalence to native mediump GLSL +or a causal measurement of intermediate FBO contents. + +| Hypothesis | Result for tested matrix | +| --- | --- | +| H1 reduced-domain history | Supported by source possibility and fixed-model spatial/amplitude agreement; native causality UNDECIDED | +| H2 new trace activation causes these edge differences | Not needed to reproduce: pristine and private OPT-off show them; R2/R3 graphical effect zero; universal neutrality NOT PROVEN | +| H3 shared rendering property | Supported: pristine/private splith complete RGB identical; common glass across layouts identical | +| H4 blur cycle alters more than source cache content | Demonstrated arrangement/all-output dirty and additional reconstructions; exact RGB contribution of each side effect remains unresolved | +| H5 another observed rendering factor | Native mediump versus ideal-model residual and timing differences remain limits; geometry/source/alpha/parameters are controlled; no alternate native cause demonstrated | + +### Required architectural answers + +| Question | Evidence-backed answer | +| --- | --- | +| Q1 pristine edges? | Yes: R0 reproduces exact standard counts and full images of R1. | +| Q2 standard and Mosaic? | Yes: common visible glass is exact; full counts differ with coverage. | +| Q3 without new traces? | Yes: R0, R1 and R2 reproduce the edge differences. | +| Q4 measurable trace effect? | Zero RGB difference in six R2/R3 states; timing differs. Neutrality is sampled, not universal. | +| Q5 same reconstruction steps? | Same source-defined blur/copy functions, different trigger/scene/output sequence: natural 1 H1 versus explicit 2 H1+2 H2. No intermediate GPU state equivalence proved. | +| Q6 blur-command contribution? | Container blur flag/arrangement and all-output dirty, with extra reconstructions observed. Their separate pixel effects cannot be isolated here. | +| Q7 central freshness? | Yes for the predefined 80,000 pixels: changes with B, exact explicit B and exact returned A in all four runs. | +| Q8 domains localize anomalies? | Right/bottom bands contain most differences; fixed model overlaps strongly but imperfectly. Not an exact causal localization. | +| Q9 source permits stale-domain sampling? | Yes: later down and up supports cross reduced fresh bounds inside full-size textures. Actual sampled old value remains unobserved. | +| Q10 level of proof? | Source possibility plus offline prediction and native final pixels; no intermediate native causality. | +| Q11 amplitude/distribution? | Max channel 6, tens of thousands of pixels, detailed rows/columns/ROIs/histograms above and in JSON. Not dismissed as rounding. | +| Q12 Mosaic clip? | Correct for sampled final draw: H1 x=[480,800), no target draw on H2, whole H2 exact. Not a universal clipping/input proof. | +| Q13 damage? | R3 extent union covers every stabilized changed pixel in four transitions; no all-frame or screenshot/native-buffer identity claim. | +| Q14 correction justified? | Reproducible shared history-sensitive edge output warrants an upstream investigation; no Mosaic-specific correction or required invariant violation is established. No algorithm correction authorized. | +| Q15 smallest next readback? | Selected seam texels and four destination pixels per B condition at D1/D2/U2, bounded protocol below; not executed. | +| Q16 blocker or separate limitation? | Shared SceneFX edge limitation, not a demonstrated Mosaic blocker. Whole-cache/native-cause gate remains open and prevents a global FEASIBLE claim; it need not block an independent ABI audit. | +| Q17 proceed ABI/package? | Yes, an isolated read-only ABI/package feasibility audit is now justified as the next separate gate. No packaging implementation or production readiness follows. Native causal study stays a separately authorized option. | + +### Proposed later minimal GPU observation — NOT EXECUTED + +Only a new ticket may authorize this. Prefer one pristine fixed-geometry run +with a paired observer-off control, strict reservations, no renderer/shader +correction and no forced clearing. At H1 B natural and the last explicit B +rebuild, record source FBO identity/dimensions and actual pass ordinal. +After D1 and before D2, read only source rectangles (193,69,12,12) and +(93,143,12,12), crossing valid 200×150 right/bottom seams. After D2 and +before U2, read (96,34,7,7) and (46,71,7,7), crossing valid 100×75 seams. +These cover the ideal bilinear neighborhoods for two selected edge samples. +Read corresponding destination pixels D2 (99,37)/(49,74) and U2 +(199,75)/(99,149) after their draw. Four rectangles plus four destination +pixels are 390 RGBA pixels per condition: 780 pixels / 3,120 bytes total +for the two B conditions if RGBA8 is valid. No whole-FBO read or image dump. + +Observe native read format, pack state, read-FBO binding, origin and texture +transform; preserve and restore observer state. Label synchronization/timing +perturbation. Compare inside-domain versus tail texels and their change between +B rebuilds with selected destination contributions under pinned taps. This +could discriminate historical values and conversion/precision hypotheses; +readback itself still needs observer comparability and does not automatically +prove every final-pixel cause. Do not execute, create its GPU code or reserve +another run in this exhausted campaign. + +### Decision and remaining scope + +The missing pristine and trace controls are now qualified. The phenomenon is +shared across pristine/private rendering and layouts; no measured OPT activation +effect or forbidden Mosaic H2 contribution appears. Native edge causality is +still unresolved, so the scientific verdict is PARTIALLY_FEASIBLE and +architectural conclusion UNDECIDED. FEASIBLE would overstate the unexplained +edge property; BLOCKED would invent a required-invariant violation. +Recommend the separate SceneFX/wlroots ABI/package audit next, carrying this +explicit shared edge limitation. If selecting an upstream graphics correction +requires causal evidence, authorize the minimal observation above separately. +Neither follow-up is implemented here. No production source or policy changes. + +Rotation/scaling/output changes, saved-buffer/popup/input paths, animations, +other drivers/renderers, all-frame damage and native FBO readback remain +NOT TESTED in this campaign. No saved-buffer experiment was relaunched. +STEP20C stays NEXT and production multi-output NOT YET SUPPORTED. + +## Final validation and sealed inventories + +PASS: shell syntax of the existing exporter (no new B shell script), AST of +all nine Python files, C17 -Wall -Wextra -Werror (unused callback parameters +explicitly exempted), duplicate-configure callback harness, final client +binary equivalence, both pristine/private SceneFX and compositor builds, +Meson mosaic-model 1/1, historical patch applicability/source equivalence, +unchanged cumulative patch forward applicability/source equivalence against +pinned pristine, loaded renderer/library identities, exact stripe calibration, +all source reservations, stable applied focus/camera/geometry, complete RGB +and edge distributions, source sampling audit, recorded R3 damage coverage, +offline/actual budgets, links/whitespace including untracked files, protected +production/V1 paths and installed /usr/bin/sway/SceneFX/wlroots hashes. +All recorded client/compositor PIDs are gone and private sockets removed. +The standalone edge-distributions.svg is an offline figure, not a 49th capture. + +NOT RUN: native FBO readback/intervention, saved-buffer/popup tests, additional +geometry/driver/timing variants, ordinary headless IPC suite (no production +change; would introduce extra compositor launches), ABI/package qualification, +installation, daily-session IPC, B commit/push. The original fixture incident +is preserved only in V1; no new run or preflight build failed in this campaign. +No production behavior is claimed by compilation, screenshots or model alone. + +Final source manifest: **1,020 entries**, SHA-256 +`044f6ab4e0d9ced000b12daa75c7cf30f234aa32fed14dc248baa9e3e11dcdff`. +Final scientific evidence manifest: **189 entries**, SHA-256 +`7554388b89fab03b5f3ad0d15c792680fcb403ee04bce8a347c55257440c73d8`. +Every entry was verified individually; sealing refuses an existing inventory. +The historical 1006/106-entry manifests remain unchanged and reverified with +the explicit pre-freeze README snapshot mapping documented by the A report. +The final result report and Git diff are captured separately from the sealed +scientific inventory to avoid self-referential digest dependencies. + +Final main, origin/main and github/main remain +`e749653ccda42abad9a66a11009be6043b5b6f62`; both remotes were directly queried +again. The real index is empty; only the five synchronized tracked documents, +this new report and the source-only edge fixture are locally modified/new. +No B commit or push. The final complete Git diff, including new files, is +retained under the freeze audit directory as complete-phase-b.diff. +Production source, cumulative patch, PKGBUILD, UX, AGENTS and historical fixture +match A's published commit. Installed libraries/compositor are hash-identical. +No command targeted daily IPC/session, QuickShell, dotfiles or Codex settings. +STEP20A DONE, STEP20B EXPERIMENTAL, STEP20C NEXT, STEP20D–G PLANNED; +production multi-output NOT YET SUPPORTED. + +## Independent publication freeze (2026-10-13) + +The new freeze ticket reverified all 1,020 source entries and 189 evidence +entries file by file before any documentation edit; both manifest SHA-256 +values above matched. Their bytes and historical evidence are preserved. +The only subsequently edited manifested source is the fixture README; its +exact historical bytes are preserved as `README.pre-freeze.md` beside it. +Use that explicit mapping when verifying the original manifest after publication. +The preceding local/uncommitted final audit describes the historical campaign, +not the publication status of this freeze. No graphical campaign, analysis +writer, model writer, seal writer or proposed GPU readback was rerun. +The verdict remains PARTIALLY_FEASIBLE and architecture UNDECIDED. diff --git a/tooling/probes/step20c-optimized-blur-edge/README.md b/tooling/probes/step20c-optimized-blur-edge/README.md new file mode 100644 index 0000000..01686cf --- /dev/null +++ b/tooling/probes/step20c-optimized-blur-edge/README.md @@ -0,0 +1,77 @@ +# Optimized blur edge causality fixture + +**EXPERIMENTAL / PRESERVED EVIDENCE; historical campaign closed.** Read the +[pre-registered protocol](../../../docs/design/STEP20C_OPTIMIZED_BLUR_EDGE_CAUSALITY_PROBE.md). +The historical V1 cache campaign is closed; never launch or analyze into it. +This fixture is a new four-slot comparative campaign, not a renderer fix. + +From a full project clone containing the pinned project commits, with Meson, +Ninja, C17, Wayland scanner, compatible wlroots 0.20 build dependencies, +Python numpy/Pillow, grim and swaymsg, use: + +```sh +PYTHONDONTWRITEBYTECODE=1 python tooling/probes/step20c-optimized-blur-edge/test-budget.py +PYTHONDONTWRITEBYTECODE=1 python tooling/probes/step20c-optimized-blur-edge/prepare.py +``` + +Preparation builds only, creates a new LAB outside Git and retrieves pinned +SceneFX. It reads the published V1 XML/patches and older preserved patch chain +without modifying them. Compiler logs and all generated files stay in LAB. +Offline callback, source, AST, ledger, patch, build and linker preflight must +pass before any run. The client ACKs background configures without drawing; +only explicit A/B/A commands can submit its three 800×600 images. + +Execute `run.py LAB R0`, then R1, R2 and R3 exactly once each, with +PYTHONDONTWRITEBYTECODE=1. The persistent locked +`~/swayfx-mosaic-optimized-edge-v1-budget.json` binds all four slots to one LAB. +Failed launch/action reservations count; no refund, reset or alternate LAB. +Each run has 3 images, 1 scoped blur cycle, 12 PNG maximum and a 150 s watchdog. +All sockets/IPC are explicitly private; cleanup targets recorded children only. +R2/R3 share binary/client/library/config; OPT_TRACE absent versus 1 toggles new +cache traces. Old private traces remain enabled in both. No GPU readback, +forced clearing, shader, algorithm, production, package or desktop changes. + +Keep exact executed sources, configurations, binary/library identities, +client events, logs, PNGs, exit codes and manifests outside Git. Analyze all +RGB pixels without excluding edge bands. Model/source sampling possibilities +are not native causality. Results and final offline validation are recorded +in the protocol after the four attempts. Fixture preservation is authorized; no new graphical run is authorized. + +## Closed V1 campaign + +LAB `/home/fy59/swayfx-mosaic-optimized-edge-a55yjb8n` consumed all four runs, +12 images,48 captures,4 cycles and two initial Mosaic corrections; no rerun +is allowed. All controls completed. R0/R1 full RGB and R2/R3 full RGB match +in six states, while the edge anomaly persists in pristine and without OPT. +Result PARTIALLY_FEASIBLE, architecture UNDECIDED; native FBO cause unresolved. +The next justified distinct gate is ABI/package audit with this limitation. +A proposed selective GPU observation is documented but not executed. + +Offline analysis in this new LAB is `analyze.py LAB` then `model.py LAB`. +`model.py` reuses exact V1 functions through AST selection without running the +historical writer. Native rebuild schedule exists only for R3. `preflight.py` +affects offline files only and must precede all graphical runs; no re-execution +of the exhausted run slots is authorized. Source/evidence manifest creation +is separate from analysis to preserve final inventories. Generated outputs +remain outside Git. All runtime/socket directories were removed and owned +children reaped; compositor exit=0, clients retain controlled-shutdown EPIPE. + +`seal.py LAB` creates each final manifest once and refuses replacement; verify +its entries rather than regenerating after sealing. Optional `plot.py LAB` +requires matplotlib and produces a standalone SVG from complete column/row +arrays; it is an offline figure, not another captured PNG. Its cache stays +in LAB. Run all derived analysis before sealing. + +## Freeze verification (2026-10-13) + +All 1,020 source and 189 evidence entries were independently reverified before +publication. Their original manifest bytes remain unchanged. The historical +manifest entry for this README (SHA-256 +`e9b4ea06512f1cbcef6107eecaa356d02767f01bb66b0a6cb3b797d8094b7d4c`) +must now be checked against [README.pre-freeze.md](README.pre-freeze.md), +the exact pre-publication snapshot; every other entry uses its recorded path. +Source manifest SHA-256: +`044f6ab4e0d9ced000b12daa75c7cf30f234aa32fed14dc248baa9e3e11dcdff`. +Evidence manifest SHA-256: +`7554388b89fab03b5f3ad0d15c792680fcb403ee04bce8a347c55257440c73d8`. +Verification must never reseal, rewrite results or rerun the exhausted campaign. diff --git a/tooling/probes/step20c-optimized-blur-edge/README.pre-freeze.md b/tooling/probes/step20c-optimized-blur-edge/README.pre-freeze.md new file mode 100644 index 0000000..517cad0 --- /dev/null +++ b/tooling/probes/step20c-optimized-blur-edge/README.pre-freeze.md @@ -0,0 +1,63 @@ +# Optimized blur edge causality fixture + +**EXPERIMENTAL / LOCAL / UNCOMMITTED.** Read the +[pre-registered protocol](../../../docs/design/STEP20C_OPTIMIZED_BLUR_EDGE_CAUSALITY_PROBE.md). +The historical V1 cache campaign is closed; never launch or analyze into it. +This fixture is a new four-slot comparative campaign, not a renderer fix. + +From a full project clone containing the pinned project commits, with Meson, +Ninja, C17, Wayland scanner, compatible wlroots 0.20 build dependencies, +Python numpy/Pillow, grim and swaymsg, use: + +```sh +PYTHONDONTWRITEBYTECODE=1 python tooling/probes/step20c-optimized-blur-edge/test-budget.py +PYTHONDONTWRITEBYTECODE=1 python tooling/probes/step20c-optimized-blur-edge/prepare.py +``` + +Preparation builds only, creates a new LAB outside Git and retrieves pinned +SceneFX. It reads the published V1 XML/patches and older preserved patch chain +without modifying them. Compiler logs and all generated files stay in LAB. +Offline callback, source, AST, ledger, patch, build and linker preflight must +pass before any run. The client ACKs background configures without drawing; +only explicit A/B/A commands can submit its three 800×600 images. + +Execute `run.py LAB R0`, then R1, R2 and R3 exactly once each, with +PYTHONDONTWRITEBYTECODE=1. The persistent locked +`~/swayfx-mosaic-optimized-edge-v1-budget.json` binds all four slots to one LAB. +Failed launch/action reservations count; no refund, reset or alternate LAB. +Each run has 3 images, 1 scoped blur cycle, 12 PNG maximum and a 150 s watchdog. +All sockets/IPC are explicitly private; cleanup targets recorded children only. +R2/R3 share binary/client/library/config; OPT_TRACE absent versus 1 toggles new +cache traces. Old private traces remain enabled in both. No GPU readback, +forced clearing, shader, algorithm, production, package or desktop changes. + +Keep exact executed sources, configurations, binary/library identities, +client events, logs, PNGs, exit codes and manifests outside Git. Analyze all +RGB pixels without excluding edge bands. Model/source sampling possibilities +are not native causality. Results and final offline validation are recorded +in the protocol after the four attempts. No commit or push for this fixture. + +## Closed V1 campaign + +LAB `/home/fy59/swayfx-mosaic-optimized-edge-a55yjb8n` consumed all four runs, +12 images,48 captures,4 cycles and two initial Mosaic corrections; no rerun +is allowed. All controls completed. R0/R1 full RGB and R2/R3 full RGB match +in six states, while the edge anomaly persists in pristine and without OPT. +Result PARTIALLY_FEASIBLE, architecture UNDECIDED; native FBO cause unresolved. +The next justified distinct gate is ABI/package audit with this limitation. +A proposed selective GPU observation is documented but not executed. + +Offline analysis in this new LAB is `analyze.py LAB` then `model.py LAB`. +`model.py` reuses exact V1 functions through AST selection without running the +historical writer. Native rebuild schedule exists only for R3. `preflight.py` +affects offline files only and must precede all graphical runs; no re-execution +of the exhausted run slots is authorized. Source/evidence manifest creation +is separate from analysis to preserve final inventories. Generated outputs +remain outside Git. All runtime/socket directories were removed and owned +children reaped; compositor exit=0, clients retain controlled-shutdown EPIPE. + +`seal.py LAB` creates each final manifest once and refuses replacement; verify +its entries rather than regenerating after sealing. Optional `plot.py LAB` +requires matplotlib and produces a standalone SVG from complete column/row +arrays; it is an offline figure, not another captured PNG. Its cache stays +in LAB. Run all derived analysis before sealing. diff --git a/tooling/probes/step20c-optimized-blur-edge/analyze.py b/tooling/probes/step20c-optimized-blur-edge/analyze.py new file mode 100644 index 0000000..c015c3d --- /dev/null +++ b/tooling/probes/step20c-optimized-blur-edge/analyze.py @@ -0,0 +1,203 @@ +#!/usr/bin/env python3 +"""Offline comparisons from retained pixels/events; no native causality invented. + +CONTRACT: full RGB and pre-registered regions; traces absent remain absent. +INVARIANT: raw runs and original source manifests are not rewritten here. +""" +import hashlib +import json +import re +import 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 = {'center': (520,100,720,500), 'source': (20,100,160,500), + 'boundary': (720,50,800,550), 'physical_right': (750,0,800,600), + 'physical_bottom': (0,550,800,600)} +PAIRS = [('P0-FX0','P0-FX2'), ('P0-FX2','P1-B-natural'), + ('P1-B-natural','P2-B-explicit'), ('P0-FX2','P3-A-natural'), + ('P3-A-natural','P3-confirm')] +NAMES = ['OPT_NODE_CREATE','OPT_NODE_RENDER','OPT_BACKGROUND_COMMIT','OPT_DIRTY_SET', + 'OPT_DIRTY_CLEAR','OPT_REGEN_ATTEMPT','OPT_REGEN_RESULT','OPT_GPU_REBUILD', + 'OPT_GPU_RESULT','OPT_GPU_COPY','OPT_GPU_COPY_DRAWN','OPT_BLUR_USE', + 'OPT_CACHE_STATE','OPT_REGULAR_NODE','OPT_OUTPUT_BUFFER','PI_SCENE', + 'PI_OWNER','PI_OUTPUT_COMMIT','PI_OUTPUT_PRESENT','TEMPORAL_DAMAGE'] + +def write(name, value): + (out / (name + '.json')).write_text(json.dumps(value, indent=2, sort_keys=True) + '\n') + +def sha(path): + return hashlib.sha256(path.read_bytes()).hexdigest() + +def crop(image, box): + left, top, right, bottom = box + return image[top:bottom,left:right] + +def metric(first, second): + delta = np.abs(first.astype(np.int16) - second.astype(np.int16)) + mask = np.any(delta, axis=2) + ys, xs = np.where(mask) + return {'pixels': int(mask.sum()), 'total_pixels': int(mask.size), + 'max_channel': int(delta.max()), 'mean_abs_channel': float(delta.mean()), + 'bbox': [int(xs.min()),int(ys.min()),int(xs.max()+1),int(ys.max()+1)] if mask.any() else None, + 'channel_amplitude_histogram': {str(i):int(n) for i,n in enumerate(np.bincount(delta.ravel(),minlength=256)) if n}, + 'changed_pixel_max_amplitude_histogram': {str(i):int(n) for i,n in enumerate(np.bincount(delta.max(axis=2).ravel(),minlength=256)) if n}} + +def compare(first, second): + return {'whole': metric(first,second), + 'ROIs': {name: metric(crop(first,box),crop(second,box)) for name,box in ROIS.items()}} + +def parsed(lines, name): + return [{'raw': line, **dict(re.findall(r'([A-Za-z_]+)=([^ ]+)',line))} + for line in lines if re.search(r'\b' + name + r'\b',line)] + +runs = {} +pixels = {} +columns = {} +rows = {} +renderers = {} +commits = {} +states = {} +geometries = {} +settings = {} +windows = {} +phase_traces = {} +damage = {} +contrast = {} +ledger = json.loads((out/'campaign-budget.json').read_text()) +for mode in ('R0','R1','R2','R3'): + run = lab/'runs'/mode + status = json.loads((run/'exit-status.json').read_text()) + events = [json.loads(line) for line in (run/'events.jsonl').read_text().splitlines()] + lines = (run/'compositor.log').read_text().splitlines() + traces = {name: parsed(lines,name) for name in NAMES} + images = {event['tag']+'-'+event['output']: np.array(Image.open(run/event['path']).convert('RGB')) + for event in events if event['kind']=='capture'} + runs[mode] = {'status':status,'events':events,'traces':traces,'images':images} + renderer_event = next(e for e in events if e['kind']=='renderer_start') + loaded = sorted(set(line.split()[-1] for line in (run/'library-maps.txt').read_text().splitlines() + if 'libscenefx' in line or 'libwlroots' in line)) + renderers[mode] = {'lines':[line for line in lines if any(text in line for text in + ('Using OpenGL ES','GL renderer:','GL vendor:','EGL vendor:','FX RENDERER:'))], + 'loaded_libraries':{path:sha(Path(path)) for path in loaded}, + 'binary':renderer_event['binary'],'binary_sha256':sha(Path(renderer_event['binary'])), + 'OPT_TRACE':renderer_event['OPT_TRACE'],'native_OPT_evidence':mode=='R3'} + client = parsed((run/'background.log').read_text().splitlines(),'CLIENT_COMMIT') + reserves = [e for e in events if e['kind']=='mutation_reserved'] + configures = parsed((run/'background.log').read_text().splitlines(),'BACKGROUND_CONFIGURED') + assert len(client)==ledger['modes'][mode]['mutation']==len(reserves) + assert [c['pattern'] for c in client]==[r['pattern'] for r in reserves] + assert all(int(c['ns'])>r['ns'] and c['size']=='800x600' for c,r in zip(client,reserves)) + assert len(status['captures'])==ledger['modes'][mode]['capture'] + assert status['duration']<150 and status['runtime_removed'] and not (run/'runtime').exists() + commits[mode] = {'client':client,'configures':configures,'reservations':reserves, + 'server':traces['OPT_BACKGROUND_COMMIT'], + 'reservation_protocol':'PASS before every observed A/B image', + 'no_configure_draw':'PASS duplicate configure ACK without autonomous image', + 'identity_limit':'surface/buffer IDs may be reused; sequence/lifetime required'} + states[mode] = [e for e in events if e['kind']=='graphic_state'] + geometries[mode] = {p.stem:json.loads(p.read_text()) for p in run.glob('*-outputs.json')} + settings[mode] = {'config':(run/'config').read_text(), + 'commands':[e for e in events if e['kind']=='ipc' and e['args'] and e['args'][0].startswith('[app_id=')], + 'pixel_effect_gate':[e for e in events if e['kind']=='P0_gate'], + 'setting_limit':'accepted config + source audit; filtering proves effect; no OPT parameters fabricated for untraced controls'} + windows[mode] = status['captures'] + if status['failure']: + pixels[mode] = {'failure':status['failure']} + continue + assert [c['pattern'] for c in client]==['A','B','A'] + baseline = images['P0-FX0-HEADLESS-2'] + p = {'comparisons':{},'H2_full':{},'capture_sha256':{q.name:sha(q) for q in run.glob('*.png')}} + columns[mode] = {}; rows[mode] = {}; contrast[mode] = {} + for first,second in PAIRS: + key=first+' -> '+second + a=images[first+'-HEADLESS-1']; b=images[second+'-HEADLESS-1'] + p['comparisons'][key]=compare(a,b) + mask=np.any(a!=b,axis=2) + columns[mode][key]=mask.sum(axis=0).astype(int).tolist() + rows[mode][key]=mask.sum(axis=1).astype(int).tolist() + for name,image in images.items(): + if name.endswith('HEADLESS-2'): + p['H2_full'][name]=metric(baseline,image) + else: + center=crop(image,ROIS['center']).astype(float) + luminance=center@[.2126,.7152,.0722] + contrast[mode][name]={'luminance_std':float(luminance.std()), + 'adjacent_luminance':float(np.abs(np.diff(luminance,axis=1)).mean()), + 'mean_RGB':center.mean(axis=(0,1)).tolist()} + # Full exposed-source calibration in every measured state, not just membership. + x=np.arange(20,160) + for tag,pattern in [('P0-FX0','A'),('P0-FX2','A'),('P1-B-natural','B'),('P2-B-explicit','B'),('P3-A-natural','A')]: + colors=np.array([[245,245,245],[15,25,45]] if pattern=='A' else [[32,192,224],[176,32,64]],dtype=np.uint8) + expected=np.broadcast_to(colors[(x//8)%2],(400,140,3)) + assert np.array_equal(expected,crop(images[tag+'-HEADLESS-1'],ROIS['source'])) + p['source_calibration']='PASS exact stripes A/B/B/A in exposed ROI' + pixels[mode]=p + phase_traces[mode]={} + for tag in ('P0-FX2','P1-B-natural','P2-B-explicit','P3-A-natural'): + capture=next(e for e in status['captures'] if e['tag']==tag and e['output']=='HEADLESS-1') + if tag=='P0-FX2': + start=next(e['after_ns'] for e in status['captures'] if e['tag']=='P0-reference') + elif tag=='P2-B-explicit': + start=next(e['ns'] for e in events if e['kind']=='explicit_cycle_start') + else: + start=reserves[1 if tag=='P1-B-natural' else 2]['ns'] + phase_traces[mode][tag]={'start_ns':start,'capture_before_ns':capture['before_ns'], + 'end_ns':capture['after_ns'], + 'trace':{name:[t for t in values if 'ns' in t and start<=int(t['ns'])<=capture['after_ns']] + for name,values in traces.items()}} + if mode!='R3': + damage[mode]={'output_state_OPT_damage':'NOT AVAILABLE', + 'old_scene_damage':traces['TEMPORAL_DAMAGE'], + 'limit':'older private scene damage (if present) is not OPT output-state buffer damage; no equivalent coverage claim'} + else: + damage[mode]={} + for first,second in [('P0-FX0','P0-FX2'),('P0-FX2','P1-B-natural'),('P1-B-natural','P2-B-explicit'),('P2-B-explicit','P3-A-natural')]: + ca=next(e for e in status['captures'] if e['tag']==first and e['output']=='HEADLESS-1') + cb=next(e for e in status['captures'] if e['tag']==second and e['output']=='HEADLESS-1') + mask=np.any(images[first+'-HEADLESS-1']!=images[second+'-HEADLESS-1'],axis=2) + cover=np.zeros((600,800),bool); records=[] + for t in traces['OPT_OUTPUT_BUFFER']: + if t['output']!='HEADLESS-1' or not ca['before_ns']<=int(t['ns'])<=cb['after_ns']:continue + left,top,right,bottom=map(int,t['damage'].split(','));cover[max(0,top):min(600,bottom),max(0,left):min(800,right)]=True;records.append(t) + damage[mode][first+' -> '+second]={'changed_pixels':int(mask.sum()), + 'uncovered_by_output_damage_extents':int((mask&~cover).sum()),'records':records, + 'limit':'extent union, stabilized captures only; no all-frame coverage/minimal damage/native PNG identity'} + +cross={} +for first,second in [('R0','R1'),('R2','R3')]: + key=first+' -> '+second + a=runs[first];b=runs[second] + comparable=not a['status']['failure'] and not b['status']['failure'] + cross[key]={'geometry_equal':states[first]==states[second], 'comparable':comparable} + # State records contain timestamps; compare applied geometry and tags only. + cross[key]['geometry_equal']=[(e['tag'],e['geometry']) for e in states[first]]==[(e['tag'],e['geometry']) for e in states[second]] + if comparable and cross[key]['geometry_equal']: + cross[key]['states']={tag:compare(a['images'][tag+'-HEADLESS-1'],b['images'][tag+'-HEADLESS-1']) + for tag in ('P0-FX0','P0-FX2','P1-B-natural','P2-B-explicit','P3-A-natural','P3-confirm')} + cross[key]['H2']=metric(a['images']['P0-FX0-HEADLESS-2'],b['images']['P0-FX0-HEADLESS-2']) + else:cross[key]['limit']='control geometry/fixture not comparable' +write('renderer-identities',renderers);write('source-commits',commits) +write('graphic-states',states);write('output-geometries',geometries);write('blur-settings',settings) +write('edge-pixel-analysis',pixels);write('column-distribution',columns);write('row-distribution',rows) +write('capture-windows',windows);write('damage-analysis',damage);write('phase-events',phase_traces) +write('filtering-analysis',contrast);write('private-trace-comparison',cross['R2 -> R3']);write('pristine-control',cross['R0 -> R1']) +for category,names in [('cache-use',['OPT_BLUR_USE','OPT_CACHE_STATE','OPT_REGULAR_NODE']), + ('dirty-events',['OPT_DIRTY_SET','OPT_DIRTY_CLEAR']), + ('regeneration-events',['OPT_REGEN_ATTEMPT','OPT_REGEN_RESULT','OPT_GPU_REBUILD','OPT_GPU_RESULT','OPT_GPU_COPY','OPT_GPU_COPY_DRAWN'])]: + write(category,{mode:{name:data['traces'][name] for name in names} for mode,data in runs.items()}) +write('matrix',{mode:{'failure':data['status']['failure'],'duration':data['status']['duration'], + 'compositor_exit':data['status']['compositor'],'captures':len(data['status']['captures']), + 'images':len(commits[mode]['client']),'runtime_removed':data['status']['runtime_removed']} + for mode,data in runs.items()}) +# Never infer the final scientific verdict merely from process success. +print(json.dumps({'edge_counts':{mode:{pair:value['whole'] for pair,value in data.get('comparisons',{}).items() + if pair in ('P1-B-natural -> P2-B-explicit','P0-FX2 -> P3-A-natural')} + for mode,data in pixels.items()}, + 'cross':{key:{'geometry_equal':data['geometry_equal'], + 'state_changed_pixels':{tag:value['whole']['pixels'] for tag,value in data.get('states',{}).items()}} + for key,data in cross.items()}},indent=2)) diff --git a/tooling/probes/step20c-optimized-blur-edge/budget.py b/tooling/probes/step20c-optimized-blur-edge/budget.py new file mode 100644 index 0000000..4d147eb --- /dev/null +++ b/tooling/probes/step20c-optimized-blur-edge/budget.py @@ -0,0 +1,69 @@ +"""Durable fail-closed reservations, bound to one LAB for this ticket. + +WHY: a failed graphical attempt must never create capacity for a rerun. +CONTRACT: exclusive flock; fsync file then atomic replace and directory fsync. +INVARIANT: exact schema, bounded counters, no reset/refund or alternate LAB. +""" +import fcntl +import json +import os +import tempfile +from pathlib import Path + +MODES = ('R0', 'R1', 'R2', 'R3') +KEYS = ('run', 'mutation', 'capture', 'cycle', 'calibration') +CAMPAIGN = 'SWAYFX_MOSAIC_STEP20C_OPTIMIZED_CACHE_FREEZE_AND_EDGE_CAUSALITY_GATE_V1' +DEFAULT = Path.home() / 'swayfx-mosaic-optimized-edge-v1-budget.json' + +def limits(mode): + return dict(run=1, mutation=3, capture=12, cycle=1, + calibration=1 if mode in ('R2', 'R3') else 0) + +def validate(state, lab): + if set(state) != {'campaign', 'lab', 'modes'} or state['campaign'] != CAMPAIGN or state['lab'] != lab: + raise RuntimeError('wrong campaign/LAB or corrupt schema') + if set(state['modes']) != set(MODES): + raise RuntimeError('corrupt modes') + for mode, counts in state['modes'].items(): + if set(counts) != set(KEYS): + raise RuntimeError('corrupt resource schema') + for key, value in counts.items(): + if type(value) is not int or not 0 <= value <= limits(mode)[key]: + raise RuntimeError('corrupt/out-of-bounds counter') + if any(counts[k] for k in KEYS if k != 'run') and counts['run'] != 1: + raise RuntimeError('action before run') + +def reserve(lab, mode, resource, path=DEFAULT): + if mode not in MODES or resource not in KEYS: + 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 KEYS} for m in MODES}} + validate(state, lab) + counts = state['modes'][mode] + if resource != 'run' and counts['run'] != 1: + raise RuntimeError('reserve run first') + if counts[resource] >= limits(mode)[resource]: + raise RuntimeError('budget exhausted: ' + resource) + 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) + try: + os.fsync(directory) + finally: + os.close(directory) + finally: + if os.path.exists(tmp): + os.unlink(tmp) + return state diff --git a/tooling/probes/step20c-optimized-blur-edge/clients.c b/tooling/probes/step20c-optimized-blur-edge/clients.c new file mode 100644 index 0000000..2c67fb4 --- /dev/null +++ b/tooling/probes/step20c-optimized-blur-edge/clients.c @@ -0,0 +1,303 @@ +#define _POSIX_C_SOURCE 200809L +#include "layer-shell-client.h" +#include "xdg-shell-client.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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) { + /* CONTRACT: exactly one runner command per reserved A/B image. + * INVARIANT: configures cannot draw, and the sequence is A/B/A only. */ + if (!strcmp(mode, "background") && + (width != 800 || height != 600 || commits >= 3 || + pattern != (commits == 1 ? 'B' : 'A'))) + exit(2); + 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, ®istry_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; +} diff --git a/tooling/probes/step20c-optimized-blur-edge/model.py b/tooling/probes/step20c-optimized-blur-edge/model.py new file mode 100644 index 0000000..577d6e7 --- /dev/null +++ b/tooling/probes/step20c-optimized-blur-edge/model.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +"""Reuse V1 ideal kernels; compare hypothesis with R3 native reconstruction order. + +CONTRACT: no GLSL/native precision equivalence, FBO contents or causal proof. +INVARIANT: model prediction is fixed before fitting; no kernel/offset fitting. +""" +import ast +import hashlib +import json +import re +import sys +from pathlib import Path +import numpy as np +from PIL import Image + +lab=Path(sys.argv[1]).resolve() +fixture=Path(__file__).resolve().parent +historical=fixture.parent/'step20c-optimized-blur-cache/edge-model.py' +source=historical.read_text() +# WHY: reuse the exact reviewed functions without executing the historical writer. +# CONTRACT: retain only function definitions/imports and W,H constants from its AST. +module=ast.parse(source) +selected=[node for node in module.body if isinstance(node,(ast.Import,ast.ImportFrom,ast.FunctionDef)) + or (isinstance(node,ast.Assign) and any(isinstance(t,ast.Tuple) for t in node.targets))] +context={} +exec(compile(ast.Module(body=selected,type_ignores=[]),str(historical),'exec'),context) +rebuild=context['rebuild'];pattern=context['pattern'] +run=lab/'runs/R3' +lines=(run/'compositor.log').read_text().splitlines() +commits=[] +for line in (run/'background.log').read_text().splitlines(): + 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+)',line)[1]) for line in lines + if line.startswith('OPT_GPU_REBUILD ') and 'output=HEADLESS-1' in line] +ping=np.zeros((600,800,3));pong=ping.copy();states=[] +for ns in rebuilds: + current=next((c for t,c in reversed(commits) if t '+second]={ + 'predicted_changed_pixels_in_visible_glass':int(pm.sum()),'native_changed_pixels':int(nm.sum()), + 'mask_intersection':int(intersection),'mask_union':int(union), + 'mask_jaccard':float(intersection/union) if union else 1, + 'predicted_max_channel':float(np.abs(pd).max()),'native_max_channel':float(np.abs(nd).max()), + 'delta_residual_mean_abs_channel':float(disagreement[:,480:].mean()), + 'predicted_columns':pm.sum(axis=0).astype(int).tolist(), + 'predicted_rows':pm.sum(axis=1).astype(int).tolist(), + 'native_columns':nm.sum(axis=0).astype(int).tolist(),'native_rows':nm.sum(axis=1).astype(int).tolist(), + 'interpretation':'fixed-kernel history compatibility only; native FBO tail and mediump behavior unobserved'} +(lab/'results/edge-model-comparison.json').write_text(json.dumps({ + 'model_source':str(historical),'model_sha256':hashlib.sha256(historical.read_bytes()).hexdigest(), + 'native_schedule':'R3 OPT_GPU_REBUILD timestamps only; no invented timestamps for untraced runs', + 'rebuild_count':len(rebuilds),'metrics':metrics,'distributions':distribution, + 'causality':'NOT DEMONSTRATED; no GPU readback or intervention'},indent=2)+'\n') +print(json.dumps({'rebuild_count':len(rebuilds),'metrics':metrics, + 'distributions':{k:{n:v for n,v in d.items() if not n.endswith(('columns','rows'))} + for k,d in distribution.items()}},indent=2)) diff --git a/tooling/probes/step20c-optimized-blur-edge/plot.py b/tooling/probes/step20c-optimized-blur-edge/plot.py new file mode 100644 index 0000000..4e4be3f --- /dev/null +++ b/tooling/probes/step20c-optimized-blur-edge/plot.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +"""Optional standalone scientific SVG from complete offline distributions.""" +import json +import sys +import os +from pathlib import Path +lab=Path(sys.argv[1]).resolve() +os.environ['MPLCONFIGDIR']=str(lab/'plot-cache') +import matplotlib +matplotlib.use('Agg') +import matplotlib.pyplot as plt + +out=lab/'results' +columns=json.loads((out/'column-distribution.json').read_text()) +rows=json.loads((out/'row-distribution.json').read_text()) +model=json.loads((out/'edge-model-comparison.json').read_text())['distributions'] +pairs=['P1-B-natural -> P2-B-explicit','P0-FX2 -> P3-A-natural'] +fig,axes=plt.subplots(2,2,figsize=(12,7),constrained_layout=True) +for index,key in enumerate(pairs): + for dim,data,name in [(0,columns,'columns'),(1,rows,'rows')]: + ax=axes[dim,index] + ax.plot(data['R0'][key],label='R0/R1 standard native',color='#376ca0',linewidth=1.1) + ax.plot(data['R2'][key],label='R2/R3 Mosaic native',color='#19816b',linewidth=1.1) + ax.plot(model[key]['predicted_'+name],label='Fixed offline model, Mosaic domain', + color='#ba6d20',linewidth=1,linestyle='--',alpha=.8) + ax.axvspan(750 if dim==0 else 550,800 if dim==0 else 600,color='#888888',alpha=.12) + ax.set_title(('B natural / explicit' if index==0 else 'A initial / return')+' — '+name) + ax.set_xlabel('Output-local '+('x' if dim==0 else 'y')+' (px)') + ax.set_ylabel('Changed pixels per '+('column' if dim==0 else 'row')) + ax.set_xlim(0,800 if dim==0 else 600);ax.grid(alpha=.2) +axes[0,0].legend(fontsize=8) +fig.suptitle('Complete edge distributions — shared native behavior; offline model is not causal proof',fontsize=12) +fig.savefig(out/'edge-distributions.svg',metadata={'Date':None}) +print('PASS standalone SVG; not an additional compositor capture') diff --git a/tooling/probes/step20c-optimized-blur-edge/preflight.py b/tooling/probes/step20c-optimized-blur-edge/preflight.py new file mode 100644 index 0000000..5802cb9 --- /dev/null +++ b/tooling/probes/step20c-optimized-blur-edge/preflight.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 +"""Offline source, callback, protocol and pinned sampling audit; no graphics.""" +import ast +import hashlib +import json +import subprocess +import sys +import xml.etree.ElementTree as ET +from pathlib import Path + +lab = Path(sys.argv[1]).resolve() +fixture = Path(__file__).resolve().parent +repo = fixture.parents[2] +results = lab / 'results' +for path in fixture.glob('*.py'): + ast.parse(path.read_text(), filename=str(path)) +for path in lab.glob('*.xml'): + ET.parse(path) +client = (fixture / 'clients.c').read_text() +assert client == (lab / 'client-source.c').read_text() +callback = client[client.index('static void layer_configure('):client.index('static void layer_close(')] +assert 'draw(' not in callback and 'ack_configure(s, serial)' in callback +# WHY: exercise the real callback text under duplicate configures without Wayland. +# CONTRACT: stub only ACK and clock; no native graphics or mutation reservation. +harness = '''#include +#include +#include +#include +struct zwlr_layer_surface_v1 {int unused;}; +static int width=420,height=320; +static unsigned commits=0,acks=0; +static long long stamp(void){return 0;} +static void zwlr_layer_surface_v1_ack_configure(struct zwlr_layer_surface_v1 *s,uint32_t serial){(void)s;(void)serial;++acks;} +''' + callback + ''' +int main(void){struct zwlr_layer_surface_v1 s={0};layer_configure(NULL,&s,1,800,600);layer_configure(NULL,&s,2,800,600);assert(acks==2 && commits==0 && width==800 && height==600);commits=1;layer_configure(NULL,&s,3,800,600);assert(acks==3 && commits==1);return 0;} +''' +(lab / 'callback-test.c').write_text(harness) +subprocess.run(['cc', '-std=c17', '-Wall', '-Wextra', '-Werror', '-Wno-unused-parameter', + str(lab / 'callback-test.c'), '-o', str(lab / 'callback-test')], check=True) +subprocess.run([str(lab / 'callback-test')], check=True) +assert 'commits >= 3' in client and "pattern != (commits == 1 ? 'B' : 'A')" in client +assert 'WL_SHM_FORMAT_ARGB8888' in client and 'WL_SHM_FORMAT_XRGB8888' in client +assert 'live_buffers >= 8' in client and 'wl_buffer_listener' in client +assert 'wl_display_roundtrip(display)' in client and 'wl_output_interface, 4' in client +run = (fixture / 'run.py').read_text() +assert "reserve(lab,mode,'mutation')" in run and "reserve(lab,mode,'capture')" in run +assert "['swaymsg','-s',str(sock)" in run and "signal.setitimer(signal.ITIMER_REAL,150)" in run +assert "if instrumented:env['OPT_TRACE']='1'" in run +# Both private controls have every same unconditional borrowed read and old trace. +trace_header = (lab / 'scenefx/include/render/opt_probe.h').read_text() +assert 'if (getenv("OPT_TRACE"))' in trace_header +for shader in ('blur1.frag', 'blur2.frag'): + rel = Path('render/fx_renderer/shaders') / shader + assert (lab / 'scene0' / rel).read_bytes() == (lab / 'scenefx' / rel).read_bytes() +command = (lab / 'pristine/sway/commands/blur.c').read_text() +assert 'arrange_container(con)' in command and 'root->all_outputs' in command +passes = [] +# Source-derived coordinate support, ideal bilinear positions, no driver claims. +for direction, level, valid in [('down', 0, (800,600)), ('down', 1, (400,300)), + ('down', 2, (200,150)), ('up', 2, (100,75)), + ('up', 1, (200,150)), ('up', 0, (400,300))]: + written = (800 // (2 ** (level + 1 if direction == 'down' else level)), + 600 // (2 ** (level + 1 if direction == 'down' else level))) + mapping = '2*(p+0.5)-0.5 plus offsets +/-5' if direction == 'down' else '(p+0.5)/2-0.5 plus axial +/-2.5 and diagonal +/-1.25' + maximum = [(2 * (v - .5) - .5 + 5) if direction == 'down' + else ((v - .5) / 2 - .5 + 2.5) for v in written] + passes.append({'direction': direction, 'level': level, 'framebuffer': [800,600], + 'fresh_input_domain': list(valid), 'written_domain': list(written), + 'sampler_mapping': mapping, 'max_unclamped_sample_xy': maximum, + 'can_read_beyond_fresh_domain': (direction == 'up') or (direction == 'down' and level > 0), + 'possible_value': 'earlier write/allocation content, native content NOT OBSERVED', + 'final_influence': 'propagated right/bottom boundary bands; strength predicted by model only'}) +source_files = [lab / 'scene0/render/fx_renderer' / p for p in + ('fx_pass.c','fx_texture.c','fx_framebuffer.c','fx_offscreen_buffers.c','shaders/blur1.frag','shaders/blur2.frag')] +(results / 'sampling-domain-audit.json').write_text(json.dumps({ + 'status': 'SOURCE POSSIBILITY; native intermediate content NOT OBSERVED', + 'source_sha256': {str(p.relative_to(lab)): hashlib.sha256(p.read_bytes()).hexdigest() for p in source_files}, + 'passes': passes, 'filter': 'GL_LINEAR', 'wrap': 'GL_CLAMP_TO_EDGE full texture, not reduced domain', + 'precision': 'native mediump GLES versus ideal offline bilinear/8-bit rounding', + 'clear': 'no full color clear between blur passes in audited render_blur_segments', + 'blur_cycle': 'scoped blur flag + arrange_container; dirty every output; natural background commit dirties its output', + 'causality': 'not established by source audit'}, indent=2) + '\n') +(results / 'preflight.json').write_text(json.dumps({ + 'AST': 'PASS', 'XML': 'PASS', 'C17_callback_duplicate_ACK_no_draw': 'PASS', + 'SHM_bounds_formats_lifetimes': 'PASS source audit, live proof pending', + 'Wayland_connections_protocols': 'PASS source audit and C17 build, live proof pending', + 'explicit_command_reservations': 'PASS source audit', + 'timeouts_owned_cleanup': 'PASS source audit', + 'shader_bytes_pristine_private': 'IDENTICAL', + 'trace_toggle': 'presence only; same private code, old traces remain', + 'graphical_runs': 0}, indent=2) + '\n') +print('PASS AST/XML/C17 callback, SHM/Wayland/reservation/cleanup audit, immutable shaders, sampling audit') diff --git a/tooling/probes/step20c-optimized-blur-edge/prepare.py b/tooling/probes/step20c-optimized-blur-edge/prepare.py new file mode 100644 index 0000000..45a9221 --- /dev/null +++ b/tooling/probes/step20c-optimized-blur-edge/prepare.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 +"""Build-only reconstruction from fixed archives and immutable source patches. + +CONTRACT: no compositor launch, system install or write to older fixtures. +INVARIANT: consumed LABs cannot be rebuilt, and prepared identities are hashed. +""" +import hashlib +import io +import json +import os +import subprocess +import sys +import tarfile +import tempfile +from pathlib import Path + +fixture = Path(__file__).resolve().parent +repo = fixture.parents[2] +lab = Path(sys.argv[1]).resolve() if len(sys.argv) == 2 else Path( + tempfile.mkdtemp(prefix='swayfx-mosaic-optimized-edge-', dir=Path.home())) +if (lab / 'runs').exists() and any((lab / 'runs').iterdir()): + raise RuntimeError('never rebuild a consumed LAB') +(lab / 'results').mkdir(parents=True, exist_ok=True) +(lab / 'runs').mkdir(exist_ok=True) +print('LAB=' + str(lab), flush=True) + +def run(args, **kwargs): + with (lab / 'build.log').open('ab') as out: + subprocess.run(args, check=True, stdout=out, stderr=subprocess.STDOUT, **kwargs) + +def sha(path): + return hashlib.sha256(path.read_bytes()).hexdigest() + +def archive(git, revision, name): + target = lab / name + if target.exists(): + raise RuntimeError('existing source tree; continue Meson explicitly, never overwrite') + target.mkdir() + data = subprocess.check_output(['git', '-C', str(git), 'archive', revision]) + tarfile.open(fileobj=io.BytesIO(data)).extractall(target, filter='data') + +scene_revision = '37ccd723bef49e6891156ffafce8f549f01446cc' +mosaic_revision = 'cd550382b311efdbfe3ed892f4e1d281d852e754' +pristine_revision = 'fd71a6bdc061bd633b488ae7b83e8a6981d22f86' +run(['git', 'clone', '--quiet', '--no-checkout', 'https://github.com/wlrfx/scenefx.git', str(lab / 'scenefx-git')]) +for name in ('scene0', 'scenefx'): + archive(lab / 'scenefx-git', scene_revision, name) +archive(repo, pristine_revision, 'pristine') +archive(repo, mosaic_revision, 'mosaic') +chains = { + 'scenefx': ['step20c-valid-grab/scenefx-popup-plus-base.patch', + 'step20c-temporal-damage/scenefx-timing.patch', + 'step20c-optimized-blur-cache/scenefx-cache-trace.patch'], + 'mosaic': ['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', + 'step20c-optimized-blur-cache/sway-source-trace.patch']} +patches = [] +(lab / 'patches').mkdir() +for tree, chain in chains.items(): + for name in chain: + source = repo / 'tooling/probes' / name + target = lab / 'patches' / source.name + target.write_bytes(source.read_bytes()) + patches.append({'path': name, 'sha256': sha(target), 'target': tree}) + run(['git', 'apply', '--check', str(target)], cwd=lab / tree) + run(['git', 'apply', str(target)], cwd=lab / tree) +for source, build in [('scene0', 'scene0-build'), ('scenefx', 'scenefx-build')]: + run(['meson', 'setup', str(lab / build), str(lab / source), '-Dexamples=false', '-Dwerror=false']) + run(['meson', 'compile', '-C', str(lab / build), '-j', '8']) +for source, 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 / source), + '-Ddefault-wallpaper=false', '-Dswaybar=false', '-Dswaynag=false', + '-Dman-pages=disabled', '-Dsd-bus-provider=libsystemd', '-Dwerror=false'] + if source == 'mosaic': + args.append('-Dmosaic-package=true') + run(args, env=env) + run(['meson', 'compile', '-C', str(lab / build), '-j', '8'], env=env) +protocols = repo / 'tooling/probes/step20c-optimized-blur-cache' +for stem, name, digest in [ + ('layer-shell', 'wlr-layer-shell-unstable-v1.xml', '996b8c66e1c2276c443336d4cb174634e8e5948823779042f244930525b79cc2'), + ('xdg-shell', 'xdg-shell.xml', '7ba7f9c8473deee674cb1f154a18abd0bb0cc072604fc055b0c15e459fc4c7df')]: + xml = protocols / name + assert sha(xml) == digest + target = lab / name + target.write_bytes(xml.read_bytes()) + run(['wayland-scanner', 'client-header', str(target), str(lab / (stem + '-client.h'))]) + run(['wayland-scanner', 'private-code', str(target), str(lab / (stem + '-protocol.c'))]) +(lab / 'client-source.c').write_bytes((fixture / 'clients.c').read_bytes()) +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(lab / 'client-source.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']) +versions = {package: subprocess.check_output(['pkg-config', '--modversion', package], text=True).strip() + for package in ('wlroots-0.20', 'scenefx-0.5', 'wayland-client', 'wayland-protocols')} +binaries = {str(p.relative_to(lab)): sha(p) for p in [ + lab / 'clients', lab / 'mosaic-build/sway/swayfx-mosaic', lab / 'pristine-build/sway/sway', + lab / 'scenefx-build/libscenefx-0.5.so', lab / 'scene0-build/libscenefx-0.5.so']} +for rel in ('mosaic-build/sway/swayfx-mosaic', 'pristine-build/sway/sway'): + run(['ldd', str(lab / rel)]) +(lab / 'results/build-identities.json').write_text(json.dumps({ + 'revisions': {'SceneFX': scene_revision, 'Mosaic': mosaic_revision, 'pristine': pristine_revision}, + 'packages': versions, 'patches': patches, 'binaries': binaries, + 'R2_R3': 'identical binary/library/client/config; only OPT_TRACE presence differs'}, indent=2) + '\n') +print('BUILD_PASS LAB=' + str(lab), flush=True) diff --git a/tooling/probes/step20c-optimized-blur-edge/run.py b/tooling/probes/step20c-optimized-blur-edge/run.py new file mode 100644 index 0000000..e4dcf71 --- /dev/null +++ b/tooling/probes/step20c-optimized-blur-edge/run.py @@ -0,0 +1,180 @@ +#!/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=['R0','R1','R2','R3']);a=p.parse_args() +lab=a.lab.resolve();mode=a.mode +if not (lab/'clients').is_file():p.error('prepared LAB required') +if not (lab/'results/preflight.json').is_file():p.error('offline preflight required') +import hashlib +identities=json.loads((lab/'results/build-identities.json').read_text()) +for rel,digest in identities['binaries'].items(): + if hashlib.sha256((lab/rel).read_bytes()).hexdigest()!=digest:p.error('changed prepared binary/library') +if (lab/'client-source.c').read_bytes()!=Path(__file__).with_name('clients.c').read_bytes():p.error('changed compiled client source') +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!='R0' +mosaic=mode in ('R2','R3') +instrumented=mode=='R3' +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 instrumented:env['OPT_TRACE']='1' +geometry_baseline={} +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()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'],OPT_TRACE=env.get('OPT_TRACE'),layout='mosaic' if mosaic else 'splith') + background=spawn([str(lab/'clients'),'background'],'background.log',True) + wait(lambda:'BACKGROUND_CONFIGURED ' in log('background.log')) + if log('background.log').count('CLIENT_COMMIT ')!=0:raise RuntimeError('unreserved configure image') + mutate('A') + command('workspace 1');command('layout mosaic' if mosaic 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 mosaic: + 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 instrumented and not native_gate():raise RuntimeError('optimized native use gate') + event('P0_gate',source=True,filtering=True,native_optimized=bool(instrumented),adjacent_fx0=adjacent0,adjacent_fx2=adjacent2) + mutate('B');snapshot('P1-B-natural') + 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') + time.sleep(.35);snapshot('P3-confirm',only_h1=True) + if not np.array_equal(image('P3-A-natural'),image('P3-confirm')):raise RuntimeError('final whole-H1 stabilization gate') + event('complete',camera_pair='NOT EXERCISED: forbidden by this campaign') +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) diff --git a/tooling/probes/step20c-optimized-blur-edge/seal.py b/tooling/probes/step20c-optimized-blur-edge/seal.py new file mode 100644 index 0000000..19fc4e1 --- /dev/null +++ b/tooling/probes/step20c-optimized-blur-edge/seal.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +"""Seal retained source and evidence inventories once, without overwriting. + +CONTRACT: raw observations and completed source fixtures precede this seal. +INVARIANT: existing manifests are refused; verification never regenerates them. +""" +import hashlib +import sys +from pathlib import Path + +lab=Path(sys.argv[1]).resolve() +fixture=Path(__file__).resolve().parent +out=lab/'results' +if any((out/name).exists() for name in ('source-sha256.txt','evidence-sha256.txt')): + raise RuntimeError('sealed inventories already exist; verify, never overwrite') + +def sha(path): + return hashlib.sha256(path.read_bytes()).hexdigest() + +sources=set(p for p in fixture.iterdir() if p.is_file()) +sources.add(fixture.parent/'step20c-optimized-blur-cache/edge-model.py') +for name in ('scene0','scenefx','mosaic','pristine'): + sources.update(p for p in (lab/name).rglob('*') + if p.is_file() and 'subprojects' not in p.relative_to(lab/name).parts) +sources.update(p for p in (lab/'patches').iterdir() if p.is_file()) +sources.update(p for p in lab.iterdir() if p.is_file() and p.suffix in ('.c','.h','.xml')) +sources.add(lab/'protocol-before-results.md') +for run in (lab/'runs').iterdir(): + sources.update(run/name for name in ('fixture-source.py','client-source.c','config')) +source_manifest=out/'source-sha256.txt' +source_manifest.write_text(''.join(f'{sha(p)} {p}\n' for p in sorted(sources))) +# Evidence includes all run files, result categories and build/analysis/model/final +# validation logs. Build binaries are identified by digests in build-identities. +evidence=set(p for p in (lab/'runs').rglob('*') if p.is_file()) +evidence.update(p for p in out.iterdir() if p.is_file() and not p.name.endswith('-sha256.txt')) +evidence.update(p for p in lab.glob('*.log') if p.is_file()) +evidence_manifest=out/'evidence-sha256.txt' +evidence_manifest.write_text(''.join(f'{sha(p)} {p}\n' for p in sorted(evidence))) +for manifest in (source_manifest,evidence_manifest): + for line in manifest.read_text().splitlines(): + digest,path=line.split(' ',1) + assert sha(Path(path))==digest,path + print(manifest.name,len(manifest.read_text().splitlines()),sha(manifest)) diff --git a/tooling/probes/step20c-optimized-blur-edge/test-budget.py b/tooling/probes/step20c-optimized-blur-edge/test-budget.py new file mode 100644 index 0000000..379fb17 --- /dev/null +++ b/tooling/probes/step20c-optimized-blur-edge/test-budget.py @@ -0,0 +1,55 @@ +"""Offline tests use disposable ledgers; never touch campaign reservations.""" +import json +import multiprocessing +import tempfile +from pathlib import Path +from budget import reserve, MODES + +def denied(call): + try: + call() + except (ValueError, RuntimeError, KeyError, TypeError): + return + raise AssertionError('accepted forbidden reservation') + +def contender(root, queue): + try: + reserve(root, 'R0', 'run', Path(root) / 'contention.json') + queue.put('accepted') + except RuntimeError: + queue.put('denied') + +with tempfile.TemporaryDirectory() as tmp: + path = Path(tmp) / 'budget.json' + denied(lambda: reserve(tmp, 'R0', 'capture', path)) + for mode in MODES: + reserve(tmp, mode, 'run', path) + denied(lambda: reserve(tmp, mode, 'run', path)) + for key, count in [('mutation', 3), ('capture', 12), ('cycle', 1), + ('calibration', 1 if mode in ('R2', 'R3') else 0)]: + for _ in range(count): + reserve(tmp, mode, key, path) + denied(lambda: reserve(tmp, mode, key, path)) + denied(lambda: reserve(tmp + '-another', 'R0', 'run', path)) + denied(lambda: reserve(tmp, 'R4', 'run', path)) + state = json.loads(path.read_text()) + assert sum(c['capture'] for c in state['modes'].values()) == 48 + assert sum(c['mutation'] for c in state['modes'].values()) == 12 + for bad in ['', '{}', '{"campaign":"wrong"}']: + path.write_text(bad) + denied(lambda: reserve(tmp, 'R0', 'run', path)) + for corrupt in [True, -1, 2, 1.0]: + state['modes']['R0']['run'] = corrupt + path.write_text(json.dumps(state)) + denied(lambda: reserve(tmp, 'R1', 'capture', path)) +with tempfile.TemporaryDirectory() as tmp: + ctx = multiprocessing.get_context('fork') + queue = ctx.Queue() + children = [ctx.Process(target=contender, args=(tmp, queue)) for _ in range(2)] + for child in children: + child.start() + for child in children: + child.join(5) + assert child.exitcode == 0 + assert sorted(queue.get(timeout=1) for _ in children) == ['accepted', 'denied'] +print('PASS exact exhaustion, no fifth run, cross-LAB, corrupt schema/counters, exclusive lock')